[wasm-gc] Implement ref.is_array, ref.as_array

Bug: v8:7748
Change-Id: Ieedb5bb0d6555cdf6c628f6700f7116ca142a2d2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3376963
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78577}
This commit is contained in:
Manos Koukoutos 2022-01-12 06:32:48 +00:00 committed by V8 LUCI CQ
parent e557383c83
commit c7f2108019
10 changed files with 145 additions and 54 deletions

View File

@ -5762,15 +5762,16 @@ void WasmGraphBuilder::DataCheck(Node* object, bool object_can_be_null,
callbacks.fail_if_not(gasm_->IsDataRefMap(map), BranchHint::kTrue);
}
void WasmGraphBuilder::FuncCheck(Node* object, bool object_can_be_null,
Callbacks callbacks) {
void WasmGraphBuilder::ManagedObjectInstanceCheck(Node* object,
bool object_can_be_null,
InstanceType instance_type,
Callbacks callbacks) {
if (object_can_be_null) {
callbacks.fail_if(IsNull(object), BranchHint::kFalse);
}
callbacks.fail_if(gasm_->IsI31(object), BranchHint::kFalse);
callbacks.fail_if_not(
gasm_->HasInstanceType(object, WASM_INTERNAL_FUNCTION_TYPE),
BranchHint::kTrue);
callbacks.fail_if_not(gasm_->HasInstanceType(object, instance_type),
BranchHint::kTrue);
}
void WasmGraphBuilder::BrOnCastAbs(
@ -5863,7 +5864,8 @@ void WasmGraphBuilder::BrOnData(Node* object, Node* /*rtt*/,
Node* WasmGraphBuilder::RefIsFunc(Node* object, bool object_can_be_null) {
auto done = gasm_->MakeLabel(MachineRepresentation::kWord32);
FuncCheck(object, object_can_be_null, TestCallbacks(&done));
ManagedObjectInstanceCheck(object, object_can_be_null,
WASM_INTERNAL_FUNCTION_TYPE, TestCallbacks(&done));
gasm_->Goto(&done, Int32Constant(1));
gasm_->Bind(&done);
return done.PhiAt(0);
@ -5872,7 +5874,9 @@ Node* WasmGraphBuilder::RefIsFunc(Node* object, bool object_can_be_null) {
Node* WasmGraphBuilder::RefAsFunc(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
auto done = gasm_->MakeLabel();
FuncCheck(object, object_can_be_null, CastCallbacks(&done, position));
ManagedObjectInstanceCheck(object, object_can_be_null,
WASM_INTERNAL_FUNCTION_TYPE,
CastCallbacks(&done, position));
gasm_->Goto(&done);
gasm_->Bind(&done);
return object;
@ -5885,10 +5889,31 @@ void WasmGraphBuilder::BrOnFunc(Node* object, Node* /*rtt*/,
Node** no_match_effect) {
BrOnCastAbs(match_control, match_effect, no_match_control, no_match_effect,
[=](Callbacks callbacks) -> void {
return FuncCheck(object, config.object_can_be_null, callbacks);
return ManagedObjectInstanceCheck(
object, config.object_can_be_null,
WASM_INTERNAL_FUNCTION_TYPE, callbacks);
});
}
Node* WasmGraphBuilder::RefIsArray(Node* object, bool object_can_be_null) {
auto done = gasm_->MakeLabel(MachineRepresentation::kWord32);
ManagedObjectInstanceCheck(object, object_can_be_null, WASM_ARRAY_TYPE,
TestCallbacks(&done));
gasm_->Goto(&done, Int32Constant(1));
gasm_->Bind(&done);
return done.PhiAt(0);
}
Node* WasmGraphBuilder::RefAsArray(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position) {
auto done = gasm_->MakeLabel();
ManagedObjectInstanceCheck(object, object_can_be_null, WASM_ARRAY_TYPE,
CastCallbacks(&done, position));
gasm_->Goto(&done);
gasm_->Bind(&done);
return object;
}
Node* WasmGraphBuilder::RefIsI31(Node* object) { return gasm_->IsI31(object); }
Node* WasmGraphBuilder::RefAsI31(Node* object,

View File

@ -522,6 +522,9 @@ class WasmGraphBuilder {
void BrOnFunc(Node* object, Node* rtt, ObjectReferenceKnowledge config,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);
Node* RefIsArray(Node* object, bool object_can_be_null);
Node* RefAsArray(Node* object, bool object_can_be_null,
wasm::WasmCodePosition position);
Node* RefIsI31(Node* object);
Node* RefAsI31(Node* object, wasm::WasmCodePosition position);
void BrOnI31(Node* object, Node* rtt, ObjectReferenceKnowledge config,
@ -719,7 +722,9 @@ class WasmGraphBuilder {
void TypeCheck(Node* object, Node* rtt, ObjectReferenceKnowledge config,
bool null_succeeds, Callbacks callbacks);
void DataCheck(Node* object, bool object_can_be_null, Callbacks callbacks);
void FuncCheck(Node* object, bool object_can_be_null, Callbacks callbacks);
void ManagedObjectInstanceCheck(Node* object, bool object_can_be_null,
InstanceType instance_type,
Callbacks callbacks);
void BrOnCastAbs(Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect,

View File

@ -5599,58 +5599,35 @@ class LiftoffCompiler {
// through to match.
LiftoffRegister DataCheck(const Value& obj, Label* no_match,
LiftoffRegList pinned, Register opt_scratch) {
LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
TypeCheckRegisters registers =
TypeCheckPrelude(obj, no_match, pinned, opt_scratch);
EmitDataRefCheck(registers.map_reg.gp(), no_match, registers.tmp_reg,
pinned);
return registers.obj_reg;
}
// Reserve all temporary registers up front, so that the cache state
// tracking doesn't get confused by the following conditional jumps.
LiftoffRegister tmp1 =
opt_scratch != no_reg
? LiftoffRegister(opt_scratch)
: pinned.set(__ GetUnusedRegister(kGpReg, pinned));
LiftoffRegister tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
if (obj.type.is_nullable()) {
LoadNullValue(tmp1.gp(), pinned);
__ emit_cond_jump(kEqual, no_match, kOptRef, obj_reg.gp(), tmp1.gp());
}
__ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnSmi);
// Load the object's map and check if it is a struct/array map.
__ LoadMap(tmp1.gp(), obj_reg.gp());
EmitDataRefCheck(tmp1.gp(), no_match, tmp2, pinned);
return obj_reg;
LiftoffRegister ArrayCheck(const Value& obj, Label* no_match,
LiftoffRegList pinned, Register opt_scratch) {
TypeCheckRegisters registers =
TypeCheckPrelude(obj, no_match, pinned, opt_scratch);
__ Load(registers.map_reg, registers.map_reg.gp(), no_reg,
wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
LoadType::kI32Load16U, pinned);
__ emit_i32_cond_jumpi(kUnequal, no_match, registers.map_reg.gp(),
WASM_ARRAY_TYPE);
return registers.obj_reg;
}
LiftoffRegister FuncCheck(const Value& obj, Label* no_match,
LiftoffRegList pinned, Register opt_scratch) {
LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
// Reserve all temporary registers up front, so that the cache state
// tracking doesn't get confused by the following conditional jumps.
LiftoffRegister tmp1 =
opt_scratch != no_reg
? LiftoffRegister(opt_scratch)
: pinned.set(__ GetUnusedRegister(kGpReg, pinned));
if (obj.type.is_nullable()) {
LoadNullValue(tmp1.gp(), pinned);
__ emit_cond_jump(kEqual, no_match, kOptRef, obj_reg.gp(), tmp1.gp());
}
__ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnSmi);
// Load the object's map and check if its InstaceType field is that of a
// function.
__ LoadMap(tmp1.gp(), obj_reg.gp());
__ Load(tmp1, tmp1.gp(), no_reg,
TypeCheckRegisters registers =
TypeCheckPrelude(obj, no_match, pinned, opt_scratch);
__ Load(registers.map_reg, registers.map_reg.gp(), no_reg,
wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
LoadType::kI32Load16U, pinned);
__ emit_i32_cond_jumpi(kUnequal, no_match, tmp1.gp(),
__ emit_i32_cond_jumpi(kUnequal, no_match, registers.map_reg.gp(),
WASM_INTERNAL_FUNCTION_TYPE);
return obj_reg;
return registers.obj_reg;
}
LiftoffRegister I31Check(const Value& object, Label* no_match,
@ -5695,6 +5672,11 @@ class LiftoffCompiler {
return AbstractTypeCheck<&LiftoffCompiler::FuncCheck>(object);
}
void RefIsArray(FullDecoder* /* decoder */, const Value& object,
Value* /* result_val */) {
return AbstractTypeCheck<&LiftoffCompiler::ArrayCheck>(object);
}
void RefIsI31(FullDecoder* decoder, const Value& object,
Value* /* result */) {
return AbstractTypeCheck<&LiftoffCompiler::I31Check>(object);
@ -5726,6 +5708,11 @@ class LiftoffCompiler {
return AbstractTypeCast<&LiftoffCompiler::I31Check>(object, decoder, kRef);
}
void RefAsArray(FullDecoder* decoder, const Value& object, Value* result) {
return AbstractTypeCast<&LiftoffCompiler::ArrayCheck>(object, decoder,
kRef);
}
template <TypeChecker type_checker>
void BrOnAbstractType(const Value& object, FullDecoder* decoder,
uint32_t br_depth) {
@ -6242,6 +6229,35 @@ class LiftoffCompiler {
}
}
struct TypeCheckRegisters {
LiftoffRegister obj_reg, map_reg, tmp_reg;
};
TypeCheckRegisters TypeCheckPrelude(const Value& obj, Label* no_match,
LiftoffRegList pinned,
Register opt_scratch) {
LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
// Reserve all temporary registers up front, so that the cache state
// tracking doesn't get confused by the following conditional jumps.
LiftoffRegister map_reg =
opt_scratch != no_reg
? LiftoffRegister(opt_scratch)
: pinned.set(__ GetUnusedRegister(kGpReg, pinned));
LiftoffRegister tmp_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
if (obj.type.is_nullable()) {
LoadNullValue(map_reg.gp(), pinned);
__ emit_cond_jump(kEqual, no_match, kOptRef, obj_reg.gp(), map_reg.gp());
}
__ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnSmi);
__ LoadMap(map_reg.gp(), obj_reg.gp());
return {obj_reg, map_reg, tmp_reg};
}
void EmitDataRefCheck(Register map, Label* not_data_ref, LiftoffRegister tmp,
LiftoffRegList pinned) {
constexpr int kInstanceTypeOffset =

View File

@ -1072,9 +1072,11 @@ struct ControlBase : public PcForErrors<validate> {
F(RefIsFunc, const Value& object, Value* result) \
F(RefIsData, const Value& object, Value* result) \
F(RefIsI31, const Value& object, Value* result) \
F(RefIsArray, 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(RefAsArray, 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) \
@ -4772,6 +4774,7 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
ABSTRACT_TYPE_CHECK(Data)
ABSTRACT_TYPE_CHECK(Func)
ABSTRACT_TYPE_CHECK(I31)
ABSTRACT_TYPE_CHECK(Array)
#undef ABSTRACT_TYPE_CHECK
#define ABSTRACT_TYPE_CAST(heap_type) \
@ -4789,6 +4792,7 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
ABSTRACT_TYPE_CAST(Data)
ABSTRACT_TYPE_CAST(Func)
ABSTRACT_TYPE_CAST(I31)
ABSTRACT_TYPE_CAST(Array)
#undef ABSTRACT_TYPE_CAST
case kExprBrOnData:

View File

@ -1248,6 +1248,15 @@ class WasmGraphBuildingInterface {
br_depth, false);
}
void RefIsArray(FullDecoder* decoder, const Value& object, Value* result) {
result->node = builder_->RefIsArray(object.node, object.type.is_nullable());
}
void RefAsArray(FullDecoder* decoder, const Value& object, Value* result) {
result->node = builder_->RefAsArray(object.node, object.type.is_nullable(),
decoder->position());
}
void RefIsI31(FullDecoder* decoder, const Value& object, Value* result) {
result->node = builder_->RefIsI31(object.node);
}

View File

@ -431,9 +431,11 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(RefIsFunc, "ref.is_func")
CASE_OP(RefIsData, "ref.is_data")
CASE_OP(RefIsI31, "ref.is_i31")
CASE_OP(RefIsArray, "ref.is_array")
CASE_OP(RefAsFunc, "ref.as_func")
CASE_OP(RefAsData, "ref.as_data")
CASE_OP(RefAsI31, "ref.as_i31")
CASE_OP(RefAsArray, "ref.as_array")
CASE_OP(BrOnFunc, "br_on_func")
CASE_OP(BrOnData, "br_on_data")
CASE_OP(BrOnI31, "br_on_i31")

View File

@ -699,9 +699,11 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(RefIsFunc, 0xfb50, _) \
V(RefIsData, 0xfb51, _) \
V(RefIsI31, 0xfb52, _) \
V(RefIsArray, 0xfb53, _) /* not standardized - V8 experimental */ \
V(RefAsFunc, 0xfb58, _) \
V(RefAsData, 0xfb59, _) \
V(RefAsI31, 0xfb5a, _) \
V(RefAsArray, 0xfb5b, _) /* not standardized - V8 experimental */ \
V(BrOnFunc, 0xfb60, _) \
V(BrOnData, 0xfb61, _) \
V(BrOnI31, 0xfb62, _) \

View File

@ -1894,9 +1894,10 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
WasmGCTester tester(execution_tier);
byte array_index = tester.DefineArray(kWasmI32, true);
byte struct_index = tester.DefineStruct({F(kWasmI32, true)});
byte function_index =
tester.DefineFunction(tester.sigs.v_v(), {}, {kExprEnd});
byte sig_index = 1;
byte sig_index = 2;
// This is just so func_index counts as "declared".
tester.AddGlobal(ValueType::Ref(sig_index, kNullable), false,
@ -1905,6 +1906,9 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
byte kDataCheckNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_DATA(WASM_REF_NULL(kAnyRefCode)), kExprEnd});
byte kArrayCheckNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_ARRAY(WASM_REF_NULL(kAnyRefCode)), kExprEnd});
byte kFuncCheckNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_REF_IS_FUNC(WASM_REF_NULL(kAnyRefCode)), kExprEnd});
@ -1916,6 +1920,10 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_AS_DATA(WASM_REF_NULL(kAnyRefCode)),
WASM_DROP, WASM_I32V(1), kExprEnd});
byte kArrayCastNull =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_AS_ARRAY(WASM_REF_NULL(kAnyRefCode)),
WASM_DROP, WASM_I32V(1), kExprEnd});
byte kFuncCastNull =
tester.DefineFunction(tester.sigs.i_v(), {},
{WASM_REF_AS_FUNC(WASM_REF_NULL(kAnyRefCode)),
@ -1934,6 +1942,12 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
DATA, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
byte kDataCheckFailure = TYPE_CHECK(DATA, WASM_I31_NEW(WASM_I32V(42)));
byte kArrayCheckSuccess = TYPE_CHECK(
ARRAY, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
byte kArrayCheckFailure =
TYPE_CHECK(ARRAY, WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
struct_index, WASM_RTT_CANON(struct_index)));
byte kFuncCheckSuccess = TYPE_CHECK(FUNC, WASM_REF_FUNC(function_index));
byte kFuncCheckFailure = TYPE_CHECK(
FUNC, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
@ -1954,6 +1968,10 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
DATA, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
byte kDataCastFailure = TYPE_CAST(DATA, WASM_I31_NEW(WASM_I32V(42)));
byte kArrayCastSuccess = TYPE_CAST(
DATA, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
WASM_RTT_CANON(array_index)));
byte kArrayCastFailure = TYPE_CAST(DATA, WASM_I31_NEW(WASM_I32V(42)));
byte kFuncCastSuccess = TYPE_CAST(FUNC, WASM_REF_FUNC(function_index));
byte kFuncCastFailure = TYPE_CAST(
FUNC, WASM_ARRAY_NEW_DEFAULT_WITH_RTT(array_index, WASM_I32V(10),
@ -2018,26 +2036,32 @@ WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) {
tester.CompileModule();
tester.CheckResult(kDataCheckNull, 0);
tester.CheckResult(kArrayCheckNull, 0);
tester.CheckResult(kFuncCheckNull, 0);
tester.CheckResult(kI31CheckNull, 0);
tester.CheckHasThrown(kDataCastNull);
tester.CheckHasThrown(kArrayCastNull);
tester.CheckHasThrown(kFuncCastNull);
tester.CheckHasThrown(kI31CastNull);
tester.CheckResult(kDataCheckSuccess, 1);
tester.CheckResult(kArrayCheckSuccess, 1);
tester.CheckResult(kFuncCheckSuccess, 1);
tester.CheckResult(kI31CheckSuccess, 1);
tester.CheckResult(kDataCheckFailure, 0);
tester.CheckResult(kArrayCheckFailure, 0);
tester.CheckResult(kFuncCheckFailure, 0);
tester.CheckResult(kI31CheckFailure, 0);
tester.CheckResult(kDataCastSuccess, 1);
tester.CheckResult(kArrayCastSuccess, 1);
tester.CheckResult(kFuncCastSuccess, 1);
tester.CheckResult(kI31CastSuccess, 1);
tester.CheckHasThrown(kDataCastFailure);
tester.CheckHasThrown(kArrayCastFailure);
tester.CheckHasThrown(kFuncCastFailure);
tester.CheckHasThrown(kI31CastFailure);

View File

@ -531,9 +531,11 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_REF_IS_FUNC(ref) ref, WASM_GC_OP(kExprRefIsFunc)
#define WASM_REF_IS_DATA(ref) ref, WASM_GC_OP(kExprRefIsData)
#define WASM_REF_IS_ARRAY(ref) ref, WASM_GC_OP(kExprRefIsArray)
#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_ARRAY(ref) ref, WASM_GC_OP(kExprRefAsArray)
#define WASM_REF_AS_I31(ref) ref, WASM_GC_OP(kExprRefAsI31)
#define WASM_BR_ON_FUNC(depth) \
WASM_GC_OP(kExprBrOnFunc), static_cast<byte>(depth)

View File

@ -507,9 +507,11 @@ let kExprBrOnCastStaticFail = 0x47;
let kExprRefIsFunc = 0x50;
let kExprRefIsData = 0x51;
let kExprRefIsI31 = 0x52;
let kExprRefIsArray = 0x53;
let kExprRefAsFunc = 0x58;
let kExprRefAsData = 0x59;
let kExprRefAsI31 = 0x5a;
let kExprRefAsArray = 0x5b;
let kExprBrOnFunc = 0x60;
let kExprBrOnData = 0x61;
let kExprBrOnI31 = 0x62;