diff --git a/JSTests/wasm/gc/extern.js b/JSTests/wasm/gc/extern.js new file mode 100644 index 0000000000000..ebc23b956f4c3 --- /dev/null +++ b/JSTests/wasm/gc/extern.js @@ -0,0 +1,183 @@ +//@ runWebAssemblySuite("--useWebAssemblyTypedFunctionReferences=true", "--useWebAssemblyGC=true") + +import * as assert from "../assert.js"; +import { compile, instantiate } from "./wast-wrapper.js"; + +function testInternalize() { + { + let m = instantiate(` + (module + (func (export "f") (param externref) (result i32) + (i31.get_s (ref.cast i31 (extern.internalize (local.get 0))))) + ) + `); + assert.eq(m.exports.f(0), 0); + assert.eq(m.exports.f(5), 5); + assert.eq(m.exports.f(5.0), 5); + assert.eq(m.exports.f(2 ** 30 - 1), 2 ** 30 - 1); + assert.eq(m.exports.f(-(2 ** 30)), -(2 ** 30)); + assert.throws( + () => m.exports.f(2 ** 30), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(2 ** 32), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(-(2 ** 30) - 1), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(5.3), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f("foo"), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(Symbol()), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + } + + // With internalize available, it's possible to create hostref values that + // need to be distinguished from eqref values. + { + let m = instantiate(` + (module + (func (export "f") (param externref) + (ref.cast eq (extern.internalize (local.get 0))) + drop) + ) + `); + m.exports.f(0); + assert.throws( + () => m.exports.f("foo"), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(2 ** 32), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(Symbol()), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + } + + { + let m = instantiate(` + (module + (func (export "f") (param externref) + (ref.cast array (extern.internalize (local.get 0))) + drop) + ) + `); + assert.throws( + () => m.exports.f("foo"), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(2 ** 32), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + assert.throws( + () => m.exports.f(Symbol()), + WebAssembly.RuntimeError, + "ref.cast failed to cast reference to target heap type" + ); + } +} + +function testExternalize() { + { + let m = instantiate(` + (module + (func (export "f") (param i32) (result externref) + (extern.externalize (i31.new (local.get 0)))) + ) + `); + assert.eq(m.exports.f(0), 0); + assert.eq(m.exports.f(5), 5); + } + + { + let m = instantiate(` + (module + (type (array i32)) + (func (export "f") (result externref) + (extern.externalize (array.new_canon 0 (i32.const 42) (i32.const 5)))) + ) + `); + assert.eq(typeof m.exports.f(), "object"); + } +} + +function testRoundtrip() { + { + let m = instantiate(` + (module + (func (param anyref) (result anyref) + (extern.internalize (extern.externalize (local.get 0)))) + (func (export "f") (param i32) (result i32) + (i31.get_s (ref.cast i31 (call 0 (i31.new (local.get 0)))))) + ) + `); + assert.eq(m.exports.f(0), 0); + assert.eq(m.exports.f(5), 5); + assert.eq(m.exports.f(2**30 - 1), 2**30 - 1); + } + + { + let m = instantiate(` + (module + (type (array i32)) + (func (param anyref) (result anyref) + (extern.internalize (extern.externalize (local.get 0)))) + (func (export "f") (result i32) + (array.get 0 + (ref.cast 0 (call 0 (array.new_canon 0 (i32.const 42) (i32.const 5)))) + (i32.const 0))) + ) + `); + assert.eq(m.exports.f(), 42); + } +} + +function testTable() { + { + let m = instantiate(` + (module + (type (struct)) + (table (export "t") 10 externref) + (func (table.set (i32.const 0) (extern.externalize (struct.new_canon 0)))) + (func (export "isStruct") (result i32) + (ref.test 0 (extern.internalize (table.get (i32.const 1))))) + (start 0) + ) + `); + assert.eq(m.exports.t.get(0) !== null, true); + assert.eq(m.exports.t.get(1), null); + m.exports.t.set(1, m.exports.t.get(0)); + assert.eq(m.exports.t.get(1) !== null, true); + assert.eq(m.exports.isStruct(), 1); + } +} + +testInternalize(); +testExternalize(); +testRoundtrip(); +testTable(); diff --git a/JSTests/wasm/wasm.json b/JSTests/wasm/wasm.json index 4861dddb37710..8c283c3db33e4 100644 --- a/JSTests/wasm/wasm.json +++ b/JSTests/wasm/wasm.json @@ -291,6 +291,8 @@ "ref.cast": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 65 }, "ref.test_null": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 72 }, "ref.cast_null": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 73 }, + "extern.internalize": { "category": "gc", "value": 251, "return": ["anyref"], "parameter": ["externref"], "immediate": [], "extendedOp": 112 }, + "extern.externalize": { "category": "gc", "value": 251, "return": ["externref"], "parameter": ["anyref"], "immediate": [], "extendedOp": 113 }, "i32.trunc_sat_f32_s": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 0 }, "i32.trunc_sat_f32_u": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 1 }, diff --git a/Source/JavaScriptCore/bytecode/BytecodeList.rb b/Source/JavaScriptCore/bytecode/BytecodeList.rb index df68cf69b86ff..fefdb261a05b0 100644 --- a/Source/JavaScriptCore/bytecode/BytecodeList.rb +++ b/Source/JavaScriptCore/bytecode/BytecodeList.rb @@ -1926,4 +1926,10 @@ value: VirtualRegister, } +op :extern_externalize, + args: { + dst: VirtualRegister, + reference: VirtualRegister, + } + end_section :Wasm diff --git a/Source/JavaScriptCore/llint/WebAssembly32_64.asm b/Source/JavaScriptCore/llint/WebAssembly32_64.asm index c4077488f28f7..809f84f3455d7 100644 --- a/Source/JavaScriptCore/llint/WebAssembly32_64.asm +++ b/Source/JavaScriptCore/llint/WebAssembly32_64.asm @@ -1164,3 +1164,8 @@ wasmOp(array_len, WasmArrayLen, macro(ctx) .nullArray: throwException(NullArrayLen) end) + +wasmOp(extern_externalize, WasmExternExternalize, macro(ctx) + mload2i(ctx, m_reference, t1, t0) + return2i(ctx, t1, t0) +end) diff --git a/Source/JavaScriptCore/llint/WebAssembly64.asm b/Source/JavaScriptCore/llint/WebAssembly64.asm index 70cdda6b406f7..887a514b96247 100644 --- a/Source/JavaScriptCore/llint/WebAssembly64.asm +++ b/Source/JavaScriptCore/llint/WebAssembly64.asm @@ -1293,6 +1293,11 @@ wasmOp(array_len, WasmArrayLen, macro(ctx) throwException(NullArrayLen) end) +wasmOp(extern_externalize, WasmExternExternalize, macro(ctx) + mloadp(ctx, m_reference, t0) + returnq(ctx, t0) +end) + if ARM64E global _wasmTailCallJSEntrySlowPathTrampoline _wasmTailCallJSEntrySlowPathTrampoline: diff --git a/Source/JavaScriptCore/runtime/JSType.cpp b/Source/JavaScriptCore/runtime/JSType.cpp index b9b13f6577eea..89d552b1a1c15 100644 --- a/Source/JavaScriptCore/runtime/JSType.cpp +++ b/Source/JavaScriptCore/runtime/JSType.cpp @@ -113,6 +113,7 @@ void printInternal(PrintStream& out, JSC::JSType type) CASE(JSWeakSetType) CASE(WebAssemblyModuleType) CASE(WebAssemblyInstanceType) + CASE(WebAssemblyGCObjectType) CASE(StringObjectType) CASE(DerivedStringObjectType) CASE(MaxJSType) diff --git a/Source/JavaScriptCore/runtime/JSType.h b/Source/JavaScriptCore/runtime/JSType.h index 47c84c3f7f1f8..108ce223055e7 100644 --- a/Source/JavaScriptCore/runtime/JSType.h +++ b/Source/JavaScriptCore/runtime/JSType.h @@ -127,6 +127,7 @@ enum JSType : uint8_t { JSWeakSetType, WebAssemblyModuleType, WebAssemblyInstanceType, + WebAssemblyGCObjectType, // Start StringObjectType types. StringObjectType, DerivedStringObjectType, diff --git a/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h b/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h index f80335348c0fc..d62d686673737 100644 --- a/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h +++ b/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h @@ -421,6 +421,8 @@ struct AirIRGeneratorBase { PartialResult WARN_UNUSED_RETURN addStructSet(ExpressionType structReference, const StructType&, uint32_t fieldIndex, ExpressionType value); PartialResult WARN_UNUSED_RETURN addRefTest(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addRefCast(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternInternalize(ExpressionType reference, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternExternalize(ExpressionType reference, ExpressionType& result); // Basic operators // @@ -883,6 +885,7 @@ struct AirIRGeneratorBase { void emitLoadRTTFromFuncref(ExpressionType, ExpressionType&); void emitLoadRTTFromObject(ExpressionType, ExpressionType&); Inst makeBranchNotRTTKind(ExpressionType, RTTKind); + Inst makeBranchNotWasmGCObject(ExpressionType); void unifyValuesWithBlock(const Stack& resultStack, const ResultList& stack); @@ -2813,7 +2816,6 @@ void AirIRGeneratorBase::emitRefTestOrCast(CastKind cas switch (static_cast(heapType)) { case Wasm::TypeKind::Funcref: case Wasm::TypeKind::Externref: - case Wasm::TypeKind::Eqref: case Wasm::TypeKind::Anyref: // Casts to these types cannot fail as they are the top types of their respective hierarchies, and static type-checking does not allow cross-hierarchy casts. break; @@ -2829,16 +2831,64 @@ void AirIRGeneratorBase::emitRefTestOrCast(CastKind cas m_currentBlock = m_code.addBlock(); } break; - case Wasm::TypeKind::I31ref: + case Wasm::TypeKind::Eqref: { + auto nop = [] (CCallHelpers&, const B3::StackmapGenerationParams&) { }; + BasicBlock* endBlock = castKind == CastKind::Cast ? continuation : trueBlock; + BasicBlock* checkObject = m_code.addBlock(); + + // Check the I31ref cast first, then branch to object cases if false. + emitCheckOrBranchForCast(CastKind::Test, [&]() { + return self().makeBranchNotInt32(reference); + }, nop, checkObject); + auto tmpForI31 = self().g32(); + auto tmpForLimit = self().g32(); + self().emitCoerceFromI64(Types::I32, reference, tmpForI31); + append(Move, Arg::bigImm(Wasm::maxI31ref), tmpForLimit); + emitCheckOrBranchForCast(CastKind::Test, [&]() { + return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::GreaterThan), tmpForI31, tmpForLimit); + }, nop, checkObject); + append(Move, Arg::bigImm(Wasm::minI31ref), tmpForLimit); + emitCheckOrBranchForCast(CastKind::Test, [&]() { + return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::LessThan), tmpForI31, tmpForLimit); + }, nop, checkObject); + append(Jump); + m_currentBlock->setSuccessors(endBlock); + + // Check that it's a GC object if not an I31. + m_currentBlock = checkObject; + emitCheckOrBranchForCast(castKind, [&]() { + return self().makeBranchNotCell(reference); + }, castFailure, falseBlock); + emitCheckOrBranchForCast(castKind, [&]() { + return makeBranchNotWasmGCObject(reference); + }, castFailure, falseBlock); + break; + } + case Wasm::TypeKind::I31ref: { emitCheckOrBranchForCast(castKind, [&]() { return self().makeBranchNotInt32(reference); }, castFailure, falseBlock); + auto tmpForI31 = self().g32(); + auto tmpForLimit = self().g32(); + self().emitCoerceFromI64(Types::I32, reference, tmpForI31); + append(Move, Arg::bigImm(Wasm::maxI31ref), tmpForLimit); + emitCheckOrBranchForCast(castKind, [&]() { + return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::GreaterThan), tmpForI31, tmpForLimit); + }, castFailure, falseBlock); + append(Move, Arg::bigImm(Wasm::minI31ref), tmpForLimit); + emitCheckOrBranchForCast(castKind, [&]() { + return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::LessThan), tmpForI31, tmpForLimit); + }, castFailure, falseBlock); break; + } case Wasm::TypeKind::Arrayref: case Wasm::TypeKind::Structref: { emitCheckOrBranchForCast(castKind, [&]() { return self().makeBranchNotCell(reference); }, castFailure, falseBlock); + emitCheckOrBranchForCast(castKind, [&]() { + return makeBranchNotWasmGCObject(reference); + }, castFailure, falseBlock); auto tmpForRTT = self().gPtr(); emitLoadRTTFromObject(reference, tmpForRTT); emitCheckOrBranchForCast(castKind, [&]() { @@ -2859,6 +2909,9 @@ void AirIRGeneratorBase::emitRefTestOrCast(CastKind cas emitCheckOrBranchForCast(castKind, [&]() { return self().makeBranchNotCell(reference); }, castFailure, falseBlock); + emitCheckOrBranchForCast(castKind, [&]() { + return makeBranchNotWasmGCObject(reference); + }, castFailure, falseBlock); emitLoadRTTFromObject(reference, tmpForRTT); emitCheckOrBranchForCast(castKind, [&]() { return makeBranchNotRTTKind(tmpForRTT, signature.expand().is() ? RTTKind::Array : RTTKind::Struct); @@ -2939,6 +2992,32 @@ Inst AirIRGeneratorBase::makeBranchNotRTTKind(Expressio return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::NotEqual), tmpForRTTKind, Arg::imm(static_cast(targetKind))); } +template +Inst AirIRGeneratorBase::makeBranchNotWasmGCObject(ExpressionType cell) +{ + auto tmpForJSType = self().g32(); + auto tmpForGCType = self().g32(); + append(Load8, Arg::addr(cell, JSCell::typeInfoTypeOffset()), tmpForJSType); + append(Move, Arg::bigImm(JSType::WebAssemblyGCObjectType), tmpForGCType); + return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::NotEqual), tmpForJSType, tmpForGCType); +} + +template +auto AirIRGeneratorBase::addExternInternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + result = self().tmpForType(anyrefType(reference.type().isNullable())); + emitCCall(&operationWasmExternInternalize, result, reference); + return { }; +} + +template +auto AirIRGeneratorBase::addExternExternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + result = self().gRef(externrefType(reference.type().isNullable())); + self().emitMoveWithoutTypeCheck(reference, result); + return { }; +} + template auto AirIRGeneratorBase::addSelect(ExpressionType condition, ExpressionType nonZero, ExpressionType zero, ExpressionType& result) -> PartialResult { diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp index 51a5de50b5e53..ec14f279b6b39 100644 --- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp @@ -613,6 +613,8 @@ class B3IRGenerator { PartialResult WARN_UNUSED_RETURN addStructSet(ExpressionType structReference, const StructType&, uint32_t fieldIndex, ExpressionType value); PartialResult WARN_UNUSED_RETURN addRefTest(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addRefCast(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternInternalize(ExpressionType reference, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternExternalize(ExpressionType reference, ExpressionType& result); // Basic operators #define X(name, opcode, short, idx, ...) \ @@ -3189,7 +3191,6 @@ void B3IRGenerator::emitRefTestOrCast(CastKind castKind, ExpressionType referenc switch (static_cast(heapType)) { case Wasm::TypeKind::Funcref: case Wasm::TypeKind::Externref: - case Wasm::TypeKind::Eqref: case Wasm::TypeKind::Anyref: // Casts to these types cannot fail as they are the top types of their respective hierarchies, and static type-checking does not allow cross-hierarchy casts. break; @@ -3204,13 +3205,38 @@ void B3IRGenerator::emitRefTestOrCast(CastKind castKind, ExpressionType referenc m_currentBlock = m_proc.addBlock(); } break; + case Wasm::TypeKind::Eqref: { + auto nop = [] (CCallHelpers&, const B3::StackmapGenerationParams&) { }; + BasicBlock* endBlock = castKind == CastKind::Cast ? continuation : trueBlock; + BasicBlock* checkObject = m_proc.addBlock(); + + // The eqref case chains together checks for i31, array, and struct with disjunctions so the control flow is more complicated, and requires some extra basic blocks to be created. + emitCheckOrBranchForCast(CastKind::Test, m_currentBlock->appendNew(m_proc, Below, origin(), get(reference), constant(Int64, JSValue::NumberTag)), nop, checkObject); + Value* untagged = m_currentBlock->appendNew(m_proc, Trunc, origin(), get(reference)); + emitCheckOrBranchForCast(CastKind::Test, m_currentBlock->appendNew(m_proc, GreaterThan, origin(), untagged, constant(Int32, Wasm::maxI31ref)), nop, checkObject); + emitCheckOrBranchForCast(CastKind::Test, m_currentBlock->appendNew(m_proc, LessThan, origin(), untagged, constant(Int32, Wasm::minI31ref)), nop, checkObject); + m_currentBlock->appendNewControlValue(m_proc, Jump, origin(), endBlock); + checkObject->addPredecessor(m_currentBlock); + endBlock->addPredecessor(m_currentBlock); + + m_currentBlock = checkObject; + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, BitAnd, origin(), get(reference), constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); + Value* jsType = m_currentBlock->appendNew(m_proc, Load8Z, Int32, origin(), get(reference), safeCast(JSCell::typeInfoTypeOffset())); + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, NotEqual, origin(), jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); + break; + } case Wasm::TypeKind::I31ref: { emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, Below, origin(), get(reference), constant(Int64, JSValue::NumberTag)), castFailure, falseBlock); + Value* untagged = m_currentBlock->appendNew(m_proc, Trunc, origin(), get(reference)); + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, GreaterThan, origin(), untagged, constant(Int32, Wasm::maxI31ref)), castFailure, falseBlock); + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, LessThan, origin(), untagged, constant(Int32, Wasm::minI31ref)), castFailure, falseBlock); break; } case Wasm::TypeKind::Arrayref: case Wasm::TypeKind::Structref: { emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, BitAnd, origin(), get(reference), constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); + Value* jsType = m_currentBlock->appendNew(m_proc, Load8Z, Int32, origin(), get(reference), safeCast(JSCell::typeInfoTypeOffset())); + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, NotEqual, origin(), jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); Value* rtt = emitLoadRTTFromObject(get(reference)); emitCheckOrBranchForCast(castKind, emitNotRTTKind(rtt, static_cast(heapType) == Wasm::TypeKind::Arrayref ? RTTKind::Array : RTTKind::Struct), castFailure, falseBlock); break; @@ -3226,6 +3252,8 @@ void B3IRGenerator::emitRefTestOrCast(CastKind castKind, ExpressionType referenc else { // The cell check is only needed for non-functions, as the typechecker does not allow non-Cell values for funcref casts. emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, BitAnd, origin(), get(reference), constant(Int64, JSValue::NotCellMask)), castFailure, falseBlock); + Value* jsType = m_currentBlock->appendNew(m_proc, Load8Z, Int32, origin(), get(reference), safeCast(JSCell::typeInfoTypeOffset())); + emitCheckOrBranchForCast(castKind, m_currentBlock->appendNew(m_proc, NotEqual, origin(), jsType, constant(Int32, JSType::WebAssemblyGCObjectType)), castFailure, falseBlock); rtt = emitLoadRTTFromObject(get(reference)); emitCheckOrBranchForCast(castKind, emitNotRTTKind(rtt, signature.expand().is() ? RTTKind::Array : RTTKind::Struct), castFailure, falseBlock); } @@ -3315,6 +3343,18 @@ Value* B3IRGenerator::emitNotRTTKind(Value* rtt, RTTKind targetKind) return m_currentBlock->appendNew(m_proc, NotEqual, origin(), kind, constant(Int32, static_cast(targetKind))); } +auto B3IRGenerator::addExternInternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + result = push(callWasmOperation(m_currentBlock, toB3Type(anyrefType()), operationWasmExternInternalize, get(reference))); + return { }; +} + +auto B3IRGenerator::addExternExternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + result = push(get(reference)); + return { }; +} + auto B3IRGenerator::addSelect(ExpressionType condition, ExpressionType nonZero, ExpressionType zero, ExpressionType& result) -> PartialResult { result = push(m_currentBlock->appendNew(m_proc, B3::Select, origin(), get(condition), get(nonZero), get(zero))); diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp index db3a6eb7de882..27b1d4437c0b4 100644 --- a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp +++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp @@ -3604,6 +3604,21 @@ class BBQJIT { PartialResult WARN_UNUSED_RETURN addRefCast(ExpressionType, bool, int32_t, ExpressionType&) BBQ_STUB PartialResult WARN_UNUSED_RETURN addRefTest(ExpressionType, bool, int32_t, ExpressionType&) BBQ_STUB + PartialResult WARN_UNUSED_RETURN addExternInternalize(ExpressionType reference, ExpressionType& result) + { + Vector arguments = { + reference + }; + emitCCall(&operationWasmExternInternalize, arguments, TypeKind::Anyref, result); + return { }; + } + + PartialResult WARN_UNUSED_RETURN addExternExternalize(ExpressionType reference, ExpressionType& result) + { + result = reference; + return { }; + } + // Basic operators PartialResult WARN_UNUSED_RETURN addSelect(Value condition, Value lhs, Value rhs, Value& result) { diff --git a/Source/JavaScriptCore/wasm/WasmFormat.h b/Source/JavaScriptCore/wasm/WasmFormat.h index 61fb4e82234fc..c4fcea2e52dbf 100644 --- a/Source/JavaScriptCore/wasm/WasmFormat.h +++ b/Source/JavaScriptCore/wasm/WasmFormat.h @@ -62,6 +62,9 @@ enum class TableElementType : uint8_t { Funcref }; +constexpr int32_t maxI31ref = 1073741823; +constexpr int32_t minI31ref = -1073741824; + inline bool isValueType(Type type) { switch (type.kind) { @@ -196,10 +199,11 @@ inline Type funcrefType() return Types::Funcref; } -inline Type externrefType() +inline Type externrefType(bool isNullable = true) { if (Options::useWebAssemblyTypedFunctionReferences()) - return Wasm::Type { Wasm::TypeKind::RefNull, static_cast(Wasm::TypeKind::Externref) }; + return Wasm::Type { isNullable ? Wasm::TypeKind::RefNull : Wasm::TypeKind::Ref, static_cast(Wasm::TypeKind::Externref) }; + ASSERT(isNullable); return Types::Externref; } @@ -209,10 +213,10 @@ inline Type eqrefType() return Wasm::Type { Wasm::TypeKind::RefNull, static_cast(Wasm::TypeKind::Eqref) }; } -inline Type anyrefType() +inline Type anyrefType(bool isNullable = true) { ASSERT(Options::useWebAssemblyGC()); - return Wasm::Type { Wasm::TypeKind::RefNull, static_cast(Wasm::TypeKind::Anyref) }; + return Wasm::Type { isNullable ? Wasm::TypeKind::RefNull : Wasm::TypeKind::Ref, static_cast(Wasm::TypeKind::Anyref) }; } inline Type arrayrefType() diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index 1233d563c35bd..503400822cfbf 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -2119,13 +2119,12 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_VALIDATOR_FAIL_IF(!isSubtype(ref.type(), anyrefType()), "ref.cast to type ", ref.type(), " expected a subtype of anyref"); break; default: - ASSERT(heapType >= 0); + ASSERT(isTypeIndexHeapType(heapType)); const TypeDefinition& signature = m_info.typeSignatures[heapType]; if (signature.expand().is()) WASM_VALIDATOR_FAIL_IF(!isSubtype(ref.type(), funcrefType()), opName, " to type ", ref.type(), " expected a funcref"); else - // FIXME: once anyref is added this can allow any subtype of that. - WASM_VALIDATOR_FAIL_IF(isExternref(ref.type()) || isSubtype(ref.type(), funcrefType()), "ref.cast to type ", ref.type(), " expected a subtype of anyref"); + WASM_VALIDATOR_FAIL_IF(!isSubtype(ref.type(), anyrefType()), "ref.cast to type ", ref.type(), " expected a subtype of anyref"); resultTypeIndex = signature.index(); break; } @@ -2142,6 +2141,26 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) return { }; } + case ExtGCOpType::ExternInternalize: { + TypedExpression reference; + WASM_TRY_POP_EXPRESSION_STACK_INTO(reference, "extern.internalize"); + WASM_VALIDATOR_FAIL_IF(!isExternref(reference.type()), "extern.internalize reference to type ", reference.type(), " expected ", TypeKind::Externref); + + ExpressionType result; + WASM_TRY_ADD_TO_CONTEXT(addExternInternalize(reference, result)); + m_expressionStack.constructAndAppend(anyrefType(reference.type().isNullable()), result); + return { }; + } + case ExtGCOpType::ExternExternalize: { + TypedExpression reference; + WASM_TRY_POP_EXPRESSION_STACK_INTO(reference, "extern.externalize"); + WASM_VALIDATOR_FAIL_IF(!isSubtype(reference.type(), anyrefType()), "extern.externalize reference to type ", reference.type(), " expected ", TypeKind::Anyref); + + ExpressionType result; + WASM_TRY_ADD_TO_CONTEXT(addExternExternalize(reference, result)); + m_expressionStack.constructAndAppend(externrefType(reference.type().isNullable()), result); + return { }; + } default: WASM_PARSER_FAIL_IF(true, "invalid extended GC op ", extOp); break; diff --git a/Source/JavaScriptCore/wasm/WasmLLIntBuiltin.h b/Source/JavaScriptCore/wasm/WasmLLIntBuiltin.h index 32c7a3a25d5dc..330a5fd32876b 100644 --- a/Source/JavaScriptCore/wasm/WasmLLIntBuiltin.h +++ b/Source/JavaScriptCore/wasm/WasmLLIntBuiltin.h @@ -46,6 +46,7 @@ enum class LLIntBuiltin : uint8_t { RefCast, ArrayNewData, ArrayNewElem, + ExternInternalize, }; } } // namespace JSC::Wasm diff --git a/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp b/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp index 7c7c6fd121600..59b92fa8a3000 100644 --- a/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp @@ -329,6 +329,8 @@ class LLIntGenerator : public BytecodeGeneratorBase { PartialResult WARN_UNUSED_RETURN addStructSet(ExpressionType structReference, const StructType&, uint32_t fieldIndex, ExpressionType value); PartialResult WARN_UNUSED_RETURN addRefTest(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addRefCast(ExpressionType reference, bool allowNull, int32_t heapType, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternInternalize(ExpressionType reference, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addExternExternalize(ExpressionType reference, ExpressionType& result); // Basic operators #define X(name, opcode, short, idx, ...) \ @@ -2123,6 +2125,22 @@ auto LLIntGenerator::addRefCast(ExpressionType reference, bool allowNull, int32_ return { }; } +auto LLIntGenerator::addExternInternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + ResultList results; + addCallBuiltin(LLIntBuiltin::ExternInternalize, { reference }, results); + ASSERT(results.size() == 1); + result = results.at(0); + return { }; +} + +auto LLIntGenerator::addExternExternalize(ExpressionType reference, ExpressionType& result) -> PartialResult +{ + result = push(); + WasmExternExternalize::emit(this, result, reference); + return { }; +} + void LLIntGenerator::linkSwitchTargets(Label& label, unsigned location) { auto it = m_switches.find(&label); diff --git a/Source/JavaScriptCore/wasm/WasmOperations.cpp b/Source/JavaScriptCore/wasm/WasmOperations.cpp index 92553cf9958bc..67c19363eda72 100644 --- a/Source/JavaScriptCore/wasm/WasmOperations.cpp +++ b/Source/JavaScriptCore/wasm/WasmOperations.cpp @@ -1040,6 +1040,11 @@ JSC_DEFINE_JIT_OPERATION(operationWasmIsSubRTT, bool, (RTT* maybeSubRTT, RTT* ta return maybeSubRTT->isSubRTT(*targetRTT); } +JSC_DEFINE_JIT_OPERATION(operationWasmExternInternalize, EncodedJSValue, (EncodedJSValue reference)) +{ + return externInternalize(reference); +} + } } // namespace JSC::Wasm IGNORE_WARNINGS_END diff --git a/Source/JavaScriptCore/wasm/WasmOperations.h b/Source/JavaScriptCore/wasm/WasmOperations.h index dc6cd17ca4db6..174015c96d6d0 100644 --- a/Source/JavaScriptCore/wasm/WasmOperations.h +++ b/Source/JavaScriptCore/wasm/WasmOperations.h @@ -112,6 +112,7 @@ JSC_DECLARE_JIT_OPERATION(operationWasmArrayNewElem, EncodedJSValue, (Instance* JSC_DECLARE_JIT_OPERATION(operationWasmArrayGet, EncodedJSValue, (Instance* instance, uint32_t typeIndex, EncodedJSValue encValue, uint32_t index)); JSC_DECLARE_JIT_OPERATION(operationWasmArraySet, void, (Instance* instance, uint32_t typeIndex, EncodedJSValue encValue, uint32_t index, EncodedJSValue value)); JSC_DECLARE_JIT_OPERATION(operationWasmIsSubRTT, bool, (Wasm::RTT*, Wasm::RTT*)); +JSC_DECLARE_JIT_OPERATION(operationWasmExternInternalize, EncodedJSValue, (EncodedJSValue)); #if USE(JSVALUE64) JSC_DECLARE_JIT_OPERATION(operationWasmRetrieveAndClearExceptionIfCatchable, ThrownExceptionInfo, (Instance*)); diff --git a/Source/JavaScriptCore/wasm/WasmOperationsInlines.h b/Source/JavaScriptCore/wasm/WasmOperationsInlines.h index fb4476674c113..b568a5ef42b5f 100644 --- a/Source/JavaScriptCore/wasm/WasmOperationsInlines.h +++ b/Source/JavaScriptCore/wasm/WasmOperationsInlines.h @@ -325,14 +325,15 @@ inline bool refCast(Instance* instance, EncodedJSValue encodedReference, bool al switch (static_cast(heapType)) { case Wasm::TypeKind::Funcref: case Wasm::TypeKind::Externref: - case Wasm::TypeKind::Eqref: case Wasm::TypeKind::Anyref: // Casts to these types cannot fail as they are the top types of their respective hierarchies, and static type-checking does not allow cross-hierarchy casts. return true; + case Wasm::TypeKind::Eqref: + return (refValue.isInt32() && refValue.asInt32() <= maxI31ref && refValue.asInt32() >= minI31ref) || jsDynamicCast(refValue) || jsDynamicCast(refValue); case Wasm::TypeKind::Nullref: return false; case Wasm::TypeKind::I31ref: - return refValue.isInt32(); + return refValue.isInt32() && refValue.asInt32() <= maxI31ref && refValue.asInt32() >= minI31ref; case Wasm::TypeKind::Arrayref: return jsDynamicCast(refValue); case Wasm::TypeKind::Structref: @@ -373,6 +374,18 @@ inline bool refCast(Instance* instance, EncodedJSValue encodedReference, bool al return false; } +inline EncodedJSValue externInternalize(EncodedJSValue reference) +{ + JSValue value = JSValue::decode(reference); + if (value.isDouble() && JSC::canBeStrictInt32(value.asDouble())) { + int32_t int32Value = JSC::toInt32(value.asDouble()); + if (int32Value <= Wasm::maxI31ref && int32Value >= Wasm::minI31ref) + return JSValue::encode(jsNumber(int32Value)); + } + + return reference; +} + inline EncodedJSValue tableGet(Instance* instance, unsigned tableIndex, int32_t signedIndex) { ASSERT(tableIndex < instance->module().moduleInformation().tableCount()); diff --git a/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp b/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp index e4553a54ad5ef..bd4d6e88121c7 100644 --- a/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp +++ b/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp @@ -808,6 +808,11 @@ WASM_SLOW_PATH_DECL(call_builtin) gprStart[0] = static_cast(result); WASM_END(); } + case Wasm::LLIntBuiltin::ExternInternalize: { + auto reference = takeGPR().encodedJSValue(); + gprStart[0] = Wasm::externInternalize(reference); + WASM_END(); + } default: RELEASE_ASSERT_NOT_REACHED(); } diff --git a/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp b/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp index 5f2fdf2f17097..94d0f934600da 100644 --- a/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp +++ b/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp @@ -573,6 +573,8 @@ const TypeDefinition& TypeInformation::signatureForLLIntBuiltin(LLIntBuiltin bui case LLIntBuiltin::ArrayNewData: case LLIntBuiltin::ArrayNewElem: return *singleton().m_Ref_I32I32I32I32; + case LLIntBuiltin::ExternInternalize: + return *singleton().m_Anyref_Externref; } RELEASE_ASSERT_NOT_REACHED(); return *singleton().m_I64_Void; @@ -856,10 +858,11 @@ TypeInformation::TypeInformation() m_I32_I32 = m_typeSet.template add(FunctionParameterTypes { { Wasm::Types::I32 }, { Wasm::Types::I32 } }).iterator->key; if (!Options::useWebAssemblyGC()) return; - // FIXME: The Ref type here is only used to compute call information, a heap type of "any" would be better once that's added. + m_I32_RefI32I32 = m_typeSet.template add(FunctionParameterTypes { { Wasm::Types::I32 }, { anyrefType(), Wasm::Types::I32, Wasm::Types::I32 } }).iterator->key; + m_Ref_RefI32I32 = m_typeSet.template add(FunctionParameterTypes { { anyrefType() }, { anyrefType(), Wasm::Types::I32, Wasm::Types::I32 } }).iterator->key; m_I32_RefI32I32 = m_typeSet.template add(FunctionParameterTypes { { Wasm::Types::I32 }, { Wasm::Type { Wasm::TypeKind::Ref, static_cast(Wasm::TypeKind::Externref) }, Wasm::Types::I32, Wasm::Types::I32 } }).iterator->key; - m_Ref_RefI32I32 = m_typeSet.template add(FunctionParameterTypes { { Wasm::Type { Wasm::TypeKind::Ref, static_cast(Wasm::TypeKind::Externref) } }, { Wasm::Type { Wasm::TypeKind::Ref, static_cast(Wasm::TypeKind::Externref) }, Wasm::Types::I32, Wasm::Types::I32 } }).iterator->key; m_Ref_I32I32I32I32 = m_typeSet.template add(FunctionParameterTypes { { arrayrefType() }, { Wasm::Types::I32, Wasm::Types::I32, Wasm::Types::I32, Wasm::Types::I32 } }).iterator->key; + m_Anyref_Externref = m_typeSet.template add(FunctionParameterTypes { { anyrefType() }, { externrefType() } }).iterator->key; } RefPtr TypeInformation::typeDefinitionForFunction(const Vector& results, const Vector& args) diff --git a/Source/JavaScriptCore/wasm/WasmTypeDefinition.h b/Source/JavaScriptCore/wasm/WasmTypeDefinition.h index b33e39960653f..221a513cd18a9 100644 --- a/Source/JavaScriptCore/wasm/WasmTypeDefinition.h +++ b/Source/JavaScriptCore/wasm/WasmTypeDefinition.h @@ -904,6 +904,7 @@ class TypeInformation { RefPtr m_I32_RefI32I32; RefPtr m_Ref_RefI32I32; RefPtr m_Ref_I32I32I32I32; + RefPtr m_Anyref_Externref; Lock m_lock; }; diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h index e24a1d2d251b2..ce8ecd57f7714 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h @@ -52,7 +52,7 @@ class JSWebAssemblyArray final : public WebAssemblyGCObjectBase { static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + return Structure::create(vm, globalObject, prototype, TypeInfo(WebAssemblyGCObjectType, StructureFlags), info()); } template diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.h index 1d0880ac04bd5..05b6dff7ffda2 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.h @@ -53,7 +53,7 @@ class JSWebAssemblyStruct final : public WebAssemblyGCObjectBase { static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + return Structure::create(vm, globalObject, prototype, TypeInfo(WebAssemblyGCObjectType, StructureFlags), info()); } static JSWebAssemblyStruct* tryCreate(JSGlobalObject*, Structure*, JSWebAssemblyInstance*, uint32_t, RefPtr); diff --git a/Source/JavaScriptCore/wasm/wasm.json b/Source/JavaScriptCore/wasm/wasm.json index 4861dddb37710..8c283c3db33e4 100644 --- a/Source/JavaScriptCore/wasm/wasm.json +++ b/Source/JavaScriptCore/wasm/wasm.json @@ -291,6 +291,8 @@ "ref.cast": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 65 }, "ref.test_null": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 72 }, "ref.cast_null": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "heap_type", "type": "varuint32"}], "extendedOp": 73 }, + "extern.internalize": { "category": "gc", "value": 251, "return": ["anyref"], "parameter": ["externref"], "immediate": [], "extendedOp": 112 }, + "extern.externalize": { "category": "gc", "value": 251, "return": ["externref"], "parameter": ["anyref"], "immediate": [], "extendedOp": 113 }, "i32.trunc_sat_f32_s": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 0 }, "i32.trunc_sat_f32_u": { "category": "conversion", "value": 252, "return": ["i32"], "parameter": ["f32"], "immediate": [], "extendedOp": 1 },