Commit e916c7d7 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm-gc] Implement br_on_non_{func, data, i31}

Additional changes:
- Clean up liftoff implementation of br_on_*.
- Bundle operations everywhere based on operation rather than type.
- Remove reference argument from WASM_BR_ON_* macros, to bring them in
  sync with WASM_BR_ON_CAST.
- Add missing function decoding unittests for br_on_*.

Bug: v8:7748
Change-Id: I5f5ebfac5b50b5a9a201acb435344d7471326242
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2909857Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74709}
parent fbfd2557
......@@ -5383,7 +5383,7 @@ class LiftoffCompiler {
template <TypeChecker type_checker>
void BrOnAbstractType(const Value& object, FullDecoder* decoder,
uint32_t br_depth, ValueKind result_kind) {
uint32_t br_depth) {
// Before branching, materialize all constants. This avoids repeatedly
// materializing them for each conditional branch.
if (br_depth != decoder->control_depth() - 1) {
......@@ -5391,36 +5391,72 @@ class LiftoffCompiler {
decoder->control_at(br_depth)->br_merge()->arity);
}
Label match, no_match;
Label no_match;
LiftoffRegister obj_reg =
(this->*type_checker)(object, &no_match, {}, no_reg);
__ bind(&match);
__ PushRegister(result_kind, obj_reg);
__ PushRegister(kRef, obj_reg);
BrOrRet(decoder, br_depth, 0);
__ bind(&no_match);
// Drop the branch's value, restore original value.
Drop(decoder);
__ PushRegister(object.type.kind(), obj_reg);
}
template <TypeChecker type_checker>
void BrOnNonAbstractType(const Value& object, FullDecoder* decoder,
uint32_t br_depth) {
// Before branching, materialize all constants. This avoids repeatedly
// materializing them for each conditional branch.
if (br_depth != decoder->control_depth() - 1) {
__ MaterializeMergedConstants(
decoder->control_at(br_depth)->br_merge()->arity);
}
Label no_match, end;
LiftoffRegister obj_reg =
(this->*type_checker)(object, &no_match, {}, no_reg);
__ PushRegister(kRef, obj_reg);
__ emit_jump(&end);
__ bind(&no_match);
BrOrRet(decoder, br_depth, 0);
__ bind(&end);
}
void BrOnData(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnAbstractType<&LiftoffCompiler::DataCheck>(object, decoder,
br_depth, kRef);
br_depth);
}
void BrOnFunc(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnAbstractType<&LiftoffCompiler::FuncCheck>(object, decoder,
br_depth, kRef);
br_depth);
}
void BrOnI31(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnAbstractType<&LiftoffCompiler::I31Check>(object, decoder,
br_depth, kRef);
br_depth);
}
void BrOnNonData(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnNonAbstractType<&LiftoffCompiler::DataCheck>(object, decoder,
br_depth);
}
void BrOnNonFunc(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnNonAbstractType<&LiftoffCompiler::FuncCheck>(object, decoder,
br_depth);
}
void BrOnNonI31(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
return BrOnNonAbstractType<&LiftoffCompiler::I31Check>(object, decoder,
br_depth);
}
void Forward(FullDecoder* decoder, const Value& from, Value* to) {
......
......@@ -1013,6 +1013,7 @@ struct ControlBase : public PcForErrors<validate> {
F(FinishFunction) \
F(OnFirstError) \
F(NextInstruction, WasmOpcode) \
F(Forward, const Value& from, Value* to) \
/* Control: */ \
F(Block, Control* block) \
F(Loop, Control* block) \
......@@ -1145,16 +1146,21 @@ struct ControlBase : public PcForErrors<validate> {
uint32_t depth) \
F(BrOnCastFail, const Value& obj, const Value& rtt, \
Value* result_on_fallthrough, uint32_t depth) \
F(RefIsData, const Value& object, Value* result) \
F(RefAsData, const Value& object, Value* result) \
F(BrOnData, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(RefIsFunc, const Value& object, Value* result) \
F(RefAsFunc, const Value& object, Value* result) \
F(BrOnFunc, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(RefIsData, const Value& object, Value* result) \
F(RefIsI31, const Value& object, Value* result) \
F(RefAsFunc, const Value& object, Value* result) \
F(RefAsData, const Value& object, Value* result) \
F(RefAsI31, const Value& object, Value* result) \
F(BrOnFunc, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(BrOnData, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(BrOnI31, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(Forward, const Value& from, Value* to)
F(BrOnNonFunc, const Value& object, Value* value_on_fallthrough, \
uint32_t br_depth) \
F(BrOnNonData, const Value& object, Value* value_on_fallthrough, \
uint32_t br_depth) \
F(BrOnNonI31, const Value& object, Value* value_on_fallthrough, \
uint32_t br_depth)
// Generic Wasm bytecode decoder with utilities for decoding immediates,
// lengths, etc.
......@@ -4503,23 +4509,24 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return 0;
}
Value obj = Peek(0, 0, kWasmAnyRef);
Control* c = control_at(branch_depth.depth);
HeapType::Representation heap_type =
opcode == kExprBrOnFunc
? HeapType::kFunc
: opcode == kExprBrOnData ? HeapType::kData : HeapType::kI31;
if (c->br_merge()->arity == 0) {
this->DecodeError("%s must target a branch of arity at least 1",
SafeOpcodeNameAt(this->pc_));
return 0;
}
// Attention: contrary to most other instructions, we modify the
// stack before calling the interface function. This makes it
// significantly more convenient to pass around the values that
// will be on the stack when the branch is taken.
// TODO(jkummerow): Reconsider this choice.
Value obj = Peek(0, 0, kWasmAnyRef);
Drop(obj);
HeapType::Representation heap_type =
opcode == kExprBrOnFunc
? HeapType::kFunc
: opcode == kExprBrOnData ? HeapType::kData : HeapType::kI31;
Value result_on_branch =
CreateValue(ValueType::Ref(heap_type, kNonNullable));
Push(result_on_branch);
......@@ -4542,6 +4549,49 @@ class WasmFullDecoder : public WasmDecoder<validate> {
Push(obj); // Restore stack state on fallthrough.
return opcode_length + branch_depth.length;
}
case kExprBrOnNonData:
case kExprBrOnNonFunc:
case kExprBrOnNonI31: {
BranchDepthImmediate<validate> branch_depth(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
control_.size())) {
return 0;
}
Control* c = control_at(branch_depth.depth);
if (c->br_merge()->arity == 0) {
this->DecodeError("%s must target a branch of arity at least 1",
SafeOpcodeNameAt(this->pc_));
return 0;
}
if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
Value obj = Peek(0, 0, kWasmAnyRef);
HeapType::Representation heap_type =
opcode == kExprBrOnNonFunc
? HeapType::kFunc
: opcode == kExprBrOnNonData ? HeapType::kData : HeapType::kI31;
Value value_on_fallthrough =
CreateValue(ValueType::Ref(heap_type, kNonNullable));
if (V8_LIKELY(current_code_reachable_and_ok_)) {
if (opcode == kExprBrOnNonFunc) {
CALL_INTERFACE(BrOnNonFunc, obj, &value_on_fallthrough,
branch_depth.depth);
} else if (opcode == kExprBrOnNonData) {
CALL_INTERFACE(BrOnNonData, obj, &value_on_fallthrough,
branch_depth.depth);
} else {
CALL_INTERFACE(BrOnNonI31, obj, &value_on_fallthrough,
branch_depth.depth);
}
c->br_merge()->reached = true;
}
Drop(obj);
Push(value_on_fallthrough);
return opcode_length + branch_depth.length;
}
default:
this->DecodeError("invalid gc opcode");
return 0;
......
......@@ -1086,6 +1086,13 @@ class WasmGraphBuildingInterface {
true);
}
void BrOnNonData(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnData>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
}
void RefIsFunc(FullDecoder* decoder, const Value& object, Value* result) {
result->node = builder_->RefIsFunc(object.node, object.type.is_nullable());
}
......@@ -1102,6 +1109,13 @@ class WasmGraphBuildingInterface {
true);
}
void BrOnNonFunc(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnFunc>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
}
void RefIsI31(FullDecoder* decoder, const Value& object, Value* result) {
result->node = builder_->RefIsI31(object.node);
}
......@@ -1117,6 +1131,13 @@ class WasmGraphBuildingInterface {
true);
}
void BrOnNonI31(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnI31>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
}
void Forward(FullDecoder* decoder, const Value& from, Value* to) {
to->node = from.node;
}
......
......@@ -187,6 +187,8 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_INT_OP(StoreMem16, "store16")
CASE_I64_OP(StoreMem32, "store32")
CASE_S128_OP(StoreMem, "store128")
CASE_OP(RefEq, "ref.eq")
CASE_OP(Let, "let")
// Exception handling opcodes.
CASE_OP(Try, "try")
......@@ -411,8 +413,9 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(BrOnFunc, "br_on_func")
CASE_OP(BrOnData, "br_on_data")
CASE_OP(BrOnI31, "br_on_i31")
CASE_OP(RefEq, "ref.eq")
CASE_OP(Let, "let")
CASE_OP(BrOnNonFunc, "br_on_non_func")
CASE_OP(BrOnNonData, "br_on_non_data")
CASE_OP(BrOnNonI31, "br_on_non_i31")
case kNumericPrefix:
case kSimdPrefix:
......
......@@ -680,7 +680,10 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(RefAsI31, 0xfb5a, _) \
V(BrOnFunc, 0xfb60, _) \
V(BrOnData, 0xfb61, _) \
V(BrOnI31, 0xfb62, _)
V(BrOnI31, 0xfb62, _) \
V(BrOnNonFunc, 0xfb63, _) \
V(BrOnNonData, 0xfb64, _) \
V(BrOnNonI31, 0xfb65, _)
#define FOREACH_ATOMIC_0_OPERAND_OPCODE(V) \
/* AtomicFence does not target a particular linear memory. */ \
......
......@@ -1399,13 +1399,13 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
// If the branch is not taken, we return 0. If it is taken, then the respective
// type check should succeed, and we return 1.
#define BR_ON(TYPE, type, value) \
tester.DefineFunction( \
tester.sigs.i_v(), {kWasmAnyRef}, \
{WASM_LOCAL_SET(0, WASM_SEQ(value)), \
WASM_REF_IS_##TYPE(WASM_BLOCK_R( \
kWasm##type##Ref, WASM_BR_ON_##TYPE(0, WASM_LOCAL_GET(0)), \
WASM_RETURN(WASM_I32V(0)))), \
#define BR_ON(TYPE, type, value) \
tester.DefineFunction( \
tester.sigs.i_v(), {kWasmAnyRef}, \
{WASM_LOCAL_SET(0, WASM_SEQ(value)), \
WASM_REF_IS_##TYPE(WASM_BLOCK_R(kWasm##type##Ref, WASM_LOCAL_GET(0), \
WASM_BR_ON_##TYPE(0), \
WASM_RETURN(WASM_I32V(0)))), \
kExprEnd})
byte kBrOnDataTaken =
......@@ -1422,34 +1422,71 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
WASM_RTT_CANON(array_index)));
#undef BR_ON
// If the branch is not taken, we return 1. If it is taken, then the respective
// type check should fail, and we return 0.
#define BR_ON_NON(TYPE, type, value) \
tester.DefineFunction( \
tester.sigs.i_v(), {kWasmAnyRef}, \
{WASM_LOCAL_SET(0, WASM_SEQ(value)), \
WASM_REF_IS_##TYPE(WASM_BLOCK_R(kWasmAnyRef, WASM_LOCAL_GET(0), \
WASM_BR_ON_NON_##TYPE(0), \
WASM_RETURN(WASM_I32V(1)))), \
kExprEnd})
byte kBrOnNonDataNotTaken =
BR_ON_NON(DATA, Data,
WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
byte kBrOnNonDataTaken = BR_ON_NON(DATA, Data, WASM_REF_FUNC(function_index));
byte kBrOnNonFuncNotTaken =
BR_ON_NON(FUNC, Func, WASM_REF_FUNC(function_index));
byte kBrOnNonFuncTaken = BR_ON_NON(FUNC, Func, WASM_I31_NEW(WASM_I32V(42)));
byte kBrOnNonI31NotTaken = BR_ON_NON(I31, I31, WASM_I31_NEW(WASM_I32V(42)));
byte kBrOnNonI31Taken =
BR_ON_NON(I31, I31,
WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
#undef BR_ON_NON
tester.CompileModule();
tester.CheckResult(kDataCheckNull, 0);
tester.CheckResult(kFuncCheckNull, 0);
tester.CheckResult(kI31CheckNull, 0);
tester.CheckHasThrown(kDataCastNull);
tester.CheckHasThrown(kFuncCastNull);
tester.CheckHasThrown(kI31CastNull);
tester.CheckResult(kDataCheckSuccess, 1);
tester.CheckResult(kFuncCheckSuccess, 1);
tester.CheckResult(kI31CheckSuccess, 1);
tester.CheckResult(kDataCheckFailure, 0);
tester.CheckResult(kFuncCheckFailure, 0);
tester.CheckResult(kI31CheckFailure, 0);
tester.CheckResult(kDataCastSuccess, 1);
tester.CheckResult(kFuncCastSuccess, 1);
tester.CheckResult(kI31CastSuccess, 1);
tester.CheckHasThrown(kDataCastFailure);
tester.CheckHasThrown(kFuncCastFailure);
tester.CheckHasThrown(kI31CastFailure);
tester.CheckResult(kBrOnDataTaken, 1);
tester.CheckResult(kBrOnDataNotTaken, 0);
tester.CheckResult(kFuncCheckNull, 0);
tester.CheckHasThrown(kFuncCastNull);
tester.CheckResult(kFuncCheckSuccess, 1);
tester.CheckResult(kFuncCheckFailure, 0);
tester.CheckResult(kFuncCastSuccess, 1);
tester.CheckHasThrown(kFuncCastFailure);
tester.CheckResult(kBrOnFuncTaken, 1);
tester.CheckResult(kBrOnFuncNotTaken, 0);
tester.CheckResult(kI31CheckNull, 0);
tester.CheckHasThrown(kI31CastNull);
tester.CheckResult(kI31CheckSuccess, 1);
tester.CheckResult(kI31CheckFailure, 0);
tester.CheckResult(kI31CastSuccess, 1);
tester.CheckHasThrown(kI31CastFailure);
tester.CheckResult(kBrOnI31Taken, 1);
tester.CheckResult(kBrOnI31NotTaken, 0);
tester.CheckResult(kBrOnNonDataTaken, 0);
tester.CheckResult(kBrOnNonDataNotTaken, 1);
tester.CheckResult(kBrOnNonFuncTaken, 0);
tester.CheckResult(kBrOnNonFuncNotTaken, 1);
tester.CheckResult(kBrOnNonI31Taken, 0);
tester.CheckResult(kBrOnNonI31NotTaken, 1);
}
WASM_COMPILED_EXEC_TEST(BasicI31) {
......
......@@ -522,18 +522,23 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_BR_ON_CAST_FAIL(depth, rtt) \
rtt, WASM_GC_OP(kExprBrOnCastFail), static_cast<byte>(depth)
#define WASM_REF_IS_DATA(ref) ref, WASM_GC_OP(kExprRefIsData)
#define WASM_REF_AS_DATA(ref) ref, WASM_GC_OP(kExprRefAsData)
#define WASM_BR_ON_DATA(depth, ref) \
ref, WASM_GC_OP(kExprBrOnData), static_cast<byte>(depth)
#define WASM_REF_IS_FUNC(ref) ref, WASM_GC_OP(kExprRefIsFunc)
#define WASM_REF_AS_FUNC(ref) ref, WASM_GC_OP(kExprRefAsFunc)
#define WASM_BR_ON_FUNC(depth, ref) \
ref, WASM_GC_OP(kExprBrOnFunc), static_cast<byte>(depth)
#define WASM_REF_IS_DATA(ref) ref, WASM_GC_OP(kExprRefIsData)
#define WASM_REF_IS_I31(ref) ref, WASM_GC_OP(kExprRefIsI31)
#define WASM_REF_AS_FUNC(ref) ref, WASM_GC_OP(kExprRefAsFunc)
#define WASM_REF_AS_DATA(ref) ref, WASM_GC_OP(kExprRefAsData)
#define WASM_REF_AS_I31(ref) ref, WASM_GC_OP(kExprRefAsI31)
#define WASM_BR_ON_I31(depth, ref) \
ref, WASM_GC_OP(kExprBrOnI31), static_cast<byte>(depth)
#define WASM_BR_ON_FUNC(depth) \
WASM_GC_OP(kExprBrOnFunc), static_cast<byte>(depth)
#define WASM_BR_ON_DATA(depth) \
WASM_GC_OP(kExprBrOnData), static_cast<byte>(depth)
#define WASM_BR_ON_I31(depth) WASM_GC_OP(kExprBrOnI31), static_cast<byte>(depth)
#define WASM_BR_ON_NON_FUNC(depth) \
WASM_GC_OP(kExprBrOnNonFunc), static_cast<byte>(depth)
#define WASM_BR_ON_NON_DATA(depth) \
WASM_GC_OP(kExprBrOnNonData), static_cast<byte>(depth)
#define WASM_BR_ON_NON_I31(depth) \
WASM_GC_OP(kExprBrOnNonI31), static_cast<byte>(depth)
#define WASM_ARRAY_NEW_WITH_RTT(index, default_value, length, rtt) \
default_value, length, rtt, WASM_GC_OP(kExprArrayNewWithRtt), \
......
......@@ -4494,6 +4494,66 @@ TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) {
"br_on_cast_fail[1] expected rtt, found i64.const of type i64");
}
TEST_F(FunctionBodyDecoderTest, BrOnAbstractType) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
TestModuleBuilder builder;
module = builder.module();
ValueType kNonNullableFunc = ValueType::Ref(HeapType::kFunc, kNonNullable);
ExpectValidates(
FunctionSig::Build(this->zone(), {kNonNullableFunc}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_FUNC(0), WASM_GC_OP(kExprRefAsFunc)});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmAnyRef}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_NON_FUNC(0)});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmDataRef}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_DATA(0), WASM_GC_OP(kExprRefAsData)});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmAnyRef}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_NON_DATA(0)});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmI31Ref}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_I31(0), WASM_GC_OP(kExprRefAsI31)});
ExpectValidates(
FunctionSig::Build(this->zone(), {kWasmAnyRef}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_NON_I31(0)});
// Unrelated types are OK.
ExpectValidates(
FunctionSig::Build(this->zone(), {kNonNullableFunc}, {kWasmDataRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_FUNC(0), WASM_GC_OP(kExprRefAsFunc)});
// Wrong branch type.
ExpectFailure(FunctionSig::Build(this->zone(), {}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_FUNC(0), WASM_UNREACHABLE},
kAppendEnd,
"br_on_func must target a branch of arity at least 1");
ExpectFailure(
FunctionSig::Build(this->zone(), {kNonNullableFunc}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_NON_FUNC(0)}, kAppendEnd,
"type error in branch[0] (expected (ref func), got anyref)");
// Wrong fallthrough type.
ExpectFailure(FunctionSig::Build(this->zone(), {kWasmDataRef}, {kWasmAnyRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_DATA(0)}, kAppendEnd,
"type error in fallthru[0] (expected dataref, got anyref)");
ExpectFailure(FunctionSig::Build(this->zone(), {kWasmAnyRef}, {kWasmAnyRef}),
{WASM_BLOCK_I(WASM_LOCAL_GET(0), WASM_BR_ON_NON_DATA(0))},
kAppendEnd,
"type error in branch[0] (expected i32, got anyref)");
// Argument type error.
ExpectFailure(
FunctionSig::Build(this->zone(), {kWasmI31Ref}, {kWasmI32}),
{WASM_LOCAL_GET(0), WASM_BR_ON_I31(0), WASM_GC_OP(kExprRefAsI31)},
kAppendEnd,
"br_on_i31[0] expected type anyref, found local.get of type i32");
}
TEST_F(FunctionBodyDecoderTest, LocalTeeTyping) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
......
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