diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index c0aa681761..04a774a912 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -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, diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h index 022461f253..634e938779 100644 --- a/src/compiler/wasm-compiler.h +++ b/src/compiler/wasm-compiler.h @@ -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, diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 1530a9a642..a94aa87d03 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -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 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 = diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h index 8d4302cb57..7d4b25a6a4 100644 --- a/src/wasm/function-body-decoder-impl.h +++ b/src/wasm/function-body-decoder-impl.h @@ -1072,9 +1072,11 @@ struct ControlBase : public PcForErrors { 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 { 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 { ABSTRACT_TYPE_CAST(Data) ABSTRACT_TYPE_CAST(Func) ABSTRACT_TYPE_CAST(I31) + ABSTRACT_TYPE_CAST(Array) #undef ABSTRACT_TYPE_CAST case kExprBrOnData: diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc index 22fc92989d..aa5d02aab3 100644 --- a/src/wasm/graph-builder-interface.cc +++ b/src/wasm/graph-builder-interface.cc @@ -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); } diff --git a/src/wasm/wasm-opcodes-inl.h b/src/wasm/wasm-opcodes-inl.h index 4ec290f836..28ae46e988 100644 --- a/src/wasm/wasm-opcodes-inl.h +++ b/src/wasm/wasm-opcodes-inl.h @@ -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") diff --git a/src/wasm/wasm-opcodes.h b/src/wasm/wasm-opcodes.h index 0e1927d088..78a2dc9976 100644 --- a/src/wasm/wasm-opcodes.h +++ b/src/wasm/wasm-opcodes.h @@ -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, _) \ diff --git a/test/cctest/wasm/test-gc.cc b/test/cctest/wasm/test-gc.cc index 93a234ff6f..67dbfdb30a 100644 --- a/test/cctest/wasm/test-gc.cc +++ b/test/cctest/wasm/test-gc.cc @@ -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); diff --git a/test/common/wasm/wasm-macro-gen.h b/test/common/wasm/wasm-macro-gen.h index c382714048..c772dec05d 100644 --- a/test/common/wasm/wasm-macro-gen.h +++ b/test/common/wasm/wasm-macro-gen.h @@ -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(depth) diff --git a/test/mjsunit/wasm/wasm-module-builder.js b/test/mjsunit/wasm/wasm-module-builder.js index 95272c3120..95a8bdaaf6 100644 --- a/test/mjsunit/wasm/wasm-module-builder.js +++ b/test/mjsunit/wasm/wasm-module-builder.js @@ -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;