From 2dd517b4524bdd7a600b55de6d1ff4991752e8dc Mon Sep 17 00:00:00 2001 From: baf9884bc6e6 Date: Fri, 16 Dec 2022 15:16:05 +0300 Subject: [PATCH] [Compiler] Support method profiling for NewobjDynrange intrinsic Supported profiling for NewobjDynrange intrinsic and different expansion of that intrinsics depending on profiling info. For monomorphic call sites expanded IR could be simplified by omitting slow path, object allocation or object resolution depending on target method info. Emitted constructor call has the same PC as the original intrinsic thus it could be inlined by the corresponding pass. Change-Id: I62843fb25c0c48cd1c6bcb8b3f56208509b2008d Signed-off-by: baf9884bc6e6 --- compiler/ecma_compiler.yaml | 12 ++ .../ecmascript_compiler_interface.h | 23 +++ compiler/optimizer/ecma_pipeline.cpp | 2 +- .../optimizer/optimizations/ecma_inlining.cpp | 5 + .../optimizations/expand_intrinsics.cpp | 176 +++++++++++++++--- .../optimizations/expand_intrinsics.h | 49 ++++- isa/isa.yaml | 3 +- .../compiler/ecmascript_runtime_interface.cpp | 31 +++ .../compiler/ecmascript_runtime_interface.h | 2 + runtime/interpreter/ecma-interpreter-inl.h | 5 +- runtime/interpreter/js_decode_call_instr.h | 14 +- tests/checked/CMakeLists.txt | 2 + tests/checked/ecma_recursive_ctor_inlining.js | 35 ++++ tests/checked/new_obj.js | 19 +- tests/checked/new_obj_profiling.js | 94 ++++++++++ 15 files changed, 436 insertions(+), 36 deletions(-) create mode 100644 tests/checked/ecma_recursive_ctor_inlining.js create mode 100644 tests/checked/new_obj_profiling.js diff --git a/compiler/ecma_compiler.yaml b/compiler/ecma_compiler.yaml index a7f4e3113..dac6af4c6 100644 --- a/compiler/ecma_compiler.yaml +++ b/compiler/ecma_compiler.yaml @@ -17,4 +17,16 @@ options: default: 4096 description: Maximum number of the IR instructions to be inlined, including instructions of the current graph. recommended_values: [50,8000] + tags: [perf] + +- name: compiler-ecma-expand-intrinsics + type: bool + default: true + description: Expand intrinsics into several IR instructions when applicable + tags: [perf] + +- name: compiler-ecma-newobj-profiling + type: bool + default: true + description: Use profiling data when expanding newobj intrinsic tags: [perf] \ No newline at end of file diff --git a/compiler/ecmascript_extensions/ecmascript_compiler_interface.h b/compiler/ecmascript_extensions/ecmascript_compiler_interface.h index 9122b4313..4ea1b0e57 100644 --- a/compiler/ecmascript_extensions/ecmascript_compiler_interface.h +++ b/compiler/ecmascript_extensions/ecmascript_compiler_interface.h @@ -16,4 +16,27 @@ #ifndef PANDA_COMPILER_ECMASCRIPT_COMPILER_INTERFACE_H #define PANDA_COMPILER_ECMASCRIPT_COMPILER_INTERFACE_H +struct NewObjDynInfo { + enum class AllocatorType { + DEFAULT, // try to allocate object with fallback into slow path + ALLOC_OBJ, // allocate regular object + UNDEFINED, // use undefined value as allocation result + SLOW_PATH // slow path allocation is required + }; + + enum class ResolverType { + NONE, // don't need a resolver at all - used with slow path allocation + RESOLVE, // result should be resoled based on returned value and allocated object + USE_CTOR_RESULT // always use value returned from a constructor + }; + + AllocatorType allocation; + ResolverType resolution; +}; + +virtual NewObjDynInfo GetNewObjDynInfo([[maybe_unused]] uintptr_t method) const +{ + return {NewObjDynInfo::AllocatorType::DEFAULT, NewObjDynInfo::ResolverType::NONE}; +} + #endif // PANDA_COMPILER_ECMASCRIPT_COMPILER_INTERFACE_H \ No newline at end of file diff --git a/compiler/optimizer/ecma_pipeline.cpp b/compiler/optimizer/ecma_pipeline.cpp index 69ad22d01..276015b0a 100644 --- a/compiler/optimizer/ecma_pipeline.cpp +++ b/compiler/optimizer/ecma_pipeline.cpp @@ -55,6 +55,7 @@ bool EcmaPipeline::RunOptimizations() auto graph = GetGraph(); ASSERT(!graph->IsOsrMode() && "We don't support OSR in JS yet"); + graph->RunPass(); // TODO(schernykh): Find way to inline in AOT and OSR mode if (!graph->IsAotMode() && !graph->IsOsrMode()) { graph->RunPass(); @@ -64,7 +65,6 @@ bool EcmaPipeline::RunOptimizations() LOG(WARNING, COMPILER) << "Compiler detected incorrect monitor policy"; return false; } - graph->RunPass(); graph->RunPass(); graph->RunPass(); graph->RunPass(); diff --git a/compiler/optimizer/optimizations/ecma_inlining.cpp b/compiler/optimizer/optimizations/ecma_inlining.cpp index 2c7914051..e7cb26c0a 100644 --- a/compiler/optimizer/optimizations/ecma_inlining.cpp +++ b/compiler/optimizer/optimizations/ecma_inlining.cpp @@ -14,8 +14,10 @@ */ #include "ecma_inlining.h" +#include "expand_intrinsics.h" #include "events/events.h" #include "optimizer/ir/graph.h" +#include "optimizer/analysis/loop_analyzer.h" #include "optimizer/optimizations/cleanup.h" #include "optimizer/optimizations/branch_elimination.h" #include "optimizer/optimizations/peepholes.h" @@ -32,6 +34,9 @@ EcmaInlining::EcmaInlining(Graph *graph, uint32_t instructions_count, uint32_t i void EcmaInlining::RunOptimizations() const { + if (GetGraph()->RunPass()) { + GetGraph()->RunPass(); + } if (GetGraph()->RunPass()) { GetGraph()->RunPass(); } diff --git a/compiler/optimizer/optimizations/expand_intrinsics.cpp b/compiler/optimizer/optimizations/expand_intrinsics.cpp index 43bbb927d..ff06a5ea5 100644 --- a/compiler/optimizer/optimizations/expand_intrinsics.cpp +++ b/compiler/optimizer/optimizations/expand_intrinsics.cpp @@ -1,6 +1,24 @@ -#include "plugins/ecmascript/compiler/optimizer/optimizations/expand_intrinsics.h" +/* + * 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 "expand_intrinsics.h" #include "plugins/ecmascript/runtime/js_tagged_value.h" #include "optimizer/ir/basicblock.h" +#include "optimizer/analysis/dominators_tree.h" +#include "optimizer/analysis/loop_analyzer.h" +#include "runtime/include/coretypes/tagged_value.h" namespace panda::compiler { @@ -33,6 +51,18 @@ bool ExpandIntrinsics::Expand(IntrinsicInst *inst) return false; } +void ExpandIntrinsics::InstAppender::Append(std::initializer_list instructions) +{ + for (auto inst : instructions) { + if (prev_ == nullptr) { + block_->AppendInst(inst); + } else { + block_->InsertAfter(inst, prev_); + } + prev_ = inst; + } +} + Inst *ExpandIntrinsics::CreateAllocDynObjIntrinsic(Inst *ctor, SaveStateInst *ss, uint32_t pc) { auto alloc_obj = @@ -66,11 +96,8 @@ Inst *ExpandIntrinsics::NewObjCreateConstructorCall(Inst *orig_call, Inst *alloc return call_ctor; } -Inst *ExpandIntrinsics::NewObjFillCurrBlock(BasicBlock *curr_block, Inst *orig_alloc, uint32_t pc) +void ExpandIntrinsics::NewObjFillCurrBlock(InstAppender *appender, Inst *alloc_obj, uint32_t pc) { - auto ctor_inst = orig_alloc->GetInput(1).GetInst(); - - auto alloc_obj = CreateAllocDynObjIntrinsic(ctor_inst, orig_alloc->GetSaveState(), pc); auto cmp = GetGraph()->CreateInstCompareAnyType(DataType::BOOL, pc); cmp->SetAnyType(AnyBaseType::ECMASCRIPT_NULL_TYPE); cmp->SetInput(0, alloc_obj); @@ -79,11 +106,10 @@ Inst *ExpandIntrinsics::NewObjFillCurrBlock(BasicBlock *curr_block, Inst *orig_a ifimm->SetOperandsType(DataType::BOOL); ifimm->SetInput(0, cmp); - curr_block->AppendInsts({alloc_obj, cmp, ifimm}); - return alloc_obj; + appender->Append({cmp, ifimm}); } -void ExpandIntrinsics::NewObjFillSlowPathBlock(BasicBlock *slow_path, Inst *orig_alloc) +void ExpandIntrinsics::NewObjFillSlowPathBlock(InstAppender *appender, Inst *orig_alloc) { auto curr_block = orig_alloc->GetBasicBlock(); auto ss = orig_alloc->CastToIntrinsic()->GetSaveState(); @@ -94,11 +120,11 @@ void ExpandIntrinsics::NewObjFillSlowPathBlock(BasicBlock *slow_path, Inst *orig } orig_alloc->SetSaveState(ss_copy); // move original intrinsic and its SaveState into a slow path - curr_block->EraseInst(orig_alloc); - slow_path->AppendInsts({ss_copy, orig_alloc}); + curr_block->EraseInst(orig_alloc, true); + appender->Append({ss_copy, orig_alloc}); } -Inst *ExpandIntrinsics::NewObjFillCallConstructorBlock(BasicBlock *block, Inst *orig_alloc, Inst *alloc_obj, +Inst *ExpandIntrinsics::NewObjFillCallConstructorBlock(InstAppender *appender, Inst *orig_alloc, Inst *alloc_obj, uint32_t pc) { auto ss = orig_alloc->GetSaveState(); @@ -112,13 +138,23 @@ Inst *ExpandIntrinsics::NewObjFillCallConstructorBlock(BasicBlock *block, Inst * ss_copy->SetVirtualRegister(ss->GetInputsCount(), vreg); auto call_ctor = NewObjCreateConstructorCall(orig_alloc, alloc_obj, ss_copy, pc); - auto ss_copy_2 = static_cast(ss_copy->Clone(GetGraph())); - for (size_t input_idx = 0; input_idx < ss_copy->GetInputsCount(); ++input_idx) { - ss_copy_2->AppendInput(ss_copy->GetInput(input_idx)); - ss_copy_2->SetVirtualRegister(input_idx, ss_copy->GetVirtualRegister(input_idx)); + appender->Append({ss_copy, call_ctor}); + + return call_ctor; +} + +Inst *ExpandIntrinsics::NewObjResolveCtorResult(InstAppender *appender, Inst *orig_alloc, Inst *alloc_obj, + Inst *call_ctor, uint32_t pc) +{ + auto ss = call_ctor->GetSaveState(); + auto ss_copy_2 = static_cast(ss->Clone(GetGraph())); + for (size_t input_idx = 0; input_idx < ss->GetInputsCount(); ++input_idx) { + ss_copy_2->AppendInput(ss->GetInput(input_idx)); + ss_copy_2->SetVirtualRegister(input_idx, ss->GetVirtualRegister(input_idx)); } ss_copy_2->AppendInput(call_ctor); - ss_copy_2->SetVirtualRegister(ss_copy->GetInputsCount(), vreg); + VirtualRegister vreg {VirtualRegister::BRIDGE, false}; + ss_copy_2->SetVirtualRegister(ss->GetInputsCount(), vreg); auto resolve_result = GetGraph()->CreateInstIntrinsic( DataType::ANY, pc, RuntimeInterface::IntrinsicId::INTRINSIC_RESOLVE_ALLOC_RESULT); @@ -133,7 +169,7 @@ Inst *ExpandIntrinsics::NewObjFillCallConstructorBlock(BasicBlock *block, Inst * resolve_result->AppendInput(ss_copy_2); resolve_result->AddInputType(DataType::NO_TYPE); - block->AppendInsts({ss_copy, call_ctor, ss_copy_2, resolve_result}); + appender->Append({ss_copy_2, resolve_result}); return resolve_result; } @@ -160,6 +196,8 @@ Inst *ExpandIntrinsics::NewObjFillCallConstructorBlock(BasicBlock *block, Inst * * * Such expansion improves performance of object allocation by using specialized * stubs outperforming generic runtime code and allows inlining of a constructor. + * + * Depending on profiling info shape of the emitted IR may change by pruning one of the branches. */ bool ExpandIntrinsics::ExpandNewObjDynRange(IntrinsicInst *inst) { @@ -170,6 +208,43 @@ bool ExpandIntrinsics::ExpandNewObjDynRange(IntrinsicInst *inst) return false; } + auto runtime = GetGraph()->GetRuntime(); + auto kind = panda::profiling::CallKind::UNKNOWN; + if (OPTIONS.IsCompilerEcmaNewobjProfiling()) { + functions_.clear(); + kind = runtime->GetCallProfile(GetGraph()->GetMethod(), inst->GetPc(), &functions_, GetGraph()->IsAotMode()); + } + RuntimeInterface::NewObjDynInfo expansion_info {RuntimeInterface::NewObjDynInfo::AllocatorType::DEFAULT, + RuntimeInterface::NewObjDynInfo::ResolverType::RESOLVE}; + if (kind == panda::profiling::CallKind::MONOMORPHIC) { + expansion_info = runtime->GetNewObjDynInfo(functions_.front()); + } + + switch (expansion_info.allocation) { + // allocation always require slow path, so it doesn't make sense to expand the intrinsic + case RuntimeInterface::NewObjDynInfo::AllocatorType::SLOW_PATH: + assert(expansion_info.resolution == RuntimeInterface::NewObjDynInfo::ResolverType::RESOLVE); + return false; + // emit simplified fast path, but only for monomorphic call sites + case RuntimeInterface::NewObjDynInfo::AllocatorType::UNDEFINED: + case RuntimeInterface::NewObjDynInfo::AllocatorType::ALLOC_OBJ: + if (kind == panda::profiling::CallKind::MONOMORPHIC) { + ExpandNewObjDynFastPath(inst, expansion_info, functions_.front()); + return true; + } else { + ExpandNewObjDynDefault(inst); + return true; + } + default: + ExpandNewObjDynDefault(inst); + return true; + } + + return false; +} + +void ExpandIntrinsics::ExpandNewObjDynDefault(Inst *inst) +{ auto curr_block = inst->GetBasicBlock(); auto succ_block = curr_block->SplitBlockAfterInstruction(inst, false); auto pc = inst->GetPc(); @@ -177,9 +252,17 @@ bool ExpandIntrinsics::ExpandNewObjDynRange(IntrinsicInst *inst) auto slow_path_block = GetGraph()->CreateEmptyBlock(curr_block); auto call_ctor_block = GetGraph()->CreateEmptyBlock(curr_block); - auto alloc_obj = NewObjFillCurrBlock(curr_block, inst, pc); - NewObjFillSlowPathBlock(slow_path_block, inst); - auto ctor_res = NewObjFillCallConstructorBlock(call_ctor_block, inst, alloc_obj, pc); + InstAppender curr_block_appender {curr_block}; + auto alloc_obj = CreateAllocDynObjIntrinsic(inst->GetInput(1).GetInst(), inst->GetSaveState(), pc); + curr_block_appender.Append({alloc_obj}); + NewObjFillCurrBlock(&curr_block_appender, alloc_obj, pc); + + InstAppender slow_path_block_appender {slow_path_block}; + NewObjFillSlowPathBlock(&slow_path_block_appender, inst); + + InstAppender call_ctor_block_appender {call_ctor_block}; + auto ctor = NewObjFillCallConstructorBlock(&call_ctor_block_appender, inst, alloc_obj, pc); + auto ctor_res = NewObjResolveCtorResult(&call_ctor_block_appender, inst, alloc_obj, ctor, pc); curr_block->AddSucc(slow_path_block); curr_block->AddSucc(call_ctor_block); @@ -191,7 +274,58 @@ bool ExpandIntrinsics::ExpandNewObjDynRange(IntrinsicInst *inst) phi->AppendInput(inst); phi->AppendInput(ctor_res); succ_block->AppendPhi(phi); - return true; + + GetGraph()->InvalidateAnalysis(); + GetGraph()->InvalidateAnalysis(); + InvalidateBlocksOrderAnalyzes(GetGraph()); +} + +void ExpandIntrinsics::ExpandNewObjDynFastPath(Inst *inst, RuntimeInterface::NewObjDynInfo info, uintptr_t target) +{ + auto curr_block = inst->GetBasicBlock(); + auto pc = inst->GetPc(); + + BuildGuard(inst, target); + + Inst *alloc_obj; + InstAppender appender {curr_block, inst}; + if (info.allocation == RuntimeInterface::NewObjDynInfo::AllocatorType::ALLOC_OBJ) { + alloc_obj = CreateAllocDynObjIntrinsic(inst->GetInput(1).GetInst(), inst->GetSaveState(), pc); + appender.Append({alloc_obj}); + } else { + ASSERT(info.allocation == RuntimeInterface::NewObjDynInfo::AllocatorType::UNDEFINED); + alloc_obj = GetGraph()->FindOrCreateConstant(DataType::Any(panda::coretypes::TaggedValue::VALUE_UNDEFINED)); + } + + auto ctor_res = NewObjFillCallConstructorBlock(&appender, inst, alloc_obj, pc); + + Inst *res = ctor_res; + + bool need_resolver = info.resolution == RuntimeInterface::NewObjDynInfo::ResolverType::RESOLVE; + if (need_resolver) { + res = NewObjResolveCtorResult(&appender, inst, alloc_obj, ctor_res, pc); + } + inst->ReplaceUsers(res); + curr_block->RemoveInst(inst); +} + +void ExpandIntrinsics::BuildGuard(Inst *inst, uintptr_t target) +{ + auto pc = inst->GetPc(); + auto load_function = GetGraph()->CreateInstFunctionImmediate(DataType::ANY, pc, target); + auto cmp_inst = GetGraph()->CreateInstCompare(DataType::BOOL, pc); + auto deopt_inst = GetGraph()->CreateInstDeoptimizeIf(DataType::BOOL, pc); + cmp_inst->SetCc(ConditionCode::CC_NE); + cmp_inst->SetInput(0, load_function); + cmp_inst->SetInput(1, inst->GetInput(1).GetInst()); + cmp_inst->SetOperandsType(DataType::ANY); + deopt_inst->SetInput(0, cmp_inst); + deopt_inst->SetInput(1, inst->GetSaveState()); + deopt_inst->SetDeoptimizeType(DeoptimizeType::INLINE_DYN); + + inst->InsertBefore(load_function); + inst->InsertBefore(cmp_inst); + inst->InsertBefore(deopt_inst); } } // namespace panda::compiler \ No newline at end of file diff --git a/compiler/optimizer/optimizations/expand_intrinsics.h b/compiler/optimizer/optimizations/expand_intrinsics.h index 80b390682..1430b6039 100644 --- a/compiler/optimizer/optimizations/expand_intrinsics.h +++ b/compiler/optimizer/optimizations/expand_intrinsics.h @@ -1,3 +1,18 @@ +/* + * 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 _COMPILER_OPTIMIZER_OPTIMIZATIONS_EXPAND_INTRINSICS_H #define _COMPILER_OPTIMIZER_OPTIMIZATIONS_EXPAND_INTRINSICS_H @@ -10,7 +25,7 @@ namespace panda::compiler { // Pass aimed to expand various intrinsics into some IR class ExpandIntrinsics : public Optimization { public: - explicit ExpandIntrinsics(Graph *graph) : Optimization(graph) {} + explicit ExpandIntrinsics(Graph *graph) : Optimization(graph), functions_(graph->GetAllocator()->Adapter()) {} const char *GetPassName() const override { @@ -19,14 +34,40 @@ public: bool RunImpl() override; + bool IsEnable() const override + { + return OPTIONS.IsCompilerEcmaExpandIntrinsics(); + } + private: + class InstAppender { + public: + explicit InstAppender(BasicBlock *block, Inst *insert_after = nullptr) : block_(block), prev_(insert_after) {} + ~InstAppender() = default; + DEFAULT_MOVE_SEMANTIC(InstAppender); + NO_COPY_SEMANTIC(InstAppender); + + void Append(std::initializer_list instruction); + + private: + BasicBlock *block_; + Inst *prev_ {nullptr}; + }; + bool Expand(IntrinsicInst *inst); bool ExpandNewObjDynRange(IntrinsicInst *inst); + void ExpandNewObjDynDefault(Inst *inst); + void ExpandNewObjDynFastPath(Inst *inst, RuntimeInterface::NewObjDynInfo info, uintptr_t target); Inst *CreateAllocDynObjIntrinsic(Inst *ctor, SaveStateInst *ss, uint32_t pc); Inst *NewObjCreateConstructorCall(Inst *orig_call, Inst *alloc_obj, SaveStateInst *ss, uint32_t pc); - Inst *NewObjFillCallConstructorBlock(BasicBlock *block, Inst *orig_alloc, Inst *alloc_obj, uint32_t pc); - void NewObjFillSlowPathBlock(BasicBlock *slow_path, Inst *orig_alloc); - Inst *NewObjFillCurrBlock(BasicBlock *curr_block, Inst *orig_alloc, uint32_t pc); + Inst *NewObjFillCallConstructorBlock(InstAppender *appender, Inst *orig_alloc, Inst *alloc_obj, uint32_t pc); + void NewObjFillSlowPathBlock(InstAppender *appender, Inst *orig_alloc); + void NewObjFillCurrBlock(InstAppender *appender, Inst *alloc_obj, uint32_t pc); + Inst *NewObjResolveCtorResult(InstAppender *appender, Inst *orig_alloc, Inst *alloc_obj, Inst *call_ctor, + uint32_t pc); + void BuildGuard(Inst *inst, uintptr_t target); + + ArenaVector functions_; }; } // namespace panda::compiler diff --git a/isa/isa.yaml b/isa/isa.yaml index c1fac40b4..67f7195db 100644 --- a/isa/isa.yaml +++ b/isa/isa.yaml @@ -825,9 +825,10 @@ groups: - sig: ecma.newobjdynrange 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, not_compilable] intrinsic_name: INTRINSIC_NEWOBJ_DYNRANGE + profile: Call - sig: ecma.supercall imm, v:in:top acc: inout:top diff --git a/runtime/compiler/ecmascript_runtime_interface.cpp b/runtime/compiler/ecmascript_runtime_interface.cpp index 507d1096a..992bd30e7 100644 --- a/runtime/compiler/ecmascript_runtime_interface.cpp +++ b/runtime/compiler/ecmascript_runtime_interface.cpp @@ -231,4 +231,35 @@ compiler::AnyBaseType EcmaRuntimeInterface::GetProfilingAnyType(RuntimeInterface return compiler::AnyBaseType::UNDEFINED_TYPE; } +RuntimeInterface::NewObjDynInfo EcmaRuntimeInterface::GetNewObjDynInfo(uintptr_t ctor) const +{ + static constexpr NewObjDynInfo slow_path = {NewObjDynInfo::AllocatorType::SLOW_PATH, + NewObjDynInfo::ResolverType::RESOLVE}; + [[maybe_unused]] ScopedMutatorLock lock; + JSObject *ctor_ptr = *reinterpret_cast(ctor); + if (ctor_ptr == nullptr || !ctor_ptr->IsJSFunction()) { + return slow_path; + } + auto js_fun = static_cast(ctor_ptr); + if (!js_fun->IsConstructor()) { + return slow_path; + } + + auto method = js_fun->GetMethod(); + + if (method->IsNative()) { + if (js_fun->IsBuiltinConstructor()) { + return {NewObjDynInfo::AllocatorType::UNDEFINED, NewObjDynInfo::ResolverType::USE_CTOR_RESULT}; + } + return slow_path; + } + if (!js_fun->IsBase()) { + if (js_fun->IsDerivedConstructor()) { + return {NewObjDynInfo::AllocatorType::UNDEFINED, NewObjDynInfo::ResolverType::RESOLVE}; + } + return slow_path; + } + return {NewObjDynInfo::AllocatorType::ALLOC_OBJ, NewObjDynInfo::ResolverType::RESOLVE}; +} + } // namespace panda::ecmascript diff --git a/runtime/compiler/ecmascript_runtime_interface.h b/runtime/compiler/ecmascript_runtime_interface.h index 99b57f47b..196462ade 100644 --- a/runtime/compiler/ecmascript_runtime_interface.h +++ b/runtime/compiler/ecmascript_runtime_interface.h @@ -102,6 +102,8 @@ public: const BytecodeInstruction *bc_inst, unsigned index, bool *is_integer_seen, bool *is_type_profiled) override; + NewObjDynInfo GetNewObjDynInfo(uintptr_t ctor) const override; + private: const EcmaVM *ecma_vm_ {nullptr}; panda::ecmascript::EcmaProfileContainer profile_; diff --git a/runtime/interpreter/ecma-interpreter-inl.h b/runtime/interpreter/ecma-interpreter-inl.h index 797270822..47bb2dd2e 100644 --- a/runtime/interpreter/ecma-interpreter-inl.h +++ b/runtime/interpreter/ecma-interpreter-inl.h @@ -387,7 +387,7 @@ public: template ALWAYS_INLINE void HandleEcmaNewobjdynrange() { - auto constexpr OPCODE = BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8; + auto constexpr OPCODE = BytecodeInstruction::Opcode::ECMA_NEWOBJDYNRANGE_PREF_IMM16_V8_PROF16; auto first_arg_reg_idx = this->GetInst().template GetVReg(); auto num_range_args = this->GetInst().template GetImm(); @@ -409,6 +409,9 @@ public: if (LIKELY(ctor.IsJSFunction() && ctor.IsConstructor())) { auto ctor_func = JSFunction::Cast(ctor.GetTaggedObject()); auto method = ctor_func->GetMethod(); + + UPDATE_CALL_PROFILE(ctor_func); + if (LIKELY(!method->IsNative() && (ctor_func->IsBase() || ctor_func->IsDerivedConstructor()))) { JSTaggedValue this_obj; if (ctor_func->IsBase()) { diff --git a/runtime/interpreter/js_decode_call_instr.h b/runtime/interpreter/js_decode_call_instr.h index 801e0143f..511e64e1f 100644 --- a/runtime/interpreter/js_decode_call_instr.h +++ b/runtime/interpreter/js_decode_call_instr.h @@ -52,8 +52,9 @@ ALWAYS_INLINE inline uint32_t JSGetNumberActualArgsDyn([[maybe_unused]] Bytecode auto imm = inst.GetImm(); ASSERT(imm >= 1); // magic, 'this' is counted in range, 'func' - not return JSMethodArgs::FIRST_ARG_IDX + (imm - 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()); auto imm = inst.GetImm(); ASSERT(imm >= 2); // magic, 'func' and 'new_target' are counted in range return JSMethodArgs::FIRST_ARG_IDX + (imm - 2); @@ -90,7 +91,7 @@ ALWAYS_INLINE inline uint16_t JSGetCalleRangeStartDyn([[maybe_unused]] BytecodeI if constexpr (op == R::template Get() || op == R::template Get() || - op == R::template Get()) { + op == R::template Get()) { return inst.GetVReg(); } @@ -146,7 +147,7 @@ ALWAYS_INLINE inline static void JSCopyArgumets(JSThread *thread, Frame *prev_fr ASSERT(num_actual_args >= JSMethodArgs::FIRST_ARG_IDX); new_frame->GetVReg(num_vregs + JSMethodArgs::FUNC_IDX).SetValue(raw_fn_object); - if constexpr (op != R::template Get()) { + if constexpr (op != R::template Get()) { new_frame->GetVReg(num_vregs + JSMethodArgs::NEW_TARGET_IDX).SetValue(JSTaggedValue::VALUE_UNDEFINED); } @@ -220,8 +221,9 @@ ALWAYS_INLINE inline static void JSCopyArgumets(JSThread *thread, Frame *prev_fr 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 + 2 + 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()); uint16_t prev_v0 = JSGetCalleRangeStartDyn(prev_inst); new_frame->GetVReg(num_vregs + JSMethodArgs::NEW_TARGET_IDX) = prev_frame->GetVReg(prev_v0 + 1); for (uint16_t i = 0; i < (num_actual_args - JSMethodArgs::FIRST_ARG_IDX); ++i) { diff --git a/tests/checked/CMakeLists.txt b/tests/checked/CMakeLists.txt index 144a0ce9b..de91ea790 100644 --- a/tests/checked/CMakeLists.txt +++ b/tests/checked/CMakeLists.txt @@ -109,6 +109,8 @@ if (NOT PANDA_TARGET_ARM32) if (NOT PANDA_ENABLE_THREAD_SANITIZER) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/acc_after_deopt.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/new_obj.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/new_obj_profiling.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_recursive_ctor_inlining.js SUPPORT_RELEASE true) endif() endif() endif() diff --git a/tests/checked/ecma_recursive_ctor_inlining.js b/tests/checked/ecma_recursive_ctor_inlining.js new file mode 100644 index 000000000..9b21ffedc --- /dev/null +++ b/tests/checked/ecma_recursive_ctor_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 Inlined constructors from expanded intrinsics recursively +//! RUN options: "--no-async-jit --compiler-hotness-threshold=10 --compiler-regex _GLOBAL::test", entry: "_GLOBAL::func_main_0" +//! EVENT /Inline,_GLOBAL::outer_ctor,_GLOBAL::inner_ctor,.*DYNAMIC_MONOMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::outer_ctor,.*DYNAMIC_MONOMORPHIC,SUCCESS/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +function inner_ctor() { + this.field = 42 +} + +function outer_ctor() { + this.field = new inner_ctor() +} + +function test() { + return new outer_ctor() +} + +for (var i = 0; i < 10000; i++) { + test() +} \ No newline at end of file diff --git a/tests/checked/new_obj.js b/tests/checked/new_obj.js index 828fc40e8..239926223 100644 --- a/tests/checked/new_obj.js +++ b/tests/checked/new_obj.js @@ -1,3 +1,18 @@ +/* + * 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. + */ + function custom_ctor(a, b) { this.a = a; this.b = b; @@ -102,7 +117,7 @@ function alloc_8() { } //! CHECKER Expand new object dyn range -//! RUN options: "--compiler-hotness-threshold=0 --compiler-regex _GLOBAL::alloc_.*", entry: "_GLOBAL::func_main_0" +//! RUN options: "--compiler-hotness-threshold=0 --compiler-ecma-newobj-profiling=false --compiler-regex _GLOBAL::alloc_.*", entry: "_GLOBAL::func_main_0" //! METHOD "alloc_1" //! PASS_AFTER "IrBuilder" //! INST_NOT /Intrinsic.AllocDynObject.*/ @@ -145,7 +160,7 @@ function alloc_8() { //! INST /Intrinsic.AllocDynObject.*/ //! CHECKER Expand new object dyn range in AOT mode -//! RUN_PAOC options: "--compiler-regex '_GLOBAL::alloc_.*'" +//! RUN_PAOC options: " --compiler-ecma-newobj-profiling=false --compiler-regex '_GLOBAL::alloc_.*'" //! METHOD "alloc_1" //! PASS_AFTER "IrBuilder" //! INST_NOT /Intrinsic.AllocDynObject.*/ diff --git a/tests/checked/new_obj_profiling.js b/tests/checked/new_obj_profiling.js new file mode 100644 index 000000000..e279d4b15 --- /dev/null +++ b/tests/checked/new_obj_profiling.js @@ -0,0 +1,94 @@ +/* + * 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. + */ + +function simple_ctor0() { +} + +function simple_ctor1() { +} + +function simple_ctor2() { +} + +function bimorphic_call_site(ctor_fun) { + return new ctor_fun() +} + +function megamorphic_call_site(ctor_fun) { + return new ctor_fun() +} + +function monomorphic_call_site_simple_ctor(ctor_fun) { + return new ctor_fun() +} + +function monomorphic_call_site_builtin_ctor() { + return new Number(42) +} + +class BaseClass { +} + +class DerivedClass extends BaseClass { +} + +function monomorphic_call_site_derived_ctor() { + return new DerivedClass() +} + +//! CHECKER Expand new object dyn range using profiling info +//! RUN options: "--compiler-hotness-threshold=10 --compiler-ecma-newobj-profiling=true --compiler-regex _GLOBAL::.*_call_site.*", entry: "_GLOBAL::func_main_0" +//! METHOD "bimorphic_call_site" +//! PASS_AFTER "Codegen" +//! INST /Intrinsic.AllocDynObject.*/ +//! INST /Intrinsic.ResolveAllocResult.*/ +//! INST /Intrinsic.NewobjDynrangeHandled*/ +//! METHOD "megamorphic_call_site" +//! PASS_AFTER "Codegen" +//! INST /Intrinsic.AllocDynObject.*/ +//! INST /Intrinsic.ResolveAllocResult.*/ +//! INST /Intrinsic.NewobjDynrangeHandled*/ +//! METHOD "monomorphic_call_site_simple_ctor" +//! PASS_AFTER "Codegen" +//! INST /Intrinsic.AllocDynObject.*/ +//! INST /Intrinsic.ResolveAllocResult.*/ +//! INST_NOT /Intrinsic.NewobjDynrangeHandled*/ +//! METHOD "monomorphic_call_site_derived_ctor" +//! PASS_AFTER "Codegen" +//! INST_NOT /Intrinsic.AllocDynObject.*/ +//! INST /Intrinsic.ResolveAllocResult.*/ +//! INST_NOT /Intrinsic.NewobjDynrangeHandled*/ +//! METHOD "monomorphic_call_site_builtin_ctor" +//! PASS_AFTER "Codegen" +//! INST_NOT /Intrinsic.AllocDynObject.*/ +//! INST_NOT /Intrinsic.ResolveAllocResult.*/ +//! INST_NOT /Intrinsic.NewobjDynrangeHandled*/ +//! EVENT /Deoptimization,.*monomorphic_call_site_simple_ctor.*,.*,IFRAME/ +for (var i = 0; i < 100000; ++i) { + bimorphic_call_site(simple_ctor0) + bimorphic_call_site(simple_ctor1) + + megamorphic_call_site(simple_ctor0) + megamorphic_call_site(simple_ctor1) + megamorphic_call_site(simple_ctor2) + + monomorphic_call_site_simple_ctor(simple_ctor0) + + monomorphic_call_site_builtin_ctor() + + monomorphic_call_site_derived_ctor() +} + +monomorphic_call_site_simple_ctor(simple_ctor1) \ No newline at end of file -- Gitee