| #ifndef wasm_analysis_reaching_definitions_transfer_function_h |
| #define wasm_analysis_reaching_definitions_transfer_function_h |
| |
| #include "ir/find_all.h" |
| #include "ir/local-graph.h" |
| #include "lattice.h" |
| #include "lattices/powerset.h" |
| #include "visitor-transfer-function.h" |
| |
| namespace wasm::analysis { |
| |
| // A state contains all the "live" LocalSet instances at some particular |
| // point in a program. The state tracks every LocalSet instance, not just |
| // ones which influence a particular local index. |
| |
| // In addition to LocalSet expressions modifying a local index's value, we |
| // must also account for the fact that the value of a local index could be |
| // the default value at initialization or its value passed in as a parameter. |
| // As a result, we use a fictitious LocalSet object held by the transfer |
| // function to represent the local index obtaining its current value on |
| // initialization or parameter passing. |
| |
| // Each local index gets an individual fictitious LocalSet, because the state |
| // tracks all local indices, and setting one local index should not affect the |
| // initial value of another. |
| |
| // When collecting results, the transfer function takes the states and converts |
| // it into a map of LocalGets to LocalSets which affect it. The fictitious |
| // inital value LocalSetes will be converted to nullptrs. |
| |
| class ReachingDefinitionsTransferFunction |
| : public VisitorTransferFunc<ReachingDefinitionsTransferFunction, |
| FinitePowersetLattice<LocalSet*>, |
| AnalysisDirection::Forward> { |
| |
| // Number of locals in the function. |
| size_t numLocals; |
| |
| // Maps a local index to a list of LocalSets that modify the index's value. |
| // The most common case is to have a single set; after that, to be a phi of 2 |
| // items, so we use a small set of size 2 to avoid allocations there. |
| std::unordered_map<Index, SmallVector<LocalSet*, 2>> indexSetses; |
| |
| // LocalGraph members we need to update. |
| LocalGraph::GetSetsMap& getSetsMap; |
| |
| // Fictitious LocalSet objects to reprsent a local index obtaining its value |
| // from its default initial value or parameter value. |
| std::vector<LocalSet> fakeInitialValueSets; |
| |
| // Pointers to the fictitious LocalSet objects. |
| std::unordered_set<LocalSet*> fakeSetPtrs; |
| |
| // Helper function which creates fictitious LocalSets for a function, |
| // inserts them into fakeInitialValueSets and fakeSetPtrs. It returns a |
| // vector of actual LocalSets in the function and fictitious LocalSets for |
| // use when instatitating the lattice. |
| static std::vector<LocalSet*> |
| listLocalSets(Function* func, |
| std::vector<LocalSet>& fakeInitialValueSets, |
| std::unordered_set<LocalSet*>& fakeSetPtrs) { |
| // Create a fictitious LocalSet for each local index. |
| for (Index i = 0; i < func->getNumLocals(); ++i) { |
| LocalSet currSet; |
| currSet.index = i; |
| fakeInitialValueSets.push_back(currSet); |
| } |
| |
| // Find all actual LocalSets. |
| FindAll<LocalSet> setFinder(func->body); |
| std::vector<LocalSet*> setsList = std::move(setFinder.list); |
| |
| // Take a pointer for each fictitious LocalSet. |
| for (size_t i = 0; i < fakeInitialValueSets.size(); ++i) { |
| setsList.push_back(&fakeInitialValueSets[i]); |
| fakeSetPtrs.insert(&fakeInitialValueSets[i]); |
| } |
| return setsList; |
| } |
| |
| public: |
| // Analysis lattice. Public, as the monotone analyzer may need it. |
| FinitePowersetLattice<LocalSet*> lattice; |
| |
| // We actually don't update LocalGraph::Locations right now since the CFG we |
| // are working with doesn't contain the correct Expression**s, but this is |
| // left in for future improvements. TODO. |
| ReachingDefinitionsTransferFunction(Function* func, |
| LocalGraph::GetSetsMap& getSetsMap, |
| LocalGraph::Locations& locations) |
| : numLocals(func->getNumLocals()), getSetsMap(getSetsMap), |
| lattice(listLocalSets(func, fakeInitialValueSets, fakeSetPtrs)) { |
| |
| // Map every local index to a set of all the local sets which affect it. |
| for (auto it = lattice.membersBegin(); it != lattice.membersEnd(); ++it) { |
| indexSetses[(*it)->index].push_back(*it); |
| } |
| } |
| |
| void visitLocalSet(LocalSet* curr) { |
| assert(currState); |
| |
| // This is only needed to derive states when solving the analysis, and |
| // not for collecting results from the final states. |
| |
| // For a LocalSet, we remove all previous sets modifying its index and |
| // adds itself. |
| auto& currSets = indexSetses[curr->index]; |
| for (auto setInstance : currSets) { |
| lattice.remove(currState, setInstance); |
| } |
| |
| lattice.add(currState, curr); |
| } |
| |
| // Only for collecting results. For curr, collects all of the sets to curr's |
| // index which are present in the state (i.e. those that set index's current |
| // value). |
| void visitLocalGet(LocalGet* curr) { |
| assert(currState); |
| |
| // This is only to be run in the second phase where we iterate over the |
| // final (i.e. solved) states. Thus, only done when collectingResults is |
| // true. |
| if (collectingResults) { |
| auto& matchingSets = indexSetses[curr->index]; |
| |
| for (auto setInstance : matchingSets) { |
| if (lattice.exists(currState, setInstance)) { |
| // If a pointer to a real LocalSet, add it, otherwise add a nullptr. |
| if (fakeSetPtrs.find(setInstance) == fakeSetPtrs.end()) { |
| getSetsMap[curr].insert(setInstance); |
| } else { |
| getSetsMap[curr].insert(nullptr); |
| } |
| } |
| } |
| } |
| } |
| |
| // At the entry point of the function, we must set the state to signify that |
| // the values in each local index are from either a parameter value or their |
| // default initialized value. This cannot be done normally, because the |
| // initial state of the entry block is initialized as the bottom element, but |
| // no one can modify it because this block doesn't depend on any other. Hence |
| // the need for this function. |
| void |
| evaluateFunctionEntry(Function* func, |
| FinitePowersetLattice<LocalSet*>::Element& inputState) { |
| for (auto currSet : fakeSetPtrs) { |
| lattice.add(&inputState, currSet); |
| } |
| } |
| |
| void print(std::ostream& os, |
| const BasicBlock* cfgBlock, |
| FinitePowersetLattice<LocalSet*>::Element& inputState) { |
| os << "Intermediate States: " << std::endl; |
| currState = &inputState; |
| currState->print(os); |
| os << std::endl; |
| auto cfgIter = cfgBlock->begin(); |
| |
| // Since we don't store the intermediate states, we need to re-run the |
| // transfer function on all the CFG node expressions to reconstruct |
| // the intermediate states here. |
| while (cfgIter != cfgBlock->end()) { |
| os << ShallowExpression{*cfgIter} << std::endl; |
| visit(*cfgIter); |
| currState->print(os); |
| os << std::endl; |
| ++cfgIter; |
| } |
| currState = nullptr; |
| } |
| }; |
| |
| } // namespace wasm::analysis |
| |
| #endif // wasm_analysis_reaching_definitions_transfer_function_h |