diff --git a/JSTests/wasm/stress/i31-get-bottom-type.js b/JSTests/wasm/stress/i31-get-bottom-type.js new file mode 100644 index 000000000000..aa86784f3ced --- /dev/null +++ b/JSTests/wasm/stress/i31-get-bottom-type.js @@ -0,0 +1,55 @@ +//@ runDefaultWasm("-m", "--useConcurrentJIT=0", "--thresholdForBBQOptimizeAfterWarmUp=10", "--thresholdForOMGOptimizeAfterWarmUp=20") + +import { instantiate } from "../gc/wast-wrapper.js"; +import * as assert from "../assert.js"; + +// i31.get_s and i31.get_u accept any subtype of i31ref, including the +// bottom type none. A null operand traps at runtime. +function testI31GetBottom() { + for (const op of ["i31.get_s", "i31.get_u"]) { + const m = instantiate(` + (module + (func (export "f") (result i32) + (${op} (ref.null none)))) + `); + for (let i = 0; i < wasmTestLoopCount; i++) + assert.throws(() => m.exports.f(), WebAssembly.RuntimeError, "i31.get_ to a null reference"); + } +} + +// any.convert_extern accepts any subtype of externref, including the +// bottom type noextern. +function testAnyConvertExternBottom() { + const m = instantiate(` + (module + (func (export "f") (result anyref) + (any.convert_extern (ref.null noextern)))) + `); + for (let i = 0; i < wasmTestLoopCount; i++) + assert.eq(m.exports.f(), null); +} + +// After a reachable block end, the BBQ pass keeps the precise type of the +// block result, here (ref null none), while the validating pass used the +// declared i31ref. The i31.get_s type check then failed only in the BBQ +// pass, and BBQPlan::work dereferenced the failed compilation result +// (https://github.com/oven-sh/bun/issues/40770). +function testI31GetBrOnNullBlockResult() { + const m = instantiate(` + (module + (func (export "f") (result i32) + (block $l1 + (return + (i31.get_s + (br_on_null $l1 + (block $l2 (result i31ref) + (ref.null none)))))) + (i32.const -1))) + `); + for (let i = 0; i < wasmTestLoopCount; i++) + assert.eq(m.exports.f(), -1); +} + +testI31GetBottom(); +testAnyConvertExternBottom(); +testI31GetBrOnNullBlockResult(); diff --git a/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp b/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp index 158107aae7a6..ed9dbffef349 100644 --- a/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp +++ b/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp @@ -98,6 +98,11 @@ void BBQPlan::work() Ref callee = BBQCallee::create(functionIndexSpace, m_moduleInformation->nameSection().get(functionIndexSpace), Ref { m_profiledCallee }); std::unique_ptr function = compileFunction(m_functionIndex, callee.get(), context, unlinkedWasmToWasmCalls); + if (!function) [[unlikely]] { + // compileFunction already called fail(), which completed the plan. + // The function keeps running in the interpreter tier. + return; + } // The finished code is patched further (wasm call-site linking in installOptimizedCallee) and // flushed once afterward, so skip LinkBuffer's finalize instruction-cache flush. diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index 4f4cf92ad41f..cc225aa7aea1 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -2463,7 +2463,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) case ExtGCOpType::I31GetS: { TypedExpression ref; WASM_TRY_POP_EXPRESSION_STACK_INTO(ref, "i31.get_s"); - WASM_VALIDATOR_FAIL_IF(!isI31ref(ref.type()), "i31.get_s ref to type ", ref.type(), " expected ", TypeKind::I31ref); + WASM_VALIDATOR_FAIL_IF(!isSubtype(ref.type(), Type { TypeKind::RefNull, typeIndexFromTypeKind(TypeKind::I31ref) }), "i31.get_s ref to type ", ref.type(), " expected ", TypeKind::I31ref); ExpressionType result; WASM_TRY_ADD_TO_CONTEXT(addI31GetS(ref, result)); @@ -2474,7 +2474,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) case ExtGCOpType::I31GetU: { TypedExpression ref; WASM_TRY_POP_EXPRESSION_STACK_INTO(ref, "i31.get_u"); - WASM_VALIDATOR_FAIL_IF(!isI31ref(ref.type()), "i31.get_u ref to type ", ref.type(), " expected ", TypeKind::I31ref); + WASM_VALIDATOR_FAIL_IF(!isSubtype(ref.type(), Type { TypeKind::RefNull, typeIndexFromTypeKind(TypeKind::I31ref) }), "i31.get_u ref to type ", ref.type(), " expected ", TypeKind::I31ref); ExpressionType result; WASM_TRY_ADD_TO_CONTEXT(addI31GetU(ref, result)); @@ -3024,7 +3024,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) case ExtGCOpType::AnyConvertExtern: { TypedExpression reference; WASM_TRY_POP_EXPRESSION_STACK_INTO(reference, "any.convert_extern"_s); - WASM_VALIDATOR_FAIL_IF(!isExternref(reference.type()), "any.convert_extern reference to type "_s, reference.type(), " expected "_s, TypeKind::Externref); + WASM_VALIDATOR_FAIL_IF(!isSubtype(reference.type(), externrefType()), "any.convert_extern reference to type "_s, reference.type(), " expected "_s, TypeKind::Externref); ExpressionType result; WASM_TRY_ADD_TO_CONTEXT(addAnyConvertExtern(reference, result));