diff --git a/compiler/ecma_compiler.yaml b/compiler/ecma_compiler.yaml index a7f4e3113fa079f4604edd871aeeca0a480204a7..dac6af4c6874bbcc4b73d8b3702f901a43553b33 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 9122b43130d64c7c5b39d6e7a9302bfbbe770af1..4ea1b0e57a75d40672729afef687b2171645d848 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 69ad22d01019bc826dc41fb2f1a9d8727c484ee0..276015b0a90151db83c4d13b05582bf45a16ba85 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 2c7914051682fbf88d53c9e25334f22537085341..e7cb26c0a99e13de89c81023dd60011c58223ec2 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 43bbb927d774aef6440f879bb2f5191a6db1c59d..ff06a5ea5902956b0f7365d57b36590485bfec5a 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 80b3906824ea2d9f3fdf433f16835023fc471d51..1430b60396e103b6222bb05a8b5df5f9d6f0e90d 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 c1fac40b4f3b0cb15eac6270e0c3a31eb3e499c6..67f7195db3507a139d9bfeb2eff7fb577850e970 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 507d1096a1442e841560aaedd9498141ac2b9821..992bd30e729573eb912119387f480e90138b0002 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 99b57f47b2ba4b20be020216711622af276e46e0..196462ade40da698fe9663953ad2b8756c7a56c3 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 797270822a44b51b511bda7a5e023439c127c94c..47bb2dd2e143ef0b3be60e66c1b90571c5dae44d 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 801e0143f8ef9736484dd78b7e4db85169573c1d..511e64e1f8e4ebded96f56d34ff13529d9e6f7fc 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 144a0ce9b31cce0086d2027dc9ce42db7db85d50..de91ea790bb35db9b163e21f34f88f703b239918 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 0000000000000000000000000000000000000000..9b21ffedc221d1e74de57c72e5c6200626a90854 --- /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 828fc40e81e6844e683e844b9ebef8ef7aa76018..239926223e1dfd19e5f8569a2c9edd064064e657 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 0000000000000000000000000000000000000000..e279d4b154f8ecb1968971cf4276dcd8af5c8efc --- /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