Commit ee89a269 authored by Matthias Liedtke's avatar Matthias Liedtke Committed by V8 LUCI CQ

[wasm-gc] Add extern.externalize

This adds `extern.externalize(ref null any): ref null extern` to wasm
which packs wasm objects into JS objects if the js-interop flag is not set.
This is the counterpart to extern.internalize introduced in
50ec8a11.

Bug: v8:7748
Change-Id: I67b8fe6d70b9f526ff6c43b0a4d7861c7ff5dad0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3825879Reviewed-by: 's avatarNico Hartmann <nicohartmann@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82492}
parent 374a93e2
......@@ -1164,4 +1164,19 @@ transitioning builtin WasmExternInternalize(implicit context: Context)(
}
return innerObject;
}
transitioning builtin WasmExternExternalize(implicit context: Context)(
anyObject: JSAny): JSAny {
typeswitch (anyObject) {
case (wasmArray: WasmArray): {
return WasmAllocateObjectWrapper(wasmArray);
}
case (wasmStruct: WasmStruct): {
return WasmAllocateObjectWrapper(wasmStruct);
}
case (JSAny): {
return anyObject;
}
}
}
}
......@@ -515,7 +515,8 @@
V(RttCanon) \
V(WasmTypeCast) \
V(WasmTypeCheck) \
V(WasmExternInternalize)
V(WasmExternInternalize) \
V(WasmExternExternalize)
#define SIMPLIFIED_OP_LIST(V) \
SIMPLIFIED_CHANGE_OP_LIST(V) \
......
......@@ -1395,6 +1395,12 @@ const Operator* SimplifiedOperatorBuilder::WasmExternInternalize() {
Operator::kEliminatable, "WasmExternInternalize",
1, 1, 1, 1, 1, 1);
}
const Operator* SimplifiedOperatorBuilder::WasmExternExternalize() {
return zone()->New<Operator>(IrOpcode::kWasmExternExternalize,
Operator::kEliminatable, "WasmExternExternalize",
1, 1, 1, 1, 1, 1);
}
#endif // V8_ENABLE_WEBASSEMBLY
const Operator* SimplifiedOperatorBuilder::CheckIf(
......
......@@ -1065,6 +1065,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* WasmTypeCheck(WasmTypeCheckConfig config);
const Operator* WasmTypeCast(WasmTypeCheckConfig config);
const Operator* WasmExternInternalize();
const Operator* WasmExternExternalize();
#endif
const Operator* DateNow();
......
......@@ -1660,6 +1660,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kIsNotNull:
case IrOpcode::kAssertNotNull:
case IrOpcode::kWasmExternInternalize:
case IrOpcode::kWasmExternExternalize:
// TODO(manoskouk): What are the constraints here?
break;
#endif // V8_ENABLE_WEBASSEMBLY
......
......@@ -1027,9 +1027,10 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
return BuildAsmjsLoadMem(MachineType::Float32(), input);
case wasm::kExprF64AsmjsLoadMem:
return BuildAsmjsLoadMem(MachineType::Float64(), input);
case wasm::kExprExternInternalize: {
case wasm::kExprExternInternalize:
return gasm_->WasmExternInternalize(input);
}
case wasm::kExprExternExternalize:
return gasm_->WasmExternExternalize(input);
default:
FATAL_UNSUPPORTED_OPCODE(opcode);
}
......@@ -6320,6 +6321,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
// already to JS.
return node;
case wasm::HeapType::kExtern:
return node;
case wasm::HeapType::kAny: {
if (!enabled_features_.has_gc()) return node;
// Wrap {node} in object wrapper if it is an array/struct.
......
......@@ -56,6 +56,8 @@ Reduction WasmGCLowering::Reduce(Node* node) {
return ReduceTypeGuard(node);
case IrOpcode::kWasmExternInternalize:
return ReduceWasmExternInternalize(node);
case IrOpcode::kWasmExternExternalize:
return ReduceWasmExternExternalize(node);
default:
return NoChange();
}
......@@ -270,6 +272,39 @@ Reduction WasmGCLowering::ReduceWasmExternInternalize(Node* node) {
return Replace(replacement);
}
Reduction WasmGCLowering::ReduceWasmExternExternalize(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmExternExternalize);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* object = NodeProperties::GetValueInput(node, 0);
gasm_.InitializeEffectControl(effect, control);
auto end = gasm_.MakeLabel(MachineRepresentation::kTaggedPointer);
if (!FLAG_wasm_gc_js_interop) {
auto wrap = gasm_.MakeLabel();
gasm_.GotoIf(gasm_.IsI31(object), &end, object);
gasm_.GotoIf(gasm_.IsDataRefMap(gasm_.LoadMap(object)), &wrap);
// This includes the case where {node == null}.
gasm_.Goto(&end, object);
gasm_.Bind(&wrap);
Node* context = gasm_.LoadImmutable(
MachineType::TaggedPointer(), instance_node_,
WasmInstanceObject::kNativeContextOffset - kHeapObjectTag);
Node* wrapped = gasm_.CallBuiltin(Builtin::kWasmAllocateObjectWrapper,
Operator::kEliminatable, object, context);
gasm_.Goto(&end, wrapped);
} else {
gasm_.Goto(&end, object);
}
gasm_.Bind(&end);
Node* replacement = end.PhiAt(0);
ReplaceWithValue(node, replacement, gasm_.effect(), gasm_.control());
node->Kill();
return Replace(replacement);
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -37,6 +37,7 @@ class WasmGCLowering final : public AdvancedReducer {
Reduction ReduceRttCanon(Node* node);
Reduction ReduceTypeGuard(Node* node);
Reduction ReduceWasmExternInternalize(Node* node);
Reduction ReduceWasmExternExternalize(Node* node);
Node* RootNode(RootIndex index);
Node* Null();
WasmGraphAssembler gasm_;
......
......@@ -376,6 +376,11 @@ Node* WasmGraphAssembler::WasmExternInternalize(Node* object) {
effect(), control()));
}
Node* WasmGraphAssembler::WasmExternExternalize(Node* object) {
return AddNode(graph()->NewNode(simplified_.WasmExternExternalize(), object,
effect(), control()));
}
// Generic HeapObject helpers.
Node* WasmGraphAssembler::HasInstanceType(Node* heap_object,
......
......@@ -252,6 +252,8 @@ class WasmGraphAssembler : public GraphAssembler {
Node* WasmExternInternalize(Node* object);
Node* WasmExternExternalize(Node* object);
// Generic helpers.
Node* HasInstanceType(Node* heap_object, InstanceType type);
......
......@@ -1815,6 +1815,26 @@ class LiftoffCompiler {
__ PushRegister(kRefNull, LiftoffRegister(kReturnRegister0));
}
return;
case kExprExternExternalize:
if (!FLAG_wasm_gc_js_interop) {
LiftoffRegList pinned;
LiftoffRegister context_reg =
pinned.set(__ GetUnusedRegister(kGpReg, pinned));
LOAD_TAGGED_PTR_INSTANCE_FIELD(context_reg.gp(), NativeContext,
pinned);
LiftoffAssembler::VarState& value =
__ cache_state()->stack_state.back();
LiftoffAssembler::VarState context(kPointerKind, context_reg, 0);
CallRuntimeStub(
WasmCode::kWasmExternExternalize,
MakeSig::Returns(kPointerKind).Params(kPointerKind, kPointerKind),
{value, context}, decoder->position());
__ DropValues(1);
__ PushRegister(kRefNull, LiftoffRegister(kReturnRegister0));
}
return;
default:
UNREACHABLE();
}
......
......@@ -2066,6 +2066,7 @@ class WasmDecoder : public Decoder {
case kExprRefIsData:
case kExprRefIsI31:
case kExprExternInternalize:
case kExprExternExternalize:
return length;
case kExprStringNewWtf16:
case kExprStringEncodeWtf16:
......@@ -5093,6 +5094,17 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
Push(intern_val);
return opcode_length;
}
case kExprExternExternalize: {
Value val = Peek(0, 0, kWasmAnyRef);
ValueType extern_type = ValueType::RefMaybeNull(
HeapType::kExtern, Nullability(val.type.is_nullable()));
Value extern_val = CreateValue(extern_type);
CALL_INTERFACE_IF_OK_AND_REACHABLE(UnOp, kExprExternExternalize, val,
&extern_val);
Drop(val);
Push(extern_val);
return opcode_length;
}
default:
this->DecodeError("invalid gc opcode: %x", opcode);
return 0;
......
......@@ -149,7 +149,8 @@ struct WasmModule;
V(WasmStringViewIterAdvance) \
V(WasmStringViewIterRewind) \
V(WasmStringViewIterSlice) \
V(WasmExternInternalize)
V(WasmExternInternalize) \
V(WasmExternExternalize)
// Sorted, disjoint and non-overlapping memory regions. A region is of the
// form [start, end). So there's no [start, end), [end, other_end),
......
......@@ -723,6 +723,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(BrOnNonI31, 0xfb65, _, "br_on_non_i31") \
V(BrOnNonArray, 0xfb67, _, "br_on_non_array") \
V(ExternInternalize, 0xfb70, _, "extern.internalize") \
V(ExternExternalize, 0xfb71, _, "extern.externalize") \
V(StringNewWtf8, 0xfb80, _, "string.new_wtf8") \
V(StringNewWtf16, 0xfb81, _, "string.new_wtf16") \
V(StringConst, 0xfb82, _, "string.const") \
......
......@@ -2069,6 +2069,18 @@ WASM_COMPILED_EXEC_TEST(WasmExternInternalize) {
tester.CheckResult(kNull, 1);
}
WASM_COMPILED_EXEC_TEST(WasmExternExternalize) {
WasmGCTester tester(execution_tier);
const byte kNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_GC_EXTERNALIZE(WASM_REF_NULL(kNoneCode))),
kExprEnd});
tester.CompileModule();
tester.CheckResult(kNull, 1);
}
} // namespace test_gc
} // namespace wasm
} // namespace internal
......
......@@ -535,6 +535,7 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
static_cast<byte>(typeidx)
#define WASM_GC_INTERNALIZE(extern) extern, WASM_GC_OP(kExprExternInternalize)
#define WASM_GC_EXTERNALIZE(ref) ref, WASM_GC_OP(kExprExternExternalize)
#define WASM_REF_IS_DATA(ref) ref, WASM_GC_OP(kExprRefIsData)
#define WASM_REF_IS_ARRAY(ref) ref, WASM_GC_OP(kExprRefIsArray)
......
......@@ -11,13 +11,51 @@ let instance = (() => {
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
builder.addFunction('struct_producer', makeSig([kWasmI32], [kWasmDataRef]))
/**
* type_producer -> create type and pass as anyref
* (implicit externalize by calling convention)
* type_externalize -> create type, externalize, pass by externref
* type_consumer -> consume type by anyref
* (implicit internalize by calling convention)
* type_internalize -> consume type by externref, internalize
*/
builder.addFunction('struct_producer', makeSig([kWasmI32], [kWasmAnyRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, struct])
.exportFunc();
builder.addFunction('struct_externalize',
makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, struct,
kGCPrefix, kExprExternExternalize,
])
.exportFunc();
builder.addFunction('struct_consumer',
makeSig([kWasmAnyRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kExprBrOnNull, 0,
kGCPrefix, kExprRefAsData,
kGCPrefix, kExprRefCastStatic, struct,
kGCPrefix, kExprStructGet, struct, 0, // value
kExprI32Const, 0, // isNull
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 0, // value (placeholder)
kExprI32Const, 1, // isNull
])
.exportFunc();
builder.addFunction('struct_internalize',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
......@@ -38,13 +76,41 @@ let instance = (() => {
])
.exportFunc();
builder.addFunction('i31_producer', makeSig([kWasmI32], [kWasmI31Ref]))
builder.addFunction('i31_producer', makeSig([kWasmI32], [kWasmAnyRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprI31New])
.exportFunc();
builder.addFunction('i31_externalize',
makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprI31New,
kGCPrefix, kExprExternExternalize,
])
.exportFunc();
builder.addFunction('i31_consumer',
makeSig([kWasmAnyRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kExprBrOnNull, 0,
kGCPrefix, kExprRefAsI31,
kGCPrefix, kExprI31GetS, // value
kExprI32Const, 0, // isNull
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 0, // value (placeholder)
kExprI32Const, 1, // isNull
])
.exportFunc();
builder.addFunction('i31_internalize',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
......@@ -66,13 +132,43 @@ let instance = (() => {
let array = builder.addArray(kWasmI32, true);
builder.addFunction('array_producer', makeSig([kWasmI32], [kWasmArrayRef]))
builder.addFunction('array_producer', makeSig([kWasmI32], [kWasmAnyRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixedStatic, array, 1])
.exportFunc();
builder.addFunction('array_externalize',
makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixedStatic, array, 1,
kGCPrefix, kExprExternExternalize,
])
.exportFunc();
builder.addFunction('array_consumer',
makeSig([kWasmAnyRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kExprBrOnNull, 0,
kGCPrefix, kExprRefAsArray,
kGCPrefix, kExprRefCastStatic, array,
kExprI32Const, 0,
kGCPrefix, kExprArrayGet, array, // value
kExprI32Const, 0, // isNull
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 0, // value (placeholder)
kExprI32Const, 1, // isNull
])
.exportFunc();
builder.addFunction('array_internalize',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
......@@ -97,12 +193,20 @@ let instance = (() => {
return builder.instantiate({});
})();
// struct
["struct", "i31", "array"].forEach((type) => {
var fnConsumer = instance.exports[`${type}_consumer`];
var fnProducer = instance.exports[`${type}_producer`];
assertEquals([0, 1], fnConsumer(null));
var obj42 = fnProducer(42);
assertEquals([42, 0], fnConsumer(obj42));
assertTraps(kTrapIllegalCast, () => fnConsumer({}));
});
for (let type of ["struct", "i31", "array"]) {
for (let consume of ["consumer", "internalize"]) {
let fnConsume = instance.exports[`${type}_${consume}`];
// A null is converted to (ref null none).
assertEquals([0, 1], fnConsume(null));
// Passing a JavaScript object is allowed and can be internalized
// but will fail on casting it.
assertTraps(kTrapIllegalCast, () => fnConsume({}));
for (let produce of ["producer", "externalize"]) {
let fnProduce = instance.exports[`${type}_${produce}`];
// Test roundtrip of a value produced in Wasm passed back to Wasm.
let obj42 = fnProduce(42);
assertEquals([42, 0], fnConsume(obj42));
}
}
}
......@@ -524,6 +524,7 @@ let kExprBrOnNonData = 0x64;
let kExprBrOnNonI31 = 0x65;
let kExprBrOnNonArray = 0x67;
let kExprExternInternalize = 0x70;
let kExprExternExternalize = 0x71;
let kExprStringNewWtf8 = 0x80;
let kExprStringNewWtf16 = 0x81;
let kExprStringConst = 0x82;
......
......@@ -4549,6 +4549,29 @@ TEST_F(FunctionBodyDecoderTest, ExternInternalize) {
"local.get of type anyref");
}
TEST_F(FunctionBodyDecoderTest, ExternExternalize) {
WASM_FEATURE_SCOPE(gc);
ExpectValidates(FunctionSig::Build(zone(), {kWasmExternRef}, {}),
{WASM_GC_EXTERNALIZE(WASM_REF_NULL(kNoneCode))});
ExpectValidates(FunctionSig::Build(zone(), {kWasmExternRef}, {kWasmAnyRef}),
{WASM_GC_EXTERNALIZE(WASM_LOCAL_GET(0))});
ExpectValidates(
FunctionSig::Build(zone(), {kWasmExternRef}, {kWasmAnyRef.AsNonNull()}),
{WASM_GC_EXTERNALIZE(WASM_LOCAL_GET(0))});
ExpectFailure(FunctionSig::Build(zone(), {kWasmExternRef}, {}),
{WASM_GC_EXTERNALIZE(kExprNop)}, kAppendEnd,
"not enough arguments on the stack for extern.externalize "
"(need 1, got 0)");
ExpectFailure(
FunctionSig::Build(zone(), {kWasmExternRef.AsNonNull()}, {kWasmAnyRef}),
{WASM_GC_EXTERNALIZE(WASM_LOCAL_GET(0))}, kAppendEnd,
"type error in fallthru[0] (expected (ref extern), got externref)");
ExpectFailure(FunctionSig::Build(zone(), {kWasmExternRef}, {kWasmExternRef}),
{WASM_GC_EXTERNALIZE(WASM_LOCAL_GET(0))}, kAppendEnd,
"extern.externalize[0] expected type anyref, found "
"local.get of type externref");
}
class BranchTableIteratorTest : public TestWithZone {
public:
BranchTableIteratorTest() : TestWithZone() {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment