From a8b52c3a4aeacf3225e0b023765f81a1ddae38cb Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 Oct 2026 11:46:42 -0700 Subject: [PATCH] [RemoveUnusedBrs]: Skip BrOn when descriptor is unreachable In `RemoveUnusedBrs::optimizeGC`, `Optimizer` walks and optimizes `BrOn` expressions bottom-up without running `ReFinalize` until the walk completes. When an inner `BrOn` in the `desc` operand of a `BrOnCastDescEq` or `BrOnCastDescEqFail` is optimized into an unconditional branch, `curr->desc->type` becomes `Type::unreachable` while `curr->type` still holds its old reference type. If the outer `BrOn` then refines `curr->castType` and calls `curr->finalize()`, `curr->type` becomes `Type::unreachable`, causing `maybeCast(curr, oldType)` to fail its reference-type assertion. Check `curr->desc && curr->desc->type == Type::unreachable` in the early return at the start of `Optimizer::visitBrOn` alongside `curr->ref->type == Type::unreachable`. --- src/passes/RemoveUnusedBrs.cpp | 11 +++---- test/lit/passes/remove-unused-brs-desc.wast | 32 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index a2961da3233..64da911a151 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -855,12 +855,13 @@ struct RemoveUnusedBrs : public WalkerPass> { void visitBrOn(BrOn* curr) { // Ignore unreachable BrOns which we cannot improve anyhow. Note that - // we must check the ref field manually, as we may be changing types as - // we go here. (Another option would be to use a TypeUpdater here - // instead of calling ReFinalize at the very end, but that would be more - // complex and slower.) + // we must check the ref and desc fields manually, as we may be changing + // types as we go here. (Another option would be to use a TypeUpdater + // here instead of calling ReFinalize at the very end, but that would be + // more complex and slower.) if (curr->type == Type::unreachable || - curr->ref->type == Type::unreachable) { + curr->ref->type == Type::unreachable || + (curr->desc && curr->desc->type == Type::unreachable)) { return; } diff --git a/test/lit/passes/remove-unused-brs-desc.wast b/test/lit/passes/remove-unused-brs-desc.wast index cdf63f2de36..4cdb1ea4ed2 100644 --- a/test/lit/passes/remove-unused-brs-desc.wast +++ b/test/lit/passes/remove-unused-brs-desc.wast @@ -783,4 +783,36 @@ ) ) ) + + ;; CHECK: (func $unreachable-desc (type $17) (param $any anyref) (param $d (ref (exact $super.desc))) (result anyref) + ;; CHECK-NEXT: (block $block (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (block (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $any + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-desc (param $any anyref) (param $d (ref (exact $super.desc))) (result anyref) + ;; The inner br_on_cast is optimized into an unconditional br, making the + ;; descriptor operand of the outer br_on_cast_desc_eq unreachable before + ;; ReFinalize runs. + (block $block (result anyref) + (br_on_cast_desc_eq $block anyref (ref null (exact $super)) + (local.tee $any + (ref.i31 + (i32.const 0) + ) + ) + (br_on_cast $block (ref (exact $super.desc)) (ref (exact $super.desc)) + (local.get $d) + ) + ) + ) + ) )