Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions JSTests/wasm/stress/i31-get-bottom-type.js
Original file line number Diff line number Diff line change
@@ -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_<sx> 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();
5 changes: 5 additions & 0 deletions Source/JavaScriptCore/wasm/WasmBBQPlan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ void BBQPlan::work()

Ref<BBQCallee> callee = BBQCallee::create(functionIndexSpace, m_moduleInformation->nameSection().get(functionIndexSpace), Ref { m_profiledCallee });
std::unique_ptr<InternalFunction> 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.
Expand Down
6 changes: 3 additions & 3 deletions Source/JavaScriptCore/wasm/WasmFunctionParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
Loading