diff options
-rw-r--r-- | src/ir/properties.h | 37 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 45 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 93 |
3 files changed, 109 insertions, 66 deletions
diff --git a/src/ir/properties.h b/src/ir/properties.h index cddcbc5f2..e7e96507c 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -255,34 +255,35 @@ inline Index getZeroExtBits(Expression* curr) { enum class FallthroughBehavior { AllowTeeBrIf, NoTeeBrIf }; -inline Expression* getImmediateFallthrough( - Expression* curr, +inline Expression** getImmediateFallthroughPtr( + Expression** currp, const PassOptions& passOptions, Module& module, FallthroughBehavior behavior = FallthroughBehavior::AllowTeeBrIf) { + auto* curr = *currp; // If the current node is unreachable, there is no value // falling through. if (curr->type == Type::unreachable) { - return curr; + return currp; } if (auto* set = curr->dynCast<LocalSet>()) { if (set->isTee() && behavior == FallthroughBehavior::AllowTeeBrIf) { - return set->value; + return &set->value; } } else if (auto* block = curr->dynCast<Block>()) { // if no name, we can't be broken to, and then can look at the fallthrough if (!block->name.is() && block->list.size() > 0) { - return block->list.back(); + return &block->list.back(); } } else if (auto* loop = curr->dynCast<Loop>()) { - return loop->body; + return &loop->body; } else if (auto* iff = curr->dynCast<If>()) { if (iff->ifFalse) { // Perhaps just one of the two actually returns. if (iff->ifTrue->type == Type::unreachable) { - return iff->ifFalse; + return &iff->ifFalse; } else if (iff->ifFalse->type == Type::unreachable) { - return iff->ifTrue; + return &iff->ifTrue; } } } else if (auto* br = curr->dynCast<Break>()) { @@ -302,25 +303,33 @@ inline Expression* getImmediateFallthrough( behavior == FallthroughBehavior::AllowTeeBrIf && EffectAnalyzer::canReorder( passOptions, module, br->condition, br->value)) { - return br->value; + return &br->value; } } else if (auto* tryy = curr->dynCast<Try>()) { if (!EffectAnalyzer(passOptions, module, tryy->body).throws()) { - return tryy->body; + return &tryy->body; } } else if (auto* as = curr->dynCast<RefCast>()) { - return as->ref; + return &as->ref; } else if (auto* as = curr->dynCast<RefAs>()) { // Extern conversions are not casts and actually produce new values. // Treating them as fallthroughs would lead to misoptimizations of // subsequent casts. if (as->op != ExternInternalize && as->op != ExternExternalize) { - return as->value; + return &as->value; } } else if (auto* br = curr->dynCast<BrOn>()) { - return br->ref; + return &br->ref; } - return curr; + return currp; +} + +inline Expression* getImmediateFallthrough( + Expression* curr, + const PassOptions& passOptions, + Module& module, + FallthroughBehavior behavior = FallthroughBehavior::AllowTeeBrIf) { + return *getImmediateFallthroughPtr(&curr, passOptions, module, behavior); } // Similar to getImmediateFallthrough, but looks through multiple children to diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index b725ef03f..6537e9b56 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1929,23 +1929,44 @@ struct OptimizeInstructions Builder builder(*getModule()); auto nullType = curr->type.getHeapType().getBottom(); { - auto* ref = curr->ref; + auto** refp = &curr->ref; while (1) { + auto* ref = *refp; + auto result = GCTypeUtils::evaluateCastCheck(ref->type, curr->type); if (result == GCTypeUtils::Success) { - // The cast will succeed, but we can't just remove the cast and - // replace it with `ref` because the intermediate expressions might - // have had side effects. We can replace the cast with a drop followed - // by a direct return of the value, though. - // - // TODO: Do this for non-null values as well by storing the value to - // return in a tee. + // The cast will succeed. This can only happen if the ref is a subtype + // of the cast instruction, which means we can replace the cast with + // the ref. + assert(Type::isSubType(ref->type, curr->type)); + if (curr->type != ref->type) { + refinalize = true; + } + // If there were no intermediate expressions, we can just skip the + // cast. + if (ref == curr->ref) { + replaceCurrent(ref); + return; + } + // Otherwise we can't just remove the cast and replace it with `ref` + // because the intermediate expressions might have had side effects. + // We can replace the cast with a drop followed by a direct return of + // the value, though. if (ref->type.isNull()) { + // We can materialize the resulting null value directly. replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), builder.makeRefNull(nullType))); return; } + // We need to use a tee to return the value since we can't materialize + // it directly. + auto scratch = builder.addVar(getFunction(), ref->type); + *refp = builder.makeLocalTee(scratch, ref, ref->type); + replaceCurrent( + builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeLocalGet(scratch, ref->type))); + return; } else if (result == GCTypeUtils::Failure) { // This cast cannot succeed, so it will trap. // Make sure to emit a block with the same type as us; leave updating @@ -1965,10 +1986,10 @@ struct OptimizeInstructions return; } - auto* last = ref; - ref = Properties::getImmediateFallthrough( - ref, getPassOptions(), *getModule()); - if (ref == last) { + auto** last = refp; + refp = Properties::getImmediateFallthroughPtr( + refp, getPassOptions(), *getModule()); + if (refp == last) { break; } } diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index c4d78d12d..ff880b475 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1108,24 +1108,36 @@ ) ) ;; CHECK: (func $ref-cast-squared-fallthrough (type $eqref_=>_none) (param $x eqref) + ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $struct - ;; CHECK-NEXT: (local.tee $x - ;; CHECK-NEXT: (ref.cast null $struct - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast null $struct + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $ref-cast-squared-fallthrough (type $eqref_=>_none) (param $x eqref) + ;; NOMNL-NEXT: (local $1 (ref null $struct)) ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.cast null $struct - ;; NOMNL-NEXT: (local.tee $x - ;; NOMNL-NEXT: (ref.cast null $struct - ;; NOMNL-NEXT: (local.get $x) + ;; NOMNL-NEXT: (block (result (ref null $struct)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.tee $x + ;; NOMNL-NEXT: (local.tee $1 + ;; NOMNL-NEXT: (ref.cast null $struct + ;; NOMNL-NEXT: (local.get $x) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.get $1) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) @@ -1877,12 +1889,7 @@ ;; CHECK: (func $ref-cast-static-null (type $void) ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) @@ -1932,12 +1939,7 @@ ;; NOMNL: (func $ref-cast-static-null (type $void) ;; NOMNL-NEXT: (local $a (ref null $A)) ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (block (result nullref) - ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.null none) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (ref.null none) - ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (ref.null none) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: (drop ;; NOMNL-NEXT: (block (result nullref) @@ -1994,7 +1996,7 @@ ) ;; A fallthrough works too. (drop - (ref.cast null $A + (ref.cast null $B (local.tee $a (ref.null none) ) @@ -2469,39 +2471,50 @@ ) ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (type $ref|eq|_=>_none) (param $x (ref eq)) + ;; CHECK-NEXT: (local $1 (ref $B)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast $A - ;; CHECK-NEXT: (block (result (ref eq)) - ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.cast $B - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast $B + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $ref-cast-static-fallthrough-remaining-nonnull (type $ref|eq|_=>_none) (param $x (ref eq)) + ;; NOMNL-NEXT: (local $1 (ref $B)) ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (ref.cast $A - ;; NOMNL-NEXT: (block (result (ref eq)) - ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining - ;; NOMNL-NEXT: (local.get $x) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (ref.cast $B - ;; NOMNL-NEXT: (local.get $x) + ;; NOMNL-NEXT: (block (result (ref $B)) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (block (result (ref $B)) + ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining + ;; NOMNL-NEXT: (local.get $x) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.tee $1 + ;; NOMNL-NEXT: (ref.cast $B + ;; NOMNL-NEXT: (local.get $x) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.get $1) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $ref-cast-static-fallthrough-remaining-nonnull (param $x (ref eq)) - ;; The input is non-nullable here, and the middle block is of a simpler - ;; type than either the parent or the child. This checks that we do not - ;; mis-optimize this case: In general the outer cast is not needed, but - ;; the middle block prevents us from seeing that (after other opts run, - ;; however, we would). + ;; The input is non-nullable here, and the middle block is of a simpler type + ;; than either the parent or the child. This checks that we do not + ;; mis-optimize this case: The outer cast is not needed, so we can optimize + ;; it out, but we have to be careful not to remove any side effects. (drop (ref.cast $A (block (result (ref eq)) |