[wasm-gc] Add new ref.cast null taking any reference

This extends crrev.com/c/3948663 (ref.cast) by adding the new
"ref.cast null" which only behaves different for null for which
it doesn't trap but instead casts the null value to the target
(null)type.

Bug: v8:7748
Change-Id: I3ac85d83cc06c95af8830c1c60ae2f28414e2570
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3960329
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83934}
This commit is contained in:
Matthias Liedtke 2022-10-26 11:45:57 +02:00 committed by V8 LUCI CQ
parent c9e8be7cd3
commit 1688cad47f
10 changed files with 182 additions and 47 deletions

View File

@ -5590,18 +5590,19 @@ Node* WasmGraphBuilder::RefCast(Node* object, Node* rtt,
}
Node* WasmGraphBuilder::RefCastAbstract(Node* object, wasm::HeapType type,
wasm::WasmCodePosition position) {
wasm::WasmCodePosition position,
bool null_succeeds) {
bool is_nullable =
compiler::NodeProperties::GetType(object).AsWasm().type.is_nullable();
switch (type.representation()) {
case wasm::HeapType::kEq:
return RefAsEq(object, is_nullable, position);
return RefAsEq(object, is_nullable, position, null_succeeds);
case wasm::HeapType::kI31:
return RefAsI31(object, position);
return RefAsI31(object, position, null_succeeds);
case wasm::HeapType::kStruct:
return RefAsStruct(object, is_nullable, position);
return RefAsStruct(object, is_nullable, position, null_succeeds);
case wasm::HeapType::kArray:
return RefAsArray(object, is_nullable, position);
return RefAsArray(object, is_nullable, position, null_succeeds);
case wasm::HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
@ -5635,8 +5636,8 @@ Node* WasmGraphBuilder::RefIsEq(Node* object, bool object_can_be_null,
}
Node* WasmGraphBuilder::RefAsEq(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
bool null_succeeds = false;
wasm::WasmCodePosition position,
bool null_succeeds) {
auto done = gasm_->MakeLabel();
EqCheck(object, object_can_be_null, CastCallbacks(&done, position),
null_succeeds);
@ -5660,8 +5661,8 @@ Node* WasmGraphBuilder::RefIsStruct(Node* object, bool object_can_be_null,
}
Node* WasmGraphBuilder::RefAsStruct(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
bool null_succeeds = false;
wasm::WasmCodePosition position,
bool null_succeeds) {
auto done = gasm_->MakeLabel();
if (!v8_flags.wasm_gc_structref_as_dataref) {
ManagedObjectInstanceCheck(object, object_can_be_null, WASM_STRUCT_TYPE,
@ -5705,8 +5706,8 @@ Node* WasmGraphBuilder::RefIsArray(Node* object, bool object_can_be_null,
}
Node* WasmGraphBuilder::RefAsArray(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
bool null_succeeds = false;
wasm::WasmCodePosition position,
bool null_succeeds) {
auto done = gasm_->MakeLabel();
ManagedObjectInstanceCheck(object, object_can_be_null, WASM_ARRAY_TYPE,
CastCallbacks(&done, position), null_succeeds);
@ -5741,8 +5742,16 @@ Node* WasmGraphBuilder::RefIsI31(Node* object, bool null_succeeds) {
return gasm_->IsI31(object);
}
Node* WasmGraphBuilder::RefAsI31(Node* object,
wasm::WasmCodePosition position) {
Node* WasmGraphBuilder::RefAsI31(Node* object, wasm::WasmCodePosition position,
bool null_succeeds) {
if (null_succeeds) {
auto done = gasm_->MakeLabel();
gasm_->GotoIf(gasm_->IsNull(object), &done);
TrapIfFalse(wasm::kTrapIllegalCast, gasm_->IsI31(object), position);
gasm_->Goto(&done);
gasm_->Bind(&done);
return object;
}
TrapIfFalse(wasm::kTrapIllegalCast, gasm_->IsI31(object), position);
return object;
}

View File

@ -498,27 +498,28 @@ class WasmGraphBuilder {
Node* RefCast(Node* object, Node* rtt, WasmTypeCheckConfig config,
wasm::WasmCodePosition position);
Node* RefCastAbstract(Node* object, wasm::HeapType type,
wasm::WasmCodePosition position);
wasm::WasmCodePosition position, bool null_succeeds);
void BrOnCast(Node* object, Node* rtt, WasmTypeCheckConfig config,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);
Node* RefIsEq(Node* object, bool object_can_be_null, bool null_succeeds);
Node* RefAsEq(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);
wasm::WasmCodePosition position, bool null_succeeds);
Node* RefIsStruct(Node* object, bool object_can_be_null, bool null_succeeds);
Node* RefAsStruct(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);
wasm::WasmCodePosition position, bool null_succeeds);
void BrOnStruct(Node* object, Node* rtt, WasmTypeCheckConfig config,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);
Node* RefIsArray(Node* object, bool object_can_be_null, bool null_succeeds);
Node* RefAsArray(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);
wasm::WasmCodePosition position, bool null_succeeds);
void BrOnArray(Node* object, Node* rtt, WasmTypeCheckConfig config,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);
Node* RefIsI31(Node* object, bool null_succeeds);
Node* RefAsI31(Node* object, wasm::WasmCodePosition position);
Node* RefAsI31(Node* object, wasm::WasmCodePosition position,
bool null_succeeds);
void BrOnI31(Node* object, Node* rtt, WasmTypeCheckConfig config,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);

View File

@ -5979,16 +5979,16 @@ class LiftoffCompiler {
}
void RefCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
Value* result_val) {
Value* result_val, bool null_succeeds) {
switch (type.representation()) {
case HeapType::kEq:
return RefAsEq(decoder, obj, result_val);
return RefAsEq(decoder, obj, result_val, null_succeeds);
case HeapType::kI31:
return RefAsI31(decoder, obj, result_val);
return RefAsI31(decoder, obj, result_val, null_succeeds);
case HeapType::kStruct:
return RefAsStruct(decoder, obj, result_val);
return RefAsStruct(decoder, obj, result_val, null_succeeds);
case HeapType::kArray:
return RefAsArray(decoder, obj, result_val);
return RefAsArray(decoder, obj, result_val, null_succeeds);
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
@ -6194,31 +6194,44 @@ class LiftoffCompiler {
template <TypeChecker type_checker>
void AbstractTypeCast(const Value& object, FullDecoder* decoder,
ValueKind result_kind) {
bool null_succeeds = false; // TODO(mliedtke): Use parameter.
ValueKind result_kind, bool null_succeeds = false) {
Label match;
Label* trap_label =
AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast);
TypeCheck check(object.type, trap_label, null_succeeds);
Initialize(check, kPeek);
FREEZE_STATE(frozen);
if (null_succeeds && check.obj_type.is_nullable()) {
__ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg,
check.null_reg(), frozen);
}
(this->*type_checker)(check, frozen);
__ bind(&match);
}
void RefAsEq(FullDecoder* decoder, const Value& object, Value* result) {
AbstractTypeCast<&LiftoffCompiler::EqCheck>(object, decoder, kRef);
void RefAsEq(FullDecoder* decoder, const Value& object, Value* result,
bool null_succeeds = false) {
AbstractTypeCast<&LiftoffCompiler::EqCheck>(object, decoder, kRef,
null_succeeds);
}
void RefAsStruct(FullDecoder* decoder, const Value& object,
Value* /* result */) {
AbstractTypeCast<&LiftoffCompiler::StructCheck>(object, decoder, kRef);
Value* /* result */, bool null_succeeds = false) {
AbstractTypeCast<&LiftoffCompiler::StructCheck>(object, decoder, kRef,
null_succeeds);
}
void RefAsI31(FullDecoder* decoder, const Value& object, Value* result) {
AbstractTypeCast<&LiftoffCompiler::I31Check>(object, decoder, kRef);
void RefAsI31(FullDecoder* decoder, const Value& object, Value* result,
bool null_succeeds = false) {
AbstractTypeCast<&LiftoffCompiler::I31Check>(object, decoder, kRef,
null_succeeds);
}
void RefAsArray(FullDecoder* decoder, const Value& object, Value* result) {
AbstractTypeCast<&LiftoffCompiler::ArrayCheck>(object, decoder, kRef);
void RefAsArray(FullDecoder* decoder, const Value& object, Value* result,
bool null_succeeds = false) {
AbstractTypeCast<&LiftoffCompiler::ArrayCheck>(object, decoder, kRef,
null_succeeds);
}
template <TypeChecker type_checker>

View File

@ -1078,7 +1078,8 @@ struct ControlBase : public PcForErrors<ValidationTag::full_validation> {
bool null_succeeds) \
F(RefCast, const Value& obj, const Value& rtt, Value* result, \
bool null_succeeds) \
F(RefCastAbstract, const Value& obj, HeapType type, Value* result) \
F(RefCastAbstract, const Value& obj, HeapType type, Value* result, \
bool null_succeeds) \
F(AssertNull, const Value& obj, Value* result) \
F(AssertNotNull, const Value& obj, Value* result) \
F(BrOnCast, const Value& obj, const Value& rtt, Value* result_on_branch, \
@ -2139,6 +2140,7 @@ class WasmDecoder : public Decoder {
return length + imm.length;
}
case kExprRefCast:
case kExprRefCastNull:
case kExprRefTest:
case kExprRefTestNull: {
HeapTypeImmediate imm(WasmFeatures::All(), decoder, pc + length,
@ -2378,6 +2380,7 @@ class WasmDecoder : public Decoder {
case kExprRefTestNull:
case kExprRefTestDeprecated:
case kExprRefCast:
case kExprRefCastNull:
case kExprRefCastDeprecated:
case kExprRefCastNop:
case kExprBrOnCast:
@ -4804,7 +4807,8 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
Push(value);
return opcode_length;
}
case kExprRefCast: {
case kExprRefCast:
case kExprRefCastNull: {
NON_CONST_ONLY
HeapTypeImmediate imm(this->enabled_, this, this->pc_ + opcode_length,
this->module_, validate);
@ -4834,8 +4838,7 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
return 0;
}
// TODO(mliedtke): Add support for ref.cast null.
bool null_succeeds = false;
bool null_succeeds = opcode == kExprRefCastNull;
Value value = CreateValue(ValueType::RefMaybeNull(
imm.type, (obj.type.is_bottom() || !null_succeeds)
? kNonNullable
@ -4874,7 +4877,8 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
if (rtt.has_value()) {
CALL_INTERFACE(RefCast, obj, rtt.value(), &value, null_succeeds);
} else {
CALL_INTERFACE(RefCastAbstract, obj, target_type, &value);
CALL_INTERFACE(RefCastAbstract, obj, target_type, &value,
null_succeeds);
}
}
}

View File

@ -1243,10 +1243,11 @@ class WasmGraphBuildingInterface {
}
void RefCastAbstract(FullDecoder* decoder, const Value& object,
wasm::HeapType type, Value* result) {
wasm::HeapType type, Value* result, bool null_succeeds) {
TFNode* node = object.node;
if (!v8_flags.experimental_wasm_assume_ref_cast_succeeds) {
node = builder_->RefCastAbstract(object.node, type, decoder->position());
node = builder_->RefCastAbstract(object.node, type, decoder->position(),
null_succeeds);
}
SetAndTypeNode(result, builder_->TypeGuard(node, result->type));
}
@ -1307,8 +1308,10 @@ class WasmGraphBuildingInterface {
}
void RefAsStruct(FullDecoder* decoder, const Value& object, Value* result) {
TFNode* cast_object = builder_->RefAsStruct(
object.node, object.type.is_nullable(), decoder->position());
bool null_succeeds = false;
TFNode* cast_object =
builder_->RefAsStruct(object.node, object.type.is_nullable(),
decoder->position(), null_succeeds);
TFNode* rename = builder_->TypeGuard(cast_object, result->type);
SetAndTypeNode(result, rename);
}
@ -1335,8 +1338,10 @@ class WasmGraphBuildingInterface {
}
void RefAsArray(FullDecoder* decoder, const Value& object, Value* result) {
TFNode* cast_object = builder_->RefAsArray(
object.node, object.type.is_nullable(), decoder->position());
bool null_succeeds = false;
TFNode* cast_object =
builder_->RefAsArray(object.node, object.type.is_nullable(),
decoder->position(), null_succeeds);
TFNode* rename = builder_->TypeGuard(cast_object, result->type);
SetAndTypeNode(result, rename);
}
@ -1361,7 +1366,9 @@ class WasmGraphBuildingInterface {
}
void RefAsI31(FullDecoder* decoder, const Value& object, Value* result) {
TFNode* cast_object = builder_->RefAsI31(object.node, decoder->position());
bool null_succeeds = false;
TFNode* cast_object =
builder_->RefAsI31(object.node, decoder->position(), null_succeeds);
TFNode* rename = builder_->TypeGuard(cast_object, result->type);
SetAndTypeNode(result, rename);
}

View File

@ -711,6 +711,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(RefTestNull, 0xfb48, _, "ref.test null") \
V(RefTestDeprecated, 0xfb44, _, "ref.test") \
V(RefCast, 0xfb41, _, "ref.cast") \
V(RefCastNull, 0xfb49, _, "ref.cast null") \
V(RefCastDeprecated, 0xfb45, _, "ref.cast") \
V(BrOnCast, 0xfb46, _, "br_on_cast") \
V(BrOnCastFail, 0xfb47, _, "br_on_cast_fail") \

View File

@ -67,6 +67,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kGCPrefix, kExprRefCast, typeCode,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
builder.addFunction(`refCastNull${typeName}`,
makeSig([kWasmExternRef], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefCastNull, typeCode,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
});
var instance = builder.instantiate();
@ -178,7 +187,6 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(structSuperObj));
@ -223,4 +231,77 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertSame(funcObj, wasm.refCastAny(funcObj));
assertEquals(1, wasm.refCastAny(1));
assertSame(jsObj, wasm.refCastAny(jsObj));
// ref.cast null
assertSame(null, wasm.refCastNullStructSuper(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSuper(undefined));
assertSame(structSuperObj, wasm.refCastNullStructSuper(structSuperObj));
assertSame(structSubObj, wasm.refCastNullStructSuper(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSuper(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSuper(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSuper(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSuper(jsObj));
assertSame(null, wasm.refCastNullStructSub(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(structSuperObj));
assertSame(structSubObj, wasm.refCastNullStructSub(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStructSub(jsObj));
assertSame(null, wasm.refCastNullArray(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(structSuperObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(structSubObj));
assertSame(arrayObj, wasm.refCastNullArray(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullArray(jsObj));
assertSame(null, wasm.refCastNullI31(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(structSuperObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(funcObj));
assertEquals(1, wasm.refCastNullI31(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullI31(jsObj));
assertSame(null, wasm.refCastNullAnyArray(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(structSuperObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(structSubObj));
assertSame(arrayObj, wasm.refCastNullAnyArray(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullAnyArray(jsObj));
assertSame(null, wasm.refCastNullStruct(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStruct(undefined));
assertSame(structSuperObj, wasm.refCastNullStruct(structSuperObj));
assertSame(structSubObj, wasm.refCastNullStruct(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStruct(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStruct(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStruct(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullStruct(jsObj));
assertSame(null, wasm.refCastNullEq(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullEq(undefined));
assertSame(structSuperObj, wasm.refCastNullEq(structSuperObj));
assertSame(structSubObj, wasm.refCastNullEq(structSubObj));
assertSame(arrayObj, wasm.refCastNullEq(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullEq(funcObj));
assertEquals(1, wasm.refCastNullEq(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastNullEq(jsObj));
assertSame(null, wasm.refCastNullAny(null));
assertSame(undefined, wasm.refCastNullAny(undefined));
assertSame(structSuperObj, wasm.refCastNullAny(structSuperObj));
assertSame(structSubObj, wasm.refCastNullAny(structSubObj));
assertSame(arrayObj, wasm.refCastNullAny(arrayObj));
assertSame(funcObj, wasm.refCastNullAny(funcObj));
assertEquals(1, wasm.refCastNullAny(1));
assertSame(jsObj, wasm.refCastNullAny(jsObj));
})();

View File

@ -26,6 +26,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprRefTest,
kExprRefTestNull,
kExprRefCast,
kExprRefCastNull,
];
for (let [source_type, target_type_imm] of types) {

View File

@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-gc --experimental-wasm-type-reflection --no-wasm-gc-structref-as-dataref
// Flags: --experimental-wasm-gc --experimental-wasm-type-reflection
// Flags: --no-wasm-gc-structref-as-dataref
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
@ -316,6 +317,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kGCPrefix, kExprRefCast, heapType,
kExprRefIsNull, // We can't expose the cast object to JS in most cases.
]).exportFunc();
builder.addFunction(`cast_null_${test.source}_to_${target}`,
makeSig([wasmRefType(creatorType)], [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprCallRef, creatorType,
kGCPrefix, kExprRefCastNull, heapType,
kExprRefIsNull, // We can't expose the cast object to JS in most cases.
]).exportFunc();
}
}
@ -341,6 +351,13 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
} else {
assertTraps(kTrapIllegalCast, () => cast(create_value));
}
let castNull = wasm[`cast_null_${test.source}_to_${target}`];
if (validValues.includes(value) || value == "nullref") {
let expected = value == "nullref" ? 1 : 0;
assertEquals(expected, castNull(create_value));
} else {
assertTraps(kTrapIllegalCast, () => castNull(create_value));
}
}
}
}

View File

@ -513,6 +513,7 @@ let kExprRefTest = 0x40;
let kExprRefTestNull = 0x48;
let kExprRefTestDeprecated = 0x44;
let kExprRefCast = 0x41;
let kExprRefCastNull = 0x49;
let kExprRefCastDeprecated = 0x45;
let kExprBrOnCast = 0x46;
let kExprBrOnCastFail = 0x47;