diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 33 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 137 |
2 files changed, 160 insertions, 10 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 2e6a2d48d..7d9111e0b 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1172,7 +1172,10 @@ struct OptimizeInstructions if (curr->type == Type::unreachable) { return; } - if (getPassOptions().ignoreImplicitTraps) { + + auto passOptions = getPassOptions(); + + if (passOptions.ignoreImplicitTraps) { // A ref.cast traps when the RTTs do not line up, which can be of one of // two sorts of issues: // 1. The value being cast is not even a subtype of the cast type. In @@ -1204,6 +1207,32 @@ struct OptimizeInstructions } } + auto features = getModule()->features; + + // Repeated identical ref.cast operations are unnecessary, if using the + // exact same rtt - the result will be the same. Find the immediate child + // cast, if there is one, and see if it is identical. + // TODO: Look even further through incompatible casts? + auto* ref = curr->ref; + while (!ref->is<RefCast>()) { + auto* last = ref; + // RefCast falls through the value, so instead of calling getFallthrough() + // to look through all fallthroughs, we must iterate manually. Keep going + // until we reach either the end of things falling-through, or a cast. + ref = Properties::getImmediateFallthrough(ref, passOptions, features); + if (ref == last) { + break; + } + } + if (auto* child = ref->dynCast<RefCast>()) { + // Check if the casts are identical. + if (ExpressionAnalyzer::equal(curr->rtt, child->rtt) && + !EffectAnalyzer(passOptions, features, curr->rtt).hasSideEffects()) { + replaceCurrent(curr->ref); + return; + } + } + // ref.cast can be reordered with ref.as_non_null, // // (ref.cast (ref.as_non_null ..)) @@ -1345,7 +1374,7 @@ private: bool areConsecutiveInputsEqual(Expression* left, Expression* right) { // First, check for side effects. If there are any, then we can't even // assume things like local.get's of the same index being identical. - PassOptions passOptions = getPassOptions(); + auto passOptions = getPassOptions(); if (EffectAnalyzer(passOptions, getModule()->features, left) .hasSideEffects() || EffectAnalyzer(passOptions, getModule()->features, right) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 9e4ec450c..12eec8f41 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -4,14 +4,6 @@ (module ;; CHECK: (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)))) - - ;; CHECK: (type $array (array (mut i8))) - - ;; CHECK: (import "env" "get-i32" (func $get-i32 (result i32))) - (import "env" "get-i32" (func $get-i32 (result i32))) - - (type $empty (struct)) - (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) @@ -19,8 +11,15 @@ (field $i64 (mut i64)) )) + ;; CHECK: (type $empty (struct )) + (type $empty (struct)) + + ;; CHECK: (type $array (array (mut i8))) (type $array (array (mut i8))) + ;; CHECK: (import "env" "get-i32" (func $get-i32 (result i32))) + (import "env" "get-i32" (func $get-i32 (result i32))) + ;; 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 (result anyref) @@ -936,4 +935,126 @@ ) ) ) + + ;; CHECK: (func $ref-cast-squared (param $x eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-squared (param $x eqref) + ;; Identical ref.casts can be folded together. + (drop + (ref.cast + (ref.cast + (local.get $x) + (rtt.canon $struct) + ) + (rtt.canon $struct) + ) + ) + ) + ;; CHECK: (func $ref-cast-squared-fallthrough (param $x eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-squared-fallthrough (param $x eqref) + ;; A fallthrough in the middle does not prevent this optimization. + (drop + (ref.cast + (local.tee $x + (ref.cast + (local.get $x) + (rtt.canon $struct) + ) + ) + (rtt.canon $struct) + ) + ) + ) + ;; CHECK: (func $ref-cast-cubed (param $x eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-cubed (param $x eqref) + ;; Three and more also work. + (drop + (ref.cast + (ref.cast + (ref.cast + (local.get $x) + (rtt.canon $struct) + ) + (rtt.canon $struct) + ) + (rtt.canon $struct) + ) + ) + ) + ;; CHECK: (func $ref-cast-squared-different (param $x eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (rtt.canon $empty) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-squared-different (param $x eqref) + ;; Different casts cannot be folded. + (drop + (ref.cast + (ref.cast + (local.get $x) + (rtt.canon $empty) + ) + (rtt.canon $struct) + ) + ) + ) + ;; CHECK: (func $ref-cast-squared-effects (param $x eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (call $get-rtt) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $get-rtt) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-squared-effects (param $x eqref) + ;; The rtts are equal but have side effects, preventing optimization. + (drop + (ref.cast + (ref.cast + (local.get $x) + (call $get-rtt) + ) + (call $get-rtt) + ) + ) + ) + + ;; Helper function for above. + ;; CHECK: (func $get-rtt (result (rtt $empty)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get-rtt (result (rtt $empty)) + (unreachable) + ) ) |