From cf038f1791ef531624098d4fd702eea022025830 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Mar 2026 12:07:09 -0700 Subject: [PATCH 1/4] Fix assertion in br_on_cast finalization If the br_on_cast is invalid because its input and output types are unrelated, the GLB used as the new cast type will be unreachable. This caused an assertion that the GLB was a reference to fail. Bail out early in this case and leave the unreachable cast type for the validator to find. --- src/wasm/wasm.cpp | 5 ++++ test/lit/validation/shared-cast.wast | 36 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/lit/validation/shared-cast.wast diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c05a23dbc9a..1560158301c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1131,6 +1131,11 @@ void BrOn::finalize() { // cast behavior. This satisfies the constraint we had before Custom // Descriptors that the cast type is a subtype of the input type. castType = Type::getGreatestLowerBound(castType, ref->type); + if (castType == Type::unreachable) { + // This is not valid. Leave it for the validator to catch. + type = Type::unreachable; + return; + } assert(castType.isRef()); } else if (op == BrOnCastDescEq || op == BrOnCastDescEqFail) { if (desc->type.isNull()) { diff --git a/test/lit/validation/shared-cast.wast b/test/lit/validation/shared-cast.wast new file mode 100644 index 00000000000..60947b93986 --- /dev/null +++ b/test/lit/validation/shared-cast.wast @@ -0,0 +1,36 @@ +;; RUN: not wasm-opt %s --all-features 2>&1 | filecheck %s + +(module + (type $shared (shared (struct))) + (type $unshared (struct)) + + (func $shared-to-unshared (param $s (ref $shared)) + ;; CHECK: [wasm-validator error in function shared-to-unshared] unreachable instruction must have unreachable child + (drop + (ref.cast (ref $unshared) + (local.get $s) + ) + ) + ) + + (func $unshared-to-shared (param $u (ref $unshared)) + ;; CHECK: [wasm-validator error in function unshared-to-shared] unreachable instruction must have unreachable child + (drop + (ref.cast (ref $shared) + (local.get $u) + ) + ) + ) + + (func $br_on_shared-to-unshared (param $s (ref $shared)) + (block $l (result (ref $unshared)) + ;; CHECK: [wasm-validator error in function br_on_shared-to-unshared] unreachable instruction must have unreachable child + (drop + (br_on_cast $l (ref $shared) (ref $unshared) + (local.get $s) + ) + ) + (unreachable) + ) + ) +) From cc1b0f5f3b59b8af42a8e9590e1a956cd61b1d14 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Mar 2026 12:20:53 -0700 Subject: [PATCH 2/4] fix test --- test/lit/validation/shared-cast.wast | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lit/validation/shared-cast.wast b/test/lit/validation/shared-cast.wast index 60947b93986..c2a07481894 100644 --- a/test/lit/validation/shared-cast.wast +++ b/test/lit/validation/shared-cast.wast @@ -5,7 +5,7 @@ (type $unshared (struct)) (func $shared-to-unshared (param $s (ref $shared)) - ;; CHECK: [wasm-validator error in function shared-to-unshared] unreachable instruction must have unreachable child + ;; CHECK: [wasm-validator error in function shared-to-unshared] unexpected false: unreachable instruction must have unreachable child (drop (ref.cast (ref $unshared) (local.get $s) @@ -14,7 +14,7 @@ ) (func $unshared-to-shared (param $u (ref $unshared)) - ;; CHECK: [wasm-validator error in function unshared-to-shared] unreachable instruction must have unreachable child + ;; CHECK: [wasm-validator error in function unshared-to-shared] unexpected false: unreachable instruction must have unreachable child (drop (ref.cast (ref $shared) (local.get $u) @@ -24,7 +24,7 @@ (func $br_on_shared-to-unshared (param $s (ref $shared)) (block $l (result (ref $unshared)) - ;; CHECK: [wasm-validator error in function br_on_shared-to-unshared] unreachable instruction must have unreachable child + ;; CHECK: [wasm-validator error in function br_on_shared-to-unshared] unexpected false: unreachable instruction must have unreachable child (drop (br_on_cast $l (ref $shared) (ref $unshared) (local.get $s) From bb54a4d3b6eb9dd6e8ec270cb0d03a891c1ab80a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 17 Mar 2026 17:45:07 -0700 Subject: [PATCH 3/4] Improve messages --- src/wasm/wasm-ir-builder.cpp | 1 - src/wasm/wasm-validator.cpp | 24 ++++++------------------ src/wasm/wasm.cpp | 20 ++++++++++++++------ test/lit/validation/shared-cast.wast | 16 ++++++++-------- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index e535c21abb0..c6ee41284e5 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1985,7 +1985,6 @@ Result<> IRBuilder::makeRefCast(Type type, bool isDesc) { return Err{"cast target must have descriptor"}; } } - RefCast curr; curr.type = type; // Placeholder value to differentiate ref.cast_desc_eq. diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index aef68ec1aa0..87c4e6dd165 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3123,8 +3123,8 @@ void FunctionValidator::visitRefTest(RefTest* curr) { return; } shouldBeEqual( - curr->castType.getHeapType().getBottom(), - curr->ref->type.getHeapType().getBottom(), + HeapType(curr->castType.getHeapType().getTop()), + HeapType(curr->ref->type.getHeapType().getTop()), curr, "ref.test target type and ref type must have a common supertype"); @@ -3167,27 +3167,15 @@ void FunctionValidator::visitRefCast(RefCast* curr) { curr->ref->type.isRef(), curr, "ref.cast ref must have ref type")) { return; } - // If the cast is unreachable but not the ref (we ruled out the former - // earlier), then the cast is unreachable because the cast type had no - // common supertype with the ref, which is invalid. This is the same as the - // check below us, but we must do it first (as getHeapType fails otherwise). - if (!shouldBeUnequal( - curr->type, - Type(Type::unreachable), - curr, - "ref.cast target type and ref type must have a common supertype")) { - return; - } // Also error (more generically) on i32 and anything else invalid here. if (!shouldBeTrue(curr->type.isRef(), curr, "ref.cast must have ref type")) { return; } shouldBeEqual( - curr->type.getHeapType().getBottom(), - curr->ref->type.getHeapType().getBottom(), + HeapType(curr->type.getHeapType().getTop()), + HeapType(curr->ref->type.getHeapType().getTop()), curr, "ref.cast target type and ref type must have a common supertype"); - // We should never have a nullable cast of a non-nullable reference, since // that unnecessarily loses type information. shouldBeTrue(curr->ref->type.isNullable() || curr->type.isNonNullable(), @@ -3259,8 +3247,8 @@ void FunctionValidator::visitBrOn(BrOn* curr) { } if (curr->ref->type != Type::unreachable) { shouldBeEqual( - curr->castType.getHeapType().getBottom(), - curr->ref->type.getHeapType().getBottom(), + HeapType(curr->castType.getHeapType().getTop()), + HeapType(curr->ref->type.getHeapType().getTop()), curr, "br_on_cast* target type and ref type must have a common supertype"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 1560158301c..8b35e6c08fe 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1059,7 +1059,12 @@ void RefTest::finalize() { } else { type = Type::i32; // Do not unnecessarily lose type information. - castType = Type::getGreatestLowerBound(castType, ref->type); + auto newCastType = Type::getGreatestLowerBound(castType, ref->type); + if (newCastType == Type::unreachable) { + // This is invalid. Leave the existing types for the validator to catch. + return; + } + castType = newCastType; } } @@ -1091,7 +1096,8 @@ void RefCast::finalize() { // We reach this before validation, so the input type might be totally wrong. // Return early in this case to avoid doing the wrong thing below. - if (!ref->type.isRef()) { + if (!ref->type.isRef() || !type.isRef() || + ref->type.getHeapType().getTop() != type.getHeapType().getTop()) { return; } @@ -1130,12 +1136,14 @@ void BrOn::finalize() { // cast type, we can improve the cast type in a way that will not change the // cast behavior. This satisfies the constraint we had before Custom // Descriptors that the cast type is a subtype of the input type. - castType = Type::getGreatestLowerBound(castType, ref->type); - if (castType == Type::unreachable) { - // This is not valid. Leave it for the validator to catch. - type = Type::unreachable; + auto newCastType = Type::getGreatestLowerBound(castType, ref->type); + if (newCastType == Type::unreachable) { + // This is not valid. Leave the original cast type in place for the + // validator to catch. + type = ref->type; return; } + castType = newCastType; assert(castType.isRef()); } else if (op == BrOnCastDescEq || op == BrOnCastDescEqFail) { if (desc->type.isNull()) { diff --git a/test/lit/validation/shared-cast.wast b/test/lit/validation/shared-cast.wast index c2a07481894..8deb1c39cff 100644 --- a/test/lit/validation/shared-cast.wast +++ b/test/lit/validation/shared-cast.wast @@ -4,27 +4,27 @@ (type $shared (shared (struct))) (type $unshared (struct)) - (func $shared-to-unshared (param $s (ref $shared)) - ;; CHECK: [wasm-validator error in function shared-to-unshared] unexpected false: unreachable instruction must have unreachable child + (func $test-shared-to-unshared (param $s (ref $shared)) + ;; CHECK: [wasm-validator error in function test-shared-to-unshared] any != (shared any): ref.test target type and ref type must have a common supertype (drop - (ref.cast (ref $unshared) + (ref.test (ref $unshared) (local.get $s) ) ) ) - (func $unshared-to-shared (param $u (ref $unshared)) - ;; CHECK: [wasm-validator error in function unshared-to-shared] unexpected false: unreachable instruction must have unreachable child + (func $cast-shared-to-unshared (param $s (ref $shared)) + ;; CHECK: [wasm-validator error in function cast-shared-to-unshared] any != (shared any): ref.cast target type and ref type must have a common supertype (drop - (ref.cast (ref $shared) - (local.get $u) + (ref.cast (ref $unshared) + (local.get $s) ) ) ) (func $br_on_shared-to-unshared (param $s (ref $shared)) (block $l (result (ref $unshared)) - ;; CHECK: [wasm-validator error in function br_on_shared-to-unshared] unexpected false: unreachable instruction must have unreachable child + ;; CHECK: [wasm-validator error in function br_on_shared-to-unshared] any != (shared any): br_on_cast* target type and ref type must have a common supertype (drop (br_on_cast $l (ref $shared) (ref $unshared) (local.get $s) From 7e8497f600a1353e530baa6e261fa8beb5276431 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 18 Mar 2026 10:22:20 -0700 Subject: [PATCH 4/4] undo whitespace change --- src/wasm/wasm-ir-builder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index c6ee41284e5..e535c21abb0 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1985,6 +1985,7 @@ Result<> IRBuilder::makeRefCast(Type type, bool isDesc) { return Err{"cast target must have descriptor"}; } } + RefCast curr; curr.type = type; // Placeholder value to differentiate ref.cast_desc_eq.