diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 22 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 262 |
2 files changed, 267 insertions, 17 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 1a474be2c..81d0aef76 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2501,11 +2501,33 @@ private: // Ignore extraneous things and compare them syntactically. We can also // look at the full fallthrough for both sides now. left = Properties::getFallthrough(left, passOptions, *getModule()); + auto* originalRight = right; right = Properties::getFallthrough(right, passOptions, *getModule()); if (!ExpressionAnalyzer::equal(left, right)) { return false; } + // We must also not have non-fallthrough effects that invalidate us, such as + // this situation: + // + // (local.get $x) + // (block + // (local.set $x ..) + // (local.get $x) + // ) + // + // The fallthroughs are identical, but the set may cause us to read a + // different value. + if (originalRight != right) { + // TODO: We could be more precise here and ignore right itself in + // originalRightEffects. + auto originalRightEffects = effects(originalRight); + auto rightEffects = effects(right); + if (originalRightEffects.invalidates(rightEffects)) { + return false; + } + } + // To be equal, they must also be known to return the same result // deterministically. return !Properties::isGenerative(left); diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 4a57c1919..ce4f2cedb 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -50,7 +50,7 @@ ;; These functions test if an `if` with subtyped arms is correctly folded ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold) - ;; CHECK: (func $if-arms-subtype-fold (type $26) (result anyref) + ;; CHECK: (func $if-arms-subtype-fold (type $27) (result anyref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $if-arms-subtype-fold (result anyref) @@ -65,7 +65,7 @@ ) ) ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold) - ;; CHECK: (func $if-arms-subtype-nofold (type $27) (param $i31ref i31ref) (result anyref) + ;; CHECK: (func $if-arms-subtype-nofold (type $28) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (if (result anyref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -308,7 +308,7 @@ ) ) - ;; CHECK: (func $redundant-non-null-casts (type $28) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) + ;; CHECK: (func $redundant-non-null-casts (type $29) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) @@ -395,7 +395,7 @@ ) ) - ;; CHECK: (func $get-eqref (type $29) (result eqref) + ;; CHECK: (func $get-eqref (type $30) (result eqref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $get-eqref (result eqref) @@ -659,7 +659,7 @@ ) ) - ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (type $30) (param $x (ref any)) (param $y anyref) + ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (type $31) (param $x (ref any)) (param $y anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.as_non_null @@ -680,7 +680,7 @@ ) ) ) - ;; CHECK: (func $ternary-identical-arms (type $31) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) + ;; CHECK: (func $ternary-identical-arms (type $32) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (if (result (ref null $struct)) @@ -735,7 +735,7 @@ ) ) ) - ;; CHECK: (func $ternary-identical-arms-no-side-effect (type $32) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) + ;; CHECK: (func $ternary-identical-arms-no-side-effect (type $33) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct $i8 ;; CHECK-NEXT: (select (result (ref $struct)) @@ -1087,7 +1087,7 @@ ) ) - ;; CHECK: (func $hoist-LUB-danger (type $33) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) + ;; CHECK: (func $hoist-LUB-danger (type $34) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then @@ -1126,7 +1126,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-of-non-null (type $34) (param $struct (ref $struct)) + ;; CHECK: (func $incompatible-cast-of-non-null (type $35) (param $struct (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref none)) ;; CHECK-NEXT: (drop @@ -1538,7 +1538,7 @@ ) ) - ;; CHECK: (func $ref.test-unreachable (type $35) (param $A (ref null $A)) + ;; CHECK: (func $ref.test-unreachable (type $36) (param $A (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $A) ;; CHECK-NEXT: (unreachable) @@ -2182,7 +2182,7 @@ ) ) - ;; CHECK: (func $ref-test-static-impossible (type $36) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) + ;; CHECK: (func $ref-test-static-impossible (type $37) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop @@ -2262,14 +2262,14 @@ ) ) - ;; CHECK: (func $impossible (type $37) (result (ref none)) + ;; CHECK: (func $impossible (type $38) (result (ref none)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $impossible (result (ref none)) (unreachable) ) - ;; CHECK: (func $bottom-type-accessors (type $38) (param $bot (ref none)) (param $null nullref) + ;; CHECK: (func $bottom-type-accessors (type $39) (param $bot (ref none)) (param $null nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -2602,7 +2602,7 @@ ) ) - ;; CHECK: (func $incompatible-cast-separate-fallthrough (type $39) (param $eqref eqref) (result structref) + ;; CHECK: (func $incompatible-cast-separate-fallthrough (type $40) (param $eqref eqref) (result structref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $eqref ;; CHECK-NEXT: (block (result (ref i31)) @@ -2739,7 +2739,7 @@ ) ) - ;; CHECK: (func $as_of_unreachable (type $40) (result (ref $A)) + ;; CHECK: (func $as_of_unreachable (type $41) (result (ref $A)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $as_of_unreachable (result (ref $A)) @@ -2753,7 +2753,7 @@ ) ) - ;; CHECK: (func $cast-internalized-extern (type $41) (param $externref externref) + ;; CHECK: (func $cast-internalized-extern (type $42) (param $externref externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (extern.internalize @@ -2882,7 +2882,7 @@ ) ) - ;; CHECK: (func $refinalize.select.arm.unknown (type $42) (param $x i32) + ;; CHECK: (func $refinalize.select.arm.unknown (type $25) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $void2) ;; CHECK-NEXT: (ref.func $refinalize.select.arm) @@ -3439,4 +3439,232 @@ ) ) ) + + ;; CHECK: (func $array.new_fixed_fallthrough (type $void) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new_fixed $array 2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (i32.const 43) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.new_fixed_fallthrough + ;; The fallthroughs are identical. The call in the middle must only happen + ;; once, which we achieve by storing it to a local. + (drop + (array.new_fixed $array 2 + (i32.const 42) + (block (result i32) + (call $array.new_fixed_fallthrough) + (i32.const 42) + ) + ) + ) + ;; As above with order flipped. + (drop + (array.new_fixed $array 2 + (block (result i32) + (call $array.new_fixed_fallthrough) + (i32.const 42) + ) + (i32.const 42) + ) + ) + ;; Still identical fallthroughs, but different effects now. + (drop + (array.new_fixed $array 2 + (block (result i32) + (call $array.new_fixed) + (i32.const 42) + ) + (block (result i32) + (call $array.new_fixed_fallthrough) + (i32.const 42) + ) + ) + ) + ;; Different fallthrough, so we cannot optimize. + (drop + (array.new_fixed $array 2 + (block (result i32) + (call $array.new_fixed) + (i32.const 42) + ) + (block (result i32) + (call $array.new_fixed_fallthrough) + (i32.const 43) ;; this changed + ) + ) + ) + ) + + ;; CHECK: (func $array.new_fixed_fallthrough_local (type $25) (param $x i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $array)) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new_fixed $array 2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.new_fixed_fallthrough_local (param $x i32) + ;; The fallthroughs are identical local.gets. + (drop + (array.new_fixed $array 2 + (local.get $x) + (block (result i32) + (call $array.new_fixed_fallthrough) + (local.get $x) + ) + ) + ) + ;; Flipped order. + (drop + (array.new_fixed $array 2 + (block (result i32) + (call $array.new_fixed_fallthrough) + (local.get $x) + ) + (local.get $x) + ) + ) + ;; The effect is now a set. We can still optimize. + (drop + (array.new_fixed $array 2 + (block (result i32) + (local.set $x + (i32.const 2) + ) + (local.get $x) + ) + (local.get $x) + ) + ) + ;; Flipped order, and now the set invalidates the get after it, preventing + ;; optimization. + (drop + (array.new_fixed $array 2 + (local.get $x) + (block (result i32) + (local.set $x + (i32.const 1) + ) + (local.get $x) + ) + ) + ) + ) ) |