From 28ef76279b918805b9b229f0acf4e26b55e3294a Mon Sep 17 00:00:00 2001 From: chernykh sergey Date: Thu, 6 Oct 2022 13:00:18 +0300 Subject: [PATCH] Support monomorphic inlining for EcmaScript. Other changes: * Support profiling for dynamic calls. Implement ProfileCallTable. * Fix restore environment for cframe with inlined methods in deoptimization. * Fix some small bugs. Signed-off-by: schernykh --- compiler/CMakeLists.txt | 3 + compiler/codegen_intrinsics_ecmascript.cpp | 17 +- compiler/ecma_compiler.yaml | 20 ++ .../ecmascript_codegen_extensions.cpp | 16 +- .../ecmascript_codegen_extensions.h | 2 +- compiler/optimizer/ecma_pipeline.cpp | 6 +- .../ir_builder/ecmascript_inst_builder.cpp | 12 +- .../optimizer/optimizations/ecma_inlining.cpp | 208 ++++++++++++++++++ .../optimizer/optimizations/ecma_inlining.h | 56 +++++ .../plugin_create_pipeline_includes.h | 15 ++ .../ecmascript_inst_builder_gen.cpp.erb | 4 +- isa/isa.yaml | 37 +++- runtime/CMakeLists.txt | 7 +- .../compiler/ecmascript_runtime_interface.cpp | 88 +++++++- .../compiler/ecmascript_runtime_interface.h | 24 ++ runtime/ecma_call_profiling_table.cpp | 111 ++++++++++ runtime/ecma_call_profiling_table.h | 50 +++++ runtime/ecma_language_context.cpp | 40 ++-- runtime/ecma_language_context.h | 4 +- runtime/ecma_profiling.h | 63 +++++- runtime/ecma_vm.cpp | 15 ++ runtime/ecma_vm.h | 13 ++ runtime/interpreter/ecma-interpreter-inl.h | 46 +++- runtime/interpreter/js_decode_call_instr.h | 102 +++++---- runtime/profiling/plugin_clear_profile.h | 45 ++++ runtime/profiling/plugin_destroy_profile.h | 15 ++ runtime/profiling/plugin_dump_profile.h | 20 ++ .../profiling/plugin_find_method_in_profile.h | 15 ++ .../profiling/plugin_get_profiling_any_type.h | 58 ----- runtime/profiling/plugin_includes.h | 18 +- runtime/profiling/plugin_includes_disasm.h | 17 ++ subproject_sources.gn | 2 + tests/checked/CMakeLists.txt | 3 + tests/checked/ecma_inlining.js | 35 +++ tests/checked/ecma_inlining_deoptimize.js | 43 ++++ tests/checked/ecma_inlining_megamorphic.js | 43 ++++ 36 files changed, 1093 insertions(+), 180 deletions(-) create mode 100644 compiler/ecma_compiler.yaml create mode 100644 compiler/optimizer/optimizations/ecma_inlining.cpp create mode 100644 compiler/optimizer/optimizations/ecma_inlining.h create mode 100644 runtime/ecma_call_profiling_table.cpp create mode 100644 runtime/ecma_call_profiling_table.h create mode 100644 runtime/profiling/plugin_clear_profile.h delete mode 100644 runtime/profiling/plugin_get_profiling_any_type.h create mode 100644 runtime/profiling/plugin_includes_disasm.h create mode 100644 tests/checked/ecma_inlining.js create mode 100644 tests/checked/ecma_inlining_deoptimize.js create mode 100644 tests/checked/ecma_inlining_megamorphic.js diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index 687ab8ea1..d866c1872 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -30,6 +30,7 @@ add_dependencies(arkcompiler isa_gen_${PROJECT_NAME}) set(COMPILER_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/optimizer/code_generator/compiler_base_types.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/optimizer/optimizations/ecma_inlining.cpp ${CMAKE_CURRENT_SOURCE_DIR}/intrinsics_type_resolving_ecmascript.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_intrinsics_ecmascript.cpp ${CMAKE_CURRENT_SOURCE_DIR}/optimizer/ir_builder/ecmascript_inst_builder.cpp @@ -47,3 +48,5 @@ add_inst_templates(${CMAKE_CURRENT_SOURCE_DIR}/optimizer/ir_builder/ecmascript_i add_merge_plugin(PLUGIN_NAME "create_pipeline.h" INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/plugin_files/plugin_create_pipeline.h") add_merge_plugin(PLUGIN_NAME "create_pipeline_includes.h" INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/plugin_files/plugin_create_pipeline_includes.h") + +add_compiler_options(${CMAKE_CURRENT_SOURCE_DIR}/ecma_compiler.yaml) diff --git a/compiler/codegen_intrinsics_ecmascript.cpp b/compiler/codegen_intrinsics_ecmascript.cpp index 1742b3597..c26eeb35c 100644 --- a/compiler/codegen_intrinsics_ecmascript.cpp +++ b/compiler/codegen_intrinsics_ecmascript.cpp @@ -33,13 +33,13 @@ static MemRef IFrameAccMemRef(Encoder *enc, Reg iframe_reg) return MemRef(iframe_reg, cross_values::GetFrameAccOffset(enc->GetArch())); } -static void ReadExtensionData(Codegen *cg, Reg dst, uint16_t env_offs) +static void ReadExtensionData(Codegen *cg, Reg dst, uint16_t env_offs, uint32_t depth) { auto enc = cg->GetEncoder(); auto arch = enc->GetArch(); if (cg->GetGraph()->SupportManagedCode()) { - enc->EncodeLdr(dst, false, MemRef(cg->SpReg(), cg->GetLanguageExtensionOffsetFromSpInBytes() + env_offs)); + enc->EncodeLdr(dst, false, MemRef(cg->SpReg(), cg->GetLanguageExtensionOffsetFromSpInBytes(depth) + env_offs)); } else { auto dst_ptr = dst.As(TypeInfo::FromDataType(DataType::POINTER, arch)); enc->EncodeLdr(dst_ptr, false, @@ -64,7 +64,7 @@ void Codegen::LdlexenvDyn([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] auto constexpr LE_OFFS = panda::ecmascript::EcmascriptEnvironment::GetLexicalEnvOffset(); if (!GetGraph()->GetMode().IsInterpreter()) { - ReadExtensionData(this, dst, LE_OFFS); + ReadExtensionData(this, dst, LE_OFFS, inst->GetInliningDepth()); } else { auto tmp1 = ConvertInstTmpReg(inst); ScopedTmpReg tmp2(enc); @@ -72,7 +72,7 @@ void Codegen::LdlexenvDyn([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg le_reg(tmp1.GetId(), Codegen::ConvertDataType(DataType::ANY, arch)); Reg iframe_reg(tmp2.GetReg().GetId(), Codegen::ConvertDataType(DataType::POINTER, arch)); - ReadExtensionData(this, le_reg, LE_OFFS); + ReadExtensionData(this, le_reg, LE_OFFS, inst->GetInliningDepth()); LoadIFramePtr(this, iframe_reg); enc->EncodeStr(le_reg, IFrameAccMemRef(enc, iframe_reg)); } @@ -135,7 +135,8 @@ void Codegen::LdLexVarDyn([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] auto tmp1 = ConvertInstTmpReg(inst); Reg lex_env(tmp1.GetId(), Codegen::ConvertDataType(DataType::ANY, arch)); - ReadExtensionData(this, lex_env, panda::ecmascript::EcmascriptEnvironment::GetLexicalEnvOffset()); + ReadExtensionData(this, lex_env, panda::ecmascript::EcmascriptEnvironment::GetLexicalEnvOffset(), + inst->GetInliningDepth()); auto begin_label = enc->CreateLabel(); auto exit_label = enc->CreateLabel(); @@ -268,13 +269,15 @@ void Codegen::GetObjectClassTypeIntrinsic([[maybe_unused]] IntrinsicInst *inst, void Codegen::GetEcmaConstantPool([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst, [[maybe_unused]] SRCREGS src) { - ReadExtensionData(this, dst, panda::ecmascript::EcmascriptEnvironment::GetConstantPoolOffset()); + ReadExtensionData(this, dst, panda::ecmascript::EcmascriptEnvironment::GetConstantPoolOffset(), + inst->GetInliningDepth()); } void Codegen::GetEcmaThisFunc([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst, [[maybe_unused]] SRCREGS src) { - ReadExtensionData(this, dst, panda::ecmascript::EcmascriptEnvironment::GetThisFuncOffset()); + ReadExtensionData(this, dst, panda::ecmascript::EcmascriptEnvironment::GetThisFuncOffset(), + inst->GetInliningDepth()); } void Codegen::GetWeakReferent([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst, diff --git a/compiler/ecma_compiler.yaml b/compiler/ecma_compiler.yaml new file mode 100644 index 000000000..a7f4e3113 --- /dev/null +++ b/compiler/ecma_compiler.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2022-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. + +options: +- name: compiler-ecma-inlining-max-insts + type: uint32_t + default: 4096 + description: Maximum number of the IR instructions to be inlined, including instructions of the current graph. + recommended_values: [50,8000] + tags: [perf] \ No newline at end of file diff --git a/compiler/ecmascript_extensions/ecmascript_codegen_extensions.cpp b/compiler/ecmascript_extensions/ecmascript_codegen_extensions.cpp index 4395c64ff..c7bcbd380 100644 --- a/compiler/ecmascript_extensions/ecmascript_codegen_extensions.cpp +++ b/compiler/ecmascript_extensions/ecmascript_codegen_extensions.cpp @@ -19,22 +19,26 @@ namespace panda::compiler { -void Codegen::GenerateEcmascriptEnvInPrologue() +void Codegen::GenerateEcmascriptEnvInPrologue(Reg jsfunc_reg, uint32_t depth) { SCOPED_DISASM_STR(this, "Create EcmascriptEnvironment in method prologue"); auto enc = GetEncoder(); auto arch = GetArch(); - auto env_sp_offset = GetLanguageExtensionOffsetFromSpInBytes(); + auto env_sp_offset = GetLanguageExtensionOffsetFromSpInBytes(depth); auto env_ptr_tls_offset = cross_values::GetManagedThreadLanguageExtensionDataOffset(arch); auto prev_env_sp_offset = env_sp_offset + cross_values::GetEcmascriptEnvironmentPrevEnvironmentOffset(arch); // Initialize EcmascriptEnv fields { - ScopedTmpReg jsfunc_reg(enc, ConvertDataType(DataType::ANY, GetArch())); - auto args_jsfunc_offset = GetStackOffset(Location::MakeStackParameter(EcmascriptCallParams::SLOT_FUNCTION)); - enc->EncodeLdr(jsfunc_reg, false, MemRef(SpReg(), args_jsfunc_offset)); - + ScopedTmpRegLazy jsfunc_reg_tmp(enc); + if (!jsfunc_reg.IsValid()) { + ASSERT(depth == 0); + jsfunc_reg_tmp.Acquire(); + auto args_jsfunc_offset = GetStackOffset(Location::MakeStackParameter(EcmascriptCallParams::SLOT_FUNCTION)); + enc->EncodeLdr(jsfunc_reg_tmp, false, MemRef(SpReg(), args_jsfunc_offset)); + jsfunc_reg = jsfunc_reg_tmp.GetReg(); + } auto cp_jsf_offset = cross_values::GetJsfunctionConstantPoolOffset(GetArch()); auto le_jsf_offset = cross_values::GetJsfunctionLexicalEnvOffset(GetArch()); diff --git a/compiler/ecmascript_extensions/ecmascript_codegen_extensions.h b/compiler/ecmascript_extensions/ecmascript_codegen_extensions.h index ea7520e98..a407f9d85 100644 --- a/compiler/ecmascript_extensions/ecmascript_codegen_extensions.h +++ b/compiler/ecmascript_extensions/ecmascript_codegen_extensions.h @@ -16,7 +16,7 @@ #ifndef PANDA_COMPILER_ECMASCRIPT_CODEGEN_EXTENSIONS_H #define PANDA_COMPILER_ECMASCRIPT_CODEGEN_EXTENSIONS_H -void GenerateEcmascriptEnvInPrologue(); +void GenerateEcmascriptEnvInPrologue(Reg jsfunc_reg, uint32_t depth); void GenerateEcmascriptEnvInEpilogue(); bool GenerateLoadObjectDynamic(Inst *inst); diff --git a/compiler/optimizer/ecma_pipeline.cpp b/compiler/optimizer/ecma_pipeline.cpp index e9a797ffc..69ad22d01 100644 --- a/compiler/optimizer/ecma_pipeline.cpp +++ b/compiler/optimizer/ecma_pipeline.cpp @@ -14,6 +14,7 @@ */ #include "ecma_pipeline.h" +#include "optimizations/ecma_inlining.h" #include "optimizer/ir/graph.h" #include "optimizer/analysis/alias_analysis.h" @@ -54,7 +55,10 @@ bool EcmaPipeline::RunOptimizations() auto graph = GetGraph(); ASSERT(!graph->IsOsrMode() && "We don't support OSR in JS yet"); - + // TODO(schernykh): Find way to inline in AOT and OSR mode + if (!graph->IsAotMode() && !graph->IsOsrMode()) { + graph->RunPass(); + } graph->RunPass(); if (!graph->RunPass()) { LOG(WARNING, COMPILER) << "Compiler detected incorrect monitor policy"; diff --git a/compiler/optimizer/ir_builder/ecmascript_inst_builder.cpp b/compiler/optimizer/ir_builder/ecmascript_inst_builder.cpp index 27adf38ae..b73ec2297 100644 --- a/compiler/optimizer/ir_builder/ecmascript_inst_builder.cpp +++ b/compiler/optimizer/ir_builder/ecmascript_inst_builder.cpp @@ -43,7 +43,7 @@ void InstBuilder::BuildEcmaFnCall(const BytecodeInstruction *bc_inst, bool is_ra Inst *callee {}; switch (bc_inst->GetOpcode()) { - case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE: { + case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16: { callee = GetDefinitionAcc(); break; } @@ -83,23 +83,23 @@ void InstBuilder::BuildEcmaFnCall(const BytecodeInstruction *bc_inst, bool is_ra // Append func, new_target, this call_inst->AppendInput(callee); call_inst->AddInputType(DataType::ANY); - call_inst->AppendInput(FindOrCreateConstant(coretypes::TaggedValue::VALUE_UNDEFINED)); + call_inst->AppendInput(FindOrCreateAnyConstant(DataType::Any(coretypes::TaggedValue::VALUE_UNDEFINED))); call_inst->AddInputType(DataType::ANY); if (!call_this) { // Assume strict mode - call_inst->AppendInput(FindOrCreateConstant(coretypes::TaggedValue::VALUE_UNDEFINED)); + call_inst->AppendInput(FindOrCreateAnyConstant(DataType::Any(coretypes::TaggedValue::VALUE_UNDEFINED))); call_inst->AddInputType(DataType::ANY); } else { num_args++; } switch (bc_inst->GetOpcode()) { - case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE: { + case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16: { FinalizeEcmaFnCall(save_state, call_inst); return; } - case BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8: - case BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8: { + case BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16: + case BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8_PROF16: { auto range_start = bc_inst->GetVReg(0) + 1; auto range_size = bc_inst->GetImm64(); diff --git a/compiler/optimizer/optimizations/ecma_inlining.cpp b/compiler/optimizer/optimizations/ecma_inlining.cpp new file mode 100644 index 000000000..2c7914051 --- /dev/null +++ b/compiler/optimizer/optimizations/ecma_inlining.cpp @@ -0,0 +1,208 @@ +/** + * 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. + */ + +#include "ecma_inlining.h" +#include "events/events.h" +#include "optimizer/ir/graph.h" +#include "optimizer/optimizations/cleanup.h" +#include "optimizer/optimizations/branch_elimination.h" +#include "optimizer/optimizations/peepholes.h" +#include "runtime/profiling/profiling.h" + +namespace panda::compiler::ecmascript { + +EcmaInlining::EcmaInlining(Graph *graph, uint32_t instructions_count, uint32_t inline_depth, uint32_t methods_inlined) + : Inlining(graph, instructions_count, inline_depth, methods_inlined), + js_functions_(graph->GetLocalAllocator()->Adapter()) +{ + instructions_limit_ = OPTIONS.GetCompilerEcmaInliningMaxInsts(); +} + +void EcmaInlining::RunOptimizations() const +{ + if (GetGraph()->RunPass()) { + GetGraph()->RunPass(); + } +} + +bool EcmaInlining::SkipBlock(const BasicBlock *block) const +{ + return Inlining::SkipBlock(block) || block->IsTry(); +} + +bool EcmaInlining::IsInstSuitableForInline(Inst *inst) const +{ + return inst->GetOpcode() == Opcode::CallDynamic; +} + +bool EcmaInlining::CheckCallKind(CallInst *call_inst) +{ + auto runtime = GetGraph()->GetRuntime(); + auto kind = + runtime->GetCallProfile(GetGraph()->GetMethod(), call_inst->GetPc(), &js_functions_, GetGraph()->IsAotMode()); + if (kind != profiling::CallKind::MONOMORPHIC) { + LOG_INLINING(DEBUG) << "Call have " << CallKindToString(kind) << " type"; + EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), "-", call_inst->GetId(), + events::InlineKind::DYNAMIC_MONOMORPHIC, events::InlineResult::FAIL_MEGAMORPHIC); + return false; + } + // TODO(schernykh): support polimorphic inlining + CHECK_EQ(js_functions_.size(), 1U); + return true; +} + +InlineContext EcmaInlining::ResolveTargets(CallInst *call_inst) +{ + js_functions_.clear(); + if (!CheckCallKind(call_inst)) { + return {}; + } + ASSERT(js_functions_[0] != 0); + return {GetGraph()->GetRuntime()->GetMethodFromFunction(js_functions_[0])}; +} + +bool EcmaInlining::CheckMethod(CallInst *call_inst, InlineContext *ctx) +{ + if (!CheckMethodCanBeInlined(call_inst, ctx)) { + return false; + } + + if (!CheckBytecode(call_inst, *ctx, nullptr)) { + return false; + } + return true; +} + +Graph *EcmaInlining::BuildGraph(CallInst *call_inst, InlineContext *ctx) +{ + auto graph_inl = GetGraph()->CreateChildGraph(ctx->method); + + // Propagate instruction id counter to inlined graph, thereby avoid instructions id duplication + graph_inl->SetCurrentInstructionId(GetGraph()->GetCurrentInstructionId()); + graph_inl->SetMaxInliningDepth(depth_); + + auto stats = GetGraph()->GetPassManager()->GetStatistics(); + auto saved_pbc_inst_num = stats->GetPbcInstNum(); + if (!TryBuildGraph(*ctx, graph_inl, call_inst, call_inst)) { + stats->SetPbcInstNum(saved_pbc_inst_num); + return nullptr; + } + + // Run basic optimizations + graph_inl->RunPass(false); + if (graph_inl->RunPass()) { + graph_inl->RunPass(); + graph_inl->RunPass(); + } + + // Don't inline if we reach the limit of instructions and method is big enough. + auto inlined_insts_count = CalculateInstructionsCount(graph_inl); + if (!CheckInstructionLimit(call_inst, ctx, inlined_insts_count)) { + stats->SetPbcInstNum(saved_pbc_inst_num); + [[maybe_unused]] auto runtime = GetGraph()->GetRuntime(); + EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), runtime->GetMethodFullName(ctx->method), + call_inst->GetId(), events::InlineKind::DYNAMIC_MONOMORPHIC, events::InlineResult::LIMIT); + LOG_INLINING(DEBUG) << "Reach the limit of instructions and method is big enough."; + return nullptr; + } + if ((depth_ + 1) < OPTIONS.GetCompilerInliningMaxDepth()) { + graph_inl->RunPass(instructions_count_ + inlined_insts_count, depth_ + 1, methods_inlined_ + 1); + } + instructions_count_ += CalculateInstructionsCount(graph_inl); + return graph_inl; +} + +void EcmaInlining::BuildGuard(CallInst *call_inst) +{ + auto load_function = GetGraph()->CreateInstFunctionImmediate(DataType::ANY, call_inst->GetPc(), js_functions_[0]); + auto cmp_inst = GetGraph()->CreateInstCompare(DataType::BOOL, call_inst->GetPc()); + auto deopt_inst = GetGraph()->CreateInstDeoptimizeIf(DataType::BOOL, call_inst->GetPc()); + cmp_inst->SetCc(ConditionCode::CC_NE); + cmp_inst->SetInput(0, load_function); + cmp_inst->SetInput(1, call_inst->GetInput(0).GetInst()); + cmp_inst->SetOperandsType(DataType::ANY); + deopt_inst->SetInput(0, cmp_inst); + deopt_inst->SetInput(1, call_inst->GetSaveState()); + deopt_inst->SetDeoptimizeType(DeoptimizeType::INLINE_DYN); + + call_inst->InsertBefore(load_function); + call_inst->InsertBefore(cmp_inst); + call_inst->InsertBefore(deopt_inst); +} + +void EcmaInlining::InsertGraph(CallInst *call_inst, const InlineContext &ctx, Graph *graph_inl) +{ + GetGraph()->SetMaxMarkerIdx(graph_inl->GetCurrentMarkerIdx()); + GetGraph()->SetCurrentInstructionId(graph_inl->GetCurrentInstructionId()); + GetGraph()->SetMaxInliningDepth(graph_inl->GetMaxInliningDepth() + 1); + + BuildGuard(call_inst); + + auto call_bb = call_inst->GetBasicBlock(); + auto call_cont_bb = call_bb->SplitBlockAfterInstruction(call_inst, false); + + UpdateParameterDataflow(graph_inl, call_inst); + UpdateDataflow(graph_inl, call_inst, call_cont_bb); + MoveConstants(graph_inl); + UpdateControlflow(graph_inl, call_bb, call_cont_bb); + + if (call_cont_bb->GetPredsBlocks().empty()) { + GetGraph()->RemoveUnreachableBlocks(); + } else { + return_blocks_.push_back(call_cont_bb); + } + + bool need_barriers = GetGraph()->GetRuntime()->IsMemoryBarrierRequired(ctx.method); + ProcessCallReturnInstructions(call_inst, call_cont_bb, true, need_barriers); + + call_inst->SetCallMethod(ctx.method); + call_inst->SetCallMethodId(GetGraph()->GetRuntime()->GetMethodId(ctx.method)); +} + +bool EcmaInlining::TryInline(CallInst *call_inst) +{ + LOG_INLINING(DEBUG) << "Try to inline DynamicCall (id=" << call_inst->GetId() << ")"; + + InlineContext ctx = ResolveTargets(call_inst); + if (ctx.method == nullptr) { + LOG_INLINING(DEBUG) << "Target methods were not found"; + return false; + } + + LOG_INLINING(DEBUG) << "Found methods: " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method, true); + + if (!CheckMethod(call_inst, &ctx)) { + LOG_INLINING(DEBUG) << "Unsuitable bytecode"; + return false; + } + + // Build graph for current target and run EcmaInlining recursively + auto graph_inl = BuildGraph(call_inst, &ctx); + if (graph_inl == nullptr) { + return false; + } + + InsertGraph(call_inst, ctx, graph_inl); + + GetGraph()->GetPassManager()->GetStatistics()->AddInlinedMethods(1); + methods_inlined_++; + EVENT_INLINE(GetGraph()->GetRuntime()->GetMethodFullName(GetGraph()->GetMethod()), + GetGraph()->GetRuntime()->GetMethodFullName(ctx.method), call_inst->GetId(), + events::InlineKind::DYNAMIC_MONOMORPHIC, events::InlineResult::SUCCESS); + LOG_INLINING(DEBUG) << "Successfully inlined: " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method); + return true; +} + +} // namespace panda::compiler::ecmascript \ No newline at end of file diff --git a/compiler/optimizer/optimizations/ecma_inlining.h b/compiler/optimizer/optimizations/ecma_inlining.h new file mode 100644 index 000000000..413e61e5c --- /dev/null +++ b/compiler/optimizer/optimizations/ecma_inlining.h @@ -0,0 +1,56 @@ +/** + * 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_INLINING_H +#define PANDA_ECMA_INLINING_H + +#include "compiler/optimizer/optimizations/inlining.h" +#include "compiler/optimizer/ir/runtime_interface.h" + +namespace panda::compiler::ecmascript { + +class EcmaInlining : public Inlining { +public: + using Inlining::Inlining; + + explicit EcmaInlining(Graph *graph, uint32_t instructions_count, uint32_t inline_depth, uint32_t methods_inlined); + explicit EcmaInlining(Graph *graph) : EcmaInlining(graph, 0, 0, 0) {}; + + NO_MOVE_SEMANTIC(EcmaInlining); + NO_COPY_SEMANTIC(EcmaInlining); + ~EcmaInlining() override = default; + +private: + void RunOptimizations() const override; + bool IsInstSuitableForInline(Inst *inst) const override; + bool SkipBlock(const BasicBlock *block) const override; + + bool TryInline(CallInst *call_inst) override; + InlineContext ResolveTargets(CallInst *call_inst); + bool CheckCallKind(CallInst *call_inst); + bool CheckMethod(CallInst *call_inst, InlineContext *ctx); + Graph *BuildGraph(CallInst *call_inst, InlineContext *ctx); + void BuildGuard(CallInst *call_inst); + void InsertGraph(CallInst *call_inst, const InlineContext &ctx, Graph *graph_inl); + +private: + // This vector contains pointers to element in EcmaCallProfilingTable + // This element contains pointer to js function + ArenaVector js_functions_; +}; + +} // namespace panda::compiler::ecmascript + +#endif // PANDA_ECMA_INLINING_H \ No newline at end of file diff --git a/compiler/plugin_files/plugin_create_pipeline_includes.h b/compiler/plugin_files/plugin_create_pipeline_includes.h index 9f62ed4df..20646c963 100644 --- a/compiler/plugin_files/plugin_create_pipeline_includes.h +++ b/compiler/plugin_files/plugin_create_pipeline_includes.h @@ -1 +1,16 @@ +/** + * 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. + */ + #include "plugins/ecmascript/compiler/optimizer/ecma_pipeline.h" \ No newline at end of file diff --git a/compiler/templates/ecmascript_inst_builder_gen.cpp.erb b/compiler/templates/ecmascript_inst_builder_gen.cpp.erb index dad342fd6..a62451596 100644 --- a/compiler/templates/ecmascript_inst_builder_gen.cpp.erb +++ b/compiler/templates/ecmascript_inst_builder_gen.cpp.erb @@ -159,7 +159,7 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N bool is_integer_seen = false; bool is_type_profiled = 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, &is_type_profiled); + auto operand_type = GetRuntime()->GetProfilingAnyType(profile, bc_inst, <%= input_index %>, &is_integer_seen, &is_type_profiled); % else auto operand_type = compiler::AnyBaseType::UNDEFINED_TYPE; % end @@ -222,7 +222,7 @@ void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // N bool is_type_profiled = 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, &is_type_profiled); + auto operand_type = GetRuntime()->GetProfilingAnyType(profile, bc_inst, <%= idx %>, &is_integer_seen, &is_type_profiled); % else auto operand_type = compiler::AnyBaseType::UNDEFINED_TYPE; % end diff --git a/isa/isa.yaml b/isa/isa.yaml index a1a78c19a..c1fac40b4 100644 --- a/isa/isa.yaml +++ b/isa/isa.yaml @@ -37,7 +37,7 @@ namespaces: profiles_schema: name: string size: int - properties: [operand_types_1, operand_types_2, operand_types_array] + properties: [operand_types_1, operand_types_2] profiles: - name: BinaryArith @@ -48,7 +48,10 @@ profiles: properties: [operand_types_1] - name: ObjByIndex size: 1 - properties: [operand_types_array] + properties: [] + - name: Call + size: 2 + properties: [] groups: - title: Ecma extension intrunctions @@ -736,72 +739,82 @@ groups: - sig: ecma.call0dyn acc: inout:top prefix: ecma - format: [pref_op_none] + format: [pref_op_none_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL0_DYN + profile: Call - sig: ecma.call1dyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL1_DYN + profile: Call - sig: ecma.call2dyn v1:in:top, v2:in:top acc: inout:top prefix: ecma - format: [pref_op_v1_8_v2_8] + format: [pref_op_v1_8_v2_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL2_DYN + profile: Call - sig: ecma.call3dyn v1:in:top, v2:in:top, v3:in:top acc: inout:top prefix: ecma - format: [pref_op_v1_8_v2_8_v3_8] + format: [pref_op_v1_8_v2_8_v3_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL3_DYN + profile: Call - sig: ecma.callirangedyn imm, v:in:top acc: out:top prefix: ecma - format: [pref_op_imm_16_v_8] + format: [pref_op_imm_16_v_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALLI_RANGE_DYN + profile: Call - sig: ecma.call0thisdyn v:in:top acc: inout:top prefix: ecma - format: [pref_op_v_8] + format: [pref_op_v_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL0_THIS_DYN + profile: Call - sig: ecma.call1thisdyn v1:in:top, v2:in:top acc: inout:top prefix: ecma - format: [pref_op_v1_8_v2_8] + format: [pref_op_v1_8_v2_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL1_THIS_DYN + profile: Call - sig: ecma.call2thisdyn v1:in:top, v2:in:top, v3:in:top acc: inout:top prefix: ecma - format: [pref_op_v1_8_v2_8_v3_8] + format: [pref_op_v1_8_v2_8_v3_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL2_THIS_DYN + profile: Call - sig: ecma.call3thisdyn v1:in:top, v2:in:top, v3:in:top, v4:in:top acc: inout:top prefix: ecma - format: [pref_op_v1_8_v2_8_v3_8_v4_8] + format: [pref_op_v1_8_v2_8_v3_8_v4_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALL3_THIS_DYN + profile: Call - sig: ecma.callithisrangedyn imm, v:in:top acc: out:top prefix: ecma - format: [pref_op_imm_16_v_8] + format: [pref_op_imm_16_v_8_prof_16] properties: [call] intrinsic_name: INTRINSIC_CALLI_THIS_RANGE_DYN + profile: Call - sig: ecma.definegettersetterbyvalue v1:in:top, v2:in:top, v3:in:top, v4:in:top acc: inout:top diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 521da2c3b..34faeb891 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -102,6 +102,7 @@ set(ECMASCRIPT_SOURCES ${ECMA_SRC_DIR}/containers/containers_arraylist.cpp ${ECMA_SRC_DIR}/containers/containers_private.cpp ${ECMA_SRC_DIR}/dump.cpp + ${ECMA_SRC_DIR}/ecma_call_profiling_table.cpp ${ECMA_SRC_DIR}/ecma_class_linker_extension.cpp ${ECMA_SRC_DIR}/ecma_entrypoints.cpp ${ECMA_SRC_DIR}/ecma_exceptions.cpp @@ -341,7 +342,9 @@ 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 "clear_profile.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_clear_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") +add_merge_plugin(PLUGIN_NAME "profiling_includes_disasm.h" + INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/profiling/plugin_includes_disasm.h") \ No newline at end of file diff --git a/runtime/compiler/ecmascript_runtime_interface.cpp b/runtime/compiler/ecmascript_runtime_interface.cpp index 6b0c61291..507d1096a 100644 --- a/runtime/compiler/ecmascript_runtime_interface.cpp +++ b/runtime/compiler/ecmascript_runtime_interface.cpp @@ -118,11 +118,11 @@ EcmaRuntimeInterface::MethodProfile EcmaRuntimeInterface::GetMethodProfile(Metho : profiling::INVALID_PROFILE; } -EcmaRuntimeInterface::BytecodeProfile EcmaRuntimeInterface::GetBytecodeProfile(MethodProfile prof, +EcmaRuntimeInterface::BytecodeProfile EcmaRuntimeInterface::GetBytecodeProfile(MethodProfile profile, const uint8_t *bc_inst, [[maybe_unused]] size_t pc) const { - if (prof == 0) { + if (profile == nullptr) { return 0; } auto profile_id = BytecodeInstruction(bc_inst).GetProfileId(); @@ -130,7 +130,29 @@ EcmaRuntimeInterface::BytecodeProfile EcmaRuntimeInterface::GetBytecodeProfile(M return 0; } // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - return reinterpret_cast(reinterpret_cast(prof) + profile_id); + return reinterpret_cast(reinterpret_cast(profile) + profile_id); +} + +profiling::CallKind EcmaRuntimeInterface::GetCallProfile(MethodPtr method, uint32_t pc, ArenaVector *methods, + bool is_aot) +{ + auto profile = GetMethodProfile(method, !is_aot); + if (profile == nullptr) { + return profiling::CallKind::UNKNOWN; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto bc_inst = MethodCast(method)->GetInstructions() + pc; + auto profile_id = BytecodeInstruction(bc_inst).GetProfileId(); + if (profile_id == -1) { + return profiling::CallKind::UNKNOWN; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto call_prof = CallProfile::FromBuffer(reinterpret_cast(profile) + profile_id); + auto call_kind = call_prof->GetCallKind(); + if (call_kind == profiling::CallKind::MONOMORPHIC) { + methods->push_back(call_prof->GetCalleePtr(ecma_vm_->GetEcmaCallProfileTable())); + } + return call_kind; } Expected EcmaRuntimeInterface::AddProfile(std::string_view fname) @@ -149,4 +171,64 @@ Expected EcmaRuntimeInterface::AddProfile(std::string_view f return true; } +compiler::AnyBaseType EcmaRuntimeInterface::GetProfilingAnyType(RuntimeInterface::BytecodeProfile profile, + const BytecodeInstruction *bc_inst, unsigned index, + bool *is_integer_seen, bool *is_type_profiled) +{ + auto kind = profiling::GetProfileKind(bc_inst->GetOpcode()); + auto ecma_prof = reinterpret_cast(profile); + panda::ecmascript::ProfilingTypeBits::Type type = panda::ecmascript::ProfilingTypeBits::NONE; + switch (kind) { + case profiling::ProfilingKind::UNARY_ARITH: { + panda::ecmascript::UnaryOperationProfile p(ecma_prof); + type = p.GetOperandType(index).GetType(); + break; + } + case profiling::ProfilingKind::BINARY_ARITH: { + panda::ecmascript::BinaryOperationProfile p(ecma_prof); + type = p.GetOperandType(index).GetType(); + auto other_operand_type = p.GetOperandType(1 - index).GetType(); + if ((type & panda::ecmascript::ProfilingTypeBits::OBJECT) == panda::ecmascript::ProfilingTypeBits::OBJECT && + (other_operand_type & panda::ecmascript::ProfilingTypeBits::OBJECT) == + panda::ecmascript::ProfilingTypeBits::OBJECT) { + *is_type_profiled = true; + return compiler::AnyBaseType::UNDEFINED_TYPE; + } + break; + } + default: + LOG(FATAL, COMMON) << "Unknown profile"; + } + + if (type == panda::ecmascript::ProfilingTypeBits::NONE) { + return compiler::AnyBaseType::UNDEFINED_TYPE; + } + + *is_type_profiled = true; + auto is_int = + (type & panda::ecmascript::ProfilingTypeBits::INTEGER) == panda::ecmascript::ProfilingTypeBits::INTEGER; + // ignore non number types to avoid compiled method destruction on deoptimization + if ((type & panda::ecmascript::ProfilingTypeBits::DOUBLE) == panda::ecmascript::ProfilingTypeBits::DOUBLE) { + *is_integer_seen = is_int; + return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; + } + + if (is_int) { + auto is_obj_seen = + (type & panda::ecmascript::ProfilingTypeBits::OBJECT) == panda::ecmascript::ProfilingTypeBits::OBJECT; + if (is_obj_seen) { + // return number type to avoid compiled method destruction on deoptimization + *is_integer_seen = true; + return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; + } + + return compiler::AnyBaseType::ECMASCRIPT_INT_TYPE; + } + + if (type == panda::ecmascript::ProfilingTypeBits::BOOLEAN) { + return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE; + } + return compiler::AnyBaseType::UNDEFINED_TYPE; +} + } // namespace panda::ecmascript diff --git a/runtime/compiler/ecmascript_runtime_interface.h b/runtime/compiler/ecmascript_runtime_interface.h index a163c10d4..99b57f47b 100644 --- a/runtime/compiler/ecmascript_runtime_interface.h +++ b/runtime/compiler/ecmascript_runtime_interface.h @@ -67,10 +67,23 @@ public: return EntrypointId::GET_GLOBAL_VAR_ADDRESS; } + bool IsMethodCanBeInlined([[maybe_unused]] MethodPtr method) const override + { + return true; + } + + size_t GetMethodCodeSize(MethodPtr method) const override + { + return JsMethodCast(method)->GetCodeSize(); + } + MethodProfile GetMethodProfile(MethodPtr method, bool from_vector) const override; BytecodeProfile GetBytecodeProfile(MethodProfile prof, const uint8_t *bc_inst, size_t pc) const override; + profiling::CallKind GetCallProfile(MethodPtr method, uint32_t pc, ArenaVector *methods, + bool is_aot) override; + Expected AddProfile(std::string_view fname) override; std::string GetMethodName([[maybe_unused]] MethodPtr method) const override @@ -78,6 +91,17 @@ public: return utf::Mutf8AsCString(JsMethodCast(method)->GetName().data); } + MethodPtr GetMethodFromFunction(uintptr_t function) const override + { + ScopedMutatorLock lock; + auto *js_func = *(reinterpret_cast(function)); + return js_func == nullptr ? nullptr : js_func->GetCallTarget(); + } + + compiler::AnyBaseType GetProfilingAnyType(RuntimeInterface::BytecodeProfile profile, + const BytecodeInstruction *bc_inst, unsigned index, bool *is_integer_seen, + bool *is_type_profiled) override; + private: const EcmaVM *ecma_vm_ {nullptr}; panda::ecmascript::EcmaProfileContainer profile_; diff --git a/runtime/ecma_call_profiling_table.cpp b/runtime/ecma_call_profiling_table.cpp new file mode 100644 index 000000000..428a16139 --- /dev/null +++ b/runtime/ecma_call_profiling_table.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021-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. + */ + +#include "plugins/ecmascript/runtime/ecma_call_profiling_table.h" +#include "plugins/ecmascript/runtime/js_object.h" + +namespace panda::ecmascript { + +EcmaCallProfilingTable::EcmaCallProfilingTable(uint32_t size) +{ + // We need to save std::numeric_limits::max() value to indentify MEGAMORPHIC profile + // And we need to reserve first element in table to indentify UNKNOWN profile + ASSERT(size <= std::numeric_limits::max() - 2U); + call_profiling_table_.resize(size + 1); + call_profiling_bit_map_.resize(size + 1); + call_profiling_bit_map_[0] = true; +} + +void EcmaCallProfilingTable::ClearObject(uint16_t idx) +{ + ASSERT(idx != 0); + os::memory::LockHolder holder(table_lock_); + LOG(DEBUG, INTERPRETER) << "CallProfileTable: Clear slot " << idx; + call_profiling_table_[idx] = nullptr; + call_profiling_bit_map_[idx] = false; +} + +std::optional EcmaCallProfilingTable::InsertNewObject(ECMAObject *js_func) +{ + os::memory::LockHolder holder(table_lock_); + // find first free elem + uint32_t idx = 1; + for (; idx < call_profiling_bit_map_.size(); idx++) { + if (!call_profiling_bit_map_[idx]) { + break; + } + } + if (idx == call_profiling_bit_map_.size()) { + LOG(DEBUG, INTERPRETER) << "CallProfileTable: Table is full. Don't insert object " << std::hex << js_func; + return std::nullopt; + } + ASSERT(idx != 0); + LOG(DEBUG, INTERPRETER) << "CallProfileTable: Insert object " << std::hex << js_func << std::dec << " in slot " + << idx; + ASSERT(call_profiling_table_[idx] == nullptr); + call_profiling_bit_map_[idx] = true; + call_profiling_table_[idx] = js_func; + return idx; +} + +ECMAObject *EcmaCallProfilingTable::GetObject(uint16_t idx) const +{ + ASSERT(idx != 0); + os::memory::LockHolder holder(table_lock_); + return call_profiling_table_[idx]; +} + +uintptr_t EcmaCallProfilingTable::GetObjectPtr(uint16_t idx) const +{ + ASSERT(idx != 0); + os::memory::LockHolder holder(table_lock_); + return reinterpret_cast(&call_profiling_table_[idx]); +} + +void EcmaCallProfilingTable::Sweep(const GCObjectVisitor &visitor) +{ + os::memory::LockHolder holder(table_lock_); + for (size_t i = 1; i < call_profiling_table_.size(); ++i) { + auto *object = call_profiling_table_[i]; + if (object == nullptr) { + continue; + } + ASSERT(!object->IsForwarded()); + if (visitor(object) == ObjectStatus::DEAD_OBJECT) { + LOG(DEBUG, GC) << "CallProfileTable: delete js function " << std::hex << object << std::dec << " in slot " + << i; + call_profiling_table_[i] = nullptr; + } + } +} + +void EcmaCallProfilingTable::UpdateMoved() +{ + os::memory::LockHolder holder(table_lock_); + for (size_t i = 1; i < call_profiling_table_.size(); ++i) { + auto *object = call_profiling_table_[i]; + if (object == nullptr) { + continue; + } + if (object->IsForwarded()) { + ObjectHeader *fwd_object = panda::mem::GetForwardAddress(object); + call_profiling_table_[i] = static_cast(fwd_object); + LOG(DEBUG, GC) << "CallProfileTable: forward " << std::hex << object << " -> " << fwd_object << std::dec + << " in slot " << i; + } + } +} + +} // namespace panda::ecmascript diff --git a/runtime/ecma_call_profiling_table.h b/runtime/ecma_call_profiling_table.h new file mode 100644 index 000000000..697412f4e --- /dev/null +++ b/runtime/ecma_call_profiling_table.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021-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 ECMASCRIPT_CALL_PROFILING_TABLE_H +#define ECMASCRIPT_CALL_PROFILING_TABLE_H + +#include "runtime/include/mem/panda_containers.h" +#include "runtime/mem/gc/bitmap.h" + +namespace panda::ecmascript { +class EcmaVM; +class ECMAObject; + +class EcmaCallProfilingTable { +public: + explicit EcmaCallProfilingTable(uint32_t size); + ~EcmaCallProfilingTable() = default; + + std::optional InsertNewObject(ECMAObject *js_func); + void ClearObject(uint16_t idx); + ECMAObject *GetObject(uint16_t idx) const; + uintptr_t GetObjectPtr(uint16_t idx) const; + void Sweep(const GCObjectVisitor &visitor); + void UpdateMoved(); + +private: + NO_COPY_SEMANTIC(EcmaCallProfilingTable); + NO_MOVE_SEMANTIC(EcmaCallProfilingTable); + +private: + PandaVector call_profiling_table_ GUARDED_BY(table_lock_); + PandaVector call_profiling_bit_map_ GUARDED_BY(table_lock_); + mutable os::memory::Mutex table_lock_; +}; + +} // namespace panda::ecmascript + +#endif // ECMASCRIPT_CALL_PROFILING_TABLE_H \ No newline at end of file diff --git a/runtime/ecma_language_context.cpp b/runtime/ecma_language_context.cpp index 3ea79ddac..45f1c3ca7 100644 --- a/runtime/ecma_language_context.cpp +++ b/runtime/ecma_language_context.cpp @@ -101,31 +101,31 @@ size_t EcmaLanguageContext::GetStringSize(const ObjectHeader *string_object) con return string->ObjectSize(); } -void EcmaLanguageContext::DeoptimizeBegin(Frame *iframe, int frame_depth) const +void EcmaLanguageContext::RestoreEnv(Frame *current_iframe, int inlining_depth) const { JSThread *js_thread = JSThread::GetCurrentRaw(); - - ecmascript::EcmascriptEnvironment *upper_env = nullptr; - ecmascript::EcmascriptEnvironment *cframe_env = js_thread->GetEcmascriptEnv(); - for (int i = 0; i < (-1 - frame_depth); ++i) { - upper_env = cframe_env; - cframe_env = cframe_env->GetPrevEnvironment(); - } - - ecmascript::EcmascriptEnvironment *iframe_env = ecmascript::JSExtFrame::FromFrame(iframe)->GetExtData(); - iframe_env->SetConstantPool(cframe_env->GetConstantPool()); - iframe_env->SetLexicalEnv(cframe_env->GetLexicalEnv()); - iframe_env->SetThisFunc(cframe_env->GetThisFunc()); - iframe_env->SetPrevEnvironment(cframe_env->GetPrevEnvironment()); - - if (upper_env == nullptr) { - js_thread->SetEcmascriptEnv(iframe_env); - } else { - upper_env->SetPrevEnvironment(iframe_env); + auto current_env = js_thread->GetEcmascriptEnv(); + ecmascript::EcmascriptEnvironment *prev_env = nullptr; + + while (inlining_depth >= 0) { + auto *iframe_env = ecmascript::JSExtFrame::FromFrame(current_iframe)->GetExtData(); + iframe_env->SetConstantPool(current_env->GetConstantPool()); + iframe_env->SetLexicalEnv(current_env->GetLexicalEnv()); + iframe_env->SetThisFunc(current_env->GetThisFunc()); + iframe_env->SetPrevEnvironment(current_env->GetPrevEnvironment()); + if (prev_env != nullptr) { + prev_env->SetPrevEnvironment(iframe_env); + } else { + js_thread->SetEcmascriptEnv(iframe_env); + } + prev_env = iframe_env; + current_iframe = current_iframe->GetPrevFrame(); + current_env = current_env->GetPrevEnvironment(); + inlining_depth--; } } -void EcmaLanguageContext::DeoptimizeEnd() const +void EcmaLanguageContext::RestorePrevEnv() const { JSThread *js_thread = JSThread::GetCurrentRaw(); ecmascript::EcmascriptEnvironment *current_env = js_thread->GetEcmascriptEnv(); diff --git a/runtime/ecma_language_context.h b/runtime/ecma_language_context.h index a07ca51a0..9259b5575 100644 --- a/runtime/ecma_language_context.h +++ b/runtime/ecma_language_context.h @@ -296,9 +296,9 @@ public: size_t GetStringSize(const ObjectHeader *string_object) const override; - void DeoptimizeBegin(Frame *iframe, int frame_depth) const override; + void RestoreEnv(Frame *current_iframe, int inlining_depth) const override; - void DeoptimizeEnd() const override; + void RestorePrevEnv() const override; bool IsEnabledCHA() const override { diff --git a/runtime/ecma_profiling.h b/runtime/ecma_profiling.h index 023181148..ac13ed73d 100644 --- a/runtime/ecma_profiling.h +++ b/runtime/ecma_profiling.h @@ -17,6 +17,8 @@ #define PANDA_ECMA_PROFILING_H #include "js_tagged_value.h" +#include "runtime/profiling/profiling.h" +#include "plugins/ecmascript/runtime/ecma_call_profiling_table.h" #include "utils/bit_field.h" namespace panda::ecmascript { @@ -316,12 +318,71 @@ protected: SET_LAST_FIELD_FINAL(RightOperandType); }; +class ECMAObject; + +class CallProfile { +public: + using Type = uint16_t; + static constexpr uintptr_t MEGAMORPHIC = std::numeric_limits::max(); + static constexpr uintptr_t UNKNOWN = 0; + + static CallProfile *FromBuffer(uint8_t *data) + { + return reinterpret_cast(data); + } + + panda::profiling::CallKind GetCallKind() const + { + if (callee_idx_ == MEGAMORPHIC) { + return panda::profiling::CallKind::MEGAMORPHIC; + } + if (callee_idx_ == UNKNOWN) { + return panda::profiling::CallKind::UNKNOWN; + } + return panda::profiling::CallKind::MONOMORPHIC; + } + + uintptr_t GetCalleePtr(EcmaCallProfilingTable *table) const + { + return table->GetObjectPtr(callee_idx_); + } + + void Clear(EcmaCallProfilingTable *table) + { + if (callee_idx_ != UNKNOWN && callee_idx_ != MEGAMORPHIC) { + table->ClearObject(callee_idx_); + } + callee_idx_ = UNKNOWN; + } + + void Update(ECMAObject *js_func, EcmaCallProfilingTable *table) + { + if (callee_idx_ == MEGAMORPHIC) { + return; + } + if (callee_idx_ == UNKNOWN) { + auto idx = table->InsertNewObject(js_func); + if (idx) { + callee_idx_ = idx.value(); + } + return; + } + ASSERT(callee_idx_ > 0 && callee_idx_ <= std::numeric_limits::max() - 1U); + if (table->GetObject(callee_idx_) != js_func) { + table->ClearObject(callee_idx_); + callee_idx_ = MEGAMORPHIC; + } + } + +private: + Type callee_idx_ {UNKNOWN}; +}; + /** * 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_vm.cpp b/runtime/ecma_vm.cpp index b8c2f4be0..99ee66d1c 100644 --- a/runtime/ecma_vm.cpp +++ b/runtime/ecma_vm.cpp @@ -181,6 +181,10 @@ EcmaVM::EcmaVM(JSRuntimeOptions options) : string_table_(new EcmaStringTable(thi is_profiling_enabled_ = options_.IsCompilerEnableJit() || options_.WasSetProfileOutput(); + if (is_profiling_enabled_) { + call_profiling_table_ = allocator->New(options_.GetCallProfilingTableSize()); + } + auto heap_manager = mm_->GetHeapManager(); auto internal_allocator = heap_manager->GetInternalAllocator(); runtime_iface_ = internal_allocator->New(this); @@ -415,6 +419,9 @@ EcmaVM::~EcmaVM() allocator->Delete(thread_manager_); allocator->Delete(runtime_iface_); allocator->Delete(compiler_); + if (call_profiling_table_ != nullptr) { + allocator->Delete(call_profiling_table_); + } mm_->Finalize(); mem::MemoryManager::Destroy(mm_); @@ -496,6 +503,9 @@ bool EcmaVM::Execute(std::unique_ptr pf, std::string_vie void EcmaVM::SweepVmRefs(const GCObjectVisitor &gc_object_visitor) { GetEcmaStringTable()->Sweep(gc_object_visitor); + if (HasEcmaCallProfileTable()) { + GetEcmaCallProfileTable()->Sweep(gc_object_visitor); + } auto it = finalization_registries_.begin(); while (it != finalization_registries_.end()) { if (gc_object_visitor(*it) == ObjectStatus::DEAD_OBJECT) { @@ -1011,6 +1021,11 @@ void EcmaVM::UpdateVmRefs() for (auto &entry : finalization_registries_) { single_ptr_visitor(Root::ROOT_VM, reinterpret_cast(&entry)); } + + // Update call profiling table + if (HasEcmaCallProfileTable()) { + GetEcmaCallProfileTable()->UpdateMoved(); + } } } // namespace panda::ecmascript diff --git a/runtime/ecma_vm.h b/runtime/ecma_vm.h index 37cd473ae..de611e467 100644 --- a/runtime/ecma_vm.h +++ b/runtime/ecma_vm.h @@ -20,6 +20,7 @@ #include "include/mem/panda_containers.h" #include "plugins/ecmascript/runtime/base/config.h" +#include "plugins/ecmascript/runtime/ecma_call_profiling_table.h" #include "plugins/ecmascript/runtime/ecma_string_table.h" #include "plugins/ecmascript/runtime/global_handle_collection.h" #include "plugins/ecmascript/runtime/js_handle.h" @@ -293,6 +294,17 @@ public: return string_table_; } + bool HasEcmaCallProfileTable() const + { + return call_profiling_table_ != nullptr; + } + + EcmaCallProfilingTable *GetEcmaCallProfileTable() const + { + ASSERT(call_profiling_table_ != nullptr); + return call_profiling_table_; + } + JSThread *GetJSThread() const { return thread_; @@ -509,6 +521,7 @@ private: bool is_profiling_enabled_ {false}; // VM memory management. + EcmaCallProfilingTable *call_profiling_table_ {nullptr}; EcmaStringTable *string_table_ {nullptr}; ObjectFactory *factory_ {nullptr}; PandaVector array_buffer_data_list_; diff --git a/runtime/interpreter/ecma-interpreter-inl.h b/runtime/interpreter/ecma-interpreter-inl.h index 791b32aae..99f4857f5 100644 --- a/runtime/interpreter/ecma-interpreter-inl.h +++ b/runtime/interpreter/ecma-interpreter-inl.h @@ -86,6 +86,13 @@ namespace panda::ecmascript { UNUSED_VAR(lhs); \ } +#define UPDATE_CALL_PROFILE(func) \ + if constexpr (IS_PROFILE_ENABLED) { \ + UpdateCallProfile(func); \ + } else { \ + UNUSED_VAR(func); \ + } + template class JSFrameHelper { public: @@ -351,6 +358,9 @@ public: js_thread->SetEcmascriptEnv(prev_env); } } else { // Interpreter + + UPDATE_CALL_PROFILE(this_func); + method->IncrementHotnessCounter(0, nullptr); // Call stackless interpreter @@ -520,7 +530,7 @@ public: { LOG_INST() << "call0.dyn"; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -531,7 +541,7 @@ public: LOG_INST() << "call1.dyn " << "v" << v0; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -542,7 +552,7 @@ public: LOG_INST() << "call2.dyn " << "v" << v0 << ", v" << v1; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -554,7 +564,7 @@ public: LOG_INST() << "call3.dyn " << "v" << v0 << ", v" << v1 << ", v" << v2; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -565,7 +575,7 @@ public: LOG_INST() << "callthis0.dyn " << "this v:" << v0; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -576,7 +586,7 @@ public: LOG_INST() << "callthis1.dyn " << "this v" << v0 << ", v" << v1; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -588,7 +598,7 @@ public: LOG_INST() << "callthis2.dyn " << "this v" << v0 << ", v" << v1 << ", v" << v2; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -601,7 +611,7 @@ public: LOG_INST() << "callthis3.dyn " << "this v" << v0 << ", v" << v1 << ", v" << v2 << ", v" << v3; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -614,7 +624,7 @@ public: LOG_INST() << "calli.rangedyn " << num_args + 3 << ", v" << v0 << " , func:" << func.GetRawData(); - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -625,7 +635,8 @@ public: LOG_INST() << "calli.dyn.this.range " << num_args + 2 << ", v" << v0; - this->template DoEcmaCallDyn(); + this->template DoEcmaCallDyn(); } template @@ -2807,6 +2818,21 @@ public: BinaryOperationProfile::Update(prof_value, left, right); } + void UpdateCallProfile(ECMAObject *func) + { + auto js_method = (JSMethod *)this->GetFrame()->GetMethod(); + if (js_method->GetProfileSize() == 0) { + return; + } + auto prof_id = this->GetInst().GetProfileId(); + ASSERT(prof_id != -1); + ASSERT(static_cast(prof_id) < js_method->GetProfileSize()); + auto profile = CallProfile::FromBuffer(js_method->GetProfilingVector() + prof_id); + ASSERT(profile != nullptr); + auto *profile_table = this->GetJSThread()->GetEcmaVM()->GetEcmaCallProfileTable(); + profile->Update(func, profile_table); + } + }; // InstructionHandler } // namespace panda::ecmascript diff --git a/runtime/interpreter/js_decode_call_instr.h b/runtime/interpreter/js_decode_call_instr.h index 5964669c1..801e0143f 100644 --- a/runtime/interpreter/js_decode_call_instr.h +++ b/runtime/interpreter/js_decode_call_instr.h @@ -16,36 +16,39 @@ ALWAYS_INLINE inline uint32_t JSGetNumberActualArgsDyn([[maybe_unused]] Bytecode { using R = BytecodeInstructionResolver; constexpr auto OP = R::template Get(); - if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 0; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 1; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 2; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 3; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == + R::template Get()) { + static_assert(FORMAT == R::template Get()); auto imm = inst.GetImm(); return JSMethodArgs::FIRST_ARG_IDX + imm; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 1; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 2; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == + R::template Get()) { + static_assert(FORMAT == R::template Get()); return JSMethodArgs::FIRST_ARG_IDX + 3; - } else if constexpr (OP == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (OP == + R::template Get()) { + static_assert(FORMAT == R::template Get()); auto imm = inst.GetImm(); ASSERT(imm >= 1); // magic, 'this' is counted in range, 'func' - not return JSMethodArgs::FIRST_ARG_IDX + (imm - 1); @@ -69,7 +72,7 @@ ALWAYS_INLINE inline int64_t JSGetCalleDyn([[maybe_unused]] Frame *frame, [[mayb using R = BytecodeInstructionResolver; constexpr auto op = R::template Get(); - if constexpr (op == R::template Get()) { + if constexpr (op == R::template Get()) { return frame->GetAccAsVReg().GetValue(); } else { return frame->GetVReg(inst.GetVReg()).GetValue(); @@ -85,8 +88,8 @@ ALWAYS_INLINE inline uint16_t JSGetCalleRangeStartDyn([[maybe_unused]] BytecodeI using R = BytecodeInstructionResolver; constexpr auto op = R::template Get(); - if constexpr (op == R::template Get() || - op == R::template Get() || + if constexpr (op == R::template Get() || + op == R::template Get() || op == R::template Get()) { return inst.GetVReg(); } @@ -147,11 +150,11 @@ ALWAYS_INLINE inline static void JSCopyArgumets(JSThread *thread, Frame *prev_fr new_frame->GetVReg(num_vregs + JSMethodArgs::NEW_TARGET_IDX).SetValue(JSTaggedValue::VALUE_UNDEFINED); } - if constexpr (op == R::template Get() || - op == R::template Get() || - op == R::template Get() || - op == R::template Get() || - op == R::template Get()) { + if constexpr (op == R::template Get() || + op == R::template Get() || + op == R::template Get() || + op == R::template Get() || + op == R::template Get()) { JSTaggedValue fn_object(raw_fn_object); uint64_t this_arg = JSTaggedValue::VALUE_UNDEFINED; if (fn_object.IsJSFunction() && !JSFunction::Cast(fn_object.GetHeapObject())->IsStrict()) { @@ -160,55 +163,58 @@ ALWAYS_INLINE inline static void JSCopyArgumets(JSThread *thread, Frame *prev_fr new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX).SetValue(this_arg); } - if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX); // Do nothing - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 1); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 2); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0) = prev_frame->GetVReg(prev_inst.GetVReg(1)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 1).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 3); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0) = prev_frame->GetVReg(prev_inst.GetVReg(1)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 1) = prev_frame->GetVReg(prev_inst.GetVReg(2)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 2).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == + R::template Get()) { + static_assert(FORMAT == R::template Get()); uint16_t prev_v0 = JSGetCalleRangeStartDyn(prev_inst); for (uint16_t i = 0; i < (num_actual_args - JSMethodArgs::FIRST_ARG_IDX); ++i) { new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + i) = prev_frame->GetVReg(prev_v0 + 1 + i); } - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX); new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 1); new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX) = prev_frame->GetVReg(prev_inst.GetVReg(1)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 2); new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX) = prev_frame->GetVReg(prev_inst.GetVReg(1)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0) = prev_frame->GetVReg(prev_inst.GetVReg(2)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 1).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == + R::template Get()) { + static_assert(FORMAT == R::template Get()); ASSERT(num_actual_args == JSMethodArgs::FIRST_ARG_IDX + 3); new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX) = prev_frame->GetVReg(prev_inst.GetVReg(1)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 0) = prev_frame->GetVReg(prev_inst.GetVReg(2)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 1) = prev_frame->GetVReg(prev_inst.GetVReg(3)); new_frame->GetVReg(num_vregs + JSMethodArgs::FIRST_ARG_IDX + 2).SetValue(prev_frame->GetAccAsVReg().GetValue()); - } else if constexpr (op == R::template Get()) { - static_assert(FORMAT == R::template Get()); + } else if constexpr (op == + R::template Get()) { + static_assert(FORMAT == R::template Get()); uint16_t prev_v0 = JSGetCalleRangeStartDyn(prev_inst); new_frame->GetVReg(num_vregs + JSMethodArgs::THIS_IDX) = prev_frame->GetVReg(prev_v0 + 1); for (uint16_t i = 0; i < (num_actual_args - JSMethodArgs::FIRST_ARG_IDX); ++i) { diff --git a/runtime/profiling/plugin_clear_profile.h b/runtime/profiling/plugin_clear_profile.h new file mode 100644 index 000000000..716880bd9 --- /dev/null +++ b/runtime/profiling/plugin_clear_profile.h @@ -0,0 +1,45 @@ + /** + * 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. + */ + +case panda_file::SourceLang::ECMASCRIPT: { + auto js_method = static_cast(method); + if (js_method->GetProfileSize() == 0) { + return; + } + auto prof_id = inst.GetProfileId(); + if (prof_id == -1) { + return; + } + ASSERT(static_cast(prof_id) < js_method->GetProfileSize()); + auto prof_data = js_method->GetProfilingVector() + prof_id; + switch (profiling::GetProfileKind(inst.GetOpcode())) { + // No need to clean profiling for this profiling kinds + case profiling::ProfilingKind::UNARY_ARITH: + case profiling::ProfilingKind::BINARY_ARITH: + case profiling::ProfilingKind::OBJ_BY_INDEX: + break; + case profiling::ProfilingKind::CALL: { + auto profile = ecmascript::CallProfile::FromBuffer(prof_data); + ASSERT(profile != nullptr); + auto *profile_table = static_cast(vm)->GetEcmaCallProfileTable(); + profile->Clear(profile_table); + break; + } + default: { + UNREACHABLE(); + break; + } + } +} \ No newline at end of file diff --git a/runtime/profiling/plugin_destroy_profile.h b/runtime/profiling/plugin_destroy_profile.h index 3a79b202a..050ef259e 100644 --- a/runtime/profiling/plugin_destroy_profile.h +++ b/runtime/profiling/plugin_destroy_profile.h @@ -1,3 +1,18 @@ + /** + * 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. + */ + 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 index 4865a7caa..c55ed85e6 100644 --- a/runtime/profiling/plugin_dump_profile.h +++ b/runtime/profiling/plugin_dump_profile.h @@ -1,3 +1,18 @@ + /** + * 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. + */ + case panda_file::SourceLang::ECMASCRIPT: { auto kind = profiling::GetProfileKind(inst->GetOpcode()); stm << profiling::GetProfilingKindName(kind) << ' '; @@ -16,6 +31,11 @@ case panda_file::SourceLang::ECMASCRIPT: { p.GetOperandType(1).Dump(stm); break; } + case profiling::ProfilingKind::CALL: { + ecmascript::CallProfile *p = ecmascript::CallProfile::FromBuffer(&ecma_prof[prof_id]); + stm << (int)p->GetCallKind(); + break; + } default: LOG(FATAL, DISASSEMBLER) << "Unknown profile"; break; diff --git a/runtime/profiling/plugin_find_method_in_profile.h b/runtime/profiling/plugin_find_method_in_profile.h index bc8d6b998..473da6e5f 100644 --- a/runtime/profiling/plugin_find_method_in_profile.h +++ b/runtime/profiling/plugin_find_method_in_profile.h @@ -1,3 +1,18 @@ + /** + * 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. + */ + case panda_file::SourceLang::ECMASCRIPT: { auto container = reinterpret_cast(profile); auto item = container->find(method_name); diff --git a/runtime/profiling/plugin_get_profiling_any_type.h b/runtime/profiling/plugin_get_profiling_any_type.h deleted file mode 100644 index 03e277a63..000000000 --- a/runtime/profiling/plugin_get_profiling_any_type.h +++ /dev/null @@ -1,58 +0,0 @@ -case panda::SourceLanguage::ECMASCRIPT: { - auto kind = profiling::GetProfileKind(bc_inst->GetOpcode()); - auto ecma_prof = reinterpret_cast(profile); - panda::ecmascript::ProfilingTypeBits::Type type = panda::ecmascript::ProfilingTypeBits::NONE; - switch (kind) { - case profiling::ProfilingKind::UNARY_ARITH: { - panda::ecmascript::UnaryOperationProfile p(ecma_prof); - type = p.GetOperandType(index).GetType(); - break; - } - case profiling::ProfilingKind::BINARY_ARITH: { - panda::ecmascript::BinaryOperationProfile p(ecma_prof); - type = p.GetOperandType(index).GetType(); - auto other_operand_type = p.GetOperandType(1 - index).GetType(); - if ((type & panda::ecmascript::ProfilingTypeBits::OBJECT) == panda::ecmascript::ProfilingTypeBits::OBJECT && - (other_operand_type & panda::ecmascript::ProfilingTypeBits::OBJECT) == - panda::ecmascript::ProfilingTypeBits::OBJECT) { - *is_type_profiled = true; - return compiler::AnyBaseType::UNDEFINED_TYPE; - } - break; - } - default: - LOG(FATAL, COMMON) << "Unknown profile"; - } - - if (type == panda::ecmascript::ProfilingTypeBits::NONE) { - return compiler::AnyBaseType::UNDEFINED_TYPE; - } - - *is_type_profiled = true; - auto is_int = - (type & panda::ecmascript::ProfilingTypeBits::INTEGER) == panda::ecmascript::ProfilingTypeBits::INTEGER; - // ignore non number types to avoid compiled method destruction on deoptimization - if ((type & panda::ecmascript::ProfilingTypeBits::DOUBLE) == panda::ecmascript::ProfilingTypeBits::DOUBLE) { - *is_integer_seen = is_int; - return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; - } - - if (is_int) { - auto is_obj_seen = - (type & panda::ecmascript::ProfilingTypeBits::OBJECT) == panda::ecmascript::ProfilingTypeBits::OBJECT; - if (is_obj_seen) { - // return number type to avoid compiled method destruction on deoptimization - *is_integer_seen = true; - return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE; - } - - return compiler::AnyBaseType::ECMASCRIPT_INT_TYPE; - } - - switch (type) { - case panda::ecmascript::ProfilingTypeBits::BOOLEAN: - return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE; - default: - return compiler::AnyBaseType::UNDEFINED_TYPE; - } -} break; diff --git a/runtime/profiling/plugin_includes.h b/runtime/profiling/plugin_includes.h index 38932baff..71510a99b 100644 --- a/runtime/profiling/plugin_includes.h +++ b/runtime/profiling/plugin_includes.h @@ -1,2 +1,18 @@ +/** + * 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. + */ + #include "plugins/ecmascript/runtime/ecma_profiling.h" -#include "serializer/serializer.h" \ No newline at end of file +#include "plugins/ecmascript/runtime/js_method.h" +#include "plugins/ecmascript/runtime/ecma_vm.h" diff --git a/runtime/profiling/plugin_includes_disasm.h b/runtime/profiling/plugin_includes_disasm.h new file mode 100644 index 000000000..dfe3f44b4 --- /dev/null +++ b/runtime/profiling/plugin_includes_disasm.h @@ -0,0 +1,17 @@ +/** + * 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. + */ + +#include "plugins/ecmascript/runtime/ecma_profiling.h" +#include "serializer/serializer.h" \ No newline at end of file diff --git a/subproject_sources.gn b/subproject_sources.gn index 5aa6f9978..d903a9a92 100644 --- a/subproject_sources.gn +++ b/subproject_sources.gn @@ -28,6 +28,7 @@ srcs_compiler = [ ] option_yaml_path = "ecmascript_plugin_options.yaml" +compiler_options_yaml_path = "compiler/ecma_compiler.yaml" inst_templates_yaml_path = "compiler/optimizer/ir_builder/ecmascript_inst_templates.yaml" runtime_option_yaml_path = "runtime_options.yaml" @@ -130,6 +131,7 @@ srcs_runtime = [ "runtime/containers/containers_arraylist.cpp", "runtime/containers/containers_private.cpp", "runtime/dump.cpp", + "runtime/ecma_call_profiling_table.cpp", "runtime/ecma_class_linker_extension.cpp", "runtime/ecma_entrypoints.cpp", "runtime/ecma_exceptions.cpp", diff --git a/tests/checked/CMakeLists.txt b/tests/checked/CMakeLists.txt index 29d004025..7bfc660ad 100644 --- a/tests/checked/CMakeLists.txt +++ b/tests/checked/CMakeLists.txt @@ -101,6 +101,9 @@ if (NOT PANDA_TARGET_ARM32) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/recompile_undefined.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/donot_recompile_not_profiled.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/obj_by_index.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining_megamorphic.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining_deoptimize.js SUPPORT_RELEASE true) # there is flaky bug when turning on TSAN if (NOT PANDA_ENABLE_THREAD_SANITIZER) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/acc_after_deopt.js SUPPORT_RELEASE true) diff --git a/tests/checked/ecma_inlining.js b/tests/checked/ecma_inlining.js new file mode 100644 index 000000000..3526aaf15 --- /dev/null +++ b/tests/checked/ecma_inlining.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021-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. + */ + +//! CHECKER Ecma Inlining. Must inline. +//! RUN options: "--no-async-jit --compiler-hotness-threshold=10 --compiler-regex _GLOBAL::test", entry: "_GLOBAL::func_main_0" +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::one,.*DYNAMIC_MONOMORPHIC,SUCCESS/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +//! EVENT_NOT /Deoptimization,_GLOBAL::.*test.*,.*,IFRAME/ +function one() { + return 1; +} + +function test() { + return one(); +} + +var sum = 0; +for (var i = 0; i < 20; i++) { + sum += test(); +} +if (sum != 20) { + throw "Wrong result: " + sum; +} \ No newline at end of file diff --git a/tests/checked/ecma_inlining_deoptimize.js b/tests/checked/ecma_inlining_deoptimize.js new file mode 100644 index 000000000..d959d8637 --- /dev/null +++ b/tests/checked/ecma_inlining_deoptimize.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021-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. + */ + + +//! CHECKER Ecma Inlining. Must deoptimize. +//! RUN options: "--no-async-jit --compiler-hotness-threshold=10 --compiler-regex _GLOBAL::test ", entry: "_GLOBAL::func_main_0" +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::one,.*DYNAMIC_MONOMORPHIC,SUCCESS/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +//! EVENT /Deoptimization,_GLOBAL::.*tes.*,.*,IFRAME/ +function one() { + return 1; +} + +function two() { + return 2; +} + +function test(i) { + var a = one; + if (i >= 10) + a = two; + return a(); +} + +var sum = 0; +for (var i = 0; i < 20; i++) { + sum += test(i); +} +if (sum != 30) { + throw "Wrong result: " + sum; +} \ No newline at end of file diff --git a/tests/checked/ecma_inlining_megamorphic.js b/tests/checked/ecma_inlining_megamorphic.js new file mode 100644 index 000000000..0bf2192b0 --- /dev/null +++ b/tests/checked/ecma_inlining_megamorphic.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021-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. + */ + + +//! CHECKER Ecma Inlining. Do not inline, megamorphic call. +//! RUN options: "--no-async-jit --compiler-hotness-threshold=10 --compiler-regex _GLOBAL::test ", entry: "_GLOBAL::func_main_0" +//! EVENT /Inline,_GLOBAL::test,-,.*,DYNAMIC_MONOMORPHIC,FAIL_MEGAMORPHIC/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ + +function one() { + return 1; +} + +function two() { + return 2; +} + +function test(i) { + var a = one; + if (i % 2 == 0) + a = two; + return a(); +} + +var sum = 0; +for (var i = 0; i < 20; i++) { + sum += test(i); +} +if (sum != 30) { + throw "Wrong result: " + sum; +} \ No newline at end of file -- Gitee