blob: baff811568fcbb426f74751e4f08007b62aa03ae [file] [log] [blame]
#ifndef wasm_analysis_reaching_definitions_transfer_function_h
#define wasm_analysis_reaching_definitions_transfer_function_h
#include "ir/local-graph.h"
#include "visitor-transfer-function.h"
namespace wasm::analysis {
class ReachingDefinitionsTransferFunction
: public VisitorTransferFunc<ReachingDefinitionsTransferFunction, FinitePowersetLattice<LocalSet*>, false> {
// In addition to LocalSet expressions modifying a local index's value, we
// must also account for the fact that the local index's default value at
// initialization is used, or that it already has a value at the beginning.
// Therefore, when creating the powerset lattice from the list of all
// LocalSetses, we also append a list of nullptrs numLocals long, one for
// each local index, at the end of the list of all LocalSetses. These nullptrs
// represent the use of a default/param value.
// FinitePowersetLattice<LocalSet*>::Element* currState = nullptr;
FinitePowersetLattice<LocalSet*>& lattice;
// Number of locals in a function.
size_t numLocals;
// Maps a local index to a list of LocalSets that modify the index's value.
std::unordered_map<Index, SmallVector<LocalSet*, 2>> indexSetses;
// LocalGraph members we need to update.
LocalGraph::GetSetses& getSetses;
// We actually don't update 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.
LocalGraph::Locations& locations;
// Signals to the visit functions whether we are solving the analysis, or
// collecting results from the solved analysis.
bool collectingResults = false;
public:
ReachingDefinitionsTransferFunction(FinitePowersetLattice<LocalSet*>& lattice,
size_t numLocals, LocalGraph::GetSetses& getSetses, LocalGraph::Locations& locations)
: lattice(lattice), numLocals(numLocals), getSetses(getSetses), locations(locations) {
// Populate indexSetses.
for (auto it = lattice.membersBegin();
it != lattice.membersEnd() - numLocals;
++it) {
indexSetses[(*it)->index].push_back(*it);
}
}
// Sets the LocalGraph members we want to update, and signals that result
// collection is about to begin.
void beginResultCollection() {
collectingResults = true;
}
// Signals that we have stopped result collection.
void endResultCollection() { collectingResults = false; }
void visitLocalSet(LocalSet* curr) {
assert(currState);
// For a LocalSet, we remove all previous setses modifying its index and
// adds itself.
auto& currSetses = indexSetses[curr->index];
for (auto setInstance : currSetses) {
lattice.remove(currState, setInstance);
}
// Removing all previous setses means that the default initial/parameter
// value is overwritten, so we need to remove this possibility from the
// state too.
currState->set(lattice.getSetSize() - numLocals + curr->index, false);
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);
if (collectingResults) {
auto& matchingSetses = indexSetses[curr->index];
for (auto setInstance : matchingSetses) {
if (lattice.exists(currState, setInstance)) {
getSetses[curr].insert(setInstance);
}
}
// LocalGraph uses a nullptr to signify that the value obtained by Curr
// could be a default initial value or a parameter value.
if (currState->get(lattice.getSetSize() - numLocals + curr->index)) {
getSetses[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 (size_t i = 0; i < numLocals; ++i) {
inputState.set(lattice.getSetSize() - numLocals + i, true);
}
}
/*
void transfer(const BasicBlock* cfgBlock,
FinitePowersetLattice<LocalSet*>::Element& inputState) {
currState = &inputState;
for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end();
++cfgIter) {
visit(*cfgIter);
}
currState = nullptr;
}
// This is a forward analysis, so we push all the block in forward order to
// reduce calculation in the worklist algorithm.
void enqueueWorklist(CFG& cfg, std::queue<const BasicBlock*>& worklist) {
for (auto it = cfg.begin(); it != cfg.end(); ++it) {
worklist.push(&(*it));
}
}
// All of a block's successors rely on it for information.
BasicBlock::Successors getDependents(const BasicBlock* currBlock) {
return currBlock->succs();
}
void collectResults(const BasicBlock* cfgBlock,
FinitePowersetLattice<LocalSet*>::Element& inputState) {
// There is currently no difference between the two functions, because the
// difference is in the visit functions.
transfer(cfgBlock, inputState);
}
*/
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