| #include <iostream> |
| |
| #include "analysis/bits-bounds-lattice.h" |
| #include "analysis/bits-bounds-transfer-function.h" |
| #include "analysis/cfg.h" |
| #include "analysis/lattice.h" |
| #include "analysis/liveness-transfer-function.h" |
| #include "analysis/monotone-analyzer.h" |
| #include "analysis/reaching-definitions-transfer-function.h" |
| #include "analysis/stack-lattice.h" |
| #include "ir/find_all.h" |
| #include "print-test.h" |
| #include "wasm.h" |
| #include "gtest/gtest.h" |
| |
| using namespace wasm; |
| using namespace wasm::analysis; |
| |
| using CFGTest = PrintTest; |
| |
| TEST_F(CFGTest, Print) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| (drop |
| (i32.const 0) |
| ) |
| (drop |
| (if (result i32) |
| (i32.const 1) |
| (block |
| (loop $loop |
| (br_if $loop |
| (i32.const 2) |
| ) |
| ) |
| (i32.const 3) |
| ) |
| (return |
| (i32.const 4) |
| ) |
| ) |
| ) |
| ) |
| ) |
| )wasm"; |
| |
| auto cfgText = R"cfg(;; preds: [], succs: [1, 5] |
| 0: |
| 0: i32.const 0 |
| 1: drop |
| 2: i32.const 1 |
| |
| ;; preds: [0], succs: [2] |
| 1: |
| |
| ;; preds: [1, 2], succs: [3, 2] |
| 2: |
| 3: i32.const 2 |
| 4: br_if $loop |
| |
| ;; preds: [2], succs: [4] |
| 3: |
| 5: loop $loop |
| |
| ;; preds: [3], succs: [6] |
| 4: |
| 6: i32.const 3 |
| 7: block |
| |
| ;; preds: [0], succs: [] |
| 5: |
| 8: i32.const 4 |
| 9: return |
| |
| ;; preds: [4], succs: [] |
| 6: |
| 10: drop |
| 11: block |
| )cfg"; |
| |
| Module wasm; |
| parseWast(wasm, moduleText); |
| |
| CFG cfg = CFG::fromFunction(wasm.getFunction("foo")); |
| |
| std::stringstream ss; |
| cfg.print(ss); |
| |
| EXPECT_EQ(ss.str(), cfgText); |
| } |
| |
| TEST_F(CFGTest, CallBlock) { |
| // Verify that a call instruction ends the current basic block. Even if the |
| // call has no control flow edges inside the function (no catches it can |
| // reach), we still end a basic block with the call, so that we preserve the |
| // property of basic blocks ending in possibly-control-flow-transferring |
| // instructions. |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| (drop |
| (i32.const 0) |
| ) |
| (call $bar) |
| (drop |
| (i32.const 1) |
| ) |
| ) |
| (func $bar) |
| ) |
| )wasm"; |
| |
| auto cfgText = R"cfg(;; preds: [], succs: [1] |
| 0: |
| 0: i32.const 0 |
| 1: drop |
| 2: call $bar |
| |
| ;; preds: [0], succs: [] |
| 1: |
| 3: i32.const 1 |
| 4: drop |
| 5: block |
| )cfg"; |
| |
| Module wasm; |
| parseWast(wasm, moduleText); |
| |
| CFG cfg = CFG::fromFunction(wasm.getFunction("foo")); |
| |
| std::stringstream ss; |
| cfg.print(ss); |
| |
| 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<FiniteIntPowersetLattice, LivenessTransferFunction> |
| 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<FiniteIntPowersetLattice, LivenessTransferFunction> |
| analyzer(lattice, transferFunction, cfg); |
| analyzer.evaluate(); |
| |
| std::stringstream ss; |
| analyzer.print(ss); |
| |
| EXPECT_EQ(ss.str(), analyzerText); |
| } |
| |
| TEST_F(CFGTest, FinitePowersetLatticeFunctioning) { |
| |
| std::vector<std::string> initialSet = {"a", "b", "c", "d", "e", "f"}; |
| FinitePowersetLattice<std::string> lattice(std::move(initialSet)); |
| |
| auto element1 = lattice.getBottom(); |
| |
| EXPECT_TRUE(element1.isBottom()); |
| EXPECT_FALSE(element1.isTop()); |
| |
| lattice.add(&element1, "c"); |
| lattice.add(&element1, "d"); |
| lattice.add(&element1, "a"); |
| |
| EXPECT_FALSE(element1.isBottom()); |
| EXPECT_FALSE(element1.isTop()); |
| |
| auto element2 = element1; |
| lattice.remove(&element2, "c"); |
| EXPECT_EQ(lattice.compare(element1, element2), LatticeComparison::GREATER); |
| lattice.add(&element2, "f"); |
| EXPECT_EQ(lattice.compare(element1, element2), |
| LatticeComparison::NO_RELATION); |
| |
| std::stringstream ss; |
| element1.print(ss); |
| EXPECT_EQ(ss.str(), "101100"); |
| ss.str(std::string()); |
| element2.print(ss); |
| EXPECT_EQ(ss.str(), "100101"); |
| ss.str(std::string()); |
| element2.makeLeastUpperBound(element1); |
| element2.print(ss); |
| EXPECT_EQ(ss.str(), "101101"); |
| } |
| |
| TEST_F(CFGTest, BlockIndexes) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| (if |
| (i32.const 1) |
| (block |
| (drop |
| (i32.const 2) |
| ) |
| (drop |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ) |
| ) |
| )wasm"; |
| |
| Module wasm; |
| parseWast(wasm, moduleText); |
| |
| auto* func = wasm.getFunction("foo"); |
| CFG cfg = CFG::fromFunction(func); |
| CFGBlockIndexes indexes(cfg); |
| |
| // The body of the function is an if. An if is a control flow structure and so |
| // it has no basic block (it can contain multiple ones). |
| auto* iff = func->body->cast<If>(); |
| EXPECT_EQ(indexes.get(iff), indexes.InvalidBlock); |
| |
| // The constant 1 is in the entry block. |
| EXPECT_EQ(indexes.get(iff->condition), Index(0)); |
| |
| // The dropped constants 2 and three are in another block, together. |
| auto* block = iff->ifTrue->cast<Block>(); |
| EXPECT_EQ(indexes.get(block->list[0]), Index(1)); |
| EXPECT_EQ(indexes.get(block->list[1]), Index(1)); |
| } |
| |
| TEST_F(CFGTest, LinearReachingDefinitions) { |
| 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) |
| ) |
| (drop |
| (local.get $c) |
| ) |
| (local.set $c |
| (i32.const 1) |
| ) |
| (local.set $a |
| (i32.const 2) |
| ) |
| ) |
| ) |
| )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<FinitePowersetLattice<LocalSet*>, |
| ReachingDefinitionsTransferFunction> |
| analyzer(transferFunction.lattice, transferFunction, cfg); |
| |
| // TODO: Make evaluating function entry point more automatic (i.e. part of |
| // existing evaluate call). |
| analyzer.evaluateFunctionEntry(func); |
| analyzer.evaluateAndCollectResults(); |
| |
| FindAll<LocalSet> foundSets(func->body); |
| FindAll<LocalGet> foundGets(func->body); |
| |
| LocalGet* getA1 = foundGets.list[0]; |
| LocalGet* getA2 = foundGets.list[1]; |
| LocalGet* getC = foundGets.list[2]; |
| LocalSet* setA1 = foundSets.list[0]; |
| |
| LocalGraph::GetSetses expectedResult; |
| expectedResult[getA1].insert(setA1); |
| expectedResult[getA2].insert(setA1); |
| expectedResult[getC].insert(nullptr); |
| |
| 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<FinitePowersetLattice<LocalSet*>, |
| ReachingDefinitionsTransferFunction> |
| analyzer(transferFunction.lattice, transferFunction, cfg); |
| analyzer.evaluateFunctionEntry(func); |
| analyzer.evaluateAndCollectResults(); |
| |
| FindAll<LocalSet> foundSets(func->body); |
| FindAll<LocalGet> 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<FinitePowersetLattice<LocalSet*>, |
| ReachingDefinitionsTransferFunction> |
| analyzer(transferFunction.lattice, transferFunction, cfg); |
| analyzer.evaluateFunctionEntry(func); |
| analyzer.evaluateAndCollectResults(); |
| |
| FindAll<LocalSet> foundSets(func->body); |
| FindAll<LocalGet> 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); |
| StackLattice<FiniteIntPowersetLattice> stackLattice(contentLattice); |
| |
| // These are constructed as expected references to compare everything else. |
| StackLattice<FiniteIntPowersetLattice>::Element expectedStack = |
| stackLattice.getBottom(); |
| FiniteIntPowersetLattice::Element expectedStackElement = |
| contentLattice.getBottom(); |
| |
| StackLattice<FiniteIntPowersetLattice>::Element firstStack = |
| stackLattice.getBottom(); |
| StackLattice<FiniteIntPowersetLattice>::Element secondStack = |
| stackLattice.getBottom(); |
| |
| for (size_t i = 0; i < 4; i++) { |
| FiniteIntPowersetLattice::Element temp = contentLattice.getBottom(); |
| for (size_t j = 0; j <= i; j++) { |
| temp.set(j, true); |
| } |
| firstStack.push(temp); |
| secondStack.push(temp); |
| if (i < 2) { |
| expectedStack.push(temp); |
| } |
| } |
| |
| EXPECT_EQ(stackLattice.compare(firstStack, secondStack), |
| LatticeComparison::EQUAL); |
| |
| for (size_t j = 0; j < 4; ++j) { |
| expectedStackElement.set(j, true); |
| } |
| |
| EXPECT_EQ(contentLattice.compare(secondStack.pop(), expectedStackElement), |
| LatticeComparison::EQUAL); |
| expectedStackElement.set(3, false); |
| EXPECT_EQ(contentLattice.compare(secondStack.pop(), expectedStackElement), |
| LatticeComparison::EQUAL); |
| |
| EXPECT_EQ(stackLattice.compare(firstStack, secondStack), |
| LatticeComparison::GREATER); |
| EXPECT_EQ(stackLattice.compare(secondStack, firstStack), |
| LatticeComparison::LESS); |
| |
| EXPECT_EQ(stackLattice.compare(secondStack, expectedStack), |
| LatticeComparison::EQUAL); |
| |
| { |
| expectedStack.stackTop().set(0, false); |
| expectedStack.stackTop().set(2, true); |
| FiniteIntPowersetLattice::Element temp = expectedStack.pop(); |
| expectedStack.stackTop().set(3, true); |
| expectedStack.push(temp); |
| |
| FiniteIntPowersetLattice::Element temp2 = contentLattice.getBottom(); |
| temp2.set(1, true); |
| temp2.set(3, true); |
| expectedStack.push(temp2); |
| } |
| |
| StackLattice<FiniteIntPowersetLattice>::Element thirdStack = |
| stackLattice.getBottom(); |
| { |
| FiniteIntPowersetLattice::Element temp = contentLattice.getBottom(); |
| temp.set(0, true); |
| temp.set(3, true); |
| FiniteIntPowersetLattice::Element temp2 = contentLattice.getBottom(); |
| temp2.set(1, true); |
| temp2.set(2, true); |
| FiniteIntPowersetLattice::Element temp3 = contentLattice.getBottom(); |
| temp3.set(1, true); |
| temp3.set(3, true); |
| thirdStack.push(std::move(temp)); |
| thirdStack.push(std::move(temp2)); |
| thirdStack.push(std::move(temp3)); |
| } |
| |
| EXPECT_EQ(stackLattice.compare(secondStack, thirdStack), |
| LatticeComparison::NO_RELATION); |
| |
| EXPECT_EQ(stackLattice.compare(thirdStack, expectedStack), |
| LatticeComparison::EQUAL); |
| |
| EXPECT_EQ(thirdStack.makeLeastUpperBound(secondStack), true); |
| |
| { |
| expectedStack.stackTop().set(0, true); |
| FiniteIntPowersetLattice::Element temp = expectedStack.pop(); |
| expectedStack.stackTop().set(0, true); |
| expectedStack.push(temp); |
| } |
| |
| EXPECT_EQ(stackLattice.compare(thirdStack, expectedStack), |
| LatticeComparison::EQUAL); |
| } |
| |
| // TODO: Add more thorough test cases for max bits analysis. |
| TEST_F(CFGTest, MaxBitsAnalysis) { |
| auto moduleText = R"wasm( |
| (module |
| (func $bar (param $x (i32)) |
| (local.set $x (i32.mul (i32.const 1234) (i32.const 432))) |
| (local.set $x (i32.add (i32.const 64) (i32.const 32))) |
| (local.set $x (i32.xor (i32.const 123) (i32.const 47))) |
| ) |
| ) |
| )wasm"; |
| |
| Module wasm; |
| parseWast(wasm, moduleText); |
| |
| Function* func = wasm.getFunction("bar"); |
| CFG cfg = CFG::fromFunction(func); |
| |
| MaxBitsLattice maxBitsLattice; |
| StackLattice<MaxBitsLattice> lattice(maxBitsLattice); |
| |
| MaxBitsTransferFunction transferFunction(maxBitsLattice); |
| MonotoneCFGAnalyzer<StackLattice<MaxBitsLattice>, MaxBitsTransferFunction> |
| analyzer(lattice, transferFunction, cfg); |
| analyzer.evaluateAndCollectResults(); |
| |
| for (auto cfgBlock = cfg.begin(); cfgBlock != cfg.end(); ++cfgBlock) { |
| for (auto expr = cfgBlock->begin(); expr != cfgBlock->end(); ++expr) { |
| std::cout << ShallowExpression{*expr} << " " |
| << transferFunction.exprMaxBounds[*expr] << std::endl; |
| } |
| } |
| } |