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

[wasm-gc] Add extern.internalize

This adds `extern.internalize(ref null extern): ref null any` to wasm
which unpacks the wrapped wasm object if the js-interop flag is not set.
I31 values are still wrapped in object wrappers and don't use SMIs.

Bug: v8:7748
Change-Id: Ie4a4507961d0ad41caf430054a3d341f474b8e66
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3819645Reviewed-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@{#82426}
parent fed595d9
......@@ -1154,4 +1154,14 @@ builtin WasmStringViewIterSlice(
kEmptyString :
string::SubString(string, Convert<uintptr>(start), Convert<uintptr>(end));
}
transitioning builtin WasmExternInternalize(implicit context: Context)(
externObject: JSAny): JSAny {
const innerObject =
WasmGetOwnProperty(externObject, WasmWrappedObjectSymbolConstant());
if (innerObject == Undefined) {
return externObject;
}
return innerObject;
}
}
......@@ -514,7 +514,8 @@
V(Null) \
V(RttCanon) \
V(WasmTypeCast) \
V(WasmTypeCheck)
V(WasmTypeCheck) \
V(WasmExternInternalize)
#define SIMPLIFIED_OP_LIST(V) \
SIMPLIFIED_CHANGE_OP_LIST(V) \
......
......@@ -1389,6 +1389,12 @@ const Operator* SimplifiedOperatorBuilder::IsNull() { return &cache_.kIsNull; }
const Operator* SimplifiedOperatorBuilder::IsNotNull() {
return &cache_.kIsNotNull;
}
const Operator* SimplifiedOperatorBuilder::WasmExternInternalize() {
return zone()->New<Operator>(IrOpcode::kWasmExternInternalize,
Operator::kEliminatable, "WasmExternInternalize",
1, 1, 1, 1, 1, 1);
}
#endif // V8_ENABLE_WEBASSEMBLY
const Operator* SimplifiedOperatorBuilder::CheckIf(
......
......@@ -1064,6 +1064,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* RttCanon(int index);
const Operator* WasmTypeCheck(WasmTypeCheckConfig config);
const Operator* WasmTypeCast(WasmTypeCheckConfig config);
const Operator* WasmExternInternalize();
#endif
const Operator* DateNow();
......
......@@ -1659,6 +1659,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kIsNull:
case IrOpcode::kIsNotNull:
case IrOpcode::kAssertNotNull:
case IrOpcode::kWasmExternInternalize:
// TODO(manoskouk): What are the constraints here?
break;
#endif // V8_ENABLE_WEBASSEMBLY
......
......@@ -1027,6 +1027,9 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
return BuildAsmjsLoadMem(MachineType::Float32(), input);
case wasm::kExprF64AsmjsLoadMem:
return BuildAsmjsLoadMem(MachineType::Float64(), input);
case wasm::kExprExternInternalize: {
return gasm_->WasmExternInternalize(input);
}
default:
FATAL_UNSUPPORTED_OPCODE(opcode);
}
......@@ -6513,6 +6516,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
case wasm::kRefNull: {
switch (type.heap_representation()) {
case wasm::HeapType::kExtern:
return input;
case wasm::HeapType::kAny:
if (!enabled_features_.has_gc()) return input;
// If this is a wrapper for arrays/structs/i31s, unpack it.
......
......@@ -54,20 +54,23 @@ Reduction WasmGCLowering::Reduce(Node* node) {
return ReduceRttCanon(node);
case IrOpcode::kTypeGuard:
return ReduceTypeGuard(node);
case IrOpcode::kWasmExternInternalize:
return ReduceWasmExternInternalize(node);
default:
return NoChange();
}
}
Node* WasmGCLowering::Null() {
Node* WasmGCLowering::RootNode(RootIndex index) {
Node* isolate_root = gasm_.LoadImmutable(
MachineType::Pointer(), instance_node_,
WasmInstanceObject::kIsolateRootOffset - kHeapObjectTag);
return gasm_.LoadImmutable(
MachineType::Pointer(), isolate_root,
IsolateData::root_slot_offset(RootIndex::kNullValue));
return gasm_.LoadImmutable(MachineType::Pointer(), isolate_root,
IsolateData::root_slot_offset(index));
}
Node* WasmGCLowering::Null() { return RootNode(RootIndex::kNullValue); }
// TODO(manoskouk): Use the Callbacks infrastructure from wasm-compiler.h to
// unify all check/cast implementations.
// TODO(manoskouk): Find a way to optimize branches on typechecks.
......@@ -236,6 +239,37 @@ Reduction WasmGCLowering::ReduceTypeGuard(Node* node) {
return Replace(alias);
}
Reduction WasmGCLowering::ReduceWasmExternInternalize(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmExternInternalize);
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) {
Node* context = gasm_.LoadImmutable(
MachineType::TaggedPointer(), instance_node_,
WasmInstanceObject::kNativeContextOffset - kHeapObjectTag);
Node* obj = gasm_.CallBuiltin(
Builtin::kWasmGetOwnProperty, Operator::kEliminatable, object,
RootNode(RootIndex::kwasm_wrapped_object_symbol), context);
// Invalid object wrappers (i.e. any other JS object that doesn't have the
// magic hidden property) will return {undefined}. Map that to {object}.
Node* is_undefined =
gasm_.TaggedEqual(obj, RootNode(RootIndex::kUndefinedValue));
gasm_.GotoIf(is_undefined, &end, object);
gasm_.Goto(&end, obj);
} 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
......@@ -36,6 +36,8 @@ class WasmGCLowering final : public AdvancedReducer {
Reduction ReduceIsNotNull(Node* node);
Reduction ReduceRttCanon(Node* node);
Reduction ReduceTypeGuard(Node* node);
Reduction ReduceWasmExternInternalize(Node* node);
Node* RootNode(RootIndex index);
Node* Null();
WasmGraphAssembler gasm_;
Node* dead_;
......
......@@ -371,6 +371,11 @@ Node* WasmGraphAssembler::AssertNotNull(Node* object) {
control()));
}
Node* WasmGraphAssembler::WasmExternInternalize(Node* object) {
return AddNode(graph()->NewNode(simplified_.WasmExternInternalize(), object,
effect(), control()));
}
// Generic HeapObject helpers.
Node* WasmGraphAssembler::HasInstanceType(Node* heap_object,
......
......@@ -250,6 +250,8 @@ class WasmGraphAssembler : public GraphAssembler {
Node* AssertNotNull(Node* object);
Node* WasmExternInternalize(Node* object);
// Generic helpers.
Node* HasInstanceType(Node* heap_object, InstanceType type);
......
......@@ -1795,6 +1795,26 @@ class LiftoffCompiler {
__ PushRegister(kI32, dst);
return;
}
case kExprExternInternalize:
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& extern_value =
__ cache_state()->stack_state.back();
LiftoffAssembler::VarState context(kPointerKind, context_reg, 0);
CallRuntimeStub(
WasmCode::kWasmExternInternalize,
MakeSig::Returns(kPointerKind).Params(kPointerKind, kPointerKind),
{extern_value, context}, decoder->position());
__ DropValues(1);
__ PushRegister(kRefNull, LiftoffRegister(kReturnRegister0));
}
return;
default:
UNREACHABLE();
}
......
......@@ -2069,6 +2069,7 @@ class WasmDecoder : public Decoder {
case kExprRefIsArray:
case kExprRefIsData:
case kExprRefIsI31:
case kExprExternInternalize:
return length;
case kExprStringNewWtf16:
case kExprStringEncodeWtf16:
......@@ -5085,6 +5086,17 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
Push(value_on_fallthrough);
return opcode_length + branch_depth.length;
}
case kExprExternInternalize: {
Value extern_val = Peek(0, 0, kWasmExternRef);
ValueType intern_type = ValueType::RefMaybeNull(
HeapType::kAny, Nullability(extern_val.type.is_nullable()));
Value intern_val = CreateValue(intern_type);
CALL_INTERFACE_IF_OK_AND_REACHABLE(UnOp, kExprExternInternalize,
extern_val, &intern_val);
Drop(extern_val);
Push(intern_val);
return opcode_length;
}
default:
this->DecodeError("invalid gc opcode: %x", opcode);
return 0;
......
......@@ -148,7 +148,8 @@ struct WasmModule;
V(WasmStringViewIterNext) \
V(WasmStringViewIterAdvance) \
V(WasmStringViewIterRewind) \
V(WasmStringViewIterSlice)
V(WasmStringViewIterSlice) \
V(WasmExternInternalize)
// Sorted, disjoint and non-overlapping memory regions. A region is of the
// form [start, end). So there's no [start, end), [end, other_end),
......
......@@ -722,6 +722,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(BrOnNonData, 0xfb64, _, "br_on_non_data") \
V(BrOnNonI31, 0xfb65, _, "br_on_non_i31") \
V(BrOnNonArray, 0xfb67, _, "br_on_non_array") \
V(ExternInternalize, 0xfb70, _, "extern.internalize") \
V(StringNewWtf8, 0xfb80, _, "string.new_wtf8") \
V(StringNewWtf16, 0xfb81, _, "string.new_wtf16") \
V(StringConst, 0xfb82, _, "string.const") \
......
......@@ -2057,6 +2057,18 @@ WASM_COMPILED_EXEC_TEST(JsAccess) {
isolate->clear_pending_exception();
}
WASM_COMPILED_EXEC_TEST(WasmExternInternalize) {
WasmGCTester tester(execution_tier);
const byte kNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_GC_INTERNALIZE(WASM_REF_NULL(kNoExternCode))),
kExprEnd});
tester.CompileModule();
tester.CheckResult(kNull, 1);
}
} // namespace test_gc
} // namespace wasm
} // namespace internal
......
......@@ -534,6 +534,8 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
WASM_GC_OP(kExprBrOnCastStaticFail), static_cast<byte>(depth), \
static_cast<byte>(typeidx)
#define WASM_GC_INTERNALIZE(extern) extern, WASM_GC_OP(kExprExternInternalize)
#define WASM_REF_IS_DATA(ref) ref, WASM_GC_OP(kExprRefIsData)
#define WASM_REF_IS_ARRAY(ref) ref, WASM_GC_OP(kExprRefIsArray)
#define WASM_REF_IS_I31(ref) ref, WASM_GC_OP(kExprRefIsI31)
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-gc
"use strict";
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
let instance = (() => {
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
builder.addFunction('struct_producer', makeSig([kWasmI32], [kWasmDataRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, struct])
.exportFunc();
builder.addFunction('struct_consumer',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
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('i31_producer', makeSig([kWasmI32], [kWasmI31Ref]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprI31New])
.exportFunc();
builder.addFunction('i31_consumer',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kExprBrOnNull, 0,
kGCPrefix, kExprRefAsI31,
kGCPrefix, kExprI31GetS, // value
kExprI32Const, 0, // isNull
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 0, // value (placeholder)
kExprI32Const, 1, // isNull
])
.exportFunc();
let array = builder.addArray(kWasmI32, true);
builder.addFunction('array_producer', makeSig([kWasmI32], [kWasmArrayRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixedStatic, array, 1])
.exportFunc();
builder.addFunction('array_consumer',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprRefIsNull,
kExprBlock, kWasmVoid,
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
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();
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({}));
});
......@@ -523,6 +523,7 @@ let kExprBrOnArray = 0x66;
let kExprBrOnNonData = 0x64;
let kExprBrOnNonI31 = 0x65;
let kExprBrOnNonArray = 0x67;
let kExprExternInternalize = 0x70;
let kExprStringNewWtf8 = 0x80;
let kExprStringNewWtf16 = 0x81;
let kExprStringConst = 0x82;
......
......@@ -4526,6 +4526,29 @@ TEST_F(FunctionBodyDecoderTest, DropOnEmptyStack) {
ExpectValidates(sigs.v_v(), {kExprUnreachable, kExprDrop}, kAppendEnd);
}
TEST_F(FunctionBodyDecoderTest, ExternInternalize) {
WASM_FEATURE_SCOPE(gc);
ExpectValidates(FunctionSig::Build(zone(), {kWasmAnyRef}, {}),
{WASM_GC_INTERNALIZE(WASM_REF_NULL(kNoExternCode))});
ExpectValidates(FunctionSig::Build(zone(), {kWasmAnyRef}, {kWasmExternRef}),
{WASM_GC_INTERNALIZE(WASM_LOCAL_GET(0))});
ExpectValidates(
FunctionSig::Build(zone(), {kWasmAnyRef}, {kWasmExternRef.AsNonNull()}),
{WASM_GC_INTERNALIZE(WASM_LOCAL_GET(0))});
ExpectFailure(FunctionSig::Build(zone(), {kWasmAnyRef}, {}),
{WASM_GC_INTERNALIZE(kExprNop)}, kAppendEnd,
"not enough arguments on the stack for extern.internalize "
"(need 1, got 0)");
ExpectFailure(
FunctionSig::Build(zone(), {kWasmAnyRef.AsNonNull()}, {kWasmExternRef}),
{WASM_GC_INTERNALIZE(WASM_LOCAL_GET(0))}, kAppendEnd,
"type error in fallthru[0] (expected (ref any), got anyref)");
ExpectFailure(FunctionSig::Build(zone(), {kWasmAnyRef}, {kWasmAnyRef}),
{WASM_GC_INTERNALIZE(WASM_LOCAL_GET(0))}, kAppendEnd,
"extern.internalize[0] expected type externref, found "
"local.get of type anyref");
}
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