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

The new ref.cast (opcode 0xfb41) takes any reference and
expects a Heaptype immediate. the HeapType can be a
concrete or an abstract type.
Differently to the old ref.cast instruction, it traps on
null. A variant which doesn't trap on null (ref.cast null)
will be added in a future CL.

Bug: v8:7748
Change-Id: Id5764a7553a57c5cb838682c9ec331d15d7d25c0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3948663
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83724}
This commit is contained in:
Matthias Liedtke 2022-10-14 17:43:55 +02:00 committed by V8 LUCI CQ
parent 2ea6a9e223
commit 7ff8d6eb9e
15 changed files with 471 additions and 53 deletions

View File

@ -5548,6 +5548,27 @@ Node* WasmGraphBuilder::RefCast(Node* object, Node* rtt,
return gasm_->WasmTypeCast(object, rtt, config);
}
Node* WasmGraphBuilder::RefCastAbstract(Node* object, wasm::HeapType type,
wasm::WasmCodePosition position) {
bool is_nullable =
compiler::NodeProperties::GetType(object).AsWasm().type.is_nullable();
switch (type.representation()) {
case wasm::HeapType::kEq:
return RefAsEq(object, is_nullable, position);
case wasm::HeapType::kI31:
return RefAsI31(object, position);
case wasm::HeapType::kStruct:
return RefAsStruct(object, is_nullable, position);
case wasm::HeapType::kArray:
return RefAsArray(object, is_nullable, position);
case wasm::HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
default:
UNREACHABLE();
}
}
void WasmGraphBuilder::BrOnCast(Node* object, Node* rtt,
WasmTypeCheckConfig config,
Node** match_control, Node** match_effect,
@ -5572,6 +5593,17 @@ Node* WasmGraphBuilder::RefIsEq(Node* object, bool object_can_be_null,
return done.PhiAt(0);
}
Node* WasmGraphBuilder::RefAsEq(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
bool null_succeeds = false;
auto done = gasm_->MakeLabel();
EqCheck(object, object_can_be_null, CastCallbacks(&done, position),
null_succeeds);
gasm_->Goto(&done);
gasm_->Bind(&done);
return object;
}
Node* WasmGraphBuilder::RefIsStruct(Node* object, bool object_can_be_null,
bool null_succeeds) {
auto done = gasm_->MakeLabel(MachineRepresentation::kWord32);

View File

@ -497,10 +497,14 @@ class WasmGraphBuilder {
Node* RefTestAbstract(Node* object, wasm::HeapType type, bool null_succeeds);
Node* RefCast(Node* object, Node* rtt, WasmTypeCheckConfig config,
wasm::WasmCodePosition position);
Node* RefCastAbstract(Node* object, wasm::HeapType type,
wasm::WasmCodePosition position);
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);
Node* RefIsStruct(Node* object, bool object_can_be_null, bool null_succeeds);
Node* RefAsStruct(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);

View File

@ -154,9 +154,17 @@ Reduction WasmGCLowering::ReduceWasmTypeCast(Node* node) {
auto end_label = gasm_.MakeLabel();
if (object_can_be_null) {
gasm_.GotoIf(gasm_.TaggedEqual(object, Null()), &end_label,
BranchHint::kFalse);
Node* is_null = gasm_.TaggedEqual(object, Null());
if (config.null_succeeds) {
gasm_.GotoIf(is_null, &end_label, BranchHint::kFalse);
} else {
gasm_.TrapIf(is_null, TrapId::kTrapIllegalCast);
}
}
// TODO(7748): Only perform SMI check if source type may contain i31, any or
// extern.
gasm_.TrapIf(gasm_.IsI31(object), TrapId::kTrapIllegalCast);
Node* map = gasm_.LoadMap(object);

View File

@ -2679,7 +2679,8 @@ class LiftoffCompiler {
__ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap);
}
void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) {
void AssertNullImpl(FullDecoder* decoder, const Value& arg, Value* result,
LiftoffCondition cond) {
LiftoffRegList pinned;
LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
Label* trap_label =
@ -2688,12 +2689,20 @@ class LiftoffCompiler {
LoadNullValue(null.gp(), pinned);
{
FREEZE_STATE(trapping);
__ emit_cond_jump(kUnequal, trap_label, kRefNull, obj.gp(), null.gp(),
__ emit_cond_jump(cond, trap_label, kRefNull, obj.gp(), null.gp(),
trapping);
}
__ PushRegister(kRefNull, obj);
}
void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) {
AssertNullImpl(decoder, arg, result, kUnequal);
}
void AssertNotNull(FullDecoder* decoder, const Value& arg, Value* result) {
AssertNullImpl(decoder, arg, result, kEqual);
}
void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {
unsupported(decoder, kOtherReason, "testing opcode");
}
@ -6010,7 +6019,7 @@ class LiftoffCompiler {
}
void RefCast(FullDecoder* decoder, const Value& obj, const Value& rtt,
Value* result) {
Value* result, bool null_succeeds) {
if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) {
// Just drop the rtt.
__ DropValues(1);
@ -6028,13 +6037,33 @@ class LiftoffCompiler {
{
FREEZE_STATE(frozen);
NullSucceeds on_null = null_succeeds ? kNullSucceeds : kNullFails;
SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(),
rtt.type, scratch_null, scratch2, trap_label, kNullSucceeds,
rtt.type, scratch_null, scratch2, trap_label, on_null,
frozen);
}
__ PushRegister(obj.type.kind(), obj_reg);
}
void RefCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
Value* result_val) {
switch (type.representation()) {
case HeapType::kEq:
return RefAsEq(decoder, obj, result_val);
case HeapType::kI31:
return RefAsI31(decoder, obj, result_val);
case HeapType::kStruct:
return RefAsStruct(decoder, obj, result_val);
case HeapType::kArray:
return RefAsArray(decoder, obj, result_val);
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
default:
UNREACHABLE();
}
}
void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt,
Value* /* result_on_branch */, uint32_t depth) {
// Avoid having sequences of branches do duplicate work.
@ -6242,6 +6271,10 @@ class LiftoffCompiler {
(this->*type_checker)(check, frozen);
}
void RefAsEq(FullDecoder* decoder, const Value& object, Value* result) {
AbstractTypeCast<&LiftoffCompiler::EqCheck>(object, decoder, kRef);
}
void RefAsStruct(FullDecoder* decoder, const Value& object,
Value* /* result */) {
AbstractTypeCast<&LiftoffCompiler::StructCheck>(object, decoder, kRef);

View File

@ -1070,8 +1070,11 @@ struct ControlBase : public PcForErrors<validate> {
bool null_succeeds) \
F(RefTestAbstract, const Value& obj, HeapType type, Value* result, \
bool null_succeeds) \
F(RefCast, const Value& obj, const Value& rtt, Value* result) \
F(RefCast, const Value& obj, const Value& rtt, Value* result, \
bool null_succeeds) \
F(RefCastAbstract, const Value& obj, HeapType type, Value* result) \
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, \
uint32_t depth) \
F(BrOnCastFail, const Value& obj, const Value& rtt, \
@ -2119,6 +2122,7 @@ class WasmDecoder : public Decoder {
if (io) io->BranchDepth(imm);
return length + imm.length;
}
case kExprRefCast:
case kExprRefTest:
case kExprRefTestNull: {
HeapTypeImmediate<validate> imm(WasmFeatures::All(), decoder,
@ -2127,7 +2131,7 @@ class WasmDecoder : public Decoder {
return length + imm.length;
}
case kExprRefTestDeprecated:
case kExprRefCast:
case kExprRefCastDeprecated:
case kExprRefCastNop: {
IndexImmediate<validate> imm(decoder, pc + length, "type index");
if (io) io->TypeIndex(imm);
@ -2358,6 +2362,7 @@ class WasmDecoder : public Decoder {
case kExprRefTestNull:
case kExprRefTestDeprecated:
case kExprRefCast:
case kExprRefCastDeprecated:
case kExprRefCastNop:
case kExprBrOnCast:
case kExprBrOnCastFail:
@ -4794,6 +4799,84 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
Push(value);
return opcode_length;
}
case kExprRefCast: {
NON_CONST_ONLY
HeapTypeImmediate<validate> imm(
this->enabled_, this, this->pc_ + opcode_length, this->module_);
if (!VALIDATE(this->ok())) return 0;
opcode_length += imm.length;
std::optional<Value> rtt;
HeapType target_type = imm.type;
if (imm.type.is_index()) {
rtt = CreateValue(ValueType::Rtt(imm.type.ref_index()));
CALL_INTERFACE_IF_OK_AND_REACHABLE(RttCanon, imm.type.ref_index(),
&rtt.value());
Push(rtt.value());
}
Value obj = Peek(rtt.has_value() ? 1 : 0);
if (!VALIDATE((obj.type.is_object_reference() &&
IsSameTypeHierarchy(obj.type.heap_type(), target_type,
this->module_)) ||
obj.type.is_bottom())) {
this->DecodeError(
obj.pc(),
"Invalid types for ref.cast: %s of type %s has to "
"be in the same reference type hierarchy as (ref %s)",
SafeOpcodeNameAt(obj.pc()), obj.type.name().c_str(),
target_type.name().c_str());
return 0;
}
// TODO(mliedtke): Add support for ref.cast null.
bool null_succeeds = false;
Value value = CreateValue(ValueType::RefMaybeNull(
imm.type, (obj.type.is_bottom() || !null_succeeds)
? kNonNullable
: obj.type.nullability()));
if (current_code_reachable_and_ok_) {
// This logic ensures that code generation can assume that functions
// can only be cast to function types, and data objects to data types.
if (V8_UNLIKELY(TypeCheckAlwaysSucceeds(obj, target_type))) {
// Drop the rtt from the stack, then forward the object value to the
// result.
if (rtt.has_value()) {
CALL_INTERFACE(Drop);
}
if (obj.type.is_nullable() && !null_succeeds) {
CALL_INTERFACE(AssertNotNull, obj, &value);
} else {
CALL_INTERFACE(Forward, obj, &value);
}
} else if (V8_UNLIKELY(TypeCheckAlwaysFails(obj, target_type,
null_succeeds))) {
if (rtt.has_value()) {
CALL_INTERFACE(Drop);
}
// Unrelated types. The only way this will not trap is if the object
// is null.
if (obj.type.is_nullable() && null_succeeds) {
// Drop rtt from the stack, then assert that obj is null.
CALL_INTERFACE(AssertNull, obj, &value);
} else {
CALL_INTERFACE(Trap, TrapReason::kTrapIllegalCast);
// We know that the following code is not reachable, but according
// to the spec it technically is. Set it to spec-only reachable.
SetSucceedingCodeDynamicallyUnreachable();
}
} else {
if (rtt.has_value()) {
CALL_INTERFACE(RefCast, obj, rtt.value(), &value, null_succeeds);
} else {
CALL_INTERFACE(RefCastAbstract, obj, target_type, &value);
}
}
}
Drop(1 + rtt.has_value());
Push(value);
return opcode_length;
}
case kExprRefTestNull:
case kExprRefTest: {
NON_CONST_ONLY
@ -4944,7 +5027,7 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
Push(value);
return opcode_length;
}
case kExprRefCast: {
case kExprRefCastDeprecated: {
NON_CONST_ONLY
IndexImmediate<validate> imm(this, this->pc_ + opcode_length,
"type index");
@ -4990,7 +5073,8 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
SetSucceedingCodeDynamicallyUnreachable();
}
} else {
CALL_INTERFACE(RefCast, obj, rtt, &value);
bool null_succeeds = true;
CALL_INTERFACE(RefCast, obj, rtt, &value, null_succeeds);
}
}
Drop(2);

View File

@ -477,6 +477,12 @@ class WasmGraphBuildingInterface {
Forward(decoder, obj, result);
}
void AssertNotNull(FullDecoder* decoder, const Value& obj, Value* result) {
builder_->TrapIfTrue(wasm::TrapReason::kTrapIllegalCast,
builder_->IsNull(obj.node), decoder->position());
Forward(decoder, obj, result);
}
void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {}
void Select(FullDecoder* decoder, const Value& cond, const Value& fval,
@ -1254,9 +1260,7 @@ class WasmGraphBuildingInterface {
}
void RefCast(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* result) {
// TODO(mliedtke): Should be a parameter for generic ref.cast instructions.
const bool null_succeeds = false;
Value* result, bool null_succeeds) {
WasmTypeCheckConfig config = ComputeWasmTypeCheckConfig(
object.type, rtt.type, decoder->module_, null_succeeds);
TFNode* cast_node = v8_flags.experimental_wasm_assume_ref_cast_succeeds
@ -1266,6 +1270,15 @@ class WasmGraphBuildingInterface {
SetAndTypeNode(result, cast_node);
}
void RefCastAbstract(FullDecoder* decoder, const Value& object,
wasm::HeapType type, Value* result) {
TFNode* node = object.node;
if (!v8_flags.experimental_wasm_assume_ref_cast_succeeds) {
node = builder_->RefCastAbstract(object.node, type, decoder->position());
}
SetAndTypeNode(result, builder_->TypeGuard(node, result->type));
}
template <void (compiler::WasmGraphBuilder::*branch_function)(
TFNode*, TFNode*, WasmTypeCheckConfig, TFNode**, TFNode**, TFNode**,
TFNode**)>

View File

@ -710,7 +710,8 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(RefTest, 0xfb40, _, "ref.test") \
V(RefTestNull, 0xfb48, _, "ref.test null") \
V(RefTestDeprecated, 0xfb44, _, "ref.test") \
V(RefCast, 0xfb45, _, "ref.cast") \
V(RefCast, 0xfb41, _, "ref.cast") \
V(RefCastDeprecated, 0xfb45, _, "ref.cast") \
V(BrOnCast, 0xfb46, _, "br_on_cast") \
V(BrOnCastFail, 0xfb47, _, "br_on_cast_fail") \
V(RefCastNop, 0xfb4c, _, "ref.cast_nop") \

View File

@ -592,17 +592,28 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0),
kExprI32Add, kExprEnd});
const byte kTestNull = tester.DefineFunction(
const byte kTestNullDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_BLOCK_R(
ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)),
{WASM_BLOCK_R(ValueType::RefNull(type_index),
WASM_LOCAL_SET(0, WASM_I32V(111)),
WASM_LOCAL_GET(1), // Put a nullref onto the value stack.
// Not taken for nullref.
WASM_BR_ON_CAST(0, type_index), WASM_GC_OP(kExprRefCast), type_index,
WASM_BR_ON_CAST(0, type_index),
WASM_GC_OP(kExprRefCastDeprecated), type_index,
WASM_LOCAL_SET(0, WASM_I32V(222))), // Final result.
WASM_DROP, WASM_LOCAL_GET(0), kExprEnd});
const byte kTestNull = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_BLOCK_R(ValueType::RefNull(type_index),
WASM_LOCAL_SET(0, WASM_I32V(111)),
WASM_LOCAL_GET(1), // Put a nullref onto the value stack.
// Not taken for nullref.
WASM_BR_ON_CAST(0, type_index), WASM_GC_OP(kExprRefCast),
type_index), // Traps
WASM_DROP, WASM_LOCAL_GET(0), kExprEnd});
const byte kTypedAfterBranch = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(42))),
@ -620,7 +631,8 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
tester.CompileModule();
tester.CheckResult(kTestStructStatic, 222);
tester.CheckResult(kTestNull, 222);
tester.CheckResult(kTestNullDeprecated, 222);
tester.CheckHasThrown(kTestNull);
tester.CheckResult(kTypedAfterBranch, 42);
}
@ -1213,6 +1225,11 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
{WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index),
kExprEnd});
const byte kRefCastNullDeprecated =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(
WASM_REF_NULL(type_index), subtype_index)),
kExprEnd});
const byte kRefCastNull =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(
@ -1223,24 +1240,32 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
{WASM_REF_IS_NULL(
WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT(subtype_index), type_index)),
kExprEnd});
const byte kRefCastUpcastNullDeprecated =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(
WASM_REF_NULL(subtype_index), type_index)),
kExprEnd});
const byte kRefCastUpcastNull =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(
WASM_REF_NULL(subtype_index), type_index)),
kExprEnd});
// Note: Casting of types from different type hierarchies is only valid for
// the deprecated cast instruction.
const byte kRefCastUnrelatedNullable = tester.DefineFunction(
tester.sigs.i_v(), {refNull(subtype_index)},
{WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)),
WASM_REF_IS_NULL(WASM_REF_CAST(WASM_LOCAL_GET(0), sig_index)),
WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0), sig_index)),
kExprEnd});
const byte kRefCastUnrelatedNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(subtype_index), sig_index)),
const byte kRefCastUnrelatedNull =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(
WASM_REF_NULL(subtype_index), sig_index)),
kExprEnd});
const byte kRefCastUnrelatedNonNullable = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(
WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index)),
{WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(
WASM_STRUCT_NEW_DEFAULT(type_index), sig_index)),
kExprEnd});
const byte kBrOnCastNull = tester.DefineFunction(
@ -1340,9 +1365,11 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
tester.CheckResult(kRefTestUnrelatedNullDeprecated, 0);
tester.CheckResult(kRefTestUnrelatedNonNullableDeprecated, 0);
tester.CheckResult(kRefCastNull, 1);
tester.CheckResult(kRefCastNullDeprecated, 1);
tester.CheckHasThrown(kRefCastNull);
tester.CheckResult(kRefCastUpcast, 0);
tester.CheckResult(kRefCastUpcastNull, 1);
tester.CheckResult(kRefCastUpcastNullDeprecated, 1);
tester.CheckHasThrown(kRefCastUpcastNull);
tester.CheckHasThrown(kRefCastUnrelatedNullable);
tester.CheckResult(kRefCastUnrelatedNull, 1);
tester.CheckHasThrown(kRefCastUnrelatedNonNullable);
@ -1621,8 +1648,8 @@ WASM_COMPILED_EXEC_TEST(CastNullRef) {
tester.CheckHasThrown(to_array);
tester.CheckHasThrown(to_struct);
tester.CheckHasThrown(to_i31);
// Static ref.cast succeeds.
tester.CheckResult(to_struct_idx, 1);
// ref.cast traps on null.
tester.CheckHasThrown(to_struct_idx);
}
WASM_COMPILED_EXEC_TEST(CallReftypeParameters) {

View File

@ -525,6 +525,8 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
ref, WASM_GC_OP(kExprRefTestDeprecated), static_cast<byte>(typeidx)
#define WASM_REF_TEST(ref, typeidx) \
ref, WASM_GC_OP(kExprRefTest), static_cast<byte>(typeidx)
#define WASM_REF_CAST_DEPRECATED(ref, typeidx) \
ref, WASM_GC_OP(kExprRefCastDeprecated), static_cast<byte>(typeidx)
#define WASM_REF_CAST(ref, typeidx) \
ref, WASM_GC_OP(kExprRefCast), static_cast<byte>(typeidx)
// Takes a reference value from the value stack to allow sequences of

View File

@ -58,6 +58,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefTestNull, typeCode,
]).exportFunc();
builder.addFunction(`refCast${typeName}`,
makeSig([kWasmExternRef], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefCast, typeCode,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
});
var instance = builder.instantiate();
@ -134,4 +143,84 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals([1, 1], wasm.refTestAny(wasm.createFuncRef()));
assertEquals([1, 1], wasm.refTestAny(1)); // ref.i31
assertEquals([1, 1], wasm.refTestAny({'JavaScript': 'Object'}));
// ref.cast
let structSuperObj = wasm.createStructSuper();
let structSubObj = wasm.createStructSub();
let arrayObj = wasm.createArray();
let jsObj = {'JavaScript': 'Object'};
let funcObj = wasm.createFuncRef();
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(undefined));
assertSame(structSuperObj, wasm.refCastStructSuper(structSuperObj));
assertSame(structSubObj, wasm.refCastStructSuper(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(structSuperObj));
assertSame(structSubObj, wasm.refCastStructSub(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(structSuperObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(structSubObj));
assertSame(arrayObj, wasm.refCastArray(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastArray(funcObj));
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));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(funcObj));
assertEquals(1, wasm.refCastI31(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastI31(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(undefined));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(structSuperObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(structSubObj));
assertSame(arrayObj, wasm.refCastAnyArray(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(undefined));
assertSame(structSuperObj, wasm.refCastStruct(structSuperObj));
assertSame(structSubObj, wasm.refCastStruct(structSubObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(funcObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastEq(null));
assertTraps(kTrapIllegalCast, () => wasm.refCastEq(undefined));
assertSame(structSuperObj, wasm.refCastEq(structSuperObj));
assertSame(structSubObj, wasm.refCastEq(structSubObj));
assertSame(arrayObj, wasm.refCastEq(arrayObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastEq(funcObj));
assertEquals(1, wasm.refCastEq(1));
assertTraps(kTrapIllegalCast, () => wasm.refCastEq(jsObj));
assertTraps(kTrapIllegalCast, () => wasm.refCastAny(null));
assertSame(undefined, wasm.refCastAny(undefined));
assertSame(structSuperObj, wasm.refCastAny(structSuperObj));
assertSame(structSubObj, wasm.refCastAny(structSubObj));
assertSame(arrayObj, wasm.refCastAny(arrayObj));
assertSame(funcObj, wasm.refCastAny(funcObj));
assertEquals(1, wasm.refCastAny(1));
assertSame(jsObj, wasm.refCastAny(jsObj));
})();

View File

@ -25,6 +25,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
let casts = [
kExprRefTest,
kExprRefTestNull,
kExprRefCast,
];
for (let [source_type, target_type_imm] of types) {
@ -33,10 +34,11 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(struct, builder.addStruct([makeField(kWasmI32, true)]));
assertEquals(array, builder.addArray(kWasmI32));
assertEquals(sig, builder.addType(makeSig([kWasmI32], [])));
builder.addFunction('refTest', makeSig([kWasmI32], [source_type]))
builder.addFunction('refTest', makeSig([source_type], []))
.addBody([
kExprLocalGet, 0,
kGCPrefix, cast, target_type_imm,
kExprDrop,
]);
assertThrows(() => builder.instantiate(),

View File

@ -6,8 +6,8 @@
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
// Test casting null from one type to another using ref.test.
(function RefTestNull() {
// Test casting null from one type to another using ref.test & ref.cast.
(function RefCastFromNull() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let structSuper = builder.addStruct([makeField(kWasmI32, true)]);
@ -36,13 +36,19 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
];
for (let [sourceType, targetType, testName] of tests) {
builder.addFunction('testNull' + testName,
makeSig([], [kWasmI32]))
builder.addFunction('testNull' + testName, makeSig([], [kWasmI32]))
.addLocals(wasmRefNullType(sourceType), 1)
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, targetType & kLeb128Mask,
]).exportFunc();
builder.addFunction('castNull' + testName, makeSig([], []))
.addLocals(wasmRefNullType(sourceType), 1)
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefCast, targetType & kLeb128Mask,
kExprDrop,
]).exportFunc();
}
let instance = builder.instantiate();
@ -50,6 +56,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
for (let [sourceType, targetType, testName] of tests) {
assertEquals(0, wasm['testNull' + testName]());
assertTraps(kTrapIllegalCast, wasm['castNull' + testName]);
}
})();
@ -80,12 +87,56 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals([1, 0, 1, 1], wasm.testFromFuncRef(wasm.fctSub));
})();
(function RefCastFuncRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sigSuper = builder.addType(makeSig([kWasmI32], []));
let sigSub = builder.addType(makeSig([kWasmI32], []), sigSuper);
builder.addFunction('fctSuper', sigSuper).addBody([]).exportFunc();
builder.addFunction('fctSub', sigSub).addBody([]).exportFunc();
builder.addFunction('castToFuncRef', makeSig([kWasmFuncRef], [kWasmFuncRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kFuncRefCode])
.exportFunc();
builder.addFunction('castToNullFuncRef',
makeSig([kWasmFuncRef], [kWasmFuncRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kNullFuncRefCode])
.exportFunc();
builder.addFunction('castToSuper', makeSig([kWasmFuncRef], [kWasmFuncRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, sigSuper])
.exportFunc();
builder.addFunction('castToSub', makeSig([kWasmFuncRef], [kWasmFuncRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, sigSub])
.exportFunc();
let instance = builder.instantiate();
let wasm = instance.exports;
let jsFct = new WebAssembly.Function(
{parameters:['i32', 'i32'], results: ['i32']},
function mul(a, b) { return a * b; });
assertSame(jsFct, wasm.castToFuncRef(jsFct));
assertSame(wasm.fctSuper, wasm.castToFuncRef(wasm.fctSuper));
assertSame(wasm.fctSub, wasm.castToFuncRef(wasm.fctSub));
assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(jsFct));
assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(wasm.fctSuper));
assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(wasm.fctSub));
assertTraps(kTrapIllegalCast, () => wasm.castToSuper(jsFct));
assertSame(wasm.fctSuper, wasm.castToSuper(wasm.fctSuper));
assertSame(wasm.fctSub, wasm.castToSuper(wasm.fctSub));
assertTraps(kTrapIllegalCast, () => wasm.castToSub(jsFct));
assertTraps(kTrapIllegalCast, () => wasm.castToSub(wasm.fctSuper));
assertSame(wasm.fctSub, wasm.castToSub(wasm.fctSub));
})();
(function RefTestExternRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('testExternRef',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32,]))
makeSig([kWasmExternRef], [kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kExternRefCode,
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kNullExternRefCode,
@ -100,6 +151,36 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals([1, 0], wasm.testExternRef(wasm.testExternRef));
})();
(function RefCastExternRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('castToExternRef',
makeSig([kWasmExternRef], [kWasmExternRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kExternRefCode])
.exportFunc();
builder.addFunction('castToNullExternRef',
makeSig([kWasmExternRef], [kWasmExternRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kNullExternRefCode])
.exportFunc();
let instance = builder.instantiate();
let wasm = instance.exports;
assertTraps(kTrapIllegalCast, () => wasm.castToExternRef(null));
assertEquals(undefined, wasm.castToExternRef(undefined));
assertEquals(1, wasm.castToExternRef(1));
let obj = {};
assertSame(obj, wasm.castToExternRef(obj));
assertSame(wasm.castToExternRef, wasm.castToExternRef(wasm.castToExternRef));
assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(null));
assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(undefined));
assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(1));
assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(obj));
assertTraps(kTrapIllegalCast,
() => wasm.castToNullExternRef(wasm.castToExternRef));
})();
(function RefTestAnyRefHierarchy() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
@ -136,6 +217,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
source: 'any',
values: ['nullref', 'i31ref', 'structSuper', 'structSub', 'array'],
targets: {
any: ['i31ref', 'structSuper', 'structSub', 'array'],
eq: ['i31ref', 'structSuper', 'structSub', 'array'],
struct: ['structSuper', 'structSub'],
anyArray: ['array'],
@ -225,6 +307,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprCallRef, creatorType,
kGCPrefix, kExprRefTestNull, heapType,
]).exportFunc();
builder.addFunction(`cast_${test.source}_to_${target}`,
makeSig([wasmRefType(creatorType)], [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprCallRef, creatorType,
kGCPrefix, kExprRefCast, heapType,
kExprRefIsNull, // We can't expose the cast object to JS in most cases.
]).exportFunc();
}
}
@ -242,6 +333,14 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
res = wasm[`test_null_${test.source}_to_${target}`](create_value);
assertEquals(
(validValues.includes(value) || value == "nullref") ? 1 : 0, res);
print(`Test ref.cast: ${test.source}(${value}) -> ${target}`);
let cast = wasm[`cast_${test.source}_to_${target}`];
if (validValues.includes(value)) {
assertEquals(0, cast(create_value));
} else {
assertTraps(kTrapIllegalCast, () => cast(create_value));
}
}
}
}

View File

@ -468,8 +468,9 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
.addBody([
// Cast from struct_a to struct_b via common base type struct_super.
kExprLocalGet, 0,
kGCPrefix, kExprRefCast, struct_super,
kGCPrefix, kExprRefCast, struct_b, // annotated as 'ref null none'
// TODO(7748): Replace cast op with "ref.cast null".
kGCPrefix, kExprRefCastDeprecated, struct_super,
kGCPrefix, kExprRefCastDeprecated, struct_b, // annotated as 'ref null none'
kExprRefIsNull,
]);
@ -514,7 +515,8 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// local.get 0 is known to be null until end of block.
kExprLocalGet, 0,
// This cast is a no-op and shold be optimized away.
kGCPrefix, kExprRefCast, struct_b,
// TODO(7748): Replace with "ref.cast null".
kGCPrefix, kExprRefCastDeprecated, struct_b,
kExprEnd,
kExprRefIsNull,
]);

View File

@ -510,7 +510,8 @@ let kExprI31GetU = 0x22;
let kExprRefTest = 0x40;
let kExprRefTestNull = 0x48;
let kExprRefTestDeprecated = 0x44;
let kExprRefCast = 0x45;
let kExprRefCast = 0x41;
let kExprRefCastDeprecated = 0x45;
let kExprBrOnCast = 0x46;
let kExprBrOnCastFail = 0x47;
let kExprRefCastNop = 0x4c;

View File

@ -1160,7 +1160,8 @@ TEST_F(FunctionBodyDecoderTest, UnreachableRefTypes) {
ExpectValidates(sigs.i_v(),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefTest), kEqRefCode});
ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast),
ExpectValidates(sigs.v_v(),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefCastDeprecated),
struct_index, kExprDrop});
ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast),
struct_index, kExprDrop});
@ -1170,6 +1171,9 @@ TEST_F(FunctionBodyDecoderTest, UnreachableRefTypes) {
ExpectValidates(&sig_v_s, {WASM_UNREACHABLE, WASM_LOCAL_GET(0), kExprBrOnNull,
0, kExprCallFunction, struct_consumer});
ExpectValidates(
FunctionSig::Build(zone(), {struct_type}, {}),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefCastDeprecated), struct_index});
ExpectValidates(FunctionSig::Build(zone(), {struct_type}, {}),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast), struct_index});
@ -4331,7 +4335,7 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
HeapType from_heap = HeapType(std::get<0>(test));
HeapType to_heap = HeapType(std::get<1>(test));
bool should_pass = std::get<2>(test);
bool should_pass_ref_test = std::get<3>(test);
bool should_pass_new_ops = std::get<3>(test);
SCOPED_TRACE("from_heap = " + from_heap.name() +
", to_heap = " + to_heap.name());
@ -4346,7 +4350,8 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
ExpectValidates(&test_sig,
{WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0),
WASM_HEAP_TYPE(to_heap))});
ExpectValidates(&cast_sig, {WASM_REF_CAST(WASM_LOCAL_GET(0),
ExpectValidates(&cast_sig,
{WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0),
WASM_HEAP_TYPE(to_heap))});
} else {
std::string error_message =
@ -4358,22 +4363,29 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
WASM_HEAP_TYPE(to_heap))},
kAppendEnd, ("ref.test" + error_message).c_str());
ExpectFailure(&cast_sig,
{WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))},
{WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0),
WASM_HEAP_TYPE(to_heap))},
kAppendEnd, ("ref.cast" + error_message).c_str());
}
if (should_pass_ref_test) {
if (should_pass_new_ops) {
ExpectValidates(&test_sig, {WASM_REF_TEST(WASM_LOCAL_GET(0),
WASM_HEAP_TYPE(to_heap))});
ExpectValidates(&cast_sig, {WASM_REF_CAST(WASM_LOCAL_GET(0),
WASM_HEAP_TYPE(to_heap))});
} else {
std::string error_message =
"Invalid types for ref.test: local.get of type " +
cast_reps[1].name() +
"local.get of type " + cast_reps[1].name() +
" has to be in the same reference type hierarchy as (ref " +
to_heap.name() + ")";
ExpectFailure(&test_sig,
{WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))},
kAppendEnd, error_message.c_str());
kAppendEnd,
("Invalid types for ref.test: " + error_message).c_str());
ExpectFailure(&cast_sig,
{WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))},
kAppendEnd,
("Invalid types for ref.cast: " + error_message).c_str());
}
}
@ -4389,10 +4401,15 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
"Invalid types for ref.test: i32.const of type i32 has to be "
"in the same reference type hierarchy as (ref 0)");
ExpectFailure(sigs.v_v(),
{WASM_REF_CAST(WASM_I32V(1), array_heap), kExprDrop},
{WASM_REF_CAST_DEPRECATED(WASM_I32V(1), array_heap), kExprDrop},
kAppendEnd,
"ref.cast[0] expected subtype of (ref null func), (ref null "
"struct) or (ref null array), found i32.const of type i32");
ExpectFailure(sigs.v_v(),
{WASM_REF_CAST(WASM_I32V(1), array_heap), kExprDrop},
kAppendEnd,
"Invalid types for ref.cast: i32.const of type i32 has to be "
"in the same reference type hierarchy as (ref 0)");
}
TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) {
@ -4407,6 +4424,10 @@ TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) {
ValueType supertype = ValueType::RefNull(super_struct);
ValueType subtype = ValueType::RefNull(sub_struct);
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmI32, subtype}, {supertype}),
{WASM_I32V(42), WASM_LOCAL_GET(0), WASM_BR_ON_CAST(0, sub_struct),
WASM_GC_OP(kExprRefCast), sub_struct});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmI32, subtype}, {supertype}),
{WASM_I32V(42), WASM_LOCAL_GET(0), WASM_BR_ON_CAST(0, sub_struct),