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

The new ref.test (opcode 0xfb40) takes an any reference (vs. data on
the old instruction) and expects a HeapType immediate.
The HeapType can be a concrete or an abstract type.

Bug: v8:7748
Change-Id: Iaa2010af21d3fee76e27a5f4476ae00f5ca837a1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3913028
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83475}
This commit is contained in:
Matthias Liedtke 2022-09-28 14:41:11 +02:00 committed by V8 LUCI CQ
parent bc4ff7caf4
commit e4828a364e
21 changed files with 742 additions and 69 deletions

View File

@ -5431,6 +5431,7 @@ WasmGraphBuilder::Callbacks WasmGraphBuilder::BranchCallbacks(
void WasmGraphBuilder::DataCheck(Node* object, bool object_can_be_null,
Callbacks callbacks) {
// TODO(7748): Is the extra null check actually beneficial for performance?
if (object_can_be_null) {
callbacks.fail_if(IsNull(object), BranchHint::kFalse);
}
@ -5439,6 +5440,17 @@ void WasmGraphBuilder::DataCheck(Node* object, bool object_can_be_null,
callbacks.fail_if_not(gasm_->IsDataRefMap(map), BranchHint::kTrue);
}
void WasmGraphBuilder::EqCheck(Node* object, bool object_can_be_null,
Callbacks callbacks) {
// TODO(7748): Is the extra null check actually beneficial for performance?
if (object_can_be_null) {
callbacks.fail_if(IsNull(object), BranchHint::kFalse);
}
callbacks.succeed_if(gasm_->IsI31(object), BranchHint::kFalse);
Node* map = gasm_->LoadMap(object);
callbacks.fail_if_not(gasm_->IsDataRefMap(map), BranchHint::kTrue);
}
void WasmGraphBuilder::ManagedObjectInstanceCheck(Node* object,
bool object_can_be_null,
InstanceType instance_type,
@ -5495,6 +5507,26 @@ Node* WasmGraphBuilder::RefTest(Node* object, Node* rtt,
return gasm_->WasmTypeCheck(object, rtt, config);
}
Node* WasmGraphBuilder::RefTestAbstract(Node* object, wasm::HeapType type) {
bool is_nullable =
compiler::NodeProperties::GetType(object).AsWasm().type.is_nullable();
switch (type.representation()) {
case wasm::HeapType::kEq:
return RefIsEq(object, is_nullable);
case wasm::HeapType::kI31:
return RefIsI31(object);
case wasm::HeapType::kData:
return RefIsData(object, is_nullable);
case wasm::HeapType::kArray:
return RefIsArray(object, is_nullable);
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();
}
}
Node* WasmGraphBuilder::RefCast(Node* object, Node* rtt,
WasmTypeCheckConfig config,
wasm::WasmCodePosition position) {
@ -5516,6 +5548,14 @@ void WasmGraphBuilder::BrOnCast(Node* object, Node* rtt,
*no_match_control = false_node;
}
Node* WasmGraphBuilder::RefIsEq(Node* object, bool object_can_be_null) {
auto done = gasm_->MakeLabel(MachineRepresentation::kWord32);
EqCheck(object, object_can_be_null, TestCallbacks(&done));
gasm_->Goto(&done, Int32Constant(1));
gasm_->Bind(&done);
return done.PhiAt(0);
}
Node* WasmGraphBuilder::RefIsData(Node* object, bool object_can_be_null) {
auto done = gasm_->MakeLabel(MachineRepresentation::kWord32);
DataCheck(object, object_can_be_null, TestCallbacks(&done));

View File

@ -494,11 +494,13 @@ class WasmGraphBuilder {
Node* RttCanon(uint32_t type_index);
Node* RefTest(Node* object, Node* rtt, WasmTypeCheckConfig config);
Node* RefTestAbstract(Node* object, wasm::HeapType type);
Node* RefCast(Node* object, Node* rtt, WasmTypeCheckConfig config,
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);
Node* RefIsData(Node* object, bool object_can_be_null);
Node* RefAsData(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);
@ -764,6 +766,7 @@ class WasmGraphBuilder {
SmallNodeVector& match_effects);
void DataCheck(Node* object, bool object_can_be_null, Callbacks callbacks);
void EqCheck(Node* object, bool object_can_be_null, Callbacks callbacks);
void ManagedObjectInstanceCheck(Node* object, bool object_can_be_null,
InstanceType instance_type,
Callbacks callbacks);

View File

@ -96,6 +96,10 @@ Reduction WasmGCLowering::ReduceWasmTypeCheck(Node* node) {
BranchHint::kFalse, gasm_.Int32Constant(0));
}
// TODO(7748): In some cases the Smi check is redundant. If we had information
// about the source type, we could skip it in those cases.
gasm_.GotoIf(gasm_.IsI31(object), &end_label, gasm_.Int32Constant(0));
Node* map = gasm_.LoadMap(object);
// First, check if types happen to be equal. This has been shown to give large

View File

@ -5913,6 +5913,20 @@ class LiftoffCompiler {
}
Register tmp1 = scratch_null; // Done with null checks.
// Add Smi check if the source type may store a Smi (i31ref or JS Smi).
ValueType i31ref = ValueType::Ref(HeapType::kI31);
// Ref.extern can also contain Smis, however there isn't any type that
// could downcast to ref.extern.
DCHECK(!rtt_type.is_reference_to(HeapType::kExtern));
// Ref.i31 check has its own implementation.
DCHECK(!rtt_type.is_reference_to(HeapType::kI31));
if (IsSubtypeOf(i31ref, obj_type, module)) {
Label* i31_target =
IsSubtypeOf(i31ref, rtt_type, module) ? &match : no_match;
__ emit_smi_check(obj_reg, i31_target, LiftoffAssembler::kJumpOnSmi,
frozen);
}
__ LoadMap(tmp1, obj_reg);
// {tmp1} now holds the object's map.
@ -5976,6 +5990,25 @@ class LiftoffCompiler {
__ PushRegister(kI32, result);
}
void RefTestAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
Value* result_val) {
switch (type.representation()) {
case HeapType::kEq:
return RefIsEq(decoder, obj, result_val);
case HeapType::kI31:
return RefIsI31(decoder, obj, result_val);
case HeapType::kData:
return RefIsData(decoder, obj, result_val);
case HeapType::kArray:
return RefIsArray(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 RefCast(FullDecoder* decoder, const Value& obj, const Value& rtt,
Value* result) {
if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) {
@ -6085,13 +6118,14 @@ class LiftoffCompiler {
LoadNullValue(check.null_reg(), pinned);
}
}
void LoadInstanceType(TypeCheck& check, const FreezeCacheState& frozen) {
void LoadInstanceType(TypeCheck& check, const FreezeCacheState& frozen,
Label* on_smi) {
if (check.obj_type.is_nullable()) {
__ emit_cond_jump(kEqual, check.no_match, kRefNull, check.obj_reg,
check.null_reg(), frozen);
}
__ emit_smi_check(check.obj_reg, check.no_match,
LiftoffAssembler::kJumpOnSmi, frozen);
__ emit_smi_check(check.obj_reg, on_smi, LiftoffAssembler::kJumpOnSmi,
frozen);
__ LoadMap(check.instance_type(), check.obj_reg);
__ Load(LiftoffRegister(check.instance_type()), check.instance_type(),
no_reg, wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
@ -6100,7 +6134,7 @@ class LiftoffCompiler {
// Abstract type checkers. They all fall through on match.
void DataCheck(TypeCheck& check, const FreezeCacheState& frozen) {
LoadInstanceType(check, frozen);
LoadInstanceType(check, frozen, check.no_match);
// We're going to test a range of WasmObject instance types with a single
// unsigned comparison.
Register tmp = check.instance_type();
@ -6111,7 +6145,7 @@ class LiftoffCompiler {
}
void ArrayCheck(TypeCheck& check, const FreezeCacheState& frozen) {
LoadInstanceType(check, frozen);
LoadInstanceType(check, frozen, check.no_match);
LiftoffRegister instance_type(check.instance_type());
__ emit_i32_cond_jumpi(kUnequal, check.no_match, check.instance_type(),
WASM_ARRAY_TYPE, frozen);
@ -6122,6 +6156,19 @@ class LiftoffCompiler {
LiftoffAssembler::kJumpOnNotSmi, frozen);
}
void EqCheck(TypeCheck& check, const FreezeCacheState& frozen) {
Label match;
LoadInstanceType(check, frozen, &match);
// We're going to test a range of WasmObject instance types with a single
// unsigned comparison.
Register tmp = check.instance_type();
__ emit_i32_subi(tmp, tmp, FIRST_WASM_OBJECT_TYPE);
__ emit_i32_cond_jumpi(kUnsignedGreaterThan, check.no_match, tmp,
LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE,
frozen);
__ bind(&match);
}
using TypeChecker = void (LiftoffCompiler::*)(TypeCheck& check,
const FreezeCacheState& frozen);
@ -6153,6 +6200,11 @@ class LiftoffCompiler {
AbstractTypeCheck<&LiftoffCompiler::DataCheck>(object);
}
void RefIsEq(FullDecoder* /* decoder */, const Value& object,
Value* /* result_val */) {
AbstractTypeCheck<&LiftoffCompiler::EqCheck>(object);
}
void RefIsArray(FullDecoder* /* decoder */, const Value& object,
Value* /* result_val */) {
AbstractTypeCheck<&LiftoffCompiler::ArrayCheck>(object);

View File

@ -14,6 +14,8 @@
#include <inttypes.h>
#include <optional>
#include "src/base/small-vector.h"
#include "src/base/strings.h"
#include "src/base/v8-fallthrough.h"
@ -1065,6 +1067,7 @@ struct ControlBase : public PcForErrors<validate> {
F(I31GetS, const Value& input, Value* result) \
F(I31GetU, const Value& input, Value* result) \
F(RefTest, const Value& obj, const Value& rtt, Value* result) \
F(RefTestAbstract, const Value& obj, HeapType type, Value* result) \
F(RefCast, const Value& obj, const Value& rtt, Value* result) \
F(AssertNull, const Value& obj, Value* result) \
F(BrOnCast, const Value& obj, const Value& rtt, Value* result_on_branch, \
@ -1072,6 +1075,7 @@ struct ControlBase : public PcForErrors<validate> {
F(BrOnCastFail, const Value& obj, const Value& rtt, \
Value* result_on_fallthrough, uint32_t depth) \
F(RefIsData, const Value& object, Value* result) \
F(RefIsEq, const Value& object, Value* result) \
F(RefIsI31, const Value& object, Value* result) \
F(RefIsArray, const Value& object, Value* result) \
F(RefAsData, const Value& object, Value* result) \
@ -2026,7 +2030,13 @@ class WasmDecoder : public Decoder {
if (io) io->BranchDepth(imm);
return length + imm.length;
}
case kExprRefTest:
case kExprRefTest: {
HeapTypeImmediate<validate> imm(WasmFeatures::All(), decoder,
pc + length, nullptr);
if (io) io->HeapType(imm);
return length + imm.length;
}
case kExprRefTestDeprecated:
case kExprRefCast:
case kExprRefCastNop: {
IndexImmediate<validate> imm(decoder, pc + length, "type index");
@ -2255,6 +2265,7 @@ class WasmDecoder : public Decoder {
case kExprArrayLenDeprecated:
case kExprArrayLen:
case kExprRefTest:
case kExprRefTestDeprecated:
case kExprRefCast:
case kExprRefCastNop:
case kExprBrOnCast:
@ -4241,20 +4252,30 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
}
}
// Checks if types are unrelated, thus type checking will always fail. Does
// not account for nullability.
// Returns true if type checking will always fail, either because the types
// are unrelated or because the target_type is one of the null sentinels and
// conversion to null does not succeed.
bool TypeCheckAlwaysFails(Value obj, HeapType expected_type) {
bool types_unrelated =
!IsSubtypeOf(ValueType::Ref(expected_type), obj.type, this->module_) &&
!IsSubtypeOf(obj.type, ValueType::RefNull(expected_type),
this->module_);
return types_unrelated ||
expected_type.representation() == HeapType::kNone ||
expected_type.representation() == HeapType::kNoFunc ||
expected_type.representation() == HeapType::kNoExtern;
}
bool TypeCheckAlwaysFails(Value obj, Value rtt) {
return !IsSubtypeOf(ValueType::Ref(rtt.type.ref_index()), obj.type,
this->module_) &&
!IsSubtypeOf(obj.type, ValueType::RefNull(rtt.type.ref_index()),
this->module_);
return TypeCheckAlwaysFails(obj, HeapType(rtt.type.ref_index()));
}
// Checks it {obj} is a subtype of {rtt}'s type, thus checking will always
// succeed. Does not account for nullability.
// Checks if {obj} is a subtype of type, thus checking will always
// succeed.
bool TypeCheckAlwaysSucceeds(Value obj, HeapType type) {
return IsSubtypeOf(obj.type, ValueType::RefNull(type), this->module_);
}
bool TypeCheckAlwaysSucceeds(Value obj, Value rtt) {
return IsSubtypeOf(obj.type, ValueType::RefNull(rtt.type.ref_index()),
this->module_);
return TypeCheckAlwaysSucceeds(obj, HeapType(rtt.type.ref_index()));
}
#define NON_CONST_ONLY \
@ -4682,6 +4703,75 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
return opcode_length;
}
case kExprRefTest: {
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);
Value value = CreateValue(kWasmI32);
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.test: %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;
}
if (V8_LIKELY(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))) {
if (rtt.has_value()) {
// Drop rtt.
CALL_INTERFACE(Drop);
}
// Type checking can still fail for null.
if (obj.type.is_nullable()) {
// We abuse ref.as_non_null, which isn't otherwise used as a unary
// operator, as a sentinel for the negation of ref.is_null.
CALL_INTERFACE(UnOp, kExprRefAsNonNull, obj, &value);
} else {
CALL_INTERFACE(Drop);
CALL_INTERFACE(I32Const, &value, 1);
}
} else if (V8_UNLIKELY(TypeCheckAlwaysFails(obj, target_type))) {
if (rtt.has_value()) {
// Drop rtt.
CALL_INTERFACE(Drop);
}
CALL_INTERFACE(Drop);
CALL_INTERFACE(I32Const, &value, 0);
} else {
if (rtt.has_value()) {
// RTT => Cast to concrete (index) type.
CALL_INTERFACE(RefTest, obj, rtt.value(), &value);
} else {
// No RTT => Cast to abstract (non-index) types.
CALL_INTERFACE(RefTestAbstract, obj, target_type, &value);
}
}
}
Drop(1 + rtt.has_value());
Push(value);
return opcode_length;
}
case kExprRefTestDeprecated: {
NON_CONST_ONLY
IndexImmediate<validate> imm(this, this->pc_ + opcode_length,
"type index");

View File

@ -1245,6 +1245,11 @@ class WasmGraphBuildingInterface {
SetAndTypeNode(result, builder_->RefTest(object.node, rtt.node, config));
}
void RefTestAbstract(FullDecoder* decoder, const Value& object,
wasm::HeapType type, Value* result) {
SetAndTypeNode(result, builder_->RefTestAbstract(object.node, type));
}
void RefCast(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* result) {
WasmTypeCheckConfig config =
@ -1294,6 +1299,11 @@ class WasmGraphBuildingInterface {
decoder, object, rtt, value_on_fallthrough, br_depth, false);
}
void RefIsEq(FullDecoder* decoder, const Value& object, Value* result) {
SetAndTypeNode(result,
builder_->RefIsEq(object.node, object.type.is_nullable()));
}
void RefIsData(FullDecoder* decoder, const Value& object, Value* result) {
SetAndTypeNode(result,
builder_->RefIsData(object.node, object.type.is_nullable()));

View File

@ -707,7 +707,8 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(I31New, 0xfb20, _, "i31.new") \
V(I31GetS, 0xfb21, _, "i31.get_s") \
V(I31GetU, 0xfb22, _, "i31.get_u") \
V(RefTest, 0xfb44, _, "ref.test") \
V(RefTest, 0xfb40, _, "ref.test") \
V(RefTestDeprecated, 0xfb44, _, "ref.test") \
V(RefCast, 0xfb45, _, "ref.cast") \
V(BrOnCast, 0xfb46, _, "br_on_cast") \
V(BrOnCastFail, 0xfb47, _, "br_on_cast_fail") \

View File

@ -98,8 +98,9 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index,
return true;
}
HeapType::Representation NullSentinelImpl(TypeInModule type) {
switch (type.type.heap_type().representation()) {
HeapType::Representation NullSentinelImpl(HeapType type,
const WasmModule* module) {
switch (type.representation()) {
case HeapType::kI31:
case HeapType::kNone:
case HeapType::kEq:
@ -118,9 +119,8 @@ HeapType::Representation NullSentinelImpl(TypeInModule type) {
case HeapType::kNoFunc:
return HeapType::kNoFunc;
default:
return type.module->has_signature(type.type.ref_index())
? HeapType::kNoFunc
: HeapType::kNone;
return module->has_signature(type.ref_index()) ? HeapType::kNoFunc
: HeapType::kNone;
}
}
@ -543,12 +543,18 @@ TypeInModule Intersection(ValueType type1, ValueType type2,
}
ValueType ToNullSentinel(TypeInModule type) {
HeapType::Representation null_heap = NullSentinelImpl(type);
HeapType::Representation null_heap =
NullSentinelImpl(type.type.heap_type(), type.module);
DCHECK(
IsHeapSubtypeOf(HeapType(null_heap), type.type.heap_type(), type.module));
return ValueType::RefNull(null_heap);
}
bool IsSameTypeHierarchy(HeapType type1, HeapType type2,
const WasmModule* module) {
return NullSentinelImpl(type1, module) == NullSentinelImpl(type2, module);
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -163,6 +163,10 @@ V8_INLINE V8_EXPORT_PRIVATE TypeInModule Intersection(TypeInModule type1,
// Returns the matching abstract null type (none, nofunc, noextern).
ValueType ToNullSentinel(TypeInModule type);
// Returns if two types share the same type hierarchy (any, extern, funcref).
bool IsSameTypeHierarchy(HeapType type1, HeapType type2,
const WasmModule* module);
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -1185,27 +1185,31 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
FunctionSig sig(1, 2, sig_types);
byte sig_index = tester.DefineSignature(&sig);
const byte kRefTestNull = tester.DefineFunction(
const byte kRefTestNullDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(type_index), subtype_index), kExprEnd});
// Upcasts should be optimized away for nominal types.
const byte kRefTestUpcast = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_STRUCT_NEW_DEFAULT(subtype_index), type_index),
{WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(type_index), subtype_index),
kExprEnd});
const byte kRefTestUpcastNull = tester.DefineFunction(
// Upcasts should be optimized away for nominal types.
const byte kRefTestUpcastDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(subtype_index), type_index), kExprEnd});
const byte kRefTestUnrelatedNullable = tester.DefineFunction(
{WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(subtype_index),
type_index),
kExprEnd});
const byte kRefTestUpcastNullDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(subtype_index), type_index),
kExprEnd});
const byte kRefTestUnrelatedNullableDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {refNull(subtype_index)},
{WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)),
WASM_REF_TEST(WASM_LOCAL_GET(0), sig_index), kExprEnd});
const byte kRefTestUnrelatedNull = tester.DefineFunction(
WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), sig_index), kExprEnd});
const byte kRefTestUnrelatedNullDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(subtype_index), sig_index), kExprEnd});
const byte kRefTestUnrelatedNonNullable = tester.DefineFunction(
{WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(subtype_index), sig_index),
kExprEnd});
const byte kRefTestUnrelatedNonNullableDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index),
{WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index),
kExprEnd});
const byte kRefCastNull =
@ -1328,12 +1332,12 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
tester.CompileModule();
tester.CheckResult(kRefTestNull, 0);
tester.CheckResult(kRefTestUpcast, 1);
tester.CheckResult(kRefTestUpcastNull, 0);
tester.CheckResult(kRefTestUnrelatedNullable, 0);
tester.CheckResult(kRefTestUnrelatedNull, 0);
tester.CheckResult(kRefTestUnrelatedNonNullable, 0);
tester.CheckResult(kRefTestNullDeprecated, 0);
tester.CheckResult(kRefTestUpcastDeprecated, 1);
tester.CheckResult(kRefTestUpcastNullDeprecated, 0);
tester.CheckResult(kRefTestUnrelatedNullableDeprecated, 0);
tester.CheckResult(kRefTestUnrelatedNullDeprecated, 0);
tester.CheckResult(kRefTestUnrelatedNonNullableDeprecated, 0);
tester.CheckResult(kRefCastNull, 1);
tester.CheckResult(kRefCastUpcast, 0);
@ -1480,6 +1484,16 @@ WASM_COMPILED_EXEC_TEST(FunctionRefs) {
const byte cast_reference = tester.DefineFunction(
&sig_func, {}, {WASM_REF_FUNC(sig_index), kExprEnd});
const byte test_deprecated = tester.DefineFunction(
tester.sigs.i_v(), {kWasmFuncRef},
{WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), sig_index), kExprEnd});
const byte test_fail_deprecated = tester.DefineFunction(
tester.sigs.i_v(), {kWasmFuncRef},
{WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), other_sig_index), kExprEnd});
const byte test = tester.DefineFunction(
tester.sigs.i_v(), {kWasmFuncRef},
{WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)),
@ -1508,6 +1522,8 @@ WASM_COMPILED_EXEC_TEST(FunctionRefs) {
CHECK_EQ(cast_function->code().raw_instruction_start(),
cast_function_reference->code().raw_instruction_start());
tester.CheckResult(test_deprecated, 1);
tester.CheckResult(test_fail_deprecated, 0);
tester.CheckResult(test, 1);
tester.CheckResult(test_fail, 0);
}

View File

@ -521,6 +521,8 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
#define WASM_REF_IS_NULL(val) val, kExprRefIsNull
#define WASM_REF_AS_NON_NULL(val) val, kExprRefAsNonNull
#define WASM_REF_EQ(lhs, rhs) lhs, rhs, kExprRefEq
#define WASM_REF_TEST_DEPRECATED(ref, typeidx) \
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(ref, typeidx) \

View File

@ -15,7 +15,7 @@ let sig = makeSig([wasmRefNullType(supertype)], [kWasmI32]);
let callee = builder.addFunction("callee", sig).addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, sub1,
kGCPrefix, kExprRefTestDeprecated, sub1,
kExprIf, kWasmVoid,
kExprLocalGet, 0,
kGCPrefix, kExprRefCast, sub1,

View File

@ -0,0 +1,133 @@
// 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
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestRefTest() {
var builder = new WasmModuleBuilder();
let structSuper = builder.addStruct([makeField(kWasmI32, true)]);
let structSub = builder.addStruct([makeField(kWasmI32, true)], structSuper);
let array = builder.addArray(kWasmI32);
let fct =
builder.addFunction('createStructSuper',
makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, structSuper,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
builder.addFunction('createStructSub', makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStructNew, structSub,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
builder.addFunction('createArray', makeSig([kWasmI32], [kWasmExternRef]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprArrayNewFixed, array, 1,
kGCPrefix, kExprExternExternalize,
]).exportFunc();
builder.addFunction('createFuncRef', makeSig([], [kWasmFuncRef]))
.addBody([
kExprRefFunc, fct.index,
]).exportFunc();
[
["StructSuper", structSuper],
["StructSub", structSub],
["Array", array],
["I31", kI31RefCode],
["AnyArray", kArrayRefCode],
["Data", kDataRefCode],
["Eq", kEqRefCode],
// 'ref.test any' is semantically the same as '!ref.is_null' here.
["Any", kAnyRefCode],
].forEach(([typeName, typeCode]) => {
builder.addFunction(`refTest${typeName}`,
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefTest, typeCode,
]).exportFunc();
});
var instance = builder.instantiate();
let wasm = instance.exports;
assertEquals(0, wasm.refTestStructSuper(null));
assertEquals(0, wasm.refTestStructSuper(undefined));
assertEquals(1, wasm.refTestStructSuper(wasm.createStructSuper()));
assertEquals(1, wasm.refTestStructSuper(wasm.createStructSub()));
assertEquals(0, wasm.refTestStructSuper(wasm.createArray()));
assertEquals(0, wasm.refTestStructSuper(wasm.createFuncRef()));
assertEquals(0, wasm.refTestStructSuper(1));
assertEquals(0, wasm.refTestStructSuper({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestStructSub(null));
assertEquals(0, wasm.refTestStructSub(undefined));
assertEquals(0, wasm.refTestStructSub(wasm.createStructSuper()));
assertEquals(1, wasm.refTestStructSub(wasm.createStructSub()));
assertEquals(0, wasm.refTestStructSub(wasm.createArray()));
assertEquals(0, wasm.refTestStructSub(wasm.createFuncRef()));
assertEquals(0, wasm.refTestStructSub(1));
assertEquals(0, wasm.refTestStructSub({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestArray(null));
assertEquals(0, wasm.refTestArray(undefined));
assertEquals(0, wasm.refTestArray(wasm.createStructSuper()));
assertEquals(0, wasm.refTestArray(wasm.createStructSub()));
assertEquals(1, wasm.refTestArray(wasm.createArray()));
assertEquals(0, wasm.refTestArray(wasm.createFuncRef()));
assertEquals(0, wasm.refTestArray(1));
assertEquals(0, wasm.refTestArray({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestI31(null));
assertEquals(0, wasm.refTestI31(undefined));
assertEquals(0, wasm.refTestI31(wasm.createStructSuper()));
assertEquals(0, wasm.refTestI31(wasm.createStructSub()));
assertEquals(0, wasm.refTestI31(wasm.createArray()));
assertEquals(0, wasm.refTestI31(wasm.createFuncRef()));
assertEquals(1, wasm.refTestI31(1));
assertEquals(0, wasm.refTestI31({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestAnyArray(null));
assertEquals(0, wasm.refTestAnyArray(undefined));
assertEquals(0, wasm.refTestAnyArray(wasm.createStructSuper()));
assertEquals(0, wasm.refTestAnyArray(wasm.createStructSub()));
assertEquals(1, wasm.refTestAnyArray(wasm.createArray()));
assertEquals(0, wasm.refTestAnyArray(wasm.createFuncRef()));
assertEquals(0, wasm.refTestAnyArray(1));
assertEquals(0, wasm.refTestAnyArray({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestData(null));
assertEquals(0, wasm.refTestData(undefined));
assertEquals(1, wasm.refTestData(wasm.createStructSuper()));
assertEquals(1, wasm.refTestData(wasm.createStructSub()));
assertEquals(1, wasm.refTestData(wasm.createArray()));
assertEquals(0, wasm.refTestData(wasm.createFuncRef()));
assertEquals(0, wasm.refTestData(1));
assertEquals(0, wasm.refTestData({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestEq(null));
assertEquals(0, wasm.refTestEq(undefined));
assertEquals(1, wasm.refTestEq(wasm.createStructSuper()));
assertEquals(1, wasm.refTestEq(wasm.createStructSub()));
assertEquals(1, wasm.refTestEq(wasm.createArray()));
assertEquals(0, wasm.refTestEq(wasm.createFuncRef()));
assertEquals(1, wasm.refTestEq(1)); // ref.i31
assertEquals(0, wasm.refTestEq({'JavaScript': 'Object'}));
assertEquals(0, wasm.refTestAny(null));
assertEquals(1, wasm.refTestAny(undefined));
assertEquals(1, wasm.refTestAny(wasm.createStructSuper()));
assertEquals(1, wasm.refTestAny(wasm.createStructSub()));
assertEquals(1, wasm.refTestAny(wasm.createArray()));
assertEquals(1, wasm.refTestAny(wasm.createFuncRef()));
assertEquals(1, wasm.refTestAny(1)); // ref.i31
assertEquals(1, wasm.refTestAny({'JavaScript': 'Object'}));
})();

View File

@ -0,0 +1,38 @@
// 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
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
(function TestRefTestInvalid() {
let struct = 0;
let array = 1;
let sig = 2;
[ // source value type |target heap type
[kWasmI32, kAnyRefCode],
[kWasmNullExternRef, struct],
[wasmRefType(struct), kNullFuncRefCode],
[wasmRefType(array), kFuncRefCode],
[wasmRefType(struct), sig],
[wasmRefType(sig), struct],
[wasmRefType(sig), kExternRefCode],
[kWasmAnyRef, kExternRefCode],
[kWasmAnyRef, kFuncRefCode],
].forEach(([source_type, target_type_imm]) => {
let builder = new WasmModuleBuilder();
let struct = builder.addStruct([makeField(kWasmI32, true)]);
let array = builder.addArray(kWasmI32);
let sig = builder.addType(makeSig([kWasmI32], []));
builder.addFunction('refTest', makeSig([kWasmI32], [source_type]))
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, target_type_imm,
]);
assertThrows(() => builder.instantiate(),
WebAssembly.CompileError,
/has to be in the same reference type hierarchy/);
});
})();

View File

@ -0,0 +1,237 @@
// 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 --experimental-wasm-type-reflection
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
// Test casting null from one type to another using ref.test.
(function RefTestNull() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let structSuper = builder.addStruct([makeField(kWasmI32, true)]);
let structSub = builder.addStruct([makeField(kWasmI32, true)], structSuper);
let array = builder.addArray(kWasmI32);
// Note: Casting between unrelated types is allowed as long as the types
// belong to the same type hierarchy (func / any / extern). In these cases the
// check will always fail.
let tests = [
[kWasmAnyRef, kWasmAnyRef, 'AnyToAny'],
[kWasmFuncRef, kWasmFuncRef, 'FuncToFunc'],
[kWasmExternRef, kWasmExternRef, 'ExternToExtern'],
[kWasmNullFuncRef, kWasmNullFuncRef, 'NullFuncToNullFunc'],
[kWasmNullExternRef, kWasmNullExternRef, 'NullExternToNullExtern'],
[structSub, array, 'StructToArray'],
[kWasmFuncRef, kWasmNullFuncRef, 'FuncToNullFunc'],
[kWasmNullFuncRef, kWasmFuncRef, 'NullFuncToFunc'],
[kWasmExternRef, kWasmNullExternRef, 'ExternToNullExtern'],
[kWasmNullExternRef, kWasmExternRef, 'NullExternToExtern'],
[kWasmNullRef, kWasmAnyRef, 'NullToAny'],
[kWasmI31Ref, structSub, 'I31ToStruct'],
[kWasmEqRef, kWasmI31Ref, 'EqToI31'],
[structSuper, structSub, 'StructSuperToStructSub'],
[structSub, structSuper, 'StructSubToStructSuper'],
];
for (let [sourceType, targetType, testName] of tests) {
builder.addFunction('testNull' + testName,
makeSig([], [kWasmI32]))
.addLocals(wasmRefNullType(sourceType), 1)
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, targetType & kLeb128Mask,
]).exportFunc();
}
let instance = builder.instantiate();
let wasm = instance.exports;
for (let [sourceType, targetType, testName] of tests) {
assertEquals(0, wasm['testNull' + testName]());
}
})();
(function RefTestFuncRef() {
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('testFromFuncRef',
makeSig([kWasmFuncRef], [kWasmI32, kWasmI32, kWasmI32, kWasmI32]))
.addBody([
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kFuncRefCode,
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kNullFuncRefCode,
kExprLocalGet, 0, kGCPrefix, kExprRefTest, sigSuper,
kExprLocalGet, 0, kGCPrefix, kExprRefTest, 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; });
assertEquals([1, 0, 0, 0], wasm.testFromFuncRef(jsFct));
assertEquals([1, 0, 1, 0], wasm.testFromFuncRef(wasm.fctSuper));
assertEquals([1, 0, 1, 1], wasm.testFromFuncRef(wasm.fctSub));
})();
(function RefTestExternRef() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('testExternRef',
makeSig([kWasmExternRef], [kWasmI32, kWasmI32,]))
.addBody([
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kExternRefCode,
kExprLocalGet, 0, kGCPrefix, kExprRefTest, kNullExternRefCode,
]).exportFunc();
let instance = builder.instantiate();
let wasm = instance.exports;
assertEquals([0, 0], wasm.testExternRef(null));
assertEquals([1, 0], wasm.testExternRef(undefined));
assertEquals([1, 0], wasm.testExternRef(1));
assertEquals([1, 0], wasm.testExternRef({}));
assertEquals([1, 0], wasm.testExternRef(wasm.testExternRef));
})();
(function RefTestAnyRefHierarchy() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let structSuper = builder.addStruct([makeField(kWasmI32, true)]);
let structSub = builder.addStruct([makeField(kWasmI32, true)], structSuper);
let array = builder.addArray(kWasmI32);
let types = {
any: kWasmAnyRef,
eq: kWasmEqRef,
data: kWasmDataRef,
anyArray: kWasmArrayRef,
array: wasmRefNullType(array),
structSuper: wasmRefNullType(structSuper),
structSub: wasmRefNullType(structSub),
};
let createBodies = {
nullref: [kExprRefNull, kNullRefCode],
i31ref: [kExprI32Const, 42, kGCPrefix, kExprI31New],
structSuper: [kExprI32Const, 42, kGCPrefix, kExprStructNew, structSuper],
structSub: [kExprI32Const, 42, kGCPrefix, kExprStructNew, structSub],
array: [kExprI32Const, 42, kGCPrefix, kExprArrayNewFixed, array, 1],
};
// Each Test lists the following:
// source => The static type of the source value.
// values => All actual values that are subtypes of the static types.
// targets => A list of types for ref.test. For each type the values are
// listed for which ref.test should return 1 (i.e. the ref.test
// should succeed).
let tests = [
{
source: 'any',
values: ['nullref', 'i31ref', 'structSuper', 'structSub', 'array'],
targets: {
eq: ['i31ref', 'structSuper', 'structSub', 'array'],
data: ['structSuper', 'structSub', 'array'],
anyArray: ['array'],
array: ['array'],
structSuper: ['structSuper', 'structSub'],
structSub: ['structSub'],
}
},
{
source: 'eq',
values: ['nullref', 'i31ref', 'structSuper', 'structSub', 'array'],
targets: {
eq: ['i31ref', 'structSuper', 'structSub', 'array'],
data: ['structSuper', 'structSub', 'array'],
anyArray: ['array'],
array: ['array'],
structSuper: ['structSuper', 'structSub'],
structSub: ['structSub'],
}
},
{
source: 'data',
values: ['nullref', 'structSuper', 'structSub', 'array'],
targets: {
eq: ['structSuper', 'structSub', 'array'],
data: ['structSuper', 'structSub', 'array'],
anyArray: ['array'],
array: ['array'],
structSuper: ['structSuper', 'structSub'],
structSub: ['structSub'],
}
},
{
source: 'anyArray',
values: ['nullref', 'array'],
targets: {
eq: ['array'],
data: ['array'],
anyArray: ['array'],
array: ['array'],
structSuper: [],
structSub: [],
}
},
{
source: 'structSuper',
values: ['nullref', 'structSuper', 'structSub'],
targets: {
eq: ['structSuper', 'structSub'],
data: ['structSuper', 'structSub'],
anyArray: [],
array: [],
structSuper: ['structSuper', 'structSub'],
structSub: ['structSub'],
}
},
];
for (let test of tests) {
let sourceType = types[test.source];
// Add creator functions.
let creatorSig = makeSig([], [sourceType]);
let creatorType = builder.addType(creatorSig);
for (let value of test.values) {
builder.addFunction(`create_${test.source}_${value}`, creatorType)
.addBody(createBodies[value]).exportFunc();
}
// Add ref.test tester functions.
// The functions take the creator functions as a callback to prevent the
// compiler to derive the actual type of the value and can only use the
// static source type.
for (let target in test.targets) {
// Get heap type for concrete types or apply Leb128 mask on the abstract
// type.
let heapType = types[target].heap_type ?? (types[target] & kLeb128Mask);
builder.addFunction(`test_${test.source}_to_${target}`,
makeSig([wasmRefType(creatorType)], [kWasmI32]))
.addBody([
kExprLocalGet, 0,
kExprCallRef, creatorType,
kGCPrefix, kExprRefTest, heapType,
]).exportFunc();
}
}
let instance = builder.instantiate();
let wasm = instance.exports;
for (let test of tests) {
for (let [target, validValues] of Object.entries(test.targets)) {
for (let value of test.values) {
print(`Test ${test.source}(${value}) -> ${target}`);
let create_value = wasm[`create_${test.source}_${value}`];
let res = wasm[`test_${test.source}_to_${target}`](create_value);
assertEquals(validValues.includes(value) ? 1 : 0, res);
}
}
}
})();

View File

@ -409,7 +409,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, sub_struct,
kGCPrefix, kExprRefTestDeprecated, sub_struct,
// These casts have to be preserved.
kExprLocalGet, 0,

View File

@ -127,7 +127,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// TF should be able to eliminate the second type check, and return the
// constant 1.
kExprLocalGet, 0,
kGCPrefix, kExprRefTest, sig])
kGCPrefix, kExprRefTestDeprecated, sig])
.exportFunc();
var instance = builder.instantiate({m : { f: function () {} }});

View File

@ -20,12 +20,12 @@ let struct_init = builder.addFunction("struct_init",
let test_pass = builder.addFunction("test_pass",
makeSig([kWasmDataRef], [kWasmI32]))
.addBody([kExprLocalGet, 0,
kGCPrefix, kExprRefTest, identical_struct_index])
kGCPrefix, kExprRefTestDeprecated, identical_struct_index])
.exportFunc();
let test_fail = builder.addFunction("test_fail",
makeSig([kWasmDataRef], [kWasmI32]))
.addBody([kExprLocalGet, 0,
kGCPrefix, kExprRefTest, distinct_struct_index])
kGCPrefix, kExprRefTestDeprecated, distinct_struct_index])
.exportFunc();
(function TestCanonicalizationSameInstance() {

View File

@ -33,7 +33,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// while (true) {
kExprLoop, kWasmVoid,
// if (ref.test temp bottom1) {
kExprLocalGet, 2, kGCPrefix, kExprRefTest, bottom1,
kExprLocalGet, 2, kGCPrefix, kExprRefTestDeprecated, bottom1,
kExprIf, kWasmVoid,
// counter += ((bottom1) temp).field_2;
// Note: This cast should get optimized away with path-based type

View File

@ -507,7 +507,8 @@ let kExprArrayNewElem = 0x1f;
let kExprI31New = 0x20;
let kExprI31GetS = 0x21;
let kExprI31GetU = 0x22;
let kExprRefTest = 0x44;
let kExprRefTest = 0x40;
let kExprRefTestDeprecated = 0x44;
let kExprRefCast = 0x45;
let kExprBrOnCast = 0x46;
let kExprBrOnCastFail = 0x47;

View File

@ -1151,8 +1151,13 @@ TEST_F(FunctionBodyDecoderTest, UnreachableRefTypes) {
{WASM_UNREACHABLE, WASM_GC_OP(kExprArrayNewDefault),
array_index, kExprDrop});
ExpectValidates(
sigs.i_v(),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefTestDeprecated), struct_index});
ExpectValidates(sigs.i_v(),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefTest), struct_index});
ExpectValidates(sigs.i_v(),
{WASM_UNREACHABLE, WASM_GC_OP(kExprRefTest), kEqRefCode});
ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast),
struct_index, kExprDrop});
@ -4303,24 +4308,31 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
HeapType::Representation func_heap_2 =
static_cast<HeapType::Representation>(builder.AddSignature(sigs.i_v()));
std::tuple<HeapType::Representation, HeapType::Representation, bool> tests[] =
{std::make_tuple(HeapType::kData, array_heap, true),
std::make_tuple(HeapType::kData, super_struct_heap, true),
std::make_tuple(HeapType::kFunc, func_heap_1, true),
std::make_tuple(func_heap_1, func_heap_1, true),
std::make_tuple(func_heap_1, func_heap_2, true),
std::make_tuple(super_struct_heap, sub_struct_heap, true),
std::make_tuple(array_heap, sub_struct_heap, true),
std::make_tuple(super_struct_heap, func_heap_1, true),
std::make_tuple(HeapType::kEq, super_struct_heap, false),
std::make_tuple(HeapType::kExtern, func_heap_1, false),
std::make_tuple(HeapType::kAny, array_heap, false),
std::make_tuple(HeapType::kI31, array_heap, false)};
std::tuple<HeapType::Representation, HeapType::Representation, bool, bool>
tests[] = {
std::make_tuple(HeapType::kData, array_heap, true, true),
std::make_tuple(HeapType::kData, super_struct_heap, true, true),
std::make_tuple(HeapType::kFunc, func_heap_1, true, true),
std::make_tuple(func_heap_1, func_heap_1, true, true),
std::make_tuple(func_heap_1, func_heap_2, true, true),
std::make_tuple(super_struct_heap, sub_struct_heap, true, true),
std::make_tuple(array_heap, sub_struct_heap, true, true),
std::make_tuple(super_struct_heap, func_heap_1, true, false),
std::make_tuple(HeapType::kEq, super_struct_heap, false, true),
std::make_tuple(HeapType::kExtern, func_heap_1, false, false),
std::make_tuple(HeapType::kAny, array_heap, false, true),
std::make_tuple(HeapType::kI31, array_heap, false, true),
std::make_tuple(HeapType::kNone, array_heap, true, true),
std::make_tuple(HeapType::kNone, func_heap_1, true, false),
};
for (auto test : tests) {
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);
SCOPED_TRACE("from_heap = " + from_heap.name() +
", to_heap = " + to_heap.name());
ValueType test_reps[] = {kWasmI32, ValueType::RefNull(from_heap)};
FunctionSig test_sig(1, 1, test_reps);
@ -4330,7 +4342,8 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
FunctionSig cast_sig(1, 1, cast_reps);
if (should_pass) {
ExpectValidates(&test_sig, {WASM_REF_TEST(WASM_LOCAL_GET(0),
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),
WASM_HEAP_TYPE(to_heap))});
@ -4340,20 +4353,40 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
"local.get of type " +
test_reps[1].name();
ExpectFailure(&test_sig,
{WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))},
{WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0),
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))},
kAppendEnd, ("ref.cast" + error_message).c_str());
}
if (should_pass_ref_test) {
ExpectValidates(&test_sig, {WASM_REF_TEST(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() +
" 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());
}
}
// Trivial type error.
ExpectFailure(sigs.v_v(),
{WASM_REF_TEST(WASM_I32V(1), array_heap), kExprDrop},
{WASM_REF_TEST_DEPRECATED(WASM_I32V(1), array_heap), kExprDrop},
kAppendEnd,
"ref.test[0] expected subtype of (ref null func) or "
"(ref null data), found i32.const of type i32");
ExpectFailure(sigs.v_v(),
{WASM_REF_TEST(WASM_I32V(1), array_heap), kExprDrop},
kAppendEnd,
"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},
kAppendEnd,
@ -4689,13 +4722,16 @@ class WasmOpcodeLengthTest : public TestWithZone {
DCHECK_LE(static_cast<uint32_t>(opcode), 0xFF);
bytes[0] = static_cast<byte>(opcode);
// Special case: select_with_type insists on a {1} immediate.
if (opcode == kExprSelectWithType) bytes[1] = 1;
if (opcode == kExprSelectWithType) {
bytes[1] = 1;
bytes[2] = kAnyRefCode;
}
}
WasmFeatures detected;
WasmDecoder<Decoder::kNoValidation> decoder(
WasmDecoder<Decoder::kBooleanValidation> decoder(
this->zone(), nullptr, WasmFeatures::All(), &detected, nullptr, bytes,
bytes + sizeof(bytes), 0);
WasmDecoder<Decoder::kNoValidation>::OpcodeLength(&decoder, bytes);
WasmDecoder<Decoder::kBooleanValidation>::OpcodeLength(&decoder, bytes);
EXPECT_TRUE(decoder.ok())
<< opcode << " aka " << WasmOpcodes::OpcodeName(opcode) << ": "
<< decoder.error().message();