blob: 18ee353921e9defeb7f973dd6a7ef088c330a0af [file] [log] [blame] [edit]
#ifndef wasm_analysis_visitor_transfer_function_h
#define wasm_analysis_visitor_transfer_function_h
#include <queue>
#include "cfg.h"
#include "lattice.h"
#include "wasm-traversal.h"
namespace wasm::analysis {
enum class AnalysisDirection { Forward, Backward };
// Utility for visitor-based transfer functions for forward and backward
// analysis. Forward analysis is chosen by default unless the template parameter
// Backward is true.
template<typename SubType, typename Lattice, AnalysisDirection Direction>
struct VisitorTransferFunc : public Visitor<SubType> {
protected:
typename Lattice::Element* currState = nullptr;
// There are two distinct phases in the execution of the analyzer. First,
// the worklist algorithm will be run to obtain a solution, calling
// transfer() for each block. Then, to collect the final states, the analyzer
// will iterate over every block, calling collectResults().
// As there is only one set of visitor functions, they are used both to
// mutate intermediate states as the transfer function, and also to
// collect results from the final states. Therefore, we have the variable
// collectingResults to signal whether we are collecting results from the
// solved analysis, as opposed to solving the analysis to the visitor
// functions.
bool collectingResults = false;
public:
// Returns an iterable to all the BasicBlocks which depend on currBlock for
// information.
BasicBlock::BasicBlockIterable getDependents(const BasicBlock* currBlock) {
if constexpr (Direction == AnalysisDirection::Backward) {
return currBlock->preds();
} else {
return currBlock->succs();
}
}
// Executes the transfer function on all the expressions of the corresponding
// CFG node, starting with the node's input state, and changes the input state
// to the final output state of the node in place.
void transfer(const BasicBlock* cfgBlock,
typename Lattice::Element& inputState) {
// If the block is empty, we propagate the state by inputState =
// outputState.
currState = &inputState;
if constexpr (Direction == AnalysisDirection::Backward) {
for (auto cfgIter = cfgBlock->rbegin(); cfgIter != cfgBlock->rend();
++cfgIter) {
static_cast<SubType*>(this)->visit(*cfgIter);
}
} else {
for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end();
++cfgIter) {
static_cast<SubType*>(this)->visit(*cfgIter);
}
}
currState = nullptr;
}
// Enqueues the worklist before the worklist algorithm is run. We want to
// evaluate the blocks in an order matching the "flow" of the analysis to
// reduce the number of state propagations needed. Thus, for a forward
// analysis, we push all the blocks in order, while for backward analysis, we
// push them in reverse order, so that later blocks are evaluated before
// earlier ones.
void enqueueWorklist(CFG& cfg, std::queue<const BasicBlock*>& worklist) {
if constexpr (Direction == AnalysisDirection::Backward) {
for (auto it = cfg.rbegin(); it != cfg.rend(); ++it) {
worklist.push(&(*it));
}
} else {
for (auto it = cfg.begin(); it != cfg.end(); ++it) {
worklist.push(&(*it));
}
}
}
// This is for collecting results after solving an analysis. Implemented in
// the same way as transfer(), but we also set the collectingResults flag.
void collectResults(const BasicBlock* cfgBlock,
typename Lattice::Element& inputState) {
collectingResults = true;
currState = &inputState;
if constexpr (Direction == AnalysisDirection::Backward) {
for (auto cfgIter = cfgBlock->rbegin(); cfgIter != cfgBlock->rend();
++cfgIter) {
static_cast<SubType*>(this)->visit(*cfgIter);
}
} else {
for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end();
++cfgIter) {
static_cast<SubType*>(this)->visit(*cfgIter);
}
}
currState = nullptr;
collectingResults = false;
}
};
} // namespace wasm::analysis
#endif // wasm_analysis_visitor_transfer_function_h