diff --git a/BUILD.gn b/BUILD.gn index f7f1ff33a0b4e92b97ca55b515ccd698fdfae0b2..4d54e4bbc061f80f0292a50c28fabd5ff5879224 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -53,6 +53,7 @@ config("runtime") { include_dirs = [ "$ark_root/plugins/ecmascript/runtime/", "$target_gen_dir", + "$root_gen_dir", "$ark_root/ark-third-party/icu/icu4c/source/common", "$ark_root/ark-third-party/icu/icu4c/source/i18n", "$ark_root/ark-third-party/icu/icu4c/source/", diff --git a/compiler/intrinsics_inline_ecmascript.inl b/compiler/intrinsics_inline_ecmascript.inl index 0378534eb426571bded007912d52339455136603..fe3be255bd32c7de82a3442b2659251b429f7f5d 100644 --- a/compiler/intrinsics_inline_ecmascript.inl +++ b/compiler/intrinsics_inline_ecmascript.inl @@ -95,7 +95,15 @@ case RuntimeInterface::IntrinsicId::INTRINSIC_TOBOOLEAN: { } case RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER: { ASSERT(types_.size() == 1); - return InlineToNumberDyn(intrinsic, types_[0]); + return panda::compiler::IrtocInlineToNumberDyn(intrinsic, types_[0]); +} +case RuntimeInterface::IntrinsicId::INTRINSIC_IS_FALSE: { + ASSERT(types_.size() == 1); + return panda::compiler::IrtocInlineIsFalseDyn(intrinsic, types_[0]); +} +case RuntimeInterface::IntrinsicId::INTRINSIC_IS_TRUE: { + ASSERT(types_.size() == 1); + return panda::compiler::IrtocInlineIsTrueDyn(intrinsic, types_[0]); } case RuntimeInterface::IntrinsicId::INTRINSIC_LDTRUE: { ASSERT(types_.size() == 0); diff --git a/compiler/optimizer/code_generator/compiler_base_types.cpp b/compiler/optimizer/code_generator/compiler_base_types.cpp index b79084ed8d42a6be5189b136f9a1a91c8d780acd..8ea97036907e1b64b06c622bcada4c7807d40306 100644 --- a/compiler/optimizer/code_generator/compiler_base_types.cpp +++ b/compiler/optimizer/code_generator/compiler_base_types.cpp @@ -22,7 +22,7 @@ namespace panda::compiler { -static void CompareAnyTypeGenDouble(Encoder *enc, Reg dst, Reg src, +static void CompareAnyTypeGenDouble(Encoder *enc, Reg dst, Reg src, bool is_int_possible, LabelHolder::LabelId id = LabelHolder::INVALID_LABEL) { static constexpr uint16_t CMP_VAL = static_cast(UINT16_MAX) >> 1U; // 0x7fff @@ -30,6 +30,13 @@ static void CompareAnyTypeGenDouble(Encoder *enc, Reg dst, Reg src, auto dst_ext = dst.As(TypeInfo(TypeInfo::TypeId::INT64)); + LabelHolder::LabelId end_label = enc->CreateLabel(); + + if (is_int_possible && id != LabelHolder::INVALID_LABEL) { + enc->EncodeShr(dst_ext, src, Imm(coretypes::TaggedValue::TAG_BITS_SHIFT)); + enc->EncodeJump(end_label, dst_ext, + Imm(coretypes::TaggedValue::TAG_INT >> coretypes::TaggedValue::TAG_BITS_SHIFT), Condition::EQ); + } // (u16) tag + 0xffff is [0xffff for Object, 0xfffe for Int, other - Double] enc->EncodeAdd(dst_ext, src, Imm(panda::coretypes::TaggedValue::TAG_MASK)); // Examine tag and shift it by one -> 0x7fff for both Object and Int @@ -42,6 +49,7 @@ static void CompareAnyTypeGenDouble(Encoder *enc, Reg dst, Reg src, } else { enc->EncodeJump(id, dst_ext, reg_cmp, Condition::GE); } + enc->BindLabel(end_label); } static void CompareAnyTypeGenBool(Encoder *enc, Reg dst, Reg src, LabelHolder::LabelId id = LabelHolder::INVALID_LABEL) @@ -173,7 +181,7 @@ bool ecmascript::CompareAnyTypeGen(const CompareAnyTypeInst *cati, EncodeVisitor CompareAnyTypeGenObject(enc, dst, src); return true; case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE: - CompareAnyTypeGenDouble(enc, dst, src); + CompareAnyTypeGenDouble(enc, dst, src, cati->IsIntegerWasSeen()); return true; case AnyBaseType::ECMASCRIPT_STRING_TYPE: CompareAnyTypeGenObjectType(codegen, enc, dst, src, cross_values::GetJstypeString(codegen->GetArch())); @@ -210,6 +218,7 @@ bool ecmascript::CompareAnyTypeGen(const CompareAnyTypeInst *cati, EncodeVisitor return false; } +// NOLINTNEXTLINE(readability-function-size) bool ecmascript::CastAnyTypeValueGen(const CastAnyTypeValueInst *cati, EncodeVisitor *enc_v) { auto dst_reg_type = cati->GetType(); @@ -243,9 +252,23 @@ bool ecmascript::CastAnyTypeValueGen(const CastAnyTypeValueInst *cati, EncodeVis return true; } case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE: { - ScopedTmpReg reg_tmp(enc, TypeInfo(TypeInfo::TypeId::INT64)); + ASSERT(dst.IsFloat()); + ScopedTmpReg reg_tmp(enc, TypeInfo(TypeInfo::INT64)); + auto end_label = enc->CreateLabel(); + auto double_label = enc->CreateLabel(); + if (cati->IsIntegerWasSeen()) { + SCOPED_DISASM_STR(enc_v->GetCodegen(), "Try cast from SMI"); + enc->EncodeShr(reg_tmp, src, Imm(coretypes::TaggedValue::TAG_BITS_SHIFT)); + enc->EncodeJump(double_label, reg_tmp, + Imm(coretypes::TaggedValue::TAG_INT >> coretypes::TaggedValue::TAG_BITS_SHIFT), + Condition::NE); + enc->EncodeCast(dst.As(FLOAT64_TYPE), true, src.As(INT32_TYPE), true); + enc->EncodeJump(end_label); + } + enc->BindLabel(double_label); enc->EncodeSub(reg_tmp, src, Imm(panda::coretypes::TaggedValue::DOUBLE_ENCODE_OFFSET)); enc->EncodeMov(dst, reg_tmp); + enc->BindLabel(end_label); return true; } case AnyBaseType::ECMASCRIPT_OBJECT_TYPE: @@ -344,7 +367,7 @@ bool ecmascript::AnyTypeCheckGen(const AnyTypeCheckInst *check_inst, EncodeVisit CompareAnyTypeGenObject(enc, tmp_reg, src); return true; case AnyBaseType::ECMASCRIPT_DOUBLE_TYPE: - CompareAnyTypeGenDouble(enc, tmp_reg, src, id); + CompareAnyTypeGenDouble(enc, tmp_reg, src, check_inst->IsIntegerWasSeen(), id); return true; case AnyBaseType::ECMASCRIPT_STRING_TYPE: CompareAnyTypeGenObjectType(codegen, enc, tmp_reg, src, cross_values::GetJstypeString(codegen->GetArch()), diff --git a/compiler/templates/ecmascript_inst_builder_gen.cpp.erb b/compiler/templates/ecmascript_inst_builder_gen.cpp.erb index f36a658ae00f8920467f88ee170fb6057d116bf9..68f7e6f2128c2c6791d568283f53987e9704ec40 100644 --- a/compiler/templates/ecmascript_inst_builder_gen.cpp.erb +++ b/compiler/templates/ecmascript_inst_builder_gen.cpp.erb @@ -97,6 +97,10 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N inst->SetFlag(inst_flags::CF); inst->SetFlag(inst_flags::TERMINATOR); % end +% has_profile = inst.profiled? +% if has_profile + [[maybe_unused]] auto profile = GetRuntime()->GetBytecodeProfile(method_profile_, bc_inst->GetAddress(), inst->GetPc()); +% end % params_arr = inst.operands % format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase % range_should_exclude_last = name == "NEWOBJDYNRANGE" || name == "SUPERCALL" || name == "CREATEOBJECTWITHEXCLUDEDKEYS" @@ -142,7 +146,7 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N % id32_index = 0 auto inst_save_state = CreateSaveState(Opcode::SaveState, GetPc(bc_inst->GetAddress())); AddInstruction(inst_save_state); -% params_arr.each do |param| +% params_arr.each_with_index do |param, input_index| % if param.imm? auto imm<%= imm_index %> = static_cast(bc_inst->GetImm<<%= format %>, <%= imm_index %>>()); inst->AddImm(GetGraph()->GetAllocator(), imm<%= imm_index %>); @@ -152,7 +156,13 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N auto input = GetDefinition(bc_inst->GetVReg<<%= format %>, <%= vreg_index %>>()); // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) if constexpr (with_speculative) { - input = BuildAnyTypeCheckInst(GetPc(bc_inst->GetAddress()), input, inst_save_state); + bool is_integer_seen = false; +% if has_profile && (inst.profile.properties.include?('operand_types_1') || inst.profile.properties.include?('operand_types_2')) + auto operand_type = GetProfilingAnyType(profile, bc_inst, <%= input_index %>, &is_integer_seen); +% else + auto operand_type = compiler::AnyBaseType::UNDEFINED_TYPE; +% end + input = BuildAnyTypeCheckInst(GetPc(bc_inst->GetAddress()), input, inst_save_state, operand_type, is_integer_seen); } inst->AppendInput(input); inst->AddInputType(DataType::ANY); @@ -203,7 +213,15 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N auto input = GetDefinitionAcc(); // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) if constexpr (with_speculative) { - input = BuildAnyTypeCheckInst(GetPc(bc_inst->GetAddress()), input, inst_save_state); + bool is_integer_seen = false; +% if has_profile && (inst.profile.properties.include?('operand_types_1') || inst.profile.properties.include?('operand_types_2')) +% idx = inst.profile.properties.include?('operand_types_2') ? 1 : 0 + auto operand_type = GetProfilingAnyType(profile, bc_inst, <%= idx %>, &is_integer_seen); +% else + auto operand_type = compiler::AnyBaseType::UNDEFINED_TYPE; +% end + input = BuildAnyTypeCheckInst(GetPc(bc_inst->GetAddress()), input, inst_save_state, operand_type, is_integer_seen); + } inst->AppendInput(input); inst->AddInputType(DataType::ANY); @@ -272,6 +290,8 @@ void InstBuilder::BuildEcmaFromIrtoc([[maybe_unused]] const BytecodeInstruction* auto id<%= id_index %> = GetGraph()->FindOrCreateConstant(id<%= id_index %>_payload); % inputs.push("id#{id_index}") % id_index = id_index + 1 +% elsif param.prof? +% # Just skip profile id % else % abort 'Unexpected param type' % end diff --git a/irtoc_scripts/interpreter_handlers.irt b/irtoc_scripts/interpreter_handlers.irt index 90f15fd21e73d2a9dff47ffd6cb5273a6972863b..b25370e1fb24d00c00abda4b007b7a63a2a2be9b 100644 --- a/irtoc_scripts/interpreter_handlers.irt +++ b/irtoc_scripts/interpreter_handlers.irt @@ -531,6 +531,10 @@ macro(:tobooleandyn_double) do |v| booltoany(Phi(res_0, res_1).b) end +macro(:tobooleandyn_bool) do |v| + v +end + cpp_function(:"IrtocInlineToBooleanDyn") do params type1: 'any' return_type 'bool' @@ -542,6 +546,10 @@ cpp_function(:"IrtocInlineToBooleanDyn") do condition "type1 == AnyBaseType::ECMASCRIPT_DOUBLE_TYPE" code { Return(tobooleandyn_double(type1)) } } + variant(:tobooleandyn_bool) { + condition "type1 == AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE" + code { Return(tobooleandyn_bool(type1)) } + } end #################################################################### @@ -568,6 +576,43 @@ cpp_function(:"IrtocInlineToNumberDyn") do } end +#################################################################### +# ISFALSE +# +macro(:isfalsedyn) do |v| + booltoany(Compare(v, Constants::TAGGED_FALSE).b) +end + +macro(:istruedyn) do |v| + booltoany(Compare(v, Constants::TAGGED_TRUE).b) +end + +cpp_function(:"IrtocInlineIsFalseDyn") do + params type1: 'any' + return_type 'bool' + variant(:isfalsedyn) { + condition :default + code { Return(isfalsedyn(type1)) } + } +end + +cpp_function(:"IrtocInlineIsTrueDyn") do + params type1: 'any' + return_type 'bool' + variant(:istruedyn) { + condition :default + code { Return(istruedyn(type1)) } + } +end + +function(:"EcmaIsfalse", params: {a: 'any'}, mode: [:Interpreter, :DynamicMethod], enable_builder: true) do + Return(isfalsedyn(a)) +end + +function(:"EcmaIstrue", params: {a: 'any'}, mode: [:Interpreter, :DynamicMethod], enable_builder: true) do + Return(istruedyn(a)) +end + #################################################################### # Create macros for binary arithmetic # @@ -616,7 +661,7 @@ end function(:"FastPathEcma#{op.capitalize}", params: {a: 'any', b: 'any'}, regmap: $full_regmap, - regalloc_set: RegMask.new($full_regmap, :arg0, :arg1, :callee0, :callee2), + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap, mode: [:FastPath, :DynamicMethod]) do # Arm32 is not supported if Options.arch == :arm32 @@ -1287,8 +1332,8 @@ macro(:access_obj_ic) do |obj, value, ic_slot, access| Goto(:Miss) } - ic_slot := map_ic_slot(ic_slot) - ic_cls := LoadArray(anytoheapobj(ic), ic_slot).any + ic_slot_mapped := map_ic_slot(ic_slot) + ic_cls := LoadArray(anytoheapobj(ic), ic_slot_mapped).any IfImm(cmpanyheapobj(ic_cls)).Imm(0).CC(:CC_EQ).b { Goto(:Miss) } @@ -1298,7 +1343,7 @@ macro(:access_obj_ic) do |obj, value, ic_slot, access| Goto(:Miss) } - handler := LoadArray(anytoheapobj(ic), AddI(ic_slot).Imm(1).i32).any + handler := LoadArray(anytoheapobj(ic), AddI(ic_slot_mapped).Imm(1).i32).any r = access.call(obj, handler, value) res := r Goto(:Exit) @@ -1507,6 +1552,9 @@ function(:EcmaStownbyindex, params: {'index'=>'i32', 'obj'=>'any', 'value'=>'any ReturnVoid() end +#################################################################################### +# LD_OBJECT_BY_VALUE +# macro(:handle_ecma_ldobjbyvalue) do |obj, key, ic_slot| IfImm(cmpanyheapobj(obj)).Imm(0).CC(:CC_NE).b { load_res := handle_load_object_dynamic(obj, key, ic_slot) @@ -1522,8 +1570,33 @@ end function(:EcmaLdobjbyvalue, params: {'obj'=>'any', 'key'=>'any', 'ic_slot'=>'i32'}, mode: [:Interpreter, :DynamicMethod], enable_builder: true) do Return(handle_ecma_ldobjbyvalue(obj, key, ic_slot)) +end + +function(:FastPathLdObjByValue, + params: {obj: 'any', key: 'any', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next end + IfImm(cmpanyheapobj(obj)).Imm(0).NE.b { + load_res := handle_load_object_dynamic(obj, key, ic_slot).any + IfImm(cmpanyhole(load_res)).Imm(0).NE { + Intrinsic(:SLOW_PATH_ENTRY, obj, key, ic_slot).Method("LdObjByValueBridge", :AddImm).Relocate.Terminator.any + } + Return(load_res).any + } Else { + Intrinsic(:SLOW_PATH_ENTRY, obj, key, ic_slot).Method("LdObjByValueBridge", :AddImm).Relocate.Terminator.any + } +end + +#################################################################################### +# ST_OBJECT_BY_VALUE +# macro(:handle_ecma_stobjbyvalue) do |obj, key, value, ic_slot| IfImm(cmpanyheapobj(obj)).Imm(0).CC(:CC_NE).b { store_res := handle_store_object_dynamic(obj, key, value, ic_slot) @@ -1540,6 +1613,33 @@ function(:EcmaStobjbyvalue, params: {'obj'=>'any', 'key'=>'any', 'value'=>'any', ReturnVoid() end +function(:FastPathStObjByValue, + params: {obj: 'any', key: 'any', value: 'any', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap, + validate: { spills_count_max: { default: 4, arm32: 9999 } } ) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next + end + + IfImm(cmpanyheapobj(obj)).Imm(0).CC(:CC_NE).b { + store_res := handle_store_object_dynamic(obj, key, value, ic_slot) + IfImm(cmpanyhole(store_res)).Imm(0).CC(:CC_NE) { + Intrinsic(:SLOW_PATH_ENTRY, obj, key, value, ic_slot).Method("StObjByValueBridge", :AddImm).Relocate.Terminator.void + } + } Else { + Intrinsic(:SLOW_PATH_ENTRY, obj, key, value, ic_slot).Method("StObjByValueBridge", :AddImm).Relocate.Terminator.void + } + ReturnVoid() +end + +#################################################################################### +# LD_OBJECT_BY_NAME +# + macro(:resolve_id) do |id| cp := Intrinsic(:GET_ECMA_CONSTANT_POOL).ref LoadArray(cp, id).any @@ -1563,6 +1663,32 @@ function(:EcmaLdobjbyname, params: {'id'=>'i32', 'obj'=>'any','ic_slot'=>'i32'}, Return(handle_ecma_ldobjbyname(obj, id, ic_slot)) end +function(:FastPathLdObjByName, + params: {id: 'i32', obj: 'any', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next + end + + key := resolve_id(id) + IfImm(cmpanyheapobj(obj)).Imm(0).CC(:CC_NE).b { + load_res := handle_load_object_dynamic(obj, key, ic_slot) + IfImm(cmpanyhole(load_res)).Imm(0).CC(:CC_NE).b { + Intrinsic(:SLOW_PATH_ENTRY, id, obj, ic_slot).Method("LdObjByNameBridge", :AddImm).Relocate.Terminator.ptr + } + Return(load_res).any + } Else { + Intrinsic(:SLOW_PATH_ENTRY, id, obj, ic_slot).Method("LdObjByNameBridge", :AddImm).Relocate.Terminator.ptr + } +end + +#################################################################################### +# ST_OBJECT_BY_NAME +# macro(:handle_ecma_stobjbyname) do |obj, id, value, ic_slot| key := resolve_id(id) IfImm(cmpanyheapobj(obj)).Imm(0).CC(:CC_NE).b { @@ -1580,6 +1706,33 @@ function(:EcmaStobjbyname, params: {'id'=>'i32', 'obj'=>'any', 'value'=>'any', ' ReturnVoid() end +function(:FastPathStObjByName, + params: {id: 'i32', obj: 'any', value: 'any', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap, + validate: { spills_count_max: { default: 4, arm32: 9999 } } ) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next + end + + key := resolve_id(id) + IfImm(cmpanyheapobj(obj)).Imm(0).NE.b { + res := handle_store_object_dynamic(obj, key, value, ic_slot) + IfImm(cmpanyhole(res)).Imm(0).NE.b { + Intrinsic(:SLOW_PATH_ENTRY, id, obj, value, ic_slot).Method("StObjByNameBridge", :AddImm).Relocate.Terminator.ptr + } + } Else { + Intrinsic(:SLOW_PATH_ENTRY, id, obj, value, ic_slot).Method("StObjByNameBridge", :AddImm).Relocate.Terminator.ptr + } + ReturnVoid() +end + +#################################################################################### +# TRY_LD_GLOBAL_BY_NAME +# macro(:handle_ecma_tryldglobalbyname) do |id, ic_slot| key := resolve_id(id) ic_res := load_global_var_ic(key, ic_slot) @@ -1595,6 +1748,9 @@ function(:EcmaTryldglobalbyname, params: {'id'=>'i32', 'ic_slot'=>'i32'}, mode: Return(handle_ecma_tryldglobalbyname(id, ic_slot)) end +#################################################################################### +# LD_GLOBAL_VAR +# macro(:handle_ecma_ldglobalvar) do |id, ic_slot| key := resolve_id(id) ic_res := load_global_var_ic(key, ic_slot) @@ -1610,15 +1766,34 @@ function(:EcmaLdglobalvar, params: {'id'=>'i32', 'ic_slot'=>'i32'}, mode: [:Inte Return(handle_ecma_ldglobalvar(id, ic_slot)) end +function(:FastPathLdGlobalVar, + params: {id: 'i32', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next + end + + key := resolve_id(id) + ic_res := load_global_var_ic(key, ic_slot).any + IfImm(cmpanyhole(ic_res)).Imm(0).CC(:CC_NE).b { + Intrinsic(:SLOW_PATH_ENTRY, id, ic_slot).Method("LdGlobalVarBridge", :AddImm).Relocate.Terminator.ptr + } + Return(ic_res).any +end + +#################################################################################### +# ST_GLOBAL_VAR +# macro(:handle_ecma_stglobalvar) do |id, value, ic_slot| key := resolve_id(id) ic_res := store_global_var_ic(key, value, ic_slot) - IfImm(cmpanyhole(ic_res)).Imm(0).CC(:CC_EQ).b { - res0 := ic_res - } Else { - res1 := ecma_intrinsic_invoke("StGlobalVarSlow", id, value, ic_slot).any + IfImm(cmpanyhole(ic_res)).Imm(0).CC(:CC_NE).b { + ecma_intrinsic_invoke("StGlobalVarSlow", id, value, ic_slot).any } - Phi(res0, res1).any end function(:EcmaStglobalvar, params: {'id'=>'i32', 'value'=>'any', 'ic_slot'=>'i32'}, mode: [:Interpreter, :DynamicMethod], enable_builder: true) do @@ -1626,4 +1801,23 @@ function(:EcmaStglobalvar, params: {'id'=>'i32', 'value'=>'any', 'ic_slot'=>'i32 ReturnVoid() end +function(:FastPathStGlobalVar, + params: {id: 'i32', value: 'any', ic_slot: 'i32'}, + mode: [:FastPath, :DynamicMethod], + regmap: $full_regmap, + regalloc_set: RegMask.new($full_regmap, :arg0, :arg1) + $panda_callees_mask - $panda_regmap) do + # Arm32 is not supported + if Options.arch == :arm32 + Intrinsic(:UNREACHABLE).Terminator.void + next + end + + key := resolve_id(id) + ic_res := store_global_var_ic(key, value, ic_slot) + IfImm(cmpanyhole(ic_res)).Imm(0).CC(:CC_NE).b { + Intrinsic(:SLOW_PATH_ENTRY, id, value, ic_slot).Method("StGlobalVarBridge", :AddImm).Relocate.Terminator.ptr + } + ReturnVoid() +end + ###################################################### diff --git a/irtoc_scripts/interpreter_main_loop.irt b/irtoc_scripts/interpreter_main_loop.irt index ad9867072c0a0172f25212db31c3bb6562ec1dbf..b23e7bdfa26d5c155c34683e33460e8c66e3a13d 100644 --- a/irtoc_scripts/interpreter_main_loop.irt +++ b/irtoc_scripts/interpreter_main_loop.irt @@ -81,7 +81,7 @@ when "ECMA_TONUMBER_PREF_V8" acc := handle_ecma_tonumber(vreg_value(op[0])) #ecma_intrinsic_setacc("Tonumber", vreg_value(op[0]).u64) - when "ECMA_TOBOOLEAN_PREF_NONE" + when "ECMA_TOBOOLEAN_PREF" acc := handle_ecma_toboolean(acc.any) #ecma_intrinsic_setacc("Toboolean", acc.u64) when "ECMA_LDTRUE_PREF_NONE" @@ -108,7 +108,7 @@ pc := handle_ecma_jcconst(Constants::VALUE_FALSE, pc, i16toi32(as_imm(op[0])), i.format.size) when "ECMA_JFALSE_PREF_IMM32" pc := handle_ecma_jcconst(Constants::VALUE_FALSE, pc, as_imm(op[0]), i.format.size) - when "ECMA_NEGATE_PREF_NONE" + when "ECMA_NEGATE_PREF" acc := handle_ecma_negate(acc.any) when "ECMA_LDNAN_PREF_NONE" ecma_intrinsic_setacc("Ldnan") diff --git a/isa/isa.yaml b/isa/isa.yaml index 659de881558a0313e031ab9ad9057045217094a1..d202450806452181dc92988ff55e933a7c3e35dd 100644 --- a/isa/isa.yaml +++ b/isa/isa.yaml @@ -27,6 +27,19 @@ properties: - tag: use_ic description: Instruction uses inline cache +profiles_schema: + name: string + size: int + properties: [operand_types_1, operand_types_2] + +profiles: + - name: BinaryArith + size: 2 + properties: [operand_types_2] + - name: UnaryArith + size: 2 + properties: [operand_types_1] + groups: - title: Ecma extension intrunctions description: Ecma extension intrunctions with prefix ecma @@ -196,27 +209,31 @@ groups: - sig: ecma.add2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_ADD2_DYN + profile: BinaryArith - sig: ecma.sub2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_SUB2_DYN + profile: BinaryArith - sig: ecma.mul2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_MUL2_DYN + profile: BinaryArith - sig: ecma.div2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_DIV2_DYN + profile: BinaryArith - sig: ecma.mod2dyn v:in:top acc: inout:top prefix: ecma @@ -226,51 +243,59 @@ groups: - sig: ecma.eqdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_EQ_DYN + profile: BinaryArith - sig: ecma.noteqdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_NOT_EQ_DYN + profile: BinaryArith - sig: ecma.lessdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_LESS_DYN + profile: BinaryArith - sig: ecma.lesseqdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_LESS_EQ_DYN + profile: BinaryArith - sig: ecma.greaterdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_GREATER_DYN + profile: BinaryArith - sig: ecma.greatereqdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_GREATER_EQ_DYN + profile: BinaryArith - sig: ecma.shl2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_SHL2_DYN + profile: BinaryArith - sig: ecma.shr2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_SHR2_DYN + profile: BinaryArith - sig: ecma.ashr2dyn v:in:top acc: inout:top prefix: ecma @@ -279,51 +304,59 @@ groups: - sig: ecma.and2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_AND2_DYN + profile: BinaryArith - sig: ecma.or2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_OR2_DYN + profile: BinaryArith - sig: ecma.xor2dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_XOR2_DYN + profile: BinaryArith - sig: ecma.tonumber v:in:top acc: out:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_TONUMBER + profile: UnaryArith - sig: ecma.negdyn v:in:top acc: out:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_NEG_DYN + profile: UnaryArith - sig: ecma.notdyn v:in:top acc: out:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_NOT_DYN + profile: UnaryArith - sig: ecma.incdyn v:in:top acc: out:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_INC_DYN + profile: UnaryArith - sig: ecma.decdyn v:in:top acc: out:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_DEC_DYN + profile: UnaryArith - sig: ecma.expdyn v:in:top acc: inout:top prefix: ecma @@ -827,24 +860,28 @@ groups: - sig: ecma.toboolean acc: inout:top prefix: ecma - format: [pref_op_none] + format: [pref_op_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_TOBOOLEAN + profile: UnaryArith - sig: ecma.negate acc: inout:top prefix: ecma - format: [pref_op_none] + format: [pref_op_prof_16] properties: [inlinable] intrinsic_name: INTRINSIC_NEGATE + profile: UnaryArith - sig: ecma.istrue acc: inout:top prefix: ecma format: [pref_op_none] + properties: [inlinable] intrinsic_name: INTRINSIC_IS_TRUE - sig: ecma.isfalse acc: inout:top prefix: ecma format: [pref_op_none] + properties: [inlinable] intrinsic_name: INTRINSIC_IS_FALSE - sig: ecma.isundefined acc: inout:top diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index a662fbb63464c8dbc9c1321f063244531a448623..577ad2ccdce30871b883a1d5bbe9e48692ed7896 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -273,3 +273,16 @@ set_source_files_properties(${ECMA_SRC_DIR}/builtins/builtins_number.cpp PROPERT set_source_files_properties(${ECMA_SRC_DIR}/builtins/builtins_relative_time_format.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow) set_source_files_properties(${ECMA_SRC_DIR}/builtins/builtins_date.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow) set_source_files_properties(${ECMA_SRC_DIR}/builtins/builtins_date_time_format.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow) + +add_merge_plugin(PLUGIN_NAME "read_profile.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_read_profile.h") +add_merge_plugin(PLUGIN_NAME "destroy_profile.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_destroy_profile.h") +add_merge_plugin(PLUGIN_NAME "find_method_in_profile.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_find_method_in_profile.h") +add_merge_plugin(PLUGIN_NAME "dump_profile.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_dump_profile.h") +add_merge_plugin(PLUGIN_NAME "profiling_includes.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_includes.h") +add_merge_plugin(PLUGIN_NAME "get_profiling_any_type.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_get_profiling_any_type.h") diff --git a/runtime/compiler/ecmascript_runtime_interface.cpp b/runtime/compiler/ecmascript_runtime_interface.cpp index 7eaeab526c38406a0361a16ccec7993b18ceca07..500d03b7e87efacba08547d3d18cf708b5438cd7 100644 --- a/runtime/compiler/ecmascript_runtime_interface.cpp +++ b/runtime/compiler/ecmascript_runtime_interface.cpp @@ -19,6 +19,7 @@ #include "plugins/ecmascript/runtime/global_dictionary-inl.h" #include "plugins/ecmascript/compiler/ecmascript_extensions/ecmascript_environment.h" #include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h" +#include "serializer/serializer.h" namespace panda::ecmascript { @@ -98,4 +99,45 @@ uintptr_t EcmaRuntimeInterface::GetGlobalVarAddress(MethodPtr method, size_t id) return reinterpret_cast(res.GetHeapObject()); } +EcmaRuntimeInterface::MethodProfile EcmaRuntimeInterface::GetMethodProfile(MethodPtr method) const +{ + auto name = GetMethodFullName(method, false); + auto prof = profile_.find(std::string(name)); + if (prof == profile_.end()) { + LOG(DEBUG, COMPILER) << "Profile not found for " << name; + } + return (prof != profile_.end()) ? reinterpret_cast(prof->second.data()) : 0; +} + +EcmaRuntimeInterface::BytecodeProfile EcmaRuntimeInterface::GetBytecodeProfile(MethodProfile prof, + const uint8_t *bc_inst, + [[maybe_unused]] size_t pc) const +{ + if (prof == 0) { + return 0; + } + auto profile_id = BytecodeInstruction(bc_inst).GetProfileId(); + if (profile_id == -1) { + return 0; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return reinterpret_cast(reinterpret_cast(prof) + profile_id); +} + +Expected EcmaRuntimeInterface::AddProfile(std::string_view fname) +{ + std::ifstream stm(fname.data(), std::ios::binary); + if (!stm.is_open()) { + return Unexpected("Cannot open profile file"); + } + + std::vector buffer {std::istreambuf_iterator(stm), std::istreambuf_iterator()}; + auto res = serializer::BufferToType(buffer.data(), buffer.size(), profile_); + if (!res) { + LOG(FATAL, RUNTIME) << "Failed to deserialize: " << res.Error(); + } + + return true; +} + } // namespace panda::ecmascript \ No newline at end of file diff --git a/runtime/compiler/ecmascript_runtime_interface.h b/runtime/compiler/ecmascript_runtime_interface.h index aea1ee38b34752387366fcb420dc95fc06b38ce9..868d7c0b2b10ad78a9ad01666c8374b5166059e0 100644 --- a/runtime/compiler/ecmascript_runtime_interface.h +++ b/runtime/compiler/ecmascript_runtime_interface.h @@ -17,6 +17,7 @@ #include "runtime/compiler.h" #include "plugins/ecmascript/runtime/js_method.h" +#include "plugins/ecmascript/runtime/ecma_profiling.h" namespace panda::ecmascript { class EcmaVM; @@ -64,8 +65,15 @@ public: return EntrypointId::GET_GLOBAL_VAR_ADDRESS; } + MethodProfile GetMethodProfile(MethodPtr method) const override; + + BytecodeProfile GetBytecodeProfile(MethodProfile prof, const uint8_t *bc_inst, size_t pc) const override; + + Expected AddProfile(std::string_view fname) override; + private: const EcmaVM *ecma_vm_ {nullptr}; + panda::ecmascript::EcmaProfileContainer profile_; }; } // namespace panda::ecmascript diff --git a/runtime/ecma_profiling.h b/runtime/ecma_profiling.h new file mode 100644 index 0000000000000000000000000000000000000000..eaefb2d9952fa8640d36412b4f548b361200e059 --- /dev/null +++ b/runtime/ecma_profiling.h @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_ECMA_PROFILING_H +#define PANDA_ECMA_PROFILING_H + +#include "js_tagged_value.h" +#include "utils/bit_field.h" + +namespace panda::ecmascript { + +class ProfilingType { +public: + enum Type : uint8_t { + NONE = 0, + INTEGER = (1U << 0U), + DOUBLE = (1U << 1U), + BOOLEAN = (1U << 2U), + STRING = (1U << 3U), + OBJECT = (1U << 4U), + LAST = OBJECT, + DOUBLE_INTEGER = DOUBLE | INTEGER, + }; + + ProfilingType() = default; + // NOLINTNEXTLINE(google-explicit-constructor) + ProfilingType(Type type) : type_(type) {} + // NOLINTNEXTLINE(google-explicit-constructor) + ProfilingType(std::underlying_type_t type) : type_(static_cast(type)) {} + + template + void Dump(T &stream); + + Type GetType() const + { + return type_; + } + + bool Contains(ProfilingType other) const + { + return (type_ & other.type_) != 0; + } + + bool operator==(ProfilingType other) + { + return type_ == other.type_; + } + + // NOLINTNEXTLINE(google-explicit-constructor) + operator Type() const + { + return type_; + } + +private: + Type type_ {NONE}; + + friend ProfilingType operator|(ProfilingType a, ProfilingType b); +}; + +inline ProfilingType GetTypeFromValue(JSTaggedValue value) +{ + if (value.IsInt()) { + return ProfilingType::INTEGER; + } + // IsDouble() is encoded as `!IsInt() && !IsObject()`, IsInt() we already tested, so check only it is not an object + if (!value.IsObject()) { + return ProfilingType::DOUBLE; + } + if (value.IsBoolean()) { + return ProfilingType::BOOLEAN; + } + return ProfilingType::OBJECT; +} + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define SET_LAST_FIELD(name) \ + using LastField = name; \ + static_assert(LastField::END_BIT <= (sizeof(typename Base::ValueType) * BITS_PER_BYTE)) + +// Check that we don't waste redundant extra bytes for a final profile. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define SET_LAST_FIELD_FINAL(name) \ + SET_LAST_FIELD(name); \ + static_assert(LastField::END_BIT > ((sizeof(typename Base::ValueType) - 1) * BITS_PER_BYTE)) + +template +class ValueProfileBase { +public: + using ValueType = T; + + static_assert(std::is_integral_v, "Only integer type supported"); + + ValueProfileBase() = default; + explicit ValueProfileBase(void *prof_data) + : value_(prof_data != nullptr ? *reinterpret_cast(prof_data) : 0) + { + } + + ValueType GetValue() const + { + return value_; + } + template + void SetField(typename FieldT::ValueType value) + { + FieldT::Set(value, &value_); + } + template + typename FieldT::ValueType GetField() const + { + return FieldT::Get(value_); + } + +private: + ValueType value_ {0}; +}; + +template +class ResultProfile : public ValueProfileBase { +public: + using Base = ValueProfileBase; + using Base::Base; + + ProfilingType GetResultType() const + { + return this->template GetField(); + } + +protected: + using ResultType = BitField; + SET_LAST_FIELD(ResultType); +}; + +class UnaryOperationProfile : public ResultProfile { +public: + using Base = ResultProfile; + using Base::Base; + + ProfilingType GetOperandType() const + { + return GetField(); + } + + ProfilingType GetOperandType([[maybe_unused]] size_t index) const + { + CHECK_LT(index, 1U); + return GetOperandType(); + } + + static void Update(Base::ValueType *data, JSTaggedValue value) + { + *data |= OperandType::Encode(GetTypeFromValue(value)); + } + +protected: + using OperandType = Base::LastField::NextField; + SET_LAST_FIELD_FINAL(OperandType); +}; + +class BinaryOperationProfile : public ResultProfile { +public: + using Base = ResultProfile; + using Base::Base; + + ProfilingType GetOperandType(size_t index) const + { + CHECK_LT(index, 2U); + return index == 0 ? GetField() : GetField(); + } + + ProfilingType GetLeftOperandType() const + { + return GetField(); + } + + ProfilingType GetRightOperandType() const + { + return GetField(); + } + + static void Update(Base::ValueType *data, JSTaggedValue lhs, JSTaggedValue rhs) + { + auto ltype = GetTypeFromValue(lhs); + auto rtype = GetTypeFromValue(rhs); + *data |= LeftOperandType::Encode(ltype) | RightOperandType::Encode(rtype); + } + +protected: + using LeftOperandType = Base::LastField::NextField; + using RightOperandType = LeftOperandType::NextField; + SET_LAST_FIELD_FINAL(RightOperandType); +}; + +template +void ProfilingType::Dump(T &stream) +{ + if (GetType() == NONE) { + stream << "[None]"; + return; + } + stream << '['; + const char *sep = ""; + if (GetType() & INTEGER) { + stream << sep << "Integer"; + sep = "|"; + } + if (GetType() & DOUBLE) { + stream << sep << "Double"; + sep = "|"; + } + if (GetType() & BOOLEAN) { + stream << sep << "Bool"; + sep = "|"; + } + if (GetType() & STRING) { + stream << sep << "String"; + sep = "|"; + } + if (GetType() & OBJECT) { + stream << sep << "Object"; + sep = "|"; + } + stream << ']'; +} + +inline ProfilingType operator|(ProfilingType a, ProfilingType b) +{ + return ProfilingType(a.type_ | b.type_); +} + +/** + * The following types are used in profile data serialization. + */ +using EcmaProfileElement = std::vector; +using EcmaProfileContainer = std::unordered_map; + +} // namespace panda::ecmascript + +#endif // PANDA_ECMA_PROFILING_H diff --git a/runtime/ecma_runtime.yaml b/runtime/ecma_runtime.yaml index 7903c9ce3fac37b9d9cdc28096d64be133d2ff25..0f203ec953cad8e5754e153a40948bf1efa1f4d1 100644 --- a/runtime/ecma_runtime.yaml +++ b/runtime/ecma_runtime.yaml @@ -314,6 +314,7 @@ intrinsics: ret: any args: [any, any, u16] impl: panda::ecmascript::intrinsics::LdObjByValue + fast_path: FastPathLdObjByValue - name: TryLdGlobalByValue space: ecmascript @@ -336,6 +337,7 @@ intrinsics: ret: any args: [any, any, acc, u16] impl: panda::ecmascript::intrinsics::StObjByValue + fast_path: FastPathStObjByValue - name: Shl2Dyn space: ecmascript @@ -1226,6 +1228,7 @@ intrinsics: ret: any args: [string_id, u16] impl: panda::ecmascript::intrinsics::LdGlobalVar + fast_path: FastPathLdGlobalVar - name: StGlobalVar space: ecmascript @@ -1237,6 +1240,7 @@ intrinsics: ret: any args: [string_id, acc, u16] impl: panda::ecmascript::intrinsics::StGlobalVar + fast_path: FastPathStGlobalVar - name: StGlobalLet space: ecmascript @@ -1259,6 +1263,7 @@ intrinsics: ret: any args: [string_id, any, u16] impl: panda::ecmascript::intrinsics::LdObjByName + fast_path: FastPathLdObjByName - name: StObjByName space: ecmascript @@ -1270,6 +1275,7 @@ intrinsics: ret: any args: [string_id, any, acc, u16] impl: panda::ecmascript::intrinsics::StObjByName + fast_path: FastPathStObjByName - name: LdObjByIndex space: ecmascript diff --git a/runtime/ecma_vm.cpp b/runtime/ecma_vm.cpp index a3156c29a33d7f3b969b3539d51f6fc868f906c9..4364466441a6a4719d9c9922e08793fbbf13d166 100644 --- a/runtime/ecma_vm.cpp +++ b/runtime/ecma_vm.cpp @@ -38,6 +38,7 @@ #include "plugins/ecmascript/runtime/js_thread.h" #include "plugins/ecmascript/runtime/mem/object_xray-inl.h" #include "plugins/ecmascript/runtime/object_factory.h" +#include "plugins/ecmascript/runtime/ecma_profiling.h" #include "plugins/ecmascript/runtime/regexp/regexp_parser_cache.h" #include "plugins/ecmascript/runtime/runtime_call_id.h" #include "plugins/ecmascript/runtime/symbol_table.h" @@ -54,6 +55,8 @@ #include "include/runtime_notification.h" #include "libpandafile/file.h" +#include "runtime/include/profiling_gen.h" + #include "runtime/compiler.h" #include "runtime/include/thread_scopes.h" #include "runtime/mem/gc/gc_root.h" @@ -70,6 +73,9 @@ #include "ecmastdlib_inline_gen.h" #include "ir-dyn-base-types.h" +#include "ic/profile_type_info.h" +#include "serializer/serializer.h" + namespace panda::ecmascript { // NOLINTNEXTLINE(fuchsia-statically-constructed-objects) static const std::string_view ENTRY_POINTER = "_GLOBAL::func_main_0"; @@ -152,6 +158,7 @@ EcmaVM *EcmaVM::Create(const JSRuntimeOptions &options) bool EcmaVM::Destroy(PandaVM *vm) { if (vm != nullptr) { + vm->SaveProfileInfo(); vm->UninitializeThreads(); vm->StopGC(); auto runtime = Runtime::GetCurrent(); @@ -342,6 +349,46 @@ bool EcmaVM::InitializeFinish() return true; } +void EcmaVM::SaveProfileInfo() +{ + if (GetOptions().WasSetProfileOutput()) { + if (profiles_methods_.empty()) { + LOG(WARNING, RUNTIME) << "Profiled methods list is empty"; + return; + } + + EcmaProfileContainer proto; + + for (auto method : profiles_methods_) { + auto method_name = method->GetFullName(); + + if (method->GetProfilingVector() != nullptr) { + panda_file::MethodDataAccessor mda(*method->GetPandaFile(), method->GetFileId()); + auto profile_data = method->GetProfilingVector(); + auto profile_size = mda.GetProfileSize().value(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + proto[method_name] = std::vector(profile_data, profile_data + profile_size); + std::cerr << "SAVE " << method_name << std::endl; + } + } + + if (!proto.empty()) { + std::ofstream stm(GetOptions().GetProfileOutput()); + if (!stm.is_open()) { + LOG(FATAL, RUNTIME) << "Failed to create profile output: " << GetOptions().GetProfileOutput(); + } + std::vector buffer; + auto res = serializer::TypeToBuffer(proto, buffer); + if (!res) { + LOG(FATAL, RUNTIME) << "Failed to serialize: " << res.Error(); + } + stm.write(reinterpret_cast(buffer.data()), buffer.size()); + LOG(INFO, RUNTIME) << "Profile with size=" << buffer.size() << " was saved to " + << GetOptions().GetProfileOutput(); + } + } +} + void EcmaVM::UninitializeThreads() { thread_->NativeCodeEnd(); diff --git a/runtime/ecma_vm.h b/runtime/ecma_vm.h index 4499509e321ccbc6e905d61f33037b4af78a73c9..a625138cd3b7ecab51b260cefd284a61836dbcd5 100644 --- a/runtime/ecma_vm.h +++ b/runtime/ecma_vm.h @@ -122,6 +122,8 @@ public: return factory_; } + void SaveProfileInfo() override; + bool Initialize() override; bool InitializeFinish() override; @@ -435,6 +437,11 @@ public: void RegisterFinalizationRegistry(JSHandle registry); + void AddMethodToProfile(JSMethod *method) + { + profiles_methods_.insert(method); + } + protected: bool CheckEntrypointSignature([[maybe_unused]] Method *entrypoint) override { @@ -490,6 +497,8 @@ private: EcmaRendezvous *rendezvous_ {nullptr}; bool isTestMode_ {false}; + PandaSet profiles_methods_; + // VM startup states. static JSRuntimeOptions options_; bool icEnable_ {true}; diff --git a/runtime/interpreter/ecma-interpreter-inl.h b/runtime/interpreter/ecma-interpreter-inl.h index 5273e0060480892cde5aa130007dbe6e6074a340..5fce35b048aa06922b59dd04de84d6bffdabd7f9 100644 --- a/runtime/interpreter/ecma-interpreter-inl.h +++ b/runtime/interpreter/ecma-interpreter-inl.h @@ -15,6 +15,7 @@ #include "plugins/ecmascript/runtime/intrinsics-inl.h" #include "plugins/ecmascript/runtime/js_generator_object.h" #include "plugins/ecmascript/runtime/js_array.h" +#include "plugins/ecmascript/runtime/ecma_profiling.h" #include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h" #include "runtime/interpreter/interpreter-inl.h" #include "runtime/interpreter/instruction_handler_base.h" @@ -58,6 +59,16 @@ namespace panda::ecmascript { } \ } while (false) +#ifdef PANDA_ENABLE_BYTECODE_PROFILING +#define UPDATE_UNARY_ARITH_PROFILE(lhs) UpdateUnaryArithProfile(lhs) +#define UPDATE_BINARY_ARITH_PROFILE(lhs, rhs) UpdateBinaryArithProfile(lhs, rhs) +#else +#define UPDATE_UNARY_ARITH_PROFILE(lhs) UNUSED_VAR(lhs) +#define UPDATE_BINARY_ARITH_PROFILE(lhs, rhs) \ + UNUSED_VAR(lhs); \ + UNUSED_VAR(rhs) +#endif + template class JSFrameHelper { public: @@ -526,6 +537,9 @@ public: uint64_t value = GetRegAsTaggedValue(v0).GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Tonumber(this->GetJSThread(), value)); + + UPDATE_UNARY_ARITH_PROFILE(value); + this->template MoveToNextInst(); } @@ -539,6 +553,9 @@ public: uint64_t value = GetRegAsTaggedValue(v0).GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::NegDyn(this->GetJSThread(), value)); + + UPDATE_UNARY_ARITH_PROFILE(value); + this->template MoveToNextInst(); } @@ -552,6 +569,9 @@ public: uint64_t value = GetRegAsTaggedValue(v0).GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::NotDyn(this->GetJSThread(), value)); + + UPDATE_UNARY_ARITH_PROFILE(value); + this->template MoveToNextInst(); } @@ -565,6 +585,9 @@ public: uint64_t value = GetRegAsTaggedValue(v0).GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::IncDyn(this->GetJSThread(), value)); + + UPDATE_UNARY_ARITH_PROFILE(value); + this->template MoveToNextInst(); } @@ -578,6 +601,9 @@ public: uint64_t value = GetRegAsTaggedValue(v0).GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::DecDyn(this->GetJSThread(), value)); + + UPDATE_UNARY_ARITH_PROFILE(value); + this->template MoveToNextInst(); } @@ -623,7 +649,10 @@ public: { LOG_INST() << "toboolean"; + UPDATE_UNARY_ARITH_PROFILE(GetAccAsTaggedValue().GetRawData()); + INTRINSIC_CALL_SETACC(intrinsics::Toboolean(GetAccAsTaggedValue().GetRawData())); + this->template MoveToNextInst(); } @@ -633,6 +662,8 @@ public: LOG_INST() << "negate"; uint64_t value = GetAccAsTaggedValue().GetRawData(); + UPDATE_UNARY_ARITH_PROFILE(value); + INTRINSIC_CALL_SETACC(intrinsics::Negate(value)); this->template MoveToNextInst(); } @@ -827,6 +858,9 @@ public: uint64_t right = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Add2Dyn(this->GetJSThread(), left, right)); + + UPDATE_BINARY_ARITH_PROFILE(left, right); + this->template MoveToNextInst(); } @@ -840,6 +874,9 @@ public: uint64_t right = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Sub2Dyn(this->GetJSThread(), left, right)); + + UPDATE_BINARY_ARITH_PROFILE(left, right); + this->template MoveToNextInst(); } @@ -853,6 +890,9 @@ public: uint64_t right = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Mul2Dyn(this->GetJSThread(), left, right)); + + UPDATE_BINARY_ARITH_PROFILE(left, right); + this->template MoveToNextInst(); } @@ -866,6 +906,9 @@ public: uint64_t right = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Div2Dyn(this->GetJSThread(), left, right)); + + UPDATE_BINARY_ARITH_PROFILE(left, right); + this->template MoveToNextInst(); } @@ -891,6 +934,8 @@ public: uint64_t left = GetRegAsTaggedValue(v0).GetRawData(); uint64_t right = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(left, right); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::EqDyn(this->GetJSThread(), left, right)); this->template MoveToNextInst(); } @@ -905,6 +950,8 @@ public: uint64_t lhs = GetRegAsTaggedValue(v0).GetRawData(); uint64_t rhs = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::NotEqDyn(this->GetJSThread(), lhs, rhs)); this->template MoveToNextInst(); } @@ -918,6 +965,8 @@ public: uint64_t left = GetRegAsTaggedValue(v0).GetRawData(); uint64_t right = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(left, right); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::LessDyn(this->GetJSThread(), left, right)); this->template MoveToNextInst(); } @@ -931,6 +980,8 @@ public: uint64_t left = GetRegAsTaggedValue(v0).GetRawData(); uint64_t right = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(left, right); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::LessEqDyn(this->GetJSThread(), left, right)); this->template MoveToNextInst(); } @@ -944,6 +995,8 @@ public: uint64_t left = GetRegAsTaggedValue(v0).GetRawData(); uint64_t right = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(left, right); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::GreaterDyn(this->GetJSThread(), left, right)); this->template MoveToNextInst(); } @@ -957,6 +1010,8 @@ public: uint64_t left = GetRegAsTaggedValue(v0).GetRawData(); uint64_t right = GetAccAsTaggedValue().GetRawData(); + UPDATE_BINARY_ARITH_PROFILE(left, right); + INTRINSIC_CALL_CHECK_SETACC(intrinsics::GreaterEqDyn(this->GetJSThread(), left, right)); this->template MoveToNextInst(); } @@ -972,6 +1027,9 @@ public: uint64_t rhs = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Shl2Dyn(this->GetJSThread(), lhs, rhs)); + + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + this->template MoveToNextInst(); } @@ -986,6 +1044,9 @@ public: uint64_t rhs = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Shr2Dyn(this->GetJSThread(), lhs, rhs)); + + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + this->template MoveToNextInst(); } @@ -1014,6 +1075,9 @@ public: uint64_t rhs = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::And2Dyn(this->GetJSThread(), lhs, rhs)); + + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + this->template MoveToNextInst(); } @@ -1028,6 +1092,9 @@ public: uint64_t rhs = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Or2Dyn(this->GetJSThread(), lhs, rhs)); + + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + this->template MoveToNextInst(); } @@ -1042,6 +1109,9 @@ public: uint64_t rhs = GetAccAsTaggedValue().GetRawData(); INTRINSIC_CALL_CHECK_SETACC(intrinsics::Xor2Dyn(this->GetJSThread(), lhs, rhs)); + + UPDATE_BINARY_ARITH_PROFILE(lhs, rhs); + this->template MoveToNextInst(); } @@ -2209,6 +2279,30 @@ public: return JSThread::Cast(this->GetThread()); } + void UpdateUnaryArithProfile(coretypes::TaggedType value) + { + auto method = static_cast(this->GetFrame()->GetMethod()); + auto prof_data = method->GetProfilingVector(); + auto prof_id = this->GetInst().GetProfileId(); + ASSERT(prof_id >= 0); + auto prof_value = reinterpret_cast(&prof_data[prof_id]); + ASSERT(method->GetProfileSize() > helpers::ToUnsigned(prof_id)); + UnaryOperationProfile::Update(prof_value, JSTaggedValue(value)); + } + + void UpdateBinaryArithProfile(coretypes::TaggedType lhs, coretypes::TaggedType rhs) + { + auto left = JSTaggedValue(lhs); + auto right = JSTaggedValue(rhs); + auto method = static_cast(this->GetFrame()->GetMethod()); + auto prof_data = method->GetProfilingVector(); + auto prof_id = this->GetInst().GetProfileId(); + ASSERT(prof_id >= 0); + auto prof_value = reinterpret_cast(&prof_data[prof_id]); + ASSERT(method->GetProfileSize() > helpers::ToUnsigned(prof_id)); + BinaryOperationProfile::Update(prof_value, left, right); + } + }; // InstructionHandler } // namespace panda::ecmascript diff --git a/runtime/interpreter/slow_runtime_stub.cpp b/runtime/interpreter/slow_runtime_stub.cpp index f672e59ea1edb1826f1c31fe4838ff77b885968e..29bd992caa24d58862eaabb46971cc66274504f1 100644 --- a/runtime/interpreter/slow_runtime_stub.cpp +++ b/runtime/interpreter/slow_runtime_stub.cpp @@ -1888,6 +1888,11 @@ void SlowRuntimeStub::ThrowDeleteSuperProperty(JSThread *thread) JSTaggedValue SlowRuntimeStub::NotifyInlineCache(JSThread *thread, JSFunction *func, JSMethod *method) { INTERPRETER_TRACE(thread, NotifyInlineCache); + + if (method->GetProfileSize() != 0) { + thread->GetEcmaVM()->AddMethodToProfile(method); + } + if (method->GetICOffsetMapping() == nullptr) { return JSTaggedValue::Undefined(); } @@ -1898,6 +1903,7 @@ JSTaggedValue SlowRuntimeStub::NotifyInlineCache(JSThread *thread, JSFunction *f JSHandle funcHandle(thread, func); JSHandle profileTypeInfo = factory->NewProfileTypeInfo(icSlotSize); funcHandle->SetProfileTypeInfo(thread, profileTypeInfo.GetTaggedValue()); + return profileTypeInfo.GetTaggedValue(); } diff --git a/runtime/js_method.cpp b/runtime/js_method.cpp index d96e59bd6a683c82ee99ec4fb214c3d295444fdf..bc29b674bbc8da47701fe462746d1e7b63c0e4b5 100644 --- a/runtime/js_method.cpp +++ b/runtime/js_method.cpp @@ -52,6 +52,12 @@ void JSMethod::SetCallTypeFromAnnotation() } } }); + auto prof_size = mda.GetProfileSize(); + if (prof_size) { + profile_size_ = prof_size.value(); + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + profiling_data_ = MakePandaUnique(RoundUp(prof_size.value(), BITS_PER_INTPTR)); + } } JSTaggedValue JSMethod::GetLength() const @@ -63,7 +69,7 @@ JSTaggedValue JSMethod::GetLength() const return length; } -std::string JSMethod::GetFullName() +std::string JSMethod::GetFullName([[maybe_unused]] bool with_signature) const { auto name = ParseFunctionName(); if (name.empty()) { diff --git a/runtime/js_method.h b/runtime/js_method.h index 35dd00177afff6875f5b17a647dfddb41ab20178..04060e7dff478b0a80c6ef2d8c1f1bb309ffbe1f 100644 --- a/runtime/js_method.h +++ b/runtime/js_method.h @@ -118,17 +118,30 @@ public: } PandaString ParseFunctionName() const; - std::string GetFullName(); + std::string GetFullName([[maybe_unused]] bool with_signature = false) const; void SetCallTypeFromAnnotation(); JSTaggedValue GetLength() const; + uint8_t *GetProfilingVector() + { + return profiling_data_.get(); + } + + uint32_t GetProfileSize() const + { + return profile_size_; + } + private: const uint8_t *bytecodeArray_ {nullptr}; + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + PandaUniquePtr profiling_data_; uint32_t bytecodeArraySize_ {0}; uint32_t slotSize_ {0}; uint8_t *icOffsetMapping_ {nullptr}; uint32_t callType_ {UINT32_MAX}; // UINT32_MAX means not found + uint32_t profile_size_ {0}; }; } // namespace panda::ecmascript diff --git a/runtime/profiling/plugin_destroy_profile.h b/runtime/profiling/plugin_destroy_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..3a79b202a5ca121866474f67d0666a3626d9ec6c --- /dev/null +++ b/runtime/profiling/plugin_destroy_profile.h @@ -0,0 +1,3 @@ +case panda_file::SourceLang::ECMASCRIPT: { + delete reinterpret_cast(profile); +} break; \ No newline at end of file diff --git a/runtime/profiling/plugin_dump_profile.h b/runtime/profiling/plugin_dump_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..4865a7caacba1e46fc34a7c05e62d4636a1321bb --- /dev/null +++ b/runtime/profiling/plugin_dump_profile.h @@ -0,0 +1,24 @@ +case panda_file::SourceLang::ECMASCRIPT: { + auto kind = profiling::GetProfileKind(inst->GetOpcode()); + stm << profiling::GetProfilingKindName(kind) << ' '; + auto &ecma_prof = *reinterpret_cast(profile); + auto prof_id = inst->GetProfileId(); + switch (kind) { + case profiling::ProfilingKind::UNARY_ARITH: { + ecmascript::UnaryOperationProfile p(&ecma_prof[prof_id]); + p.GetOperandType(0).Dump(stm); + break; + } + case profiling::ProfilingKind::BINARY_ARITH: { + ecmascript::BinaryOperationProfile p(&ecma_prof[prof_id]); + p.GetOperandType(0).Dump(stm); + stm << ", "; + p.GetOperandType(1).Dump(stm); + break; + } + default: + LOG(FATAL, DISASSEMBLER) << "Unknown profile"; + break; + } + break; +} \ No newline at end of file diff --git a/runtime/profiling/plugin_find_method_in_profile.h b/runtime/profiling/plugin_find_method_in_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..bc8d6b998e82325192f35a103206f2ffaaea8dfe --- /dev/null +++ b/runtime/profiling/plugin_find_method_in_profile.h @@ -0,0 +1,8 @@ +case panda_file::SourceLang::ECMASCRIPT: { + auto container = reinterpret_cast(profile); + auto item = container->find(method_name); + if (item == container->end()) { + LOG(FATAL, RUNTIME) << "Profile not found for method: " << method_name; + } + return reinterpret_cast(&item->second); +} break; \ No newline at end of file diff --git a/runtime/profiling/plugin_get_profiling_any_type.h b/runtime/profiling/plugin_get_profiling_any_type.h new file mode 100644 index 0000000000000000000000000000000000000000..a5ed31cae1d38490700b4200ec73ae4b487c1600 --- /dev/null +++ b/runtime/profiling/plugin_get_profiling_any_type.h @@ -0,0 +1,33 @@ +case panda::SourceLanguage::ECMASCRIPT: { + auto kind = profiling::GetProfileKind(bc_inst->GetOpcode()); + auto ecma_prof = reinterpret_cast(profile); + panda::ecmascript::ProfilingType type; + switch (kind) { + case profiling::ProfilingKind::UNARY_ARITH: { + panda::ecmascript::UnaryOperationProfile p(ecma_prof); + type = p.GetOperandType(index); + break; + } + case profiling::ProfilingKind::BINARY_ARITH: { + panda::ecmascript::BinaryOperationProfile p(ecma_prof); + type = p.GetOperandType(index); + break; + } + default: + LOG(FATAL, COMMON) << "Unknown profile"; + } + switch (type) { + case panda::ecmascript::ProfilingType::BOOLEAN: + return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE; + case panda::ecmascript::ProfilingType::INTEGER: + return compiler::AnyBaseType::ECMASCRIPT_INT_TYPE; + case panda::ecmascript::ProfilingType::DOUBLE: + *is_integer_seen = false; + return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; + case panda::ecmascript::ProfilingType::DOUBLE_INTEGER: + *is_integer_seen = true; + return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; + default: + return compiler::AnyBaseType::UNDEFINED_TYPE; + } +} break; \ No newline at end of file diff --git a/runtime/profiling/plugin_includes.h b/runtime/profiling/plugin_includes.h new file mode 100644 index 0000000000000000000000000000000000000000..38932baff8572c408adf0260de09ef3167250805 --- /dev/null +++ b/runtime/profiling/plugin_includes.h @@ -0,0 +1,2 @@ +#include "plugins/ecmascript/runtime/ecma_profiling.h" +#include "serializer/serializer.h" \ No newline at end of file diff --git a/runtime/profiling/plugin_read_profile.h b/runtime/profiling/plugin_read_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..259b9acdab8c371f8c8b9b7e29616fc61255b5c7 --- /dev/null +++ b/runtime/profiling/plugin_read_profile.h @@ -0,0 +1,11 @@ +case panda_file::SourceLang::ECMASCRIPT: { + std::vector buffer {std::istreambuf_iterator(stm), std::istreambuf_iterator()}; + auto container = new ecmascript::EcmaProfileContainer; + // auto container = std::make_unique(); + auto res = serializer::BufferToType(buffer.data(), buffer.size(), *container); + if (!res) { + delete container; + return Unexpected(res.Error()); + } + return reinterpret_cast(container); +} break; \ No newline at end of file diff --git a/tests/compiler/types_resolving_ecma_tests.cpp b/tests/compiler/types_resolving_ecma_tests.cpp index 2b1a94d63f414625ed08f4d1fd9bc8f177127578..3d156833bc7f595bdab76c34763f0df6ebb507f9 100644 --- a/tests/compiler/types_resolving_ecma_tests.cpp +++ b/tests/compiler/types_resolving_ecma_tests.cpp @@ -239,7 +239,7 @@ TEST_F(TypeResolvingTest, ToNumber) auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_INT_TYPE, RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER); ASSERT_TRUE(graph->RunPass()); - ASSERT_TRUE(graph->RunPass()); + graph->RunPass(); GraphChecker(graph).Check(); auto graph_opt = CreateGraphDynWithDefaultRuntime(); @@ -264,7 +264,7 @@ TEST_F(TypeResolvingTest, ToNumber) auto graph = ConstructGraphWithIntrinsic(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, RuntimeInterface::IntrinsicId::INTRINSIC_TONUMBER); ASSERT_TRUE(graph->RunPass()); - ASSERT_TRUE(graph->RunPass()); + graph->RunPass(); GraphChecker(graph).Check(); auto graph_opt = CreateGraphDynWithDefaultRuntime();