From 96153b2f61a667a6b595a4626033c00a06b289e9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 18 May 2026 14:41:13 -0700 Subject: [PATCH 1/3] Skip folding branches of unreachable If OptimizeInstructions generally skips optimizing unreachable code, leaving that to DCE instead. But it did not have this check before folding identical instructions out of If arms. The fuzzer found a case where cont.new instructions were hoisted out of an unreachable If but not refinalized, resulting in the concretely-typed cont.new having an unreachable child. This caused an assertion failure in the validator. Fix the root problem by skipping this optimization for unreachable Ifs, but also fix the validator so that it will no longer crash on such invalid IR. --- src/passes/OptimizeInstructions.cpp | 2 +- src/wasm/wasm-validator.cpp | 3 +- .../lit/passes/optimize-instructions-mvp.wast | 31 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 9ea8a7aa982..e61deed16aa 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -5761,7 +5761,7 @@ struct OptimizeInstructions } } - if (!neverFold) { + if (!neverFold && curr->condition->type != Type::unreachable) { // Identical code on both arms can be folded out, e.g. // // (select diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index b099926112c..64b67e51edd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4340,7 +4340,8 @@ void FunctionValidator::visitContNew(ContNew* curr) { auto cont = curr->type.getHeapType().getContinuation(); assert(cont.type.isSignature()); - shouldBeTrue(HeapType::isSubType(curr->func->type.getHeapType(), cont.type), + shouldBeTrue(curr->func->type.isRef() && + HeapType::isSubType(curr->func->type.getHeapType(), cont.type), curr, "cont.new function reference must be a subtype"); } diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index a301cc6858c..021b868bcc1 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -16046,6 +16046,37 @@ ) ) ) + ;; CHECK: (func $ternary-identical-arms-if-unreachable (param $x i32) (param $y i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-if-unreachable (param $x i32) (param $y i32) + (drop + ;; Leave this to DCE instead of optimizing. + (if (result i32) + (unreachable) + (then + (i32.eqz (local.get $x)) + ) + (else + (i32.eqz (local.get $y)) + ) + ) + ) + ) ;; CHECK: (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (f32.demote_f64 From 53b72266a5ed5c2b9c8a5cb77c8170aed43615eb Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 18 May 2026 21:33:35 -0700 Subject: [PATCH 2/3] update tests --- .../legalize-js-interface-exported-helpers.wast | 6 ++++-- test/lit/passes/optimize-instructions-gc.wast | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/test/lit/passes/legalize-js-interface-exported-helpers.wast b/test/lit/passes/legalize-js-interface-exported-helpers.wast index bed768f1a0d..18b93cfc987 100644 --- a/test/lit/passes/legalize-js-interface-exported-helpers.wast +++ b/test/lit/passes/legalize-js-interface-exported-helpers.wast @@ -8,8 +8,6 @@ (module (export "get_i64" (func $get_i64)) (import "env" "imported" (func $imported (result i64))) - (export "__set_temp_ret" (func $__set_temp_ret)) - (export "__get_temp_ret" (func $__get_temp_ret)) ;; CHECK: (type $0 (func (result i32))) ;; CHECK: (type $1 (func (result i64))) @@ -20,6 +18,10 @@ ;; CHECK: (export "get_i64" (func $legalstub$get_i64)) + ;; CHECK: (export "__set_temp_ret" (func $__set_temp_ret)) + (export "__set_temp_ret" (func $__set_temp_ret)) + ;; CHECK: (export "__get_temp_ret" (func $__get_temp_ret)) + (export "__get_temp_ret" (func $__get_temp_ret)) ;; CHECK: (func $get_i64 (result i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $legalfunc$imported) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 9dc52715724..c8532cf7ae4 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -3695,13 +3695,15 @@ ;; CHECK: (func $comp-i31-struct-unreachable-if (type $4) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (if (result i32) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if (result (ref i31)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From f678024974ec49e6851e618515ed6f6ec396ba06 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 19 May 2026 18:19:10 -0700 Subject: [PATCH 3/3] add test --- test/gtest/validator.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/gtest/validator.cpp b/test/gtest/validator.cpp index e984945226c..906e9369464 100644 --- a/test/gtest/validator.cpp +++ b/test/gtest/validator.cpp @@ -90,3 +90,28 @@ TEST(ValidatorTest, UnreachableCastDescEq) { WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet; EXPECT_FALSE(WasmValidator{}.validate(func.get(), module, flags)); } + +TEST(ValidatorTest, ContNewUnreachable) { + Module module; + module.features = FeatureSet::All; + Builder builder(module); + + auto sig = Signature(Type::none, Type::none); + module.addFunction(builder.makeFunction( + "f", {}, Signature(Type::none, Type::none), {}, builder.makeUnreachable())); + + auto contType = HeapType(Continuation(sig)); + auto contRefType = Type(contType, NonNullable); + + // Create a cont.new with a concrete type despite an unreachable child. This + // is not valid, but we should not crash while validating it. + auto* contNew = builder.makeContNew(contType, builder.makeUnreachable()); + contNew->type = contRefType; + + auto testFunc = builder.makeFunction( + "test", {}, Signature(Type::none, contRefType), {}, contNew); + + auto flags = + WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet; + EXPECT_FALSE(WasmValidator{}.validate(testFunc.get(), module, flags)); +}