| #include "ir/local-graph.h" |
| #include "parser/wat-parser.h" |
| #include "wasm.h" |
| |
| #include "gtest/gtest.h" |
| |
| using LocalGraphTest = ::testing::Test; |
| |
| using namespace wasm; |
| |
| TEST_F(LocalGraphTest, ObstacleBasics) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo (result i32) |
| ;; A local with a set and a get, and some nops in between. |
| (local $x i32) |
| (nop) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| )wasm"; |
| |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| |
| // Get access to the contents of the wasm. |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* nopA = block->list[0]->cast<Nop>(); |
| auto* set = block->list[1]->cast<LocalSet>(); |
| auto* nopB = block->list[2]->cast<Nop>(); |
| auto* get = block->list[3]->cast<LocalGet>(); |
| |
| { |
| LazyLocalGraph graph(func, &wasm); |
| // The set has one get. |
| EXPECT_EQ(graph.getSetInfluences(set).size(), 1U); |
| } |
| |
| { |
| // Construct the graph with an obstacle class, Nop. |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // The set has one get, like before. |
| EXPECT_EQ(graph.getSetInfluences(set).size(), 1U); |
| // If the first nop is an obstacle, nothing changes: the path between the |
| // set and get does not include it. |
| EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 1U); |
| EXPECT_EQ(*graph.canMoveSet(set, nopA).begin(), get); |
| // But if the second one is an obstacle, it severs the connection. |
| EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 0U); |
| } |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleMultiblock) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo (result i32) |
| ;; An if between the set and get. |
| (local $x i32) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (if |
| (i32.const 42) |
| (then |
| (nop) |
| ) |
| (else |
| (nop) |
| ) |
| ) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* set = block->list[0]->cast<LocalSet>(); |
| auto* iff = block->list[1]->cast<If>(); |
| auto* nopA = iff->ifTrue->cast<Nop>(); |
| auto* nopB = iff->ifTrue->cast<Nop>(); |
| auto* nopC = block->list[2]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // No matter which if arm is an obstacle, we still connect. |
| EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 1U); |
| EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 1U); |
| // But the nop after the if stops us. |
| EXPECT_EQ(graph.canMoveSet(set, nopC).size(), 0U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleUnreachable) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo (result i32) |
| ;; An unreachable between the set and get. |
| (local $x i32) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (nop) |
| (unreachable) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* set = block->list[0]->cast<LocalSet>(); |
| auto* nopA = block->list[1]->cast<Nop>(); |
| auto* nopB = block->list[3]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // The get is unreachable, and the set has no gets. |
| EXPECT_EQ(graph.getSetInfluences(set).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 0U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleMultiGet) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| ;; A set with multiple gets. |
| (local $x i32) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (nop) |
| (drop |
| (local.get $x) |
| ) |
| (nop) |
| (drop |
| (local.get $x) |
| ) |
| (nop) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* set = block->list[0]->cast<LocalSet>(); |
| auto* nopA = block->list[1]->cast<Nop>(); |
| auto* nopB = block->list[3]->cast<Nop>(); |
| auto* nopC = block->list[5]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // The first nop blocks them both, but not the second, and the third blocks |
| // nothing. |
| EXPECT_EQ(graph.canMoveSet(set, nopA).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(set, nopB).size(), 1U); |
| EXPECT_EQ(graph.canMoveSet(set, nopC).size(), 2U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleMultiSet) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| ;; Two sets. |
| (local $x i32) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (local.set $x |
| (i32.const 20) |
| ) |
| (nop) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* setA = block->list[0]->cast<LocalSet>(); |
| auto* setB = block->list[1]->cast<LocalSet>(); |
| auto* nop = block->list[2]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // The first set has no gets, the second has one. |
| EXPECT_EQ(graph.getSetInfluences(setA).size(), 0U); |
| EXPECT_EQ(graph.getSetInfluences(setB).size(), 1U); |
| // The nop blocks on the second (and the first, but it had none anyhow). |
| EXPECT_EQ(graph.canMoveSet(setA, nop).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(setB, nop).size(), 0U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleMultiSetIndexes) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| ;; Two sets of different indexes. |
| (local $x i32) |
| (local $y i32) |
| (local.set $x |
| (i32.const 10) |
| ) |
| (local.set $y |
| (i32.const 20) |
| ) |
| (nop) |
| (drop |
| (local.get $x) |
| ) |
| (nop) |
| (drop |
| (local.get $y) |
| ) |
| (nop) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* setA = block->list[0]->cast<LocalSet>(); |
| auto* setB = block->list[1]->cast<LocalSet>(); |
| auto* nopA = block->list[2]->cast<Nop>(); |
| auto* nopB = block->list[4]->cast<Nop>(); |
| auto* nopC = block->list[6]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // The first nop blocks them both. |
| EXPECT_EQ(graph.canMoveSet(setA, nopA).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(setB, nopA).size(), 0U); |
| // The second nop only blocks one. |
| EXPECT_EQ(graph.canMoveSet(setA, nopB).size(), 1U); |
| EXPECT_EQ(graph.canMoveSet(setB, nopB).size(), 0U); |
| // The last nop blocks nothing. |
| EXPECT_EQ(graph.canMoveSet(setA, nopC).size(), 1U); |
| EXPECT_EQ(graph.canMoveSet(setB, nopC).size(), 1U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleMultiSetIf) { |
| auto moduleText = R"wasm( |
| (module |
| (func $foo |
| ;; Two sets in an if. |
| (local $x i32) |
| (if |
| (i32.const 42) |
| (then |
| (local.set $x |
| (i32.const 10) |
| ) |
| ) |
| (else |
| (local.set $x |
| (i32.const 20) |
| ) |
| ) |
| ) |
| (nop) |
| (drop |
| (local.get $x) |
| ) |
| (nop) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* block = func->body->cast<Block>(); |
| auto* iff = block->list[0]->cast<If>(); |
| auto* setA = iff->ifTrue->cast<LocalSet>(); |
| auto* setB = iff->ifFalse->cast<LocalSet>(); |
| auto* nopA = block->list[1]->cast<Nop>(); |
| auto* nopB = block->list[3]->cast<Nop>(); |
| |
| LazyLocalGraph graph(func, &wasm, Nop::SpecificId); |
| // Both sets have a get. |
| EXPECT_EQ(graph.getSetInfluences(setA).size(), 1U); |
| EXPECT_EQ(graph.getSetInfluences(setB).size(), 1U); |
| // The first nop blocks both. |
| EXPECT_EQ(graph.canMoveSet(setA, nopA).size(), 0U); |
| EXPECT_EQ(graph.canMoveSet(setB, nopA).size(), 0U); |
| // The first nop blocks neither. |
| EXPECT_EQ(graph.canMoveSet(setA, nopB).size(), 1U); |
| EXPECT_EQ(graph.canMoveSet(setB, nopB).size(), 1U); |
| } |
| |
| TEST_F(LocalGraphTest, ObstacleStructSet) { |
| // Use something other than a nop to obstruct. Here we show a realistic |
| // situation using GC. |
| auto moduleText = R"wasm( |
| (module |
| (type $struct (struct (field (mut i32)))) |
| |
| (func $test |
| (local $struct (ref null $struct)) |
| (block $label |
| ;; A struct.set that may be skipped by the br in the if. |
| (struct.set $struct 0 |
| (local.tee $struct |
| (struct.new_default $struct) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (br $label) |
| ) |
| (else |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $struct 0 |
| (local.get $struct) |
| ) |
| ) |
| ) |
| ) |
| )wasm"; |
| Module wasm; |
| ASSERT_FALSE(WATParser::parseModule(wasm, moduleText).getErr()); |
| auto* func = wasm.functions[0].get(); |
| auto* outerBlock = func->body->cast<Block>(); |
| auto* block = outerBlock->list[0]->cast<Block>(); |
| auto* structSet = block->list[0]->cast<StructSet>(); |
| auto* tee = structSet->ref->cast<LocalSet>(); |
| |
| LazyLocalGraph graph(func, &wasm, StructSet::SpecificId); |
| // The tee has one get. |
| EXPECT_EQ(graph.getSetInfluences(tee).size(), 1U); |
| // The struct.set blocks one path, but not the path that skips it via the br. |
| EXPECT_EQ(graph.canMoveSet(tee, structSet).size(), 1U); |
| } |