| #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 | |
| // initial 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 represent 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 instantiating 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 |