[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:
parent
bc4ff7caf4
commit
e4828a364e
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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()));
|
||||
|
@ -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") \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) \
|
||||
|
@ -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,
|
||||
|
133
test/mjsunit/wasm/gc-casts-from-any.js
Normal file
133
test/mjsunit/wasm/gc-casts-from-any.js
Normal 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'}));
|
||||
})();
|
38
test/mjsunit/wasm/gc-casts-invalid.js
Normal file
38
test/mjsunit/wasm/gc-casts-invalid.js
Normal 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/);
|
||||
});
|
||||
})();
|
237
test/mjsunit/wasm/gc-casts-subtypes.js
Normal file
237
test/mjsunit/wasm/gc-casts-subtypes.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
@ -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,
|
||||
|
@ -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 () {} }});
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user