From 024ef4ff9b0ceb83a5dd4c2c0cd53852ff15f721 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Oct 2023 15:58:26 -0700 Subject: [PATCH 1/5] [analysis] Simplify core analysis code Simplify the monotone analyzer by replacing all the state it used to store in `BlockState` with a simple vector of lattice elements. Use simple indices to refer to both blocks and their associated states in the vector. Remove the ability for transfer functions to control the initial enqueued order of basic blocks since that was a leaky abstraction. Replace the worklist with a UniqueDeferredQueue since that has generally proven to be more efficient in smiilarly contexts, and more importantly, it has a nicer API. Make miscellaneous simplifications to other code as well. Delete a few unit tests that exposed the order in which blocks were analyzed because they printed intermediate results. These tests should be replaced with tests of analyses' public APIs in the future. --- src/analysis/cfg.h | 2 + src/analysis/lattice.h | 5 +- src/analysis/liveness-transfer-function.h | 12 +- src/analysis/monotone-analyzer-impl.h | 98 +++---- src/analysis/monotone-analyzer.h | 23 +- src/analysis/transfer-function.h | 24 +- src/analysis/visitor-transfer-function.h | 47 +--- src/tools/wasm-fuzz-lattices.cpp | 24 +- test/gtest/cfg.cpp | 305 ---------------------- 9 files changed, 86 insertions(+), 454 deletions(-) diff --git a/src/analysis/cfg.h b/src/analysis/cfg.h index ff9b05849eb..cd1a60815d5 100644 --- a/src/analysis/cfg.h +++ b/src/analysis/cfg.h @@ -73,6 +73,8 @@ struct CFG { reverse_iterator rbegin() const { return blocks.rbegin(); } reverse_iterator rend() const { return blocks.rend(); } + const BasicBlock& operator[](size_t i) const { return *(begin() + i); } + static CFG fromFunction(Function* func); void print(std::ostream& os, Module* wasm = nullptr) const; diff --git a/src/analysis/lattice.h b/src/analysis/lattice.h index 8d2b222435d..7865de20b90 100644 --- a/src/analysis/lattice.h +++ b/src/analysis/lattice.h @@ -19,7 +19,7 @@ #if __cplusplus >= 202002L #include -#endif +#endif // __cplusplus >= 202002L namespace wasm::analysis { @@ -45,6 +45,7 @@ concept Lattice = requires(const L& lattice, typename L::Element& elem) { // Lattices must have elements. typename L::Element; + requires std::copyable; // We need to be able to get the bottom element. { lattice.getBottom() } noexcept -> std::same_as; // Elements should be comparable. TODO: use <=> and std::three_way_comparable @@ -57,7 +58,7 @@ concept Lattice = requires(const L& lattice, { elem.makeLeastUpperBound(constElem) } noexcept -> std::same_as; }; -#else +#else // __cplusplus >= 202002L #define Lattice typename diff --git a/src/analysis/liveness-transfer-function.h b/src/analysis/liveness-transfer-function.h index 802f7f3fc43..f93fbfe9e0c 100644 --- a/src/analysis/liveness-transfer-function.h +++ b/src/analysis/liveness-transfer-function.h @@ -27,23 +27,21 @@ struct LivenessTransferFunction // to be passed in, where the temp copy is modified in place to produce the // intermediate states. void print(std::ostream& os, - const BasicBlock* cfgBlock, + const BasicBlock& bb, FiniteIntPowersetLattice::Element& inputState) { os << "Intermediate States (reverse order): " << std::endl; currState = &inputState; currState->print(os); os << std::endl; - auto cfgIter = cfgBlock->rbegin(); // 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->rend()) { - os << ShallowExpression{*cfgIter} << std::endl; - visit(*cfgIter); + for (auto it = bb.rbegin(); it != bb.rend(); ++it) { + os << ShallowExpression{*it} << "\n"; + visit(*it); currState->print(os); - os << std::endl; - ++cfgIter; + os << "\n"; } currState = nullptr; } diff --git a/src/analysis/monotone-analyzer-impl.h b/src/analysis/monotone-analyzer-impl.h index f6b8694937e..1a7fbcd5346 100644 --- a/src/analysis/monotone-analyzer-impl.h +++ b/src/analysis/monotone-analyzer-impl.h @@ -8,71 +8,44 @@ namespace wasm::analysis { -// All states are set to the bottom lattice element using the lattice in this -// constructor. -template -inline BlockState::BlockState(const BasicBlock* underlyingBlock, L& lattice) - : cfgBlock(underlyingBlock), inputState(lattice.getBottom()) {} - -// Prints out inforamtion about a CFG node's state, but not intermediate states. -template inline void BlockState::print(std::ostream& os) { - os << "CFG Block: " << cfgBlock->getIndex() << std::endl; - os << "Input State: "; - inputState.print(os); - os << std::endl << "Predecessors:"; - for (auto pred : cfgBlock->preds()) { - os << " " << pred.getIndex(); - } - os << std::endl << "Successors:"; - for (auto succ : cfgBlock->succs()) { - os << " " << succ.getIndex(); - } - os << std::endl; -} - template inline MonotoneCFGAnalyzer::MonotoneCFGAnalyzer(L& lattice, TxFn& txfn, CFG& cfg) - : lattice(lattice), txfn(txfn), cfg(cfg) { - - // Construct BlockStates for each BasicBlock. - for (auto it = cfg.begin(); it != cfg.end(); it++) { - stateBlocks.emplace_back(&(*it), lattice); - } -} + : lattice(lattice), txfn(txfn), cfg(cfg), + states(cfg.size(), lattice.getBottom()) {} template inline void MonotoneCFGAnalyzer::evaluateFunctionEntry(Function* func) { - txfn.evaluateFunctionEntry(func, stateBlocks[0].inputState); + txfn.evaluateFunctionEntry(func, states[0]); } template inline void MonotoneCFGAnalyzer::evaluate() { - std::queue worklist; + UniqueDeferredQueue worklist; - // Transfer function enqueues the work in some order which is efficient. - txfn.enqueueWorklist(cfg, worklist); + // Start with all blocks on the work list. TODO: optimize the iteration order + // using e.g. strongly-connected components. + for (Index i = 0; i < cfg.size(); ++i) { + worklist.push(i); + } while (!worklist.empty()) { - BlockState& currBlockState = stateBlocks[worklist.front()->getIndex()]; - worklist.pop(); - - // For each expression, applies the transfer function, using the expression, - // on the state of the expression it depends upon (here the next expression) - // to arrive at the expression's state. The beginning and end states of the - // CFG block will be updated. - typename L::Element outputState = currBlockState.inputState; - txfn.transfer(currBlockState.cfgBlock, outputState); - - // Propagate state to dependents of currBlockState. - for (auto& dep : txfn.getDependents(currBlockState.cfgBlock)) { - // If we need to change the input state of a dependent, we need - // to enqueue the dependent to recalculate it. - if (stateBlocks[dep.getIndex()].inputState.makeLeastUpperBound( - outputState)) { - worklist.push(&dep); + // The index of the block we will analyze. + Index i = worklist.pop(); + + // Apply the transfer function to the input state to compute the output + // state for the block. + auto state = states[i]; + txfn.transfer(cfg[i], state); + + // Propagate state to the dependent blocks. + for (auto& dep : txfn.getDependents(cfg[i])) { + // If the input state for the dependent block changes, we need to + // re-analyze it. + if (states[i].makeLeastUpperBound(state)) { + worklist.push(dep.getIndex()); } } } @@ -80,14 +53,12 @@ inline void MonotoneCFGAnalyzer::evaluate() { template inline void MonotoneCFGAnalyzer::collectResults() { - for (BlockState currBlockState : stateBlocks) { - typename L::Element inputStateCopy = currBlockState.inputState; - + for (Index i = 0, size = cfg.size(); i < size; ++i) { // The transfer function generates the final set of states and uses it to // produce useful information. For example, in reaching definitions // analysis, these final states are used to populate a mapping of // local.get's to a set of local.set's that affect its value. - txfn.collectResults(currBlockState.cfgBlock, inputStateCopy); + txfn.collectResults(cfg[i], states[i]); } } @@ -96,12 +67,21 @@ inline void MonotoneCFGAnalyzer::collectResults() { template inline void MonotoneCFGAnalyzer::print(std::ostream& os) { os << "CFG Analyzer" << std::endl; - for (auto state : stateBlocks) { - state.print(os); - typename L::Element temp = state.inputState; - txfn.print(os, state.cfgBlock, temp); + for (Index i = 0, size = cfg.size(); i < size; ++i) { + os << "CFG Block: " << cfg[i].getIndex() << std::endl; + os << "Input State: "; + states[i].print(os); + for (auto& pred : cfg[i].preds()) { + os << " " << pred.getIndex(); + } + os << std::endl << "Successors:"; + for (auto& succ : cfg[i].succs()) { + os << " " << succ.getIndex(); + } + os << "\n"; + txfn.print(os, cfg[i], states[i]); } - os << "End" << std::endl; + os << "End\n"; } } // namespace wasm::analysis diff --git a/src/analysis/monotone-analyzer.h b/src/analysis/monotone-analyzer.h index f2a5932235c..6025d8ce052 100644 --- a/src/analysis/monotone-analyzer.h +++ b/src/analysis/monotone-analyzer.h @@ -11,27 +11,16 @@ namespace wasm::analysis { -// A node which contains all the lattice states for a given CFG node. -template struct BlockState { - - // CFG node corresponding to this state block. - const BasicBlock* cfgBlock; - // State at which the analysis flow starts for a CFG. For instance, the ending - // state for backward analysis, or the beginning state for forward analysis. - typename L::Element inputState; - - // All states are set to the bottom lattice element in this constructor. - BlockState(const BasicBlock* underlyingBlock, L& lattice); - - // Prints out BlockState information, but not any intermediate states. - void print(std::ostream& os); -}; - template class MonotoneCFGAnalyzer { + using Element = typename L::Element; + L& lattice; TxFn& txfn; CFG& cfg; - std::vector> stateBlocks; + + // The lattice element representing the program state before each block. + std::vector states; + // std::vector> stateBlocks; public: // Will constuct BlockState objects corresponding to BasicBlocks from the diff --git a/src/analysis/transfer-function.h b/src/analysis/transfer-function.h index 88fd8a1b1fa..3f99f2f081e 100644 --- a/src/analysis/transfer-function.h +++ b/src/analysis/transfer-function.h @@ -18,50 +18,40 @@ #define wasm_analysis_transfer_function_h #if __cplusplus >= 202002L + #include #include #include -#endif - -#include #include "cfg.h" #include "lattice.h" +#include "support/unique_deferring_queue.h" namespace wasm::analysis { -#if __cplusplus >= 202002L - template concept BasicBlockInputRange = std::ranges::input_range && std::is_same, BasicBlock>::value; template -concept TransferFunctionImpl = requires(TxFn& txfn, - const CFG& cfg, - BasicBlock* bb, - typename L::Element& elem, - std::queue& bbq) { +concept TransferFunctionImpl = requires( + TxFn& txfn, const CFG& cfg, const BasicBlock& bb, typename L::Element& elem) { // Apply the transfer function to update a lattice element with information // from a basic block. { txfn.transfer(bb, elem) } noexcept -> std::same_as; - // Initializes the worklist of basic blocks, which can affect performance - // depending on the direction of the analysis. TODO: Unlock performance - // benefits while exposing fewer implementation details. - { txfn.enqueueWorklist(cfg, bbq) } noexcept -> std::same_as; // Get a range over the basic blocks that depend on the given block. { txfn.getDependents(bb) } noexcept -> BasicBlockInputRange; }; #define TransferFunction TransferFunctionImpl -#else +} // namespace wasm::analysis + +#else // __cplusplus >= 202002L #define TransferFunction typename #endif // __cplusplus >= 202002L -} // namespace wasm::analysis - #endif // wasm_analysis_transfer_function_h diff --git a/src/analysis/visitor-transfer-function.h b/src/analysis/visitor-transfer-function.h index 79acf00da87..355fbcaccfd 100644 --- a/src/analysis/visitor-transfer-function.h +++ b/src/analysis/visitor-transfer-function.h @@ -5,6 +5,7 @@ #include "cfg.h" #include "lattice.h" +#include "support/unique_deferring_queue.h" #include "wasm-traversal.h" namespace wasm::analysis { @@ -36,71 +37,47 @@ struct VisitorTransferFunc : public Visitor { // Returns an iterable to all the BasicBlocks which depend on currBlock for // information. BasicBlock::BasicBlockIterable - getDependents(const BasicBlock* currBlock) noexcept { + getDependents(const BasicBlock& currBlock) noexcept { if constexpr (Direction == AnalysisDirection::Backward) { - return currBlock->preds(); + return currBlock.preds(); } else { - return currBlock->succs(); + 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, + void transfer(const BasicBlock& bb, typename L::Element& inputState) noexcept { // 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) { + for (auto cfgIter = bb.rbegin(); cfgIter != bb.rend(); ++cfgIter) { static_cast(this)->visit(*cfgIter); } } else { - for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end(); - ++cfgIter) { + for (auto cfgIter = bb.begin(); cfgIter != bb.end(); ++cfgIter) { static_cast(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(const CFG& cfg, - std::queue& worklist) noexcept { - 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 L::Element& inputState) { + void collectResults(const BasicBlock& bb, typename L::Element& inputState) { collectingResults = true; currState = &inputState; if constexpr (Direction == AnalysisDirection::Backward) { - for (auto cfgIter = cfgBlock->rbegin(); cfgIter != cfgBlock->rend(); - ++cfgIter) { - static_cast(this)->visit(*cfgIter); + for (auto it = bb.rbegin(); it != bb.rend(); ++it) { + static_cast(this)->visit(*it); } } else { - for (auto cfgIter = cfgBlock->begin(); cfgIter != cfgBlock->end(); - ++cfgIter) { - static_cast(this)->visit(*cfgIter); + for (auto it = bb.begin(); it != bb.end(); ++it) { + static_cast(this)->visit(*it); } } currState = nullptr; diff --git a/src/tools/wasm-fuzz-lattices.cpp b/src/tools/wasm-fuzz-lattices.cpp index fe801ba1296..39010422391 100644 --- a/src/tools/wasm-fuzz-lattices.cpp +++ b/src/tools/wasm-fuzz-lattices.cpp @@ -192,7 +192,7 @@ template struct AnalysisChecker { // the transfer function is monotonic. If this is violated, then we print out // the CFG block input which caused the transfer function to exhibit // non-monotonic behavior. - void checkMonotonicity(const BasicBlock* cfgBlock, + void checkMonotonicity(const BasicBlock& bb, typename L::Element& first, typename L::Element& second, typename L::Element& firstResult, @@ -233,7 +233,7 @@ template struct AnalysisChecker { secondResult.print(ss); ss << "\n show that the transfer function is not monotone when given the " "input:\n"; - cfgBlock->print(ss); + bb.print(ss); ss << "\n"; Fatal() << ss.str(); @@ -260,19 +260,19 @@ template struct AnalysisChecker { typename L::Element x, typename L::Element y, typename L::Element z) { - for (auto cfgIter = cfg.begin(); cfgIter != cfg.end(); ++cfgIter) { + for (const auto& bb : cfg) { // Apply transfer function on each lattice element. - typename L::Element xResult = x; - txfn.transfer(&(*cfgIter), xResult); - typename L::Element yResult = y; - txfn.transfer(&(*cfgIter), yResult); - typename L::Element zResult = z; - txfn.transfer(&(*cfgIter), zResult); + auto xResult = x; + txfn.transfer(bb, xResult); + auto yResult = y; + txfn.transfer(bb, yResult); + auto zResult = z; + txfn.transfer(bb, zResult); // Check monotonicity for every pair of transfer function outputs. - checkMonotonicity(&(*cfgIter), x, y, xResult, yResult); - checkMonotonicity(&(*cfgIter), x, z, xResult, zResult); - checkMonotonicity(&(*cfgIter), y, z, yResult, zResult); + checkMonotonicity(bb, x, y, xResult, yResult); + checkMonotonicity(bb, x, z, xResult, zResult); + checkMonotonicity(bb, y, z, yResult, zResult); } } }; diff --git a/test/gtest/cfg.cpp b/test/gtest/cfg.cpp index 3bb3ec1045b..8093e9f28a9 100644 --- a/test/gtest/cfg.cpp +++ b/test/gtest/cfg.cpp @@ -133,174 +133,6 @@ TEST_F(CFGTest, CallBlock) { EXPECT_EQ(ss.str(), cfgText); } -TEST_F(CFGTest, LinearLiveness) { - auto moduleText = R"wasm( - (module - (func $bar - (local $a (i32)) - (local $b (i32)) - (local $c (i32)) - (local.set $a - (i32.const 1) - ) - (drop - (local.get $a) - ) - (local.set $b - (local.get $a) - ) - (local.set $c - (i32.const 1) - ) - (drop - (local.get $c) - ) - ) - ) - )wasm"; - - auto analyzerText = R"analyzer(CFG Analyzer -CFG Block: 0 -Input State: 000 -Predecessors: -Successors: -Intermediate States (reverse order): -000 -block -000 -drop -000 -local.get $2 -001 -local.set $2 -000 -i32.const 1 -000 -local.set $1 -000 -local.get $0 -100 -drop -100 -local.get $0 -100 -local.set $0 -000 -i32.const 1 -000 -End -)analyzer"; - - Module wasm; - parseWast(wasm, moduleText); - - CFG cfg = CFG::fromFunction(wasm.getFunction("bar")); - size_t numLocals = wasm.getFunction("bar")->getNumLocals(); - - FiniteIntPowersetLattice lattice(numLocals); - LivenessTransferFunction transferFunction; - - MonotoneCFGAnalyzer - analyzer(lattice, transferFunction, cfg); - analyzer.evaluate(); - - std::stringstream ss; - analyzer.print(ss); - - EXPECT_EQ(ss.str(), analyzerText); -} - -TEST_F(CFGTest, NonlinearLiveness) { - auto moduleText = R"wasm( - (module - (func $bar - (local $a (i32)) - (local $b (i32)) - (local.set $a - (i32.const 1) - ) - (if - (i32.eq - (local.get $a) - (i32.const 2) - ) - (local.set $b - (i32.const 4) - ) - (drop - (local.get $a) - ) - ) - ) - ) - )wasm"; - - auto analyzerText = R"analyzer(CFG Analyzer -CFG Block: 0 -Input State: 10 -Predecessors: -Successors: 1 2 -Intermediate States (reverse order): -10 -i32.eq -10 -i32.const 2 -10 -local.get $0 -10 -local.set $0 -00 -i32.const 1 -00 -CFG Block: 1 -Input State: 00 -Predecessors: 0 -Successors: 3 -Intermediate States (reverse order): -00 -local.set $1 -00 -i32.const 4 -00 -CFG Block: 2 -Input State: 00 -Predecessors: 0 -Successors: 3 -Intermediate States (reverse order): -00 -drop -00 -local.get $0 -10 -CFG Block: 3 -Input State: 00 -Predecessors: 2 1 -Successors: -Intermediate States (reverse order): -00 -block -00 -End -)analyzer"; - - Module wasm; - parseWast(wasm, moduleText); - - CFG cfg = CFG::fromFunction(wasm.getFunction("bar")); - size_t numLocals = wasm.getFunction("bar")->getNumLocals(); - - FiniteIntPowersetLattice lattice(numLocals); - LivenessTransferFunction transferFunction; - - MonotoneCFGAnalyzer - analyzer(lattice, transferFunction, cfg); - analyzer.evaluate(); - - std::stringstream ss; - analyzer.print(ss); - - EXPECT_EQ(ss.str(), analyzerText); -} TEST_F(CFGTest, FinitePowersetLatticeFunctioning) { @@ -443,143 +275,6 @@ TEST_F(CFGTest, LinearReachingDefinitions) { EXPECT_EQ(expectedResult, getSetses); } -TEST_F(CFGTest, ReachingDefinitionsIf) { - auto moduleText = R"wasm( - (module - (func $bar - (local $a (i32)) - (local $b (i32)) - (local.set $a - (i32.const 1) - ) - (if - (i32.eq - (local.get $a) - (i32.const 2) - ) - (local.set $b - (i32.const 3) - ) - (local.set $a - (i32.const 4) - ) - ) - (drop - (local.get $b) - ) - (drop - (local.get $a) - ) - ) - ) - )wasm"; - - Module wasm; - parseWast(wasm, moduleText); - - Function* func = wasm.getFunction("bar"); - CFG cfg = CFG::fromFunction(func); - - LocalGraph::GetSetses getSetses; - LocalGraph::Locations locations; - ReachingDefinitionsTransferFunction transferFunction( - func, getSetses, locations); - - MonotoneCFGAnalyzer, - ReachingDefinitionsTransferFunction> - analyzer(transferFunction.lattice, transferFunction, cfg); - analyzer.evaluateFunctionEntry(func); - analyzer.evaluateAndCollectResults(); - - FindAll foundSets(func->body); - FindAll foundGets(func->body); - - LocalGet* getA1 = foundGets.list[0]; - LocalGet* getB = foundGets.list[1]; - LocalGet* getA2 = foundGets.list[2]; - LocalSet* setA1 = foundSets.list[0]; - LocalSet* setB = foundSets.list[1]; - LocalSet* setA2 = foundSets.list[2]; - - LocalGraph::GetSetses expectedResult; - expectedResult[getA1].insert(setA1); - expectedResult[getB].insert(nullptr); - expectedResult[getB].insert(setB); - expectedResult[getA2].insert(setA1); - expectedResult[getA2].insert(setA2); - - EXPECT_EQ(expectedResult, getSetses); -} - -TEST_F(CFGTest, ReachingDefinitionsLoop) { - auto moduleText = R"wasm( - (module - (func $bar (param $a (i32)) (param $b (i32)) - (loop $loop - (drop - (local.get $a) - ) - (local.set $a - (i32.add - (i32.const 1) - (local.get $a) - ) - ) - (br_if $loop - (i32.le_u - (local.get $a) - (i32.const 7) - ) - ) - ) - (local.set $b - (i32.sub - (local.get $b) - (local.get $a) - ) - ) - ) - ) - )wasm"; - - Module wasm; - parseWast(wasm, moduleText); - - Function* func = wasm.getFunction("bar"); - CFG cfg = CFG::fromFunction(func); - - LocalGraph::GetSetses getSetses; - LocalGraph::Locations locations; - ReachingDefinitionsTransferFunction transferFunction( - func, getSetses, locations); - - MonotoneCFGAnalyzer, - ReachingDefinitionsTransferFunction> - analyzer(transferFunction.lattice, transferFunction, cfg); - analyzer.evaluateFunctionEntry(func); - analyzer.evaluateAndCollectResults(); - - FindAll foundSets(func->body); - FindAll foundGets(func->body); - - LocalGet* getA1 = foundGets.list[0]; - LocalGet* getA2 = foundGets.list[1]; - LocalGet* getA3 = foundGets.list[2]; - LocalGet* getB = foundGets.list[3]; - LocalGet* getA4 = foundGets.list[4]; - LocalSet* setA = foundSets.list[0]; - - LocalGraph::GetSetses expectedResult; - expectedResult[getA1].insert(nullptr); - expectedResult[getA1].insert(setA); - expectedResult[getA2].insert(nullptr); - expectedResult[getA2].insert(setA); - expectedResult[getA3].insert(setA); - expectedResult[getB].insert(nullptr); - expectedResult[getA4].insert(setA); - - EXPECT_EQ(expectedResult, getSetses); -} TEST_F(CFGTest, StackLatticeFunctioning) { FiniteIntPowersetLattice contentLattice(4); From dd4546f1bc60724fd0b0b30f475bebab9a34cef7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Oct 2023 17:43:53 -0700 Subject: [PATCH 2/5] fix bug and restore some tests --- src/analysis/monotone-analyzer-impl.h | 2 +- test/gtest/cfg.cpp | 138 +++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/analysis/monotone-analyzer-impl.h b/src/analysis/monotone-analyzer-impl.h index 1a7fbcd5346..7e73fc1c48c 100644 --- a/src/analysis/monotone-analyzer-impl.h +++ b/src/analysis/monotone-analyzer-impl.h @@ -44,7 +44,7 @@ inline void MonotoneCFGAnalyzer::evaluate() { for (auto& dep : txfn.getDependents(cfg[i])) { // If the input state for the dependent block changes, we need to // re-analyze it. - if (states[i].makeLeastUpperBound(state)) { + if (states[dep.getIndex()].makeLeastUpperBound(state)) { worklist.push(dep.getIndex()); } } diff --git a/test/gtest/cfg.cpp b/test/gtest/cfg.cpp index 8093e9f28a9..9c6e8573aee 100644 --- a/test/gtest/cfg.cpp +++ b/test/gtest/cfg.cpp @@ -133,7 +133,6 @@ TEST_F(CFGTest, CallBlock) { EXPECT_EQ(ss.str(), cfgText); } - TEST_F(CFGTest, FinitePowersetLatticeFunctioning) { std::vector initialSet = {"a", "b", "c", "d", "e", "f"}; @@ -275,6 +274,143 @@ TEST_F(CFGTest, LinearReachingDefinitions) { EXPECT_EQ(expectedResult, getSetses); } +TEST_F(CFGTest, ReachingDefinitionsIf) { + auto moduleText = R"wasm( + (module + (func $bar + (local $a (i32)) + (local $b (i32)) + (local.set $a + (i32.const 1) + ) + (if + (i32.eq + (local.get $a) + (i32.const 2) + ) + (local.set $b + (i32.const 3) + ) + (local.set $a + (i32.const 4) + ) + ) + (drop + (local.get $b) + ) + (drop + (local.get $a) + ) + ) + ) + )wasm"; + + Module wasm; + parseWast(wasm, moduleText); + + Function* func = wasm.getFunction("bar"); + CFG cfg = CFG::fromFunction(func); + + LocalGraph::GetSetses getSetses; + LocalGraph::Locations locations; + ReachingDefinitionsTransferFunction transferFunction( + func, getSetses, locations); + + MonotoneCFGAnalyzer, + ReachingDefinitionsTransferFunction> + analyzer(transferFunction.lattice, transferFunction, cfg); + analyzer.evaluateFunctionEntry(func); + analyzer.evaluateAndCollectResults(); + + FindAll foundSets(func->body); + FindAll foundGets(func->body); + + LocalGet* getA1 = foundGets.list[0]; + LocalGet* getB = foundGets.list[1]; + LocalGet* getA2 = foundGets.list[2]; + LocalSet* setA1 = foundSets.list[0]; + LocalSet* setB = foundSets.list[1]; + LocalSet* setA2 = foundSets.list[2]; + + LocalGraph::GetSetses expectedResult; + expectedResult[getA1].insert(setA1); + expectedResult[getB].insert(nullptr); + expectedResult[getB].insert(setB); + expectedResult[getA2].insert(setA1); + expectedResult[getA2].insert(setA2); + + EXPECT_EQ(expectedResult, getSetses); +} + +TEST_F(CFGTest, ReachingDefinitionsLoop) { + auto moduleText = R"wasm( + (module + (func $bar (param $a (i32)) (param $b (i32)) + (loop $loop + (drop + (local.get $a) + ) + (local.set $a + (i32.add + (i32.const 1) + (local.get $a) + ) + ) + (br_if $loop + (i32.le_u + (local.get $a) + (i32.const 7) + ) + ) + ) + (local.set $b + (i32.sub + (local.get $b) + (local.get $a) + ) + ) + ) + ) + )wasm"; + + Module wasm; + parseWast(wasm, moduleText); + + Function* func = wasm.getFunction("bar"); + CFG cfg = CFG::fromFunction(func); + + LocalGraph::GetSetses getSetses; + LocalGraph::Locations locations; + ReachingDefinitionsTransferFunction transferFunction( + func, getSetses, locations); + + MonotoneCFGAnalyzer, + ReachingDefinitionsTransferFunction> + analyzer(transferFunction.lattice, transferFunction, cfg); + analyzer.evaluateFunctionEntry(func); + analyzer.evaluateAndCollectResults(); + + FindAll foundSets(func->body); + FindAll foundGets(func->body); + + LocalGet* getA1 = foundGets.list[0]; + LocalGet* getA2 = foundGets.list[1]; + LocalGet* getA3 = foundGets.list[2]; + LocalGet* getB = foundGets.list[3]; + LocalGet* getA4 = foundGets.list[4]; + LocalSet* setA = foundSets.list[0]; + + LocalGraph::GetSetses expectedResult; + expectedResult[getA1].insert(nullptr); + expectedResult[getA1].insert(setA); + expectedResult[getA2].insert(nullptr); + expectedResult[getA2].insert(setA); + expectedResult[getA3].insert(setA); + expectedResult[getB].insert(nullptr); + expectedResult[getA4].insert(setA); + + EXPECT_EQ(expectedResult, getSetses); +} TEST_F(CFGTest, StackLatticeFunctioning) { FiniteIntPowersetLattice contentLattice(4); From 5be5539ec29eaee324961517592e0e1dc7cfca12 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Oct 2023 19:20:58 -0700 Subject: [PATCH 3/5] missing include --- src/analysis/monotone-analyzer-impl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/analysis/monotone-analyzer-impl.h b/src/analysis/monotone-analyzer-impl.h index 7e73fc1c48c..e52ac57595e 100644 --- a/src/analysis/monotone-analyzer-impl.h +++ b/src/analysis/monotone-analyzer-impl.h @@ -5,6 +5,7 @@ #include #include "monotone-analyzer.h" +#include "support/unique_deferring_queue.h" namespace wasm::analysis { From 38049700c63bd8f2531c25ddc7783d32c3184a2b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 24 Oct 2023 14:46:30 -0700 Subject: [PATCH 4/5] remove stale comment --- src/analysis/monotone-analyzer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/analysis/monotone-analyzer.h b/src/analysis/monotone-analyzer.h index 6025d8ce052..5ca8caacc2f 100644 --- a/src/analysis/monotone-analyzer.h +++ b/src/analysis/monotone-analyzer.h @@ -20,7 +20,6 @@ template class MonotoneCFGAnalyzer { // The lattice element representing the program state before each block. std::vector states; - // std::vector> stateBlocks; public: // Will constuct BlockState objects corresponding to BasicBlocks from the From 409e7baa12309daaa55a96ebf8577a1e0cb5f4f0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 24 Oct 2023 16:14:05 -0700 Subject: [PATCH 5/5] range-based for loop --- src/analysis/visitor-transfer-function.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis/visitor-transfer-function.h b/src/analysis/visitor-transfer-function.h index 355fbcaccfd..d77fc9fcd10 100644 --- a/src/analysis/visitor-transfer-function.h +++ b/src/analysis/visitor-transfer-function.h @@ -59,8 +59,8 @@ struct VisitorTransferFunc : public Visitor { static_cast(this)->visit(*cfgIter); } } else { - for (auto cfgIter = bb.begin(); cfgIter != bb.end(); ++cfgIter) { - static_cast(this)->visit(*cfgIter); + for (auto* inst : bb) { + static_cast(this)->visit(inst); } } currState = nullptr;