summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp22
-rw-r--r--test/lit/passes/optimize-instructions-gc.wast262
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)
+ )
+ )
+ )
+ )
)