From beb3417be995740e89967ef6b299c5f6c66ccf13 Mon Sep 17 00:00:00 2001 From: Sergey Chernykh Date: Mon, 15 May 2023 13:36:29 +0300 Subject: [PATCH] implement ecma polymorphic inlining Signed-off-by: Sergey Chernykh --- .../intrinsics_type_resolving_ecmascript.cpp | 38 +++- .../optimizer/optimizations/ecma_inlining.cpp | 215 ++++++++++++++---- .../optimizer/optimizations/ecma_inlining.h | 12 +- isa/isa.yaml | 2 +- .../compiler/ecmascript_runtime_interface.cpp | 93 ++++++-- .../compiler/ecmascript_runtime_interface.h | 14 +- runtime/ecma_profiling.h | 70 ++++-- runtime/interpreter/fast_runtime_stub-inl.h | 2 +- tests/checked/CMakeLists.txt | 8 +- tests/checked/ecma_inlining_megamorphic.js | 47 ++-- ...lining.js => ecma_inlining_monomorphic.js} | 2 +- tests/checked/ecma_inlining_polymorphic.js | 61 +++++ tests/checked/ecma_inlining_polymorphic_2.js | 64 ++++++ 13 files changed, 514 insertions(+), 114 deletions(-) rename tests/checked/{ecma_inlining.js => ecma_inlining_monomorphic.js} (96%) create mode 100644 tests/checked/ecma_inlining_polymorphic.js create mode 100644 tests/checked/ecma_inlining_polymorphic_2.js diff --git a/compiler/intrinsics_type_resolving_ecmascript.cpp b/compiler/intrinsics_type_resolving_ecmascript.cpp index 03741c3c7..e2c719d09 100644 --- a/compiler/intrinsics_type_resolving_ecmascript.cpp +++ b/compiler/intrinsics_type_resolving_ecmascript.cpp @@ -429,26 +429,48 @@ bool TypesResolving::InlineStObjByValue(IntrinsicInst *intrinsic) bool TypesResolving::GetICForMemNamedAccess(IntrinsicInst *intrinsic) { - auto method = GetGraph()->GetMethod(); + auto pc = intrinsic->GetPc(); + auto runtime = GetGraph()->GetRuntime(); auto save_state = intrinsic->GetSaveState(); ASSERT(save_state != nullptr); - if (auto caller_inst = save_state->GetCallerInst(); caller_inst != nullptr) { + auto caller_inst = save_state->GetCallerInst(); + if (caller_inst != nullptr) { ASSERT(caller_inst->IsInlined()); - method = caller_inst->GetCallMethod(); + auto func = caller_inst->GetFunctionObject(); + if (func != 0) { + if (!runtime->GetProfileDataForNamedAccess(GetGraph()->GetMethod(), func, pc, &named_access_profile_)) { + return false; + } + } else if (!runtime->GetProfileDataForNamedAccess(caller_inst->GetCallMethod(), pc, &named_access_profile_)) { + return false; + } + } else if (!runtime->GetProfileDataForNamedAccess(GetGraph()->GetMethod(), pc, &named_access_profile_)) { + return false; } - return GetGraph()->GetRuntime()->GetProfileDataForNamedAccess(method, intrinsic->GetPc(), &named_access_profile_); + return true; } bool TypesResolving::GetICForMemValueAccess(IntrinsicInst *intrinsic) { - auto method = GetGraph()->GetMethod(); + auto pc = intrinsic->GetPc(); + auto runtime = GetGraph()->GetRuntime(); auto save_state = intrinsic->GetSaveState(); ASSERT(save_state != nullptr); - if (auto caller_inst = save_state->GetCallerInst(); caller_inst != nullptr) { + auto caller_inst = save_state->GetCallerInst(); + if (caller_inst != nullptr) { ASSERT(caller_inst->IsInlined()); - method = caller_inst->GetCallMethod(); + auto func = caller_inst->GetFunctionObject(); + if (func != 0) { + if (!runtime->GetProfileDataForValueAccess(GetGraph()->GetMethod(), func, pc, &named_access_profile_)) { + return false; + } + } else if (!runtime->GetProfileDataForValueAccess(caller_inst->GetCallMethod(), pc, &named_access_profile_)) { + return false; + } + } else if (!runtime->GetProfileDataForValueAccess(GetGraph()->GetMethod(), pc, &named_access_profile_)) { + return false; } - return GetGraph()->GetRuntime()->GetProfileDataForValueAccess(method, intrinsic->GetPc(), &named_access_profile_); + return true; } Inst *TypesResolving::InsertCheckAndCastInstructions(IntrinsicInst *intrinsic) diff --git a/compiler/optimizer/optimizations/ecma_inlining.cpp b/compiler/optimizer/optimizations/ecma_inlining.cpp index dd3ffc224..7da0b8a16 100644 --- a/compiler/optimizer/optimizations/ecma_inlining.cpp +++ b/compiler/optimizer/optimizations/ecma_inlining.cpp @@ -52,19 +52,20 @@ bool EcmaInlining::IsInstSuitableForInline(Inst *inst) const return inst->GetOpcode() == Opcode::CallDynamic; } -bool EcmaInlining::CheckCallKind(CallInst *call_inst) +bool EcmaInlining::ResolveTargets(CallInst *call_inst) { + ASSERT(js_functions_.empty()); auto runtime = GetGraph()->GetRuntime(); auto kind = runtime->GetCallProfile(GetGraph()->GetMethod(), call_inst->GetPc(), &js_functions_, GetGraph()->IsAotMode()); - if (kind != profiling::CallKind::MONOMORPHIC) { + if (kind == profiling::CallKind::MEGAMORPHIC || kind == profiling::CallKind::UNKNOWN) { 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); + events::InlineKind::DYNAMIC_POLYMORPHIC, + kind == profiling::CallKind::MEGAMORPHIC ? events::InlineResult::FAIL_MEGAMORPHIC + : events::InlineResult::FAIL_RESOLVE); return false; } - // TODO(schernykh): support polimorphic inlining - CHECK_EQ(js_functions_.size(), 1U); return true; } @@ -93,19 +94,6 @@ RuntimeInterface::MethodPtr EcmaInlining::TryResolveTargetStatic(CallInst *call_ return nullptr; } -InlineContext EcmaInlining::ResolveTargets(CallInst *call_inst) -{ - js_functions_.clear(); - if (auto method = TryResolveTargetStatic(call_inst); method != nullptr) { - return {method}; - } - if (CheckCallKind(call_inst)) { - ASSERT(js_functions_[0] != 0); - return {GetGraph()->GetRuntime()->GetMethodFromFunctionAndSaveJsFunction(js_functions_[0])}; - } - return {}; -} - bool EcmaInlining::CheckMethod(CallInst *call_inst, InlineContext *ctx) { if (!CheckMethodCanBeInlined(call_inst, ctx)) { @@ -118,7 +106,7 @@ bool EcmaInlining::CheckMethod(CallInst *call_inst, InlineContext *ctx) return true; } -Graph *EcmaInlining::BuildGraph(CallInst *call_inst, InlineContext *ctx) +Graph *EcmaInlining::BuildGraph(InlineContext *ctx, CallInst *call_inst, CallInst *new_call_inst) { auto graph_inl = GetGraph()->CreateChildGraph(ctx->method); @@ -128,7 +116,7 @@ Graph *EcmaInlining::BuildGraph(CallInst *call_inst, InlineContext *ctx) auto stats = GetGraph()->GetPassManager()->GetStatistics(); auto saved_pbc_inst_num = stats->GetPbcInstNum(); - if (!TryBuildGraph(*ctx, graph_inl, call_inst, call_inst)) { + if (!TryBuildGraph(*ctx, graph_inl, call_inst, new_call_inst)) { stats->SetPbcInstNum(saved_pbc_inst_num); return nullptr; } @@ -155,11 +143,15 @@ Graph *EcmaInlining::BuildGraph(CallInst *call_inst, InlineContext *ctx) graph_inl->RunPass(instructions_count_ + inlined_insts_count, depth_ + 1, methods_inlined_ + 1); } instructions_count_ += CalculateInstructionsCount(graph_inl); + GetGraph()->SetMaxMarkerIdx(graph_inl->GetCurrentMarkerIdx()); + GetGraph()->SetCurrentInstructionId(graph_inl->GetCurrentInstructionId()); + GetGraph()->SetMaxInliningDepth(graph_inl->GetMaxInliningDepth() + 1); return graph_inl; } void EcmaInlining::BuildGuard(CallInst *call_inst) { + ASSERT(js_functions_.size() == 1U); 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()); @@ -183,10 +175,6 @@ void EcmaInlining::BuildGuard(CallInst *call_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); - if (!IsStaticInlining()) { BuildGuard(call_inst); } @@ -210,19 +198,17 @@ void EcmaInlining::InsertGraph(CallInst *call_inst, const InlineContext &ctx, Gr call_inst->SetCallMethod(ctx.method); call_inst->SetCallMethodId(GetGraph()->GetRuntime()->GetMethodId(ctx.method)); + if (!IsStaticInlining()) { + call_inst->SetFunctionObject(js_functions_[0]); + } } -bool EcmaInlining::TryInline(CallInst *call_inst) +bool EcmaInlining::DoEcmaMonomorphicInilning(CallInst *call_inst, InlineContext &ctx) { - 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); + ASSERT(js_functions_.size() <= 1U); + LOG_INLINING(DEBUG) << "Try inline monomorphic" << (IsStaticInlining() ? "(static)" : "") << " CallDynamic:"; + LOG_INLINING(DEBUG) << " instruction: " << *call_inst; + LOG_INLINING(DEBUG) << "Method: " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method, true); if (!CheckMethod(call_inst, &ctx)) { LOG_INLINING(DEBUG) << "Unsuitable bytecode"; @@ -230,21 +216,172 @@ bool EcmaInlining::TryInline(CallInst *call_inst) } // Build graph for current target and run EcmaInlining recursively - auto graph_inl = BuildGraph(call_inst, &ctx); + auto graph_inl = BuildGraph(&ctx, call_inst); 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(), - js_functions_.empty() ? events::InlineKind::STATIC : events::InlineKind::DYNAMIC_MONOMORPHIC, + IsStaticInlining() ? events::InlineKind::STATIC : events::InlineKind::DYNAMIC_MONOMORPHIC, events::InlineResult::SUCCESS); - LOG_INLINING(DEBUG) << "Successfully inlined: " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method); + LOG_INLINING(DEBUG) << "Successfully monomorophic " << (IsStaticInlining() ? "(static)" : "") + << " inlined:" << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method); + return true; +} + +void EcmaInlining::CreateCompareFunctions(CallInst *call_inst, uintptr_t js_function, BasicBlock *call_bb) +{ + auto load_function_inst = GetGraph()->CreateInstFunctionImmediate(DataType::ANY, call_inst->GetPc(), js_function); + auto cmp_inst = GetGraph()->CreateInstCompare(DataType::BOOL, call_inst->GetPc()); + auto if_inst = GetGraph()->CreateInstIfImm(DataType::BOOL, call_inst->GetPc(), ConditionCode::CC_NE, 0); + cmp_inst->SetCc(ConditionCode::CC_EQ); + cmp_inst->SetInput(0, load_function_inst); + cmp_inst->SetInput(1, call_inst->GetInput(0).GetInst()); + cmp_inst->SetOperandsType(DataType::ANY); + if_inst->SetInput(0, cmp_inst); + if_inst->SetOperandsType(DataType::BOOL); + call_bb->AppendInst(load_function_inst); + call_bb->AppendInst(cmp_inst); + call_bb->AppendInst(if_inst); +} + +bool EcmaInlining::DoEcmaPolymorphicInilning(CallInst *call_inst) +{ + ASSERT(js_functions_.size() > 1U); + ASSERT(js_functions_.size() <= profiling::MAX_FUNC_NUMBER); + LOG_INLINING(DEBUG) << "Try inline polymorphic call(" << js_functions_.size() << " receivers):"; + LOG_INLINING(DEBUG) << " instruction: " << *call_inst; + + bool has_unreachable_blocks = false; + PhiInst *phi_inst = nullptr; + BasicBlock *call_bb = nullptr; + BasicBlock *call_cont_bb = nullptr; + auto runtime = GetGraph()->GetRuntime(); + auto inlined_methods = methods_inlined_; + for (uintptr_t func : js_functions_) { + InlineContext ctx {runtime->GetMethodFromFunction(func)}; + LOG_INLINING(DEBUG) << "Method: " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method, true); + if (!CheckMethod(call_inst, &ctx)) { + continue; + } + CallInst *new_call_inst = call_inst->Clone(GetGraph())->CastToCallDynamic(); + new_call_inst->SetCallMethodId(runtime->GetMethodId(ctx.method)); + new_call_inst->SetCallMethod(ctx.method); + + // Build graph for current target and run EcmaInlining recursively + auto graph_inl = BuildGraph(&ctx, call_inst, new_call_inst); + if (graph_inl == nullptr) { + continue; + } + // vregs_count_ += graph_inl->GetVRegsCount(); + if (call_bb == nullptr) { + // Split block by call instruction + call_bb = call_inst->GetBasicBlock(); + call_cont_bb = call_bb->SplitBlockAfterInstruction(call_inst, false); + if (call_inst->GetType() != DataType::VOID) { + phi_inst = GetGraph()->CreateInstPhi(call_inst->GetType(), call_inst->GetPc()); + phi_inst->ReserveInputs(js_functions_.size() << 1U); + call_cont_bb->AppendPhi(phi_inst); + } + } else { + auto new_call_bb = GetGraph()->CreateEmptyBlock(call_bb); + call_bb->GetLoop()->AppendBlock(new_call_bb); + call_bb->AddSucc(new_call_bb); + call_bb = new_call_bb; + } + CreateCompareFunctions(call_inst, func, call_bb); + + // Create call_inlined_block + auto call_inlined_block = GetGraph()->CreateEmptyBlock(call_bb); + call_bb->GetLoop()->AppendBlock(call_inlined_block); + call_bb->AddSucc(call_inlined_block); + + // Insert Call.inlined in call_inlined_block + new_call_inst->AppendInput(call_inst->GetObjectInst()); + new_call_inst->AppendInput(call_inst->GetSaveState()); + new_call_inst->SetInlined(true); + new_call_inst->SetFlag(inst_flags::NO_DST); + new_call_inst->SetFunctionObject(func); + call_inlined_block->PrependInst(new_call_inst); + + // Create return_inlined_block and inster PHI for non void functions + auto return_inlined_block = GetGraph()->CreateEmptyBlock(call_bb); + call_bb->GetLoop()->AppendBlock(return_inlined_block); + PhiInst *local_phi_inst = nullptr; + if (call_inst->GetType() != DataType::VOID) { + local_phi_inst = GetGraph()->CreateInstPhi(call_inst->GetType(), call_inst->GetPc()); + local_phi_inst->ReserveInputs(js_functions_.size()); + return_inlined_block->AppendPhi(local_phi_inst); + } + + // Inlined graph between call_inlined_block and return_inlined_block + UpdateParameterDataflow(graph_inl, call_inst); + UpdateDataflow(graph_inl, call_inst, local_phi_inst, phi_inst); + MoveConstants(graph_inl); + UpdateControlflow(graph_inl, call_inlined_block, return_inlined_block); + if (!return_inlined_block->GetPredsBlocks().empty()) { + auto inlined_return = GetGraph()->CreateInstReturnInlined(DataType::VOID, INVALID_PC); + return_inlined_block->PrependInst(inlined_return); + inlined_return->SetInput(0, new_call_inst->GetSaveState()); + if (call_inst->GetType() != DataType::VOID) { + ASSERT(phi_inst); + // clang-tidy think that phi_inst can be nullptr + phi_inst->AppendInput(local_phi_inst); // NOLINT + } + return_inlined_block->AddSucc(call_cont_bb); + } else { + // We need remove return_inlined_block if inlined graph doesn't have Return inst(only Throw or Deoptimize) + has_unreachable_blocks = true; + } + GetGraph()->GetPassManager()->GetStatistics()->AddInlinedMethods(1); + EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), runtime->GetMethodFullName(ctx.method), + call_inst->GetId(), events::InlineKind::DYNAMIC_POLYMORPHIC, events::InlineResult::SUCCESS); + LOG_INLINING(DEBUG) << "Successfully polymorphic inlined: " << GetMethodFullName(GetGraph(), ctx.method); + methods_inlined_++; + } + if (call_bb == nullptr) { + // Nothing was inlined + return false; + } + if (call_cont_bb->GetPredsBlocks().empty() || has_unreachable_blocks) { + GetGraph()->RemoveUnreachableBlocks(); + } + if (methods_inlined_ - inlined_methods == js_functions_.size()) { + InsertDeoptimizeInst(call_inst, call_bb, DeoptimizeType::INLINE_DYN); + } else { + InsertCallInst(call_inst, call_bb, call_cont_bb, phi_inst); + } + + if (call_inst->GetType() != DataType::VOID) { + call_inst->ReplaceUsers(phi_inst); + } + + ProcessCallReturnInstructions(call_inst, call_cont_bb, true); + call_inst->GetBasicBlock()->RemoveInst(call_inst); return true; } +bool EcmaInlining::TryInline(CallInst *call_inst) +{ + js_functions_.clear(); + auto method = TryResolveTargetStatic(call_inst); + if (method != nullptr) { + InlineContext ctx {method}; + return DoEcmaMonomorphicInilning(call_inst, ctx); + } + if (!ResolveTargets(call_inst)) { + LOG_INLINING(DEBUG) << "Target methods were not found for CallDynaimc(id = " << call_inst->GetId() << ")"; + return false; + } + ASSERT(!js_functions_.empty()); + if (IsMonomorphicInlining()) { + InlineContext ctx {GetGraph()->GetRuntime()->GetMethodFromFunction(js_functions_[0])}; + return DoEcmaMonomorphicInilning(call_inst, ctx); + } + return DoEcmaPolymorphicInilning(call_inst); +} + } // namespace panda::compiler::ecmascript diff --git a/compiler/optimizer/optimizations/ecma_inlining.h b/compiler/optimizer/optimizations/ecma_inlining.h index 5f9789a60..077a367ef 100644 --- a/compiler/optimizer/optimizations/ecma_inlining.h +++ b/compiler/optimizer/optimizations/ecma_inlining.h @@ -38,12 +38,15 @@ private: bool SkipBlock(const BasicBlock *block) const override; bool TryInline(CallInst *call_inst) override; - InlineContext ResolveTargets(CallInst *call_inst); + bool DoEcmaMonomorphicInilning(CallInst *call_inst, InlineContext &ctx); + bool DoEcmaPolymorphicInilning(CallInst *call_inst); + bool ResolveTargets(CallInst *call_inst); RuntimeInterface::MethodPtr TryResolveTargetStatic(CallInst *call_inst) const; bool IsInstSuitableForEcmaStaticInlining(Inst *define_func) const; bool CheckCallKind(CallInst *call_inst); bool CheckMethod(CallInst *call_inst, InlineContext *ctx); - Graph *BuildGraph(CallInst *call_inst, InlineContext *ctx); + Graph *BuildGraph(InlineContext *ctx, CallInst *call_inst, CallInst *new_call_inst = nullptr); + void CreateCompareFunctions(CallInst *call_inst, uintptr_t js_function, BasicBlock *call_bb); void BuildGuard(CallInst *call_inst); void InsertGraph(CallInst *call_inst, const InlineContext &ctx, Graph *graph_inl); bool IsStaticInlining() const @@ -51,6 +54,11 @@ private: return js_functions_.empty(); } + bool IsMonomorphicInlining() const + { + return js_functions_.size() == 1U; + } + private: // This vector contains pointers to element in EcmaCallProfilingTable // This element contains pointer to js function diff --git a/isa/isa.yaml b/isa/isa.yaml index 9d988d88f..d382e2c0f 100644 --- a/isa/isa.yaml +++ b/isa/isa.yaml @@ -62,7 +62,7 @@ profiles: size: 1 properties: [] - name: Call - size: 2 + size: 8 properties: [] groups: diff --git a/runtime/compiler/ecmascript_runtime_interface.cpp b/runtime/compiler/ecmascript_runtime_interface.cpp index c1cdbe495..d7c613467 100644 --- a/runtime/compiler/ecmascript_runtime_interface.cpp +++ b/runtime/compiler/ecmascript_runtime_interface.cpp @@ -143,8 +143,13 @@ profiling::CallKind EcmaRuntimeInterface::GetCallProfile(MethodPtr method, uint3 // 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())); + if (call_kind != profiling::CallKind::UNKNOWN && call_kind != profiling::CallKind::MEGAMORPHIC) { + for (auto ptr : call_prof->GetCalleesPtr(ecma_vm_->GetEcmaCallProfileTable())) { + if (ptr == CallProfile::UNKNOWN) { + break; + } + methods->push_back(ptr); + } } return call_kind; } @@ -544,6 +549,32 @@ inline ProfileTypeInfo *EcmaRuntimeInterface::GetProfileTypeInfo(PandaRuntimeInt return nullptr; } +inline ProfileTypeInfo *EcmaRuntimeInterface::GetProfileTypeInfo(uintptr_t func_address, uintptr_t slot_id, + uint8_t *slot) +{ + // ProfileTypeInfo is manage object + // We need to have lock in caller method, because we return ProfileTypeInfo + ASSERT(ecma_vm_->GetMutatorLock()->HasLock()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto js_func = TaggedValue(reinterpret_cast(func_address)[0]); + if (!js_func.IsHeapObject()) { + return nullptr; + } + auto func = JSFunction::Cast(js_func.GetHeapObject()); + uint8_t *mapping = func->GetMethod()->GetICMapping(); + if (mapping == nullptr) { + return nullptr; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + *slot = mapping[slot_id]; + auto profile_info_obj = func->GetProfileTypeInfo(); + if (profile_info_obj.IsUndefined()) { + return nullptr; + } + + return ProfileTypeInfo::Cast(profile_info_obj.GetTaggedObject()); +} + bool EcmaRuntimeInterface::GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slot_id, ArenaVector *profile) { @@ -564,6 +595,28 @@ bool EcmaRuntimeInterface::GetProfileDataForNamedAccess(PandaRuntimeInterface::M return false; } +bool EcmaRuntimeInterface::GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t func_address, + uintptr_t slot_id, ArenaVector *profile) +{ + if (profile == nullptr) { + return false; + } + profile->clear(); + ASSERT(func_address != 0); + ScopedMutatorLock lock; + + uint8_t slot; + ProfileTypeInfo *profile_type_info = GetProfileTypeInfo(func_address, slot_id, &slot); + if (profile_type_info == nullptr) { + return false; + } + if (AddProfileInfo(m, profile, profile_type_info, slot)) { + AddProfileInfo(m, profile, profile_type_info, slot + 2); + return true; + } + return false; +} + bool EcmaRuntimeInterface::GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slot_id, ArenaVector *profile) { @@ -581,6 +634,25 @@ bool EcmaRuntimeInterface::GetProfileDataForValueAccess(PandaRuntimeInterface::M return AddProfileValueInfo(m, profile, profile_type_info, slot); } +bool EcmaRuntimeInterface::GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t func_address, + uintptr_t slot_id, ArenaVector *profile) +{ + if (profile == nullptr) { + return false; + } + profile->clear(); + ASSERT(func_address != 0); + ScopedMutatorLock lock; + + uint8_t slot; + ProfileTypeInfo *profile_type_info = GetProfileTypeInfo(func_address, slot_id, &slot); + if (profile_type_info == nullptr) { + return false; + } + + return AddProfileValueInfo(m, profile, profile_type_info, slot); +} + void EcmaRuntimeInterface::CleanFunction(Method *method) { os::memory::LockHolder lock(mutex_); @@ -816,21 +888,4 @@ PandaRuntimeInterface::MethodPtr EcmaRuntimeInterface::GetMethodByIdAndSaveJsFun return method; } -PandaRuntimeInterface::MethodPtr EcmaRuntimeInterface::GetMethodFromFunctionAndSaveJsFunction(uintptr_t function) -{ - ScopedMutatorLock lock; - auto *js_func = *(reinterpret_cast(function)); - if (js_func == nullptr) { - return nullptr; - } - auto method = js_func->GetCallTarget(); - - // Store JsFunction for futher optimizations - auto gos = ecma_vm_->GetGlobalObjectStorage(); - auto func_ref = gos->Add(JSTaggedValue(js_func).GetHeapObject(), panda::mem::Reference::ObjectType::GLOBAL); - AddFunctionInMap(method, func_ref); - - return method; -} - } // namespace panda::ecmascript diff --git a/runtime/compiler/ecmascript_runtime_interface.h b/runtime/compiler/ecmascript_runtime_interface.h index 6bb1266c5..f48cc8fd0 100644 --- a/runtime/compiler/ecmascript_runtime_interface.h +++ b/runtime/compiler/ecmascript_runtime_interface.h @@ -141,8 +141,12 @@ public: MethodPtr GetMethodByIdAndSaveJsFunction(MethodPtr parent_method, MethodId id) override; - MethodPtr GetMethodFromFunctionAndSaveJsFunction(uintptr_t function) override; - + 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, profiling::AnyInputType *allowed_input_type, @@ -154,8 +158,13 @@ public: NewObjDynInfo GetNewObjDynInfo(uintptr_t ctor) const override; bool GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slot_id, ArenaVector *profile) override; + bool GetProfileDataForNamedAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t func_address, uintptr_t slot_id, + ArenaVector *profile) override; bool GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t slot_id, ArenaVector *profile) override; + bool GetProfileDataForValueAccess(PandaRuntimeInterface::MethodPtr m, uintptr_t func_address, uintptr_t slot_id, + ArenaVector *profile) override; + void CleanFunction(Method *method); void AddFunctionInMap(Method *method, panda::mem::Reference *func) @@ -186,6 +195,7 @@ private: ProfileTypeInfo *profile_type_info, uint8_t slot); bool AddElementInfo(ArenaVector *profile, ProfileTypeInfo *profile_type_info, uint8_t slot); ProfileTypeInfo *GetProfileTypeInfo(PandaRuntimeInterface::MethodPtr m, uintptr_t slot_id, uint8_t *slot); + ProfileTypeInfo *GetProfileTypeInfo(uintptr_t func_address, uintptr_t slot_id, uint8_t *slot); size_t GetLexicalEnvParentEnvIndex() const override; diff --git a/runtime/ecma_profiling.h b/runtime/ecma_profiling.h index ce07cf5ae..56803fd83 100644 --- a/runtime/ecma_profiling.h +++ b/runtime/ecma_profiling.h @@ -465,11 +465,12 @@ protected: class ECMAObject; class CallProfile { -public: using Type = uint16_t; static constexpr uintptr_t MEGAMORPHIC = std::numeric_limits::max(); - static constexpr uintptr_t UNKNOWN = 0; +public: + static constexpr Type MAX_FUNC_NUMBER = panda::profiling::MAX_FUNC_NUMBER; + static constexpr uintptr_t UNKNOWN = 0; static CallProfile *FromBuffer(uint8_t *data) { return reinterpret_cast(data); @@ -478,52 +479,79 @@ public: panda::profiling::CallKind GetCallKind() const { // Atomic with acquire order reason: profile data may be updated while the compiler thread loads it - auto callee_idx = reinterpret_cast *>(&callee_idx_)->load(std::memory_order_acquire); + auto callee_idx = + reinterpret_cast *>(&callees_idx_[0])->load(std::memory_order_acquire); if (callee_idx == MEGAMORPHIC) { return panda::profiling::CallKind::MEGAMORPHIC; } if (callee_idx == UNKNOWN) { return panda::profiling::CallKind::UNKNOWN; } - return panda::profiling::CallKind::MONOMORPHIC; + // Atomic with acquire order reason: profile data may be updated while the compiler thread loads it + if (reinterpret_cast *>(&callees_idx_[1])->load(std::memory_order_acquire) == UNKNOWN) { + return panda::profiling::CallKind::MONOMORPHIC; + } + return panda::profiling::CallKind::POLYMORPHIC; } - uintptr_t GetCalleePtr(EcmaCallProfilingTable *table) const + std::array GetCalleesPtr(EcmaCallProfilingTable *table) const { - return table->GetObjectPtr(callee_idx_); + std::array obj_ptrs {}; + for (size_t i = 0; i < MAX_FUNC_NUMBER; ++i) { + if (callees_idx_[i] == UNKNOWN) { + return obj_ptrs; + } + obj_ptrs[i] = table->GetObjectPtr(callees_idx_[i]); + } + return obj_ptrs; } void Clear(EcmaCallProfilingTable *table) { - if (callee_idx_ != UNKNOWN && callee_idx_ != MEGAMORPHIC) { - table->ClearObject(callee_idx_); + if (callees_idx_[0] == MEGAMORPHIC) { + return; + } + for (size_t i = 0; i < MAX_FUNC_NUMBER; ++i) { + if (callees_idx_[i] == UNKNOWN) { + break; + } + table->ClearObject(callees_idx_[i]); + callees_idx_[i] = UNKNOWN; } - callee_idx_ = UNKNOWN; } void Update(ECMAObject *js_func, EcmaCallProfilingTable *table) { - if (callee_idx_ == MEGAMORPHIC) { + if (callees_idx_[0] == MEGAMORPHIC) { return; } - if (callee_idx_ == UNKNOWN) { - auto idx = table->InsertNewObject(js_func); - if (idx) { - // Atomic with release order reason: profile data may be updated while the compiler thread loads it - reinterpret_cast *>(&callee_idx_)->store(idx.value(), std::memory_order_release); + for (size_t i = 0; i < MAX_FUNC_NUMBER; ++i) { + if (callees_idx_[i] == UNKNOWN) { + auto idx = table->InsertNewObject(js_func); + if (idx) { + // Atomic with release order reason: profile data may be updated while the compiler thread loads it + reinterpret_cast *>(&callees_idx_[i]) + ->store(idx.value(), std::memory_order_release); + } + return; + } + ASSERT(callees_idx_[i] > 0 && callees_idx_[i] <= std::numeric_limits::max() - 1U); + if (table->GetObject(callees_idx_[i]) == js_func) { + return; } - return; } - ASSERT(callee_idx_ > 0 && callee_idx_ <= std::numeric_limits::max() - 1U); - if (table->GetObject(callee_idx_) != js_func) { - table->ClearObject(callee_idx_); + for (size_t i = 0; i < MAX_FUNC_NUMBER; ++i) { + table->ClearObject(callees_idx_[i]); // Atomic with release order reason: profile data may be updated while the compiler thread loads it - reinterpret_cast *>(&callee_idx_)->store(MEGAMORPHIC, std::memory_order_release); + reinterpret_cast *>(&callees_idx_[i])->store(MEGAMORPHIC, std::memory_order_release); } } private: - Type callee_idx_ {UNKNOWN}; + std::array callees_idx_ {}; + // if you want to increase callees_idx_ size, + // you need to update size in profiles::Call in ecmascript/isa/isa.yaml + static_assert(sizeof(callees_idx_) == 8); }; /** diff --git a/runtime/interpreter/fast_runtime_stub-inl.h b/runtime/interpreter/fast_runtime_stub-inl.h index d4b761b39..672c418aa 100644 --- a/runtime/interpreter/fast_runtime_stub-inl.h +++ b/runtime/interpreter/fast_runtime_stub-inl.h @@ -90,7 +90,7 @@ JSTaggedValue FastRuntimeStub::FastMod(JSTaggedValue left, JSTaggedValue right) if (right.IsInt() && left.IsInt()) { int i_right = right.GetInt(); int i_left = left.GetInt(); - if (i_right > 0 && i_left > 0) { + if (i_right > 0 && i_left >= 0) { return JSTaggedValue(i_left % i_right); } } diff --git a/tests/checked/CMakeLists.txt b/tests/checked/CMakeLists.txt index b2493ec75..57facf57a 100644 --- a/tests/checked/CMakeLists.txt +++ b/tests/checked/CMakeLists.txt @@ -106,10 +106,12 @@ if (NOT PANDA_TARGET_ARM32) 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}/obj_by_name.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/obj_by_value.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_static.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) + 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_polymorphic.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining_polymorphic_2.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining_monomorphic.js SUPPORT_RELEASE true) + panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_inlining_static.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ecma_call_profile_clear.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/peephole_negoverflowandzerocheck.js SUPPORT_RELEASE true) panda_add_checked_test_ecma(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ldlex.js SUPPORT_RELEASE true) diff --git a/tests/checked/ecma_inlining_megamorphic.js b/tests/checked/ecma_inlining_megamorphic.js index 0bf2192b0..07d0839a1 100644 --- a/tests/checked/ecma_inlining_megamorphic.js +++ b/tests/checked/ecma_inlining_megamorphic.js @@ -13,12 +13,6 @@ * 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; } @@ -27,17 +21,36 @@ function two() { return 2; } -function test(i) { - var a = one; - if (i % 2 == 0) - a = two; - return a(); +function three() { + return 3; +} + +function four() { + return 4; +} + +function five() { + return 5; +} + +function test(func) { + return func(); } -var sum = 0; -for (var i = 0; i < 20; i++) { - sum += test(i); +var funcs = [one, two, three, four, five]; + +function test_inlining(N, except_res) { + var sum = 0; + for (var i = 0; i < 20; i++) { + sum += test(funcs[i % N]); + } + if (sum != except_res) { + throw "Wrong result for N = " + N + ". Result = " + sum + ", excpected = " + except_res; + } } -if (sum != 30) { - throw "Wrong result: " + sum; -} \ No newline at end of file + +//! 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_POLYMORPHIC,FAIL_MEGAMORPHIC/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +test_inlining(5, 60) \ No newline at end of file diff --git a/tests/checked/ecma_inlining.js b/tests/checked/ecma_inlining_monomorphic.js similarity index 96% rename from tests/checked/ecma_inlining.js rename to tests/checked/ecma_inlining_monomorphic.js index 7e026efd3..ebc103feb 100644 --- a/tests/checked/ecma_inlining.js +++ b/tests/checked/ecma_inlining_monomorphic.js @@ -13,7 +13,7 @@ * limitations under the License. */ -//! CHECKER Ecma Inlining. Must inline. +//! CHECKER Ecma Inlining. Must inline, monomorphic call. //! 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/ diff --git a/tests/checked/ecma_inlining_polymorphic.js b/tests/checked/ecma_inlining_polymorphic.js new file mode 100644 index 000000000..e879e4223 --- /dev/null +++ b/tests/checked/ecma_inlining_polymorphic.js @@ -0,0 +1,61 @@ +/* + * 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 one() { + return 1; +} + +function two() { + return 2; +} + +function three() { + return 3; +} + +function four() { + return 4; +} + +function five() { + return 5; +} + +function test(func) { + return func(); +} + +var funcs = [one, two, three, four, five]; + +function test_inlining(N, except_res) { + var sum = 0; + for (var i = 0; i < 20; i++) { + sum += test(funcs[i % N]); + } + if (sum != except_res) { + throw "Wrong result for N = " + N + ". Result = " + sum + ", excpected = " + except_res; + } +} + +//! CHECKER Ecma Inlining. Must inline, polymorphic call. +//! 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_POLYMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::two,.*DYNAMIC_POLYMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::three,.*DYNAMIC_POLYMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::four,.*DYNAMIC_POLYMORPHIC,SUCCESS/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +//! EVENT_NOT /Deoptimization,_GLOBAL::.*test.*,.*,IFRAME/ +test_inlining(4, 50) + diff --git a/tests/checked/ecma_inlining_polymorphic_2.js b/tests/checked/ecma_inlining_polymorphic_2.js new file mode 100644 index 000000000..69878c525 --- /dev/null +++ b/tests/checked/ecma_inlining_polymorphic_2.js @@ -0,0 +1,64 @@ +/* + * 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 one() { + return 1; +} + +function two() { + return 2; +} + +function three() { + return 3; +} + +function four() { + try { + return 4; + } catch { + return -1; + } +} + +function five() { + return 5; +} + +function test(func) { + return func(); +} + +var funcs = [one, two, three, four, five]; + +function test_inlining(N, except_res) { + var sum = 0; + for (var i = 0; i < 20; i++) { + sum += test(funcs[i % N]); + } + if (sum != except_res) { + throw "Wrong result for N = " + N + ". Result = " + sum + ", excpected = " + except_res; + } +} + +//! CHECKER Ecma Inlining. Must inline, polymorphic call. +//! 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_POLYMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::two,.*DYNAMIC_POLYMORPHIC,SUCCESS/ +//! EVENT /Inline,_GLOBAL::test,_GLOBAL::three,.*DYNAMIC_POLYMORPHIC,SUCCESS/ +//! EVENT /Compilation,_GLOBAL::test,.*,COMPILED/ +//! EVENT_NOT /Deoptimization,_GLOBAL::.*test.*,.*,IFRAME/ +test_inlining(4, 50) + -- Gitee