Commit 103a42d3 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[wasm-gc] Bring V8 up to date with latest spec updates

Changes:
- Remove the restriction that ref.test, ref.cast and br_on_cast may only
  cast to subtypes of the cast object's type. Optimize unrelated type
  casts in the decoder. Add tests.
- Generalize Unreachable() interface function to Trap(TrapReason).
- Fix rtt.sub to be able to accept an rtt without depth. Modify related
  test accordingly.
- Type local.tee according to the local's type as opposed to the value's
  type.

Bug: v8:7748, v8:11541
Change-Id: I4d1846a2cfda891d32a9c1ed26781e4518d4cdf9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2756210Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73461}
parent e090f835
......@@ -2364,13 +2364,37 @@ class LiftoffCompiler {
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
}
void Unreachable(FullDecoder* decoder) {
Label* unreachable_label =
AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapUnreachable);
__ emit_jump(unreachable_label);
WasmCode::RuntimeStubId GetRuntimeStubIdForTrapReason(TrapReason reason) {
switch (reason) {
#define RUNTIME_STUB_FOR_TRAP(trap_reason) \
case k##trap_reason: \
return WasmCode::kThrowWasm##trap_reason;
FOREACH_WASM_TRAPREASON(RUNTIME_STUB_FOR_TRAP)
#undef RUNTIME_STUB_FOR_TRAP
default:
UNREACHABLE();
}
}
void Trap(FullDecoder* decoder, TrapReason reason) {
Label* trap_label =
AddOutOfLineTrap(decoder, GetRuntimeStubIdForTrapReason(reason));
__ emit_jump(trap_label);
__ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap);
}
void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) {
LiftoffRegList pinned;
LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
Label* trap_label =
AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapNullDereference);
LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned);
LoadNullValue(null.gp(), pinned);
__ emit_cond_jump(kUnequal, trap_label, kOptRef, obj.gp(), null.gp());
__ PushRegister(kOptRef, obj);
}
void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {
unsupported(decoder, kOtherReason, "testing opcode");
}
......
......@@ -1063,7 +1063,7 @@ struct ControlBase : public PcForErrors<validate> {
const TableIndexImmediate<validate>& imm) \
F(TableSet, const Value& index, const Value& value, \
const TableIndexImmediate<validate>& imm) \
F(Unreachable) \
F(Trap, TrapReason reason) \
F(NopForTestingUnsupportedInLiftoff) \
F(Select, const Value& cond, const Value& fval, const Value& tval, \
Value* result) \
......@@ -1159,6 +1159,7 @@ struct ControlBase : public PcForErrors<validate> {
F(RttSub, uint32_t type_index, const Value& parent, Value* result) \
F(RefTest, const Value& obj, const Value& rtt, 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, \
uint32_t depth) \
F(RefIsData, const Value& object, Value* result) \
......@@ -2943,7 +2944,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
DECODE(Unreachable) {
CALL_INTERFACE_IF_REACHABLE(Unreachable);
CALL_INTERFACE_IF_REACHABLE(Trap, TrapReason::kTrapUnreachable);
EndControl();
return 1;
}
......@@ -3079,8 +3080,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
DECODE(LocalTee) {
LocalIndexImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_ + 1, imm)) return 0;
Value value = Peek(0, 0, this->local_type(imm.index));
Value result = CreateValue(value.type);
ValueType local_type = this->local_type(imm.index);
Value value = Peek(0, 0, local_type);
Value result = CreateValue(local_type);
CALL_INTERFACE_IF_REACHABLE(LocalTee, value, &result, imm);
Drop(value);
Push(result);
......@@ -4011,6 +4013,14 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
}
bool ObjectRelatedWithRtt(Value obj, Value rtt) {
return IsSubtypeOf(ValueType::Ref(rtt.type.ref_index(), kNonNullable),
obj.type, this->module_) ||
IsSubtypeOf(obj.type,
ValueType::Ref(rtt.type.ref_index(), kNullable),
this->module_);
}
int DecodeGCOpcode(WasmOpcode opcode, uint32_t opcode_length) {
switch (opcode) {
case kExprStructNewWithRtt: {
......@@ -4302,8 +4312,10 @@ class WasmFullDecoder : public WasmDecoder<validate> {
"rtt for a supertype of type " + std::to_string(imm.index));
return 0;
}
Value value =
CreateValue(ValueType::Rtt(imm.index, parent.type.depth() + 1));
Value value = parent.type.has_depth()
? CreateValue(ValueType::Rtt(
imm.index, parent.type.depth() + 1))
: CreateValue(ValueType::Rtt(imm.index));
CALL_INTERFACE_IF_REACHABLE(RttSub, imm.index, parent, &value);
Drop(parent);
......@@ -4329,15 +4341,14 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 0;
}
if (!obj.type.is_bottom() && !rtt.type.is_bottom()) {
if (!VALIDATE(IsSubtypeOf(
ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type,
this->module_))) {
PopTypeError(
0, obj,
"supertype of type " + std::to_string(rtt.type.ref_index()));
return 0;
}
// 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_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
CALL_INTERFACE_IF_REACHABLE(RefTest, obj, rtt, &value);
} else {
// Unrelated types. Will always fail.
CALL_INTERFACE_IF_REACHABLE(I32Const, &value, 0);
}
}
Drop(2);
Push(value);
......@@ -4359,17 +4370,25 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 0;
}
if (!obj.type.is_bottom() && !rtt.type.is_bottom()) {
if (!VALIDATE(IsSubtypeOf(
ValueType::Ref(rtt.type.ref_index(), kNonNullable), obj.type,
this->module_))) {
PopTypeError(
0, obj,
"supertype of type " + std::to_string(rtt.type.ref_index()));
return 0;
}
Value value = CreateValue(
ValueType::Ref(rtt.type.ref_index(), obj.type.nullability()));
// 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_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
CALL_INTERFACE_IF_REACHABLE(RefCast, obj, rtt, &value);
} else {
// Unrelated types. The only way this will not trap is if the object
// is null.
if (obj.type.is_nullable()) {
// Drop rtt from the stack, then assert that obj is null.
CALL_INTERFACE_IF_REACHABLE(Drop);
CALL_INTERFACE_IF_REACHABLE(AssertNull, obj, &value);
} else {
// TODO(manoskouk): Change the trap label.
CALL_INTERFACE_IF_REACHABLE(Trap, TrapReason::kTrapIllegalCast);
EndControl();
}
}
Drop(2);
Push(value);
}
......@@ -4396,14 +4415,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
PopTypeError(0, obj, "subtype of (ref null func) or (ref null data)");
return 0;
}
// The static type of {obj} must be a supertype of {rtt}'s type.
if (!VALIDATE(rtt.type.is_bottom() || obj.type.is_bottom() ||
IsHeapSubtypeOf(rtt.type.ref_index(),
obj.type.heap_representation(),
this->module_))) {
PopTypeError(1, rtt, obj.type);
return 0;
}
Control* c = control_at(branch_depth.depth);
if (c->br_merge()->arity == 0) {
this->DecodeError(
......@@ -4423,13 +4434,18 @@ class WasmFullDecoder : public WasmDecoder<validate> {
Push(result_on_branch);
TypeCheckBranchResult check_result = TypeCheckBranch(c, true, 0);
if (V8_LIKELY(check_result == kReachableBranch)) {
// The {value_on_branch} parameter we pass to the interface must be
// pointer-identical to the object on the stack, so we can't reuse
// {result_on_branch} which was passed-by-value to {Push}.
// 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_LIKELY(ObjectRelatedWithRtt(obj, rtt))) {
// The {value_on_branch} parameter we pass to the interface must
// be pointer-identical to the object on the stack, so we can't
// reuse {result_on_branch} which was passed-by-value to {Push}.
Value* value_on_branch = stack_value(1);
CALL_INTERFACE(BrOnCast, obj, rtt, value_on_branch,
branch_depth.depth);
c->br_merge()->reached = true;
}
// Otherwise the types are unrelated. Do not branch.
} else if (check_result == kInvalidStack) {
return 0;
}
......
......@@ -397,13 +397,22 @@ class WasmGraphBuildingInterface {
builder_->TableSet(imm.index, index.node, value.node, decoder->position());
}
void Unreachable(FullDecoder* decoder) {
void Trap(FullDecoder* decoder, TrapReason reason) {
ValueVector values;
if (FLAG_wasm_loop_unrolling) {
BuildNestedLoopExits(decoder, decoder->control_depth() - 1, false,
values);
}
builder_->Trap(wasm::TrapReason::kTrapUnreachable, decoder->position());
builder_->Trap(reason, decoder->position());
}
void AssertNull(FullDecoder* decoder, const Value& obj, Value* result) {
builder_->TrapIfFalse(
wasm::TrapReason::kTrapIllegalCast,
builder_->Binop(kExprRefEq, obj.node, builder_->RefNull(),
decoder->position()),
decoder->position());
result->node = obj.node;
}
void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {}
......@@ -631,15 +640,15 @@ class WasmGraphBuildingInterface {
}
void BrOnNull(FullDecoder* decoder, const Value& ref_object, uint32_t depth) {
SsaEnv* non_null_env = ssa_env_;
SsaEnv* null_env = Split(decoder->zone(), non_null_env);
non_null_env->SetNotMerged();
builder_->BrOnNull(ref_object.node, &null_env->control,
&non_null_env->control);
builder_->SetControl(non_null_env->control);
SetEnv(null_env);
SsaEnv* false_env = ssa_env_;
SsaEnv* true_env = Split(decoder->zone(), false_env);
false_env->SetNotMerged();
builder_->BrOnNull(ref_object.node, &true_env->control,
&false_env->control);
builder_->SetControl(false_env->control);
SetEnv(true_env);
BrOrRet(decoder, depth, 1);
SetEnv(non_null_env);
SetEnv(false_env);
}
void SimdOp(FullDecoder* decoder, WasmOpcode opcode, Vector<Value> args,
......
......@@ -863,6 +863,105 @@ WASM_COMPILED_EXEC_TEST(BasicRtt) {
tester.CheckResult(kRefCast, 43);
}
WASM_COMPILED_EXEC_TEST(RefTrivialCasts) {
WasmGCTester tester(execution_tier);
byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
byte subtype_index =
tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmS128, false)});
ValueType sig_types[] = {kWasmS128, kWasmI32, kWasmF64};
FunctionSig sig(1, 2, sig_types);
byte sig_index = tester.DefineSignature(&sig);
const byte kRefTestNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(type_index), WASM_RTT_CANON(subtype_index)),
kExprEnd});
const byte kRefTestUpcast = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(
WASM_STRUCT_NEW_DEFAULT(
subtype_index,
WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
WASM_RTT_CANON(type_index)),
kExprEnd});
const byte kRefTestUpcastNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(subtype_index), WASM_RTT_CANON(type_index)),
kExprEnd});
const byte kRefTestUnrelated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(
WASM_STRUCT_NEW_DEFAULT(
subtype_index,
WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
WASM_RTT_CANON(sig_index)),
kExprEnd});
const byte kRefTestUnrelatedNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(subtype_index), WASM_RTT_CANON(sig_index)),
kExprEnd});
const byte kRefTestUnrelatedNonNullable = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(
WASM_STRUCT_NEW_DEFAULT(type_index, WASM_RTT_CANON(type_index)),
WASM_RTT_CANON(sig_index)),
kExprEnd});
const byte kRefCastNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(type_index),
WASM_RTT_CANON(subtype_index))),
kExprEnd});
const byte kRefCastUpcast = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(
WASM_STRUCT_NEW_DEFAULT(
subtype_index,
WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
WASM_RTT_CANON(type_index))),
kExprEnd});
const byte kRefCastUpcastNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(subtype_index),
WASM_RTT_CANON(type_index))),
kExprEnd});
const byte kRefCastUnrelated = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(
WASM_STRUCT_NEW_DEFAULT(
subtype_index,
WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index))),
WASM_RTT_CANON(sig_index))),
kExprEnd});
const byte kRefCastUnrelatedNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(subtype_index),
WASM_RTT_CANON(sig_index))),
kExprEnd});
const byte kRefCastUnrelatedNonNullable = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(
WASM_STRUCT_NEW_DEFAULT(type_index, WASM_RTT_CANON(type_index)),
WASM_RTT_CANON(sig_index))),
kExprEnd});
tester.CompileModule();
tester.CheckResult(kRefTestNull, 0);
tester.CheckResult(kRefTestUpcast, 1);
tester.CheckResult(kRefTestUpcastNull, 0);
tester.CheckResult(kRefTestUnrelated, 0);
tester.CheckResult(kRefTestUnrelatedNull, 0);
tester.CheckResult(kRefTestUnrelatedNonNullable, 0);
tester.CheckResult(kRefCastNull, 1);
tester.CheckResult(kRefCastUpcast, 0);
tester.CheckResult(kRefCastUpcastNull, 1);
tester.CheckHasThrown(kRefCastUnrelated);
tester.CheckResult(kRefCastUnrelatedNull, 1);
tester.CheckHasThrown(kRefCastUnrelatedNonNullable);
}
WASM_EXEC_TEST(NoDepthRtt) {
WasmGCTester tester(execution_tier);
......@@ -871,14 +970,19 @@ WASM_EXEC_TEST(NoDepthRtt) {
tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)});
const byte empty_struct_index = tester.DefineStruct({});
ValueType kRttTypeNoDepth = ValueType::Rtt(type_index);
FunctionSig sig_t1_v_nd(1, 0, &kRttTypeNoDepth);
ValueType kRttSubtypeNoDepth = ValueType::Rtt(subtype_index);
FunctionSig sig_t2_v_nd(1, 0, &kRttSubtypeNoDepth);
const byte kRttTypeCanon = tester.DefineFunction(
&sig_t1_v_nd, {}, {WASM_RTT_CANON(type_index), kExprEnd});
const byte kRttSubtypeCanon = tester.DefineFunction(
&sig_t2_v_nd, {}, {WASM_RTT_CANON(subtype_index), kExprEnd});
const byte kRttSubtypeSub = tester.DefineFunction(
&sig_t2_v_nd, {},
{WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)), kExprEnd});
{WASM_RTT_SUB(subtype_index, WASM_CALL_FUNCTION0(kRttTypeCanon)),
kExprEnd});
const byte kTestCanon = tester.DefineFunction(
tester.sigs.i_v(), {optref(type_index)},
......@@ -1059,25 +1163,6 @@ WASM_COMPILED_EXEC_TEST(CallRef) {
tester.CheckResult(caller, 47, 5);
}
WASM_COMPILED_EXEC_TEST(RefTestCastNull) {
WasmGCTester tester(execution_tier);
byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
const byte kRefTestNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_TEST(WASM_REF_NULL(type_index), WASM_RTT_CANON(type_index)),
kExprEnd});
const byte kRefCastNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(type_index),
WASM_RTT_CANON(type_index))),
kExprEnd});
tester.CompileModule();
tester.CheckResult(kRefTestNull, 0);
tester.CheckResult(kRefCastNull, 1);
}
WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
WasmGCTester tester(execution_tier);
......
......@@ -4269,17 +4269,18 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
HeapType::Representation func_heap_2 =
static_cast<HeapType::Representation>(builder.AddSignature(sigs.i_v()));
// Passing/failing tests due to static subtyping.
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, false),
std::make_tuple(func_heap_1, func_heap_2, true),
std::make_tuple(super_struct_heap, sub_struct_heap, true),
std::make_tuple(sub_struct_heap, super_struct_heap, false),
std::make_tuple(sub_struct_heap, array_heap, false),
std::make_tuple(HeapType::kFunc, array_heap, false)};
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::kAny, func_heap_1, false),
std::make_tuple(HeapType::kI31, array_heap, false)};
for (auto test : tests) {
HeapType from_heap = HeapType(std::get<0>(test));
......@@ -4308,9 +4309,9 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
ExpectValidates(&cast_sig,
{WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1))});
} else {
std::string error_message = "[0] expected supertype of type " +
std::to_string(to_heap.ref_index()) +
", found local.get of type " +
std::string error_message =
"[0] expected subtype of (ref null func) or (ref null data), found "
"local.get of type " +
test_reps[1].name();
ExpectFailure(&test_sig,
{WASM_REF_TEST(WASM_LOCAL_GET(0),
......@@ -4339,20 +4340,27 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) {
kAppendEnd,
"ref.cast[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
}
TEST_F(FunctionBodyDecoderTest, LocalTeeTyping) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
TestModuleBuilder builder;
module = builder.module();
byte array_type = builder.AddArray(kWasmI8, true);
ValueType types[] = {ValueType::Ref(array_type, kNonNullable)};
FunctionSig sig(1, 0, types);
AddLocals(ValueType::Ref(array_type, kNullable), 1);
// Trivial type error.
ExpectFailure(
sigs.v_v(),
{WASM_REF_TEST(WASM_I32V(1), WASM_RTT_CANON(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_CAST(WASM_I32V(1), WASM_RTT_CANON(array_heap)), kExprDrop},
kAppendEnd,
"ref.cast[0] expected subtype of (ref null func) or (ref null data), "
"found i32.const of type i32");
&sig,
{WASM_LOCAL_TEE(0, WASM_ARRAY_NEW_DEFAULT(array_type, WASM_I32V(5),
WASM_RTT_CANON(array_type)))},
kAppendEnd, "expected (ref 0), got (ref null 0)");
}
// This tests that num_locals_ in decoder remains consistent, even if we fail
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment