Skip to content

Commit

Permalink
[Wasm-GC] Add extern.internalize/externalize
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=251039

Reviewed by Justin Michaud.

Adds extern conversion instructions to convert between anyref (internal
references) and externref. The any to extern direction is currently a
no-op.

The extern to any direction requires checking for Numbers in the i31
range and converting them to i31ref representation.

The any to extern is a no-op because JSC's value representation allows
i31s to just be JSC 32-bit exact integers, and structs and arrays are
represented as objects. This would need to change if the representation
were to change, so that, e.g., structs and arrays have an optimized
non-object wasm representation and a wrapper object is used to interop
with JS.

The addition of internalize means non-Wasm values (JS objects, strings,
etc) can be converted into an internal reference as "host references"
that are opaque to Wasm. These need to be distinguishable from true Wasm
values, including in JIT code, which requires adding a WasmGCObjectType
JSType.

* JSTests/wasm/gc/extern.js: Added.
(testInternalize):
(testRoundtrip):
(testTable):
* JSTests/wasm/wasm.json:
* Source/JavaScriptCore/bytecode/BytecodeList.rb:
* Source/JavaScriptCore/llint/WebAssembly32_64.asm:
* Source/JavaScriptCore/llint/WebAssembly64.asm:
* Source/JavaScriptCore/runtime/JSType.cpp:
(WTF::printInternal):
* Source/JavaScriptCore/runtime/JSType.h:
* Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h:
(JSC::Wasm::ExpressionType>::emitRefTestOrCast):
(JSC::Wasm::ExpressionType>::makeBranchNotWasmGCObject):
(JSC::Wasm::ExpressionType>::addExternInternalize):
(JSC::Wasm::ExpressionType>::addExternExternalize):
* Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::emitRefTestOrCast):
(JSC::Wasm::B3IRGenerator::addExternInternalize):
(JSC::Wasm::B3IRGenerator::addExternExternalize):
* Source/JavaScriptCore/wasm/WasmBBQJIT.cpp:
(JSC::Wasm::BBQJIT::addExternInternalize):
(JSC::Wasm::BBQJIT::addExternExternalize):
* Source/JavaScriptCore/wasm/WasmFormat.h:
(JSC::Wasm::externrefType):
(JSC::Wasm::anyrefType):
* Source/JavaScriptCore/wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
* Source/JavaScriptCore/wasm/WasmLLIntBuiltin.h:
* Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:
(JSC::Wasm::LLIntGenerator::addExternInternalize):
(JSC::Wasm::LLIntGenerator::addExternExternalize):
* Source/JavaScriptCore/wasm/WasmOperations.cpp:
(JSC::Wasm::JSC_DEFINE_JIT_OPERATION):
* Source/JavaScriptCore/wasm/WasmOperations.h:
* Source/JavaScriptCore/wasm/WasmOperationsInlines.h:
(JSC::Wasm::refCast):
(JSC::Wasm::externInternalize):
* Source/JavaScriptCore/wasm/WasmSlowPaths.cpp:
(JSC::LLInt::WASM_SLOW_PATH_DECL):
* Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp:
(JSC::Wasm::TypeInformation::signatureForLLIntBuiltin):
(JSC::Wasm::TypeInformation::TypeInformation):
* Source/JavaScriptCore/wasm/WasmTypeDefinition.h:
* Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h:
* Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.h:
* Source/JavaScriptCore/wasm/wasm.json:

Canonical link: https://commits.webkit.org/262282@main
  • Loading branch information
takikawa committed Mar 29, 2023
1 parent b8bf248 commit e537d9e
Show file tree
Hide file tree
Showing 23 changed files with 425 additions and 16 deletions.
183 changes: 183 additions & 0 deletions JSTests/wasm/gc/extern.js
Original file line number Diff line number Diff line change
@@ -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();
2 changes: 2 additions & 0 deletions JSTests/wasm/wasm.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
6 changes: 6 additions & 0 deletions Source/JavaScriptCore/bytecode/BytecodeList.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1926,4 +1926,10 @@
value: VirtualRegister,
}

op :extern_externalize,
args: {
dst: VirtualRegister,
reference: VirtualRegister,
}

end_section :Wasm
5 changes: 5 additions & 0 deletions Source/JavaScriptCore/llint/WebAssembly32_64.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
5 changes: 5 additions & 0 deletions Source/JavaScriptCore/llint/WebAssembly64.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/JSType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/JSType.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ enum JSType : uint8_t {
JSWeakSetType,
WebAssemblyModuleType,
WebAssemblyInstanceType,
WebAssemblyGCObjectType,
// Start StringObjectType types.
StringObjectType,
DerivedStringObjectType,
Expand Down
83 changes: 81 additions & 2 deletions Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -2813,7 +2816,6 @@ void AirIRGeneratorBase<Derived, ExpressionType>::emitRefTestOrCast(CastKind cas
switch (static_cast<TypeKind>(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;
Expand All @@ -2829,16 +2831,64 @@ void AirIRGeneratorBase<Derived, ExpressionType>::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, [&]() {
Expand All @@ -2859,6 +2909,9 @@ void AirIRGeneratorBase<Derived, ExpressionType>::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<Wasm::ArrayType>() ? RTTKind::Array : RTTKind::Struct);
Expand Down Expand Up @@ -2939,6 +2992,32 @@ Inst AirIRGeneratorBase<Derived, ExpressionType>::makeBranchNotRTTKind(Expressio
return Inst(Branch32, nullptr, Arg::relCond(MacroAssembler::NotEqual), tmpForRTTKind, Arg::imm(static_cast<uint8_t>(targetKind)));
}

template <typename Derived, typename ExpressionType>
Inst AirIRGeneratorBase<Derived, ExpressionType>::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 <typename Derived, typename ExpressionType>
auto AirIRGeneratorBase<Derived, ExpressionType>::addExternInternalize(ExpressionType reference, ExpressionType& result) -> PartialResult
{
result = self().tmpForType(anyrefType(reference.type().isNullable()));
emitCCall(&operationWasmExternInternalize, result, reference);
return { };
}

template <typename Derived, typename ExpressionType>
auto AirIRGeneratorBase<Derived, ExpressionType>::addExternExternalize(ExpressionType reference, ExpressionType& result) -> PartialResult
{
result = self().gRef(externrefType(reference.type().isNullable()));
self().emitMoveWithoutTypeCheck(reference, result);
return { };
}

template<typename Derived, typename ExpressionType>
auto AirIRGeneratorBase<Derived, ExpressionType>::addSelect(ExpressionType condition, ExpressionType nonZero, ExpressionType zero, ExpressionType& result) -> PartialResult
{
Expand Down
Loading

0 comments on commit e537d9e

Please sign in to comment.