* Copyright (c) 2023-2026 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 "ecmascript/pgo_profiler/pgo_profiler.h"
#include "ecmascript/enum_conversion.h"
#include "ecmascript/interpreter/interpreter-inl.h"
#include "ecmascript/jit/jit_profiler.h"
#include "ecmascript/pgo_profiler/pgo_trace.h"
namespace panda::ecmascript::pgo {
void PGOProfiler::RecordProfileType(JSHClass *hclass, JSPandaFile *pandaFile, int32_t traceId)
{
if (!isEnable_) {
return;
}
ProfileType traceType = GetProfileType(hclass);
if (traceType.IsNone()) {
pgo::ApEntityId abcId(0);
pgo::PGOProfilerManager::GetInstance()->GetPandaFileId(pandaFile->GetJSPandaFileDesc(), abcId);
SetRootProfileType(hclass, abcId, traceId, ProfileType::Kind::ObjectLiteralId);
}
}
void PGOProfiler::ProfileDefineClass(JSTaggedType ctor)
{
if (!isEnable_) {
return;
}
auto ctorValue = JSTaggedValue(ctor);
if (!ctorValue.IsJSFunction()) {
return;
}
JSThread *thread = vm_->GetJSThread();
auto ctorFunc = JSFunction::Cast(ctorValue.GetTaggedObject());
auto ctorMethodValue = ctorFunc->GetMethod(thread);
if (!ctorMethodValue.IsMethod()) {
return;
}
auto ctorMethod = Method::Cast(ctorMethodValue);
auto entityId = ctorMethod->GetMethodId().GetOffset();
if (!InsertDefinedCtor(entityId)) {
InsertSkipCtorMethodIdSafe(ctorMethod->GetMethodId());
return;
}
auto abcId = GetMethodAbcId(thread, ctorFunc);
auto chc = ctorFunc->GetClass();
SetRootProfileType(chc, abcId, entityId, ProfileType::Kind::ConstructorId);
auto protoOrHClass = ctorFunc->GetProtoOrHClass(thread);
if (protoOrHClass.IsJSHClass()) {
auto ihc = JSHClass::Cast(protoOrHClass.GetTaggedObject());
SetRootProfileType(ihc, abcId, entityId, ProfileType::Kind::ClassId);
protoOrHClass = ihc->GetProto(thread);
}
if (protoOrHClass.IsJSObject()) {
auto phc = protoOrHClass.GetTaggedObject()->GetClass();
SetRootProfileType(phc, abcId, entityId, ProfileType::Kind::PrototypeId);
}
}
void PGOProfiler::ProfileClassRootHClass(JSTaggedType ctor, JSTaggedType rootHcValue, ProfileType::Kind kind)
{
if (!isEnable_) {
return;
}
auto ctorValue = JSTaggedValue(ctor);
if (!ctorValue.IsJSFunction()) {
return;
}
JSThread *thread = vm_->GetJSThread();
auto ctorFunc = JSFunction::Cast(ctorValue.GetTaggedObject());
if (!FunctionKindVerify(ctorFunc)) {
return;
}
auto ctorMethodValue = ctorFunc->GetMethod(thread);
if (!ctorMethodValue.IsMethod()) {
return;
}
auto ctorMethod = Method::Cast(ctorMethodValue);
auto entityId = ctorMethod->GetMethodId().GetOffset();
if (IsSkippableCtor(entityId)) {
return;
}
auto rootHc = JSHClass::Cast(JSTaggedValue(rootHcValue).GetTaggedObject());
auto abcId = GetMethodAbcId(thread, ctorFunc);
SetRootProfileType(rootHc, abcId, entityId, kind);
}
void PGOProfiler::ProfileNapiRootHClass(JSTaggedType ctor, JSTaggedType rootHcValue, ProfileType::Kind kind)
{
if (!isEnable_) {
return;
}
auto ctorValue = JSTaggedValue(ctor);
if (!ctorValue.IsJSFunction()) {
return;
}
JSThread *thread = vm_->GetJSThread();
auto ctorFunc = JSFunction::Cast(ctorValue.GetTaggedObject());
auto ctorMethodValue = ctorFunc->GetMethod(thread);
if (!ctorMethodValue.IsMethod()) {
return;
}
auto entityId = Method::Cast(ctorMethodValue)->GetMethodId().GetOffset();
auto rootHc = JSHClass::Cast(JSTaggedValue(rootHcValue).GetTaggedObject());
auto abcId = GetMethodAbcId(thread, ctorFunc);
SetRootProfileType(rootHc, abcId, entityId, kind);
}
void PGOProfiler::ProfileProtoTransitionClass(JSHandle<JSFunction> func,
JSHandle<JSHClass> hclass,
JSHandle<JSTaggedValue> proto)
{
if (!isEnable_) {
return;
}
auto thread = vm_->GetJSThread();
JSHClass *phc = proto->GetTaggedObject()->GetClass();
JSHClass *phcRoot = JSHClass::FindRootHClass(thread, phc);
auto* transitionTable = vm_->GetFunctionProtoTransitionTable();
JSTaggedType baseIhc = transitionTable->GetFakeParent(JSTaggedType(phcRoot));
if (baseIhc == 0) {
LOG_PGO(DEBUG) << "fake parent not found!";
ProfileClassRootHClass(func.GetTaggedType(), hclass.GetTaggedType());
return;
}
JSHClass *baseRoot = JSHClass::FindRootHClass(thread, JSHClass::Cast(JSTaggedValue(baseIhc).GetTaggedObject()));
if (GetProfileType(baseRoot).IsNone()) {
return;
}
JSHandle<JSTaggedValue> keyHandle(thread, thread->GlobalConstants()->GetGlobalConstantObject(
static_cast<size_t>(ConstantIndex::PROTO_TRANS_ROOT_HCLASS_SYMBOL_INDEX)));
PropertyDescriptor desc(thread);
JSObject::GetOwnProperty(thread, JSHandle<JSObject>::Cast(func), keyHandle, desc);
JSTaggedType ihc = (desc.GetValue())->GetRawData();
if (JSTaggedValue(ihc).IsUndefined()) {
LOG_PGO(DEBUG) << "maybe the prototype of the current function is just the initial prototype!";
ProfileClassRootHClass(func.GetTaggedType(), hclass.GetTaggedType());
return;
}
[[maybe_unused]] bool success = transitionTable->TryInsertFakeParentItem(hclass.GetTaggedType(), ihc);
ASSERT(success == true);
ProfileClassRootHClass(func.GetTaggedType(), ihc, ProfileType::Kind::ClassId);
ProfileClassRootHClass(func.GetTaggedType(), hclass.GetTaggedType(), ProfileType::Kind::TransitionClassId);
}
void PGOProfiler::ProfileProtoTransitionPrototype(JSHandle<JSFunction> func,
JSHandle<JSTaggedValue> prototype,
JSHandle<JSTaggedValue> oldPrototype,
JSHandle<JSTaggedValue> baseIhc)
{
if (!isEnable_) {
return;
}
if (!oldPrototype->IsECMAObject()) {
return;
}
auto thread = vm_->GetJSThread();
auto method = func->GetMethod(thread);
if (Method::Cast(method)->IsNativeWithCallField()) {
return;
}
JSHandle<JSTaggedValue> keyHandle(thread, thread->GlobalConstants()->GetGlobalConstantObject(
static_cast<size_t>(ConstantIndex::PROTO_TRANS_ROOT_HCLASS_SYMBOL_INDEX)));
PropertyDescriptor desc(thread);
JSObject::GetOwnProperty(thread, JSHandle<JSObject>::Cast(func), keyHandle, desc);
if (!(desc.GetValue())->IsUndefined()) {
return;
}
JSHandle<JSTaggedValue> transIhc(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> transPhc(thread, prototype->GetTaggedObject()->GetClass());
if (JSHandle<JSHClass>(baseIhc)->IsDictionaryMode() || JSHandle<JSHClass>(transPhc)->IsDictionaryMode()) {
return;
}
JSHClass *baseRoot = JSHClass::FindRootHClass(thread, JSHClass::Cast(baseIhc.GetTaggedValue().GetTaggedObject()));
if (GetProfileType(baseRoot).IsNone()) {
return;
}
auto* transitionTable = vm_->GetFunctionProtoTransitionTable();
bool success = transitionTable->TryInsertFakeParentItem(transPhc.GetTaggedType(), baseIhc.GetTaggedType());
if (!success) {
return;
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSHClass> ihc = factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT, oldPrototype);
PropertyDescriptor definePropertyDesc(thread, JSHandle<JSTaggedValue>(ihc), false, false, false);
JSObject::DefineOwnProperty(thread, JSHandle<JSObject>::Cast(func), keyHandle, definePropertyDesc);
JSHClass *phc0Root = JSHClass::FindRootHClass(thread, oldPrototype->GetTaggedObject()->GetClass());
ProfileClassRootHClass(func.GetTaggedType(), JSTaggedType(phc0Root), ProfileType::Kind::PrototypeId);
ProfileClassRootHClass(func.GetTaggedType(), transPhc.GetTaggedType(), ProfileType::Kind::TransitionPrototypeId);
}
void PGOProfiler::ProfileDefineGetterSetter(JSHClass* receiverHClass,
JSHClass* holderHClass,
const JSHandle<JSTaggedValue>& func,
int32_t pcOffset)
{
if (!isEnable_) {
return;
}
JSTaggedValue funcValue = JSTaggedValue(func.GetTaggedValue());
if (!funcValue.IsJSFunction()) {
return;
}
JSThread *thread = vm_->GetJSThread();
auto methodValue = JSFunction::Cast(funcValue)->GetMethod(thread);
if (!methodValue.IsMethod()) {
return;
}
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSFunction> function(func);
#if !ENABLE_MEMORY_OPTIMIZATION
WorkNode* workNode = reinterpret_cast<WorkNode*>(function->GetWorkNodePointer());
if (workNode != nullptr) {
workNode->SetValue(JSTaggedType(JSFunction::Cast(funcValue)));
}
#endif
JSHandle<JSTaggedValue> key(thread, JSTaggedValue(pcOffset));
JSHandle<JSTaggedValue> receiverHClassHandle(thread, receiverHClass);
JSHandle<JSTaggedValue> holderHClassHandle(thread, holderHClass);
JSHandle<JSTaggedValue> profileTypeInfoValue(thread, function->GetRawProfileTypeInfo(thread));
JSHandle<ProfileTypeInfoCell> profileTypeInfoCell(profileTypeInfoValue);
if (function->HasProfileTypeInfo(thread)) {
JSHandle<ProfileTypeInfo> profileTypeInfo(thread, profileTypeInfoCell->GetValue(thread));
JSHandle<NumberDictionary> dictJShandle = ProfileTypeInfo::CreateOrGetExtraInfoMap(thread, profileTypeInfo);
int entry = dictJShandle->FindEntry(thread, key.GetTaggedValue());
if (entry == -1) {
ProfileTypeInfo::UpdateExtraInfoMap(thread, dictJShandle, key, receiverHClassHandle, holderHClassHandle,
profileTypeInfo);
return;
}
ExtraProfileTypeInfo *mapInfoObj =
ExtraProfileTypeInfo::Cast(dictJShandle->GetValue(thread, entry).GetTaggedObject());
if (mapInfoObj->GetReceiver(thread) == receiverHClassHandle.GetTaggedValue().CreateAndGetWeakRef() &&
mapInfoObj->GetHolder(thread) == holderHClassHandle.GetTaggedValue()) {
return;
}
ExtraProfileTypeInfo::Cast(dictJShandle->GetValue(thread, entry).GetTaggedObject())->Clear(thread);
}
}
void PGOProfiler::UpdateRootProfileTypeSafe(JSHClass* oldHClass, JSHClass* newHClass)
{
if (!isEnable_) {
return;
}
ProfileType oldPt = GetProfileType(oldHClass);
if (oldPt.IsRootType()) {
newHClass->SetPGOProfileType(oldPt.GetRaw());
oldHClass->ClearPGOProfileType();
}
}
void PGOProfiler::UpdateTrackArrayLength(JSTaggedValue trackInfoVal, uint32_t newSize)
{
if (trackInfoVal.IsHeapObject() && trackInfoVal.IsWeak()) {
auto trackInfo = TrackInfo::Cast(trackInfoVal.GetWeakReferentUnChecked());
uint32_t oldSize = trackInfo->GetArrayLength();
if (oldSize >= newSize) {
return;
}
trackInfo->SetArrayLength(newSize);
UpdateTrackInfo(JSTaggedValue(trackInfo));
}
}
void PGOProfiler::UpdateTrackSpaceFlag(TaggedObject *object, RegionSpaceFlag spaceFlag)
{
if (!object->GetClass()->IsTrackInfoObject()) {
return;
}
auto trackInfo = TrackInfo::Cast(object);
RegionSpaceFlag oldFlag = trackInfo->GetSpaceFlag();
if (oldFlag == RegionSpaceFlag::IN_YOUNG_SPACE) {
trackInfo->SetSpaceFlag(spaceFlag);
UpdateTrackInfo(JSTaggedValue(trackInfo));
}
}
void PGOProfiler::UpdateTrackInfo(JSTaggedValue trackInfoVal)
{
if (trackInfoVal.IsHeapObject()) {
auto trackInfo = TrackInfo::Cast(trackInfoVal.GetTaggedObject());
auto thread = vm_->GetJSThread();
auto func = trackInfo->GetCachedFunc(thread);
if (!func.IsWeak()) {
return;
}
TaggedObject *object = func.GetWeakReferentUnChecked();
if (!object->GetClass()->IsJSFunction()) {
return;
}
JSFunction* function = JSFunction::Cast(object);
if (!function->HasProfileTypeInfo(thread)) {
return;
}
auto profileTypeInfoVal = function->GetProfileTypeInfo(thread);
if (profileTypeInfoVal.IsUndefined() || !profileTypeInfoVal.IsTaggedArray()) {
return;
}
auto profileTypeInfo = ProfileTypeInfo::Cast(profileTypeInfoVal.GetTaggedObject());
if (profileTypeInfo->IsProfileTypeInfoWithBigMethod()) {
return;
}
if (!profileTypeInfo->IsProfileTypeInfoPreDumped()) {
if (profileTypeInfo->IsProfileTypeInfoInit()) {
profileTypeInfo->SetPreDumpPeriodIndex();
PGOPreDump(JSTaggedType(object));
} else {
profileTypeInfo->SetPreDumpPeriodIndex();
}
}
}
}
void PGOProfiler::PGODump(JSTaggedType func)
{
if (!isEnable_ || !vm_->GetJSOptions().IsEnableProfileDump()) {
return;
}
auto funcValue = JSTaggedValue(func);
if (!funcValue.IsJSFunction()) {
return;
}
auto methodValue = JSFunction::Cast(funcValue)->GetMethod(vm_->GetJSThread());
if (!methodValue.IsMethod()) {
return;
}
#if ENABLE_MEMORY_OPTIMIZATION
auto workNode = nativeAreaAllocator_->New<WorkNode>(func);
dumpWorkList_.PushBack(workNode);
#else
auto function = JSFunction::Cast(funcValue);
auto workNode = reinterpret_cast<WorkNode *>(function->GetWorkNodePointer());
if (workNode == nullptr) {
workNode = nativeAreaAllocator_->New<WorkNode>(JSTaggedType(function));
function->SetWorkNodePointer(reinterpret_cast<uintptr_t>(workNode));
dumpWorkList_.PushBack(workNode);
} else {
workNode->SetValue(JSTaggedType(function));
auto workList = workNode->GetWorkList();
if (workList == &preDumpWorkList_) {
preDumpWorkList_.Remove(workNode);
}
if (workList != &dumpWorkList_) {
dumpWorkList_.PushBack(workNode);
}
}
#endif
manager_->TryDispatchDumpTask(this);
}
void PGOProfiler::SuspendByGC()
{
if (!isEnable_) {
return;
}
state_->SuspendByGC();
}
void PGOProfiler::ResumeByGC()
{
if (!isEnable_) {
return;
}
state_->ResumeByGC(this);
}
bool PGOProfiler::SetStartIfStop()
{
if (!isEnable_) {
return false;
}
return state_->SetStartIfStop();
}
void PGOProfiler::SetStopAndNotify()
{
if (!isEnable_) {
return;
}
state_->SetStopAndNotify();
}
void PGOProfiler::PGOPreDump(JSTaggedType func)
{
if (!isEnable_ || !vm_->GetJSOptions().IsEnableProfileDump()) {
return;
}
auto funcValue = JSTaggedValue(func);
if (!funcValue.IsJSFunction()) {
return;
}
auto methodValue = JSFunction::Cast(funcValue)->GetMethod(vm_->GetJSThread());
if (!methodValue.IsMethod()) {
return;
}
#if ENABLE_MEMORY_OPTIMIZATION
auto workNode = nativeAreaAllocator_->New<WorkNode>(func);
preDumpWorkList_.PushBack(workNode);
#else
auto function = JSFunction::Cast(funcValue);
auto workNode = reinterpret_cast<WorkNode *>(function->GetWorkNodePointer());
if (workNode == nullptr) {
workNode = nativeAreaAllocator_->New<WorkNode>(JSTaggedType(function));
function->SetWorkNodePointer(reinterpret_cast<uintptr_t>(workNode));
preDumpWorkList_.PushBack(workNode);
} else {
workNode->SetValue(JSTaggedType(function));
auto workList = workNode->GetWorkList();
if (workList == &dumpWorkList_) {
workList->Remove(workNode);
}
if (workList != &preDumpWorkList_) {
preDumpWorkList_.PushBack(workNode);
}
}
#endif
}
void PGOProfiler::UpdateExtraProfileTypeInfo(ApEntityId abcId,
const CString& recordName,
EntityId methodId,
WorkNode* current)
{
JSTaggedValue funcValue = JSTaggedValue(current->GetValue());
if (!funcValue.IsJSFunction()) {
return;
}
auto func = JSFunction::Cast(funcValue);
if (!func->HasProfileTypeInfo(vm_->GetJSThread())) {
return;
}
JSThread *thread = vm_->GetJSThread();
ProfileTypeInfoCell *cell = ProfileTypeInfoCell::Cast(func->GetRawProfileTypeInfo(thread));
ProfileTypeInfo *info = ProfileTypeInfo::Cast((cell->GetValue(thread)).GetTaggedObject());
if ((info->GetExtraInfoMap(thread)).IsHole() || (info->GetExtraInfoMap(thread)).IsUndefined()) {
return;
}
NumberDictionary *dict = NumberDictionary::Cast(info->GetExtraInfoMap(thread).GetTaggedObject());
int size = dict->Size();
for (int hashIndex = 0; hashIndex < size; hashIndex++) {
JSTaggedValue key(dict->GetKey(thread, hashIndex));
if (!key.IsUndefined() && !key.IsHole()) {
JSTaggedValue val(dict->GetValue(thread, hashIndex));
ExtraProfileTypeInfo *extraInfo = ExtraProfileTypeInfo::Cast(val.GetTaggedObject());
if (!extraInfo->IsValid(thread)) {
continue;
}
AddObjectInfo(abcId, recordName, methodId, key.GetInt(), extraInfo->GetReceiverHClass(thread),
extraInfo->GetReceiverHClass(thread), extraInfo->GetHolderHClass(thread));
}
}
}
bool PGOProfiler::HasValidExtraProfileTypeInfo(JSFunction *func)
{
JSThread *thread = vm_->GetJSThread();
if (!func->HasProfileTypeInfo(thread)) {
return false;
}
ProfileTypeInfoCell *profileCell = ProfileTypeInfoCell::Cast(func->GetRawProfileTypeInfo(thread));
ProfileTypeInfo *profileInfo = ProfileTypeInfo::Cast((profileCell->GetValue(thread)).GetTaggedObject());
JSTaggedValue map = profileInfo->GetExtraInfoMap(thread);
if (map.IsHole() || map.IsUndefined()) {
return false;
}
NumberDictionary *numberDict = NumberDictionary::Cast(map.GetTaggedObject());
return numberDict->GetEntrySize() > 0;
}
void PGOProfiler::ProcessExtraProfileTypeInfo(JSFunction *func, ApEntityId abcId, const CString &recordName,
JSTaggedValue methodValue, WorkNode *current)
{
if (!HasValidExtraProfileTypeInfo(func)) {
return;
}
Method* method = Method::Cast(methodValue.GetTaggedObject());
EntityId methodId = method->GetMethodId();
UpdateExtraProfileTypeInfo(abcId, recordName, methodId, current);
}
void PGOProfiler::DumpBeforeDestroy(JSThread *thread)
{
if (!isEnable_ || !vm_->GetJSOptions().IsEnableProfileDump()) {
return;
}
LOG_PGO(INFO) << "dump profiler before destroy: " << this;
state_->StartDumpBeforeDestroy(thread);
{
if (g_isEnableCMCGC) {
ThreadNativeScope scope(thread);
HandlePGODump();
HandlePGOPreDump();
} else {
HandlePGODump();
HandlePGOPreDump();
}
}
state_->SetStopAndNotify();
}
void PGOProfiler::HandlePGOPreDump()
{
JSThread *thread = vm_->GetJSThread();
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "PGOProfiler::HandlePGOPreDump", "");
if (!isEnable_ || !vm_->GetJSOptions().IsEnableProfileDump()) {
return;
}
LockHolder lock(PGOProfilerManager::GetPGOInfoMutex());
ToRunningScope scope(holder_, g_isEnableCMCGC);
DISALLOW_GARBAGE_COLLECTION;
preDumpWorkList_.Iterate([this, thread](WorkNode *current) {
JSTaggedValue funcValue = JSTaggedValue(current->GetValue());
if (!funcValue.IsJSFunction()) {
return;
}
auto func = JSFunction::Cast(funcValue);
if (func->IsSendableOrConcurrentFunction(thread)) {
return;
}
auto lexicalEnv = func->GetLexicalEnv(thread);
auto globalEnv = BaseEnv::Cast(lexicalEnv.GetTaggedObject())->GetGlobalEnv(thread);
if (globalEnv.IsHole()) {
return;
}
JSTaggedValue methodValue = func->GetMethod(thread);
if (!methodValue.IsMethod()) {
return;
}
CString recordName = func->GetRecordName(thread);
if (recordName.empty()) {
return;
}
auto abcId = GetMethodAbcId(thread, func);
ProcessExtraProfileTypeInfo(func, abcId, recordName, methodValue, current);
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddMethod(thread, recordType, Method::Cast(methodValue), SampleMode::HOTNESS_MODE);
ProfileBytecode(abcId, recordName, funcValue);
if (PGOTrace::GetInstance()->IsEnable()) {
PGOTrace::GetInstance()->TryGetMethodData(thread, methodValue, false);
}
});
}
void PGOProfiler::HandlePGODump()
{
JSThread *thread = vm_->GetJSThread();
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "PGOProfiler::HandlePGODump", "");
if (!isEnable_ || !vm_->GetJSOptions().IsEnableProfileDump()) {
return;
}
LockHolder lock(PGOProfilerManager::GetPGOInfoMutex());
ToRunningScope scope(holder_, g_isEnableCMCGC);
DISALLOW_GARBAGE_COLLECTION;
auto current = PopFromProfileQueue();
while (current != nullptr) {
JSTaggedValue value = JSTaggedValue(current->GetValue());
if (value.IsUndefined()) {
current = PopFromProfileQueue();
continue;
}
if (!value.IsJSFunction()) {
current = PopFromProfileQueue();
continue;
}
auto func = JSFunction::Cast(value);
if (func->IsSendableOrConcurrentFunction(thread) || !IsProfileTypeInfoDumped(thread, func)) {
current = PopFromProfileQueue();
continue;
}
auto lexicalEnv = func->GetLexicalEnv(thread);
auto globalEnv = BaseEnv::Cast(lexicalEnv.GetTaggedObject())->GetGlobalEnv(thread);
if (globalEnv.IsHole()) {
current = PopFromProfileQueue();
continue;
}
JSTaggedValue methodValue = func->GetMethod(thread);
if (!methodValue.IsMethod()) {
current = PopFromProfileQueue();
continue;
}
CString recordName = func->GetRecordName(thread);
if (recordName.empty()) {
current = PopFromProfileQueue();
continue;
}
auto abcId = GetMethodAbcId(thread, func);
ProcessExtraProfileTypeInfo(func, abcId, recordName, methodValue, current);
ProfileType recordType = GetRecordProfileType(abcId, recordName);
if (recordInfos_->AddMethod(thread, recordType, Method::Cast(methodValue), SampleMode::HOTNESS_MODE)) {
methodCount_++;
}
ProfileBytecode(abcId, recordName, value);
current = PopFromProfileQueue();
if (PGOTrace::GetInstance()->IsEnable()) {
PGOTrace::GetInstance()->TryGetMethodData(thread, methodValue, true);
}
}
}
void PGOProfiler::TrySave()
{
if (manager_->IsForceDump()) {
return;
}
auto interval = std::chrono::system_clock::now() - saveTimestamp_;
auto minIntervalOption = vm_->GetJSOptions().GetPGOSaveMinInterval();
auto mergeMinInterval = std::chrono::milliseconds(minIntervalOption * MS_PRE_SECOND);
if (methodCount_ >= MERGED_EVERY_COUNT && interval > mergeMinInterval) {
LOG_PGO(INFO) << "trigger save task, methodCount_ = " << methodCount_;
state_->SetSaveAndNotify();
manager_->SavePGOInfo();
SetSaveTimestamp(std::chrono::system_clock::now());
methodCount_ = 0;
}
}
PGOProfiler::WorkNode* PGOProfiler::PopFromProfileQueue()
{
WorkNode* node = nullptr;
while (node == nullptr) {
if (g_isEnableCMCGC) {
holder_->CheckSafepointIfSuspended();
}
if (state_->GCIsWaiting()) {
break;
}
if (dumpWorkList_.IsEmpty()) {
break;
}
node = dumpWorkList_.PopFront();
}
return node;
}
void PGOProfiler::ProfileBytecode(ApEntityId abcId, const CString& recordName, JSTaggedValue funcValue)
{
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "PGOProfiler::ProfileBytecode", "");
ClockScope start;
JSFunction *function = JSFunction::Cast(funcValue);
JSThread *thread = vm_->GetAssociatedJSThread();
JSTaggedValue funcEnv = function->GetLexicalEnv(thread);
JSTaggedValue globalEnv = BaseEnv::Cast(funcEnv.GetTaggedObject())->GetGlobalEnv(thread);
SetCurrentGlobalEnv(globalEnv);
if (function->IsSendableOrConcurrentFunction(thread)) {
return;
}
Method *method = Method::Cast(function->GetMethod(thread));
JSTaggedValue profileTypeInfoVal = function->GetProfileTypeInfo(thread);
ASSERT(!profileTypeInfoVal.IsUndefined());
auto profileTypeInfo = ProfileTypeInfo::Cast(profileTypeInfoVal.GetTaggedObject());
auto methodId = method->GetMethodId();
auto pcStart = method->GetBytecodeArray();
auto codeSize = method->GetCodeSize(thread);
BytecodeInstruction bcIns(pcStart);
auto bcInsLast = bcIns.JumpTo(codeSize);
bool isForceDump = vm_->GetJSOptions().IsPgoForceDump();
while (bcIns.GetAddress() != bcInsLast.GetAddress()) {
if (!isForceDump) {
if (g_isEnableCMCGC) {
if (holder_->CheckSafepointIfSuspended()) {
break;
}
}
if (state_->GCIsWaiting()) {
break;
}
}
auto opcode = bcIns.GetOpcode();
auto bcOffset = bcIns.GetAddress() - pcStart;
auto pc = bcIns.GetAddress();
switch (opcode) {
case EcmaOpcode::LDTHISBYNAME_IMM8_ID16:
case EcmaOpcode::LDOBJBYNAME_IMM8_ID16:
case EcmaOpcode::LDPRIVATEPROPERTY_IMM8_IMM16_IMM16: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpICByName(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::LOAD);
break;
}
case EcmaOpcode::LDTHISBYNAME_IMM16_ID16:
case EcmaOpcode::LDOBJBYNAME_IMM16_ID16: {
uint16_t slotId = READ_INST_16_0();
DumpICByName(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::LOAD);
break;
}
case EcmaOpcode::LDOBJBYVALUE_IMM8_V8:
case EcmaOpcode::LDTHISBYVALUE_IMM8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpICByValue(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::LOAD);
break;
}
case EcmaOpcode::LDOBJBYVALUE_IMM16_V8:
case EcmaOpcode::LDTHISBYVALUE_IMM16: {
uint16_t slotId = READ_INST_16_0();
DumpICByValue(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::LOAD);
break;
}
case EcmaOpcode::STOBJBYNAME_IMM8_ID16_V8:
case EcmaOpcode::STTHISBYNAME_IMM8_ID16:
case EcmaOpcode::DEFINEPROPERTYBYNAME_IMM8_ID16_V8:
case EcmaOpcode::STPRIVATEPROPERTY_IMM8_IMM16_IMM16_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpICByName(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::STORE);
break;
}
case EcmaOpcode::STOBJBYNAME_IMM16_ID16_V8:
case EcmaOpcode::STTHISBYNAME_IMM16_ID16: {
uint16_t slotId = READ_INST_16_0();
DumpICByName(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::STORE);
break;
}
case EcmaOpcode::STOBJBYVALUE_IMM8_V8_V8:
case EcmaOpcode::STOWNBYINDEX_IMM8_V8_IMM16:
case EcmaOpcode::STTHISBYVALUE_IMM8_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpICByValue(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::STORE);
break;
}
case EcmaOpcode::STOBJBYVALUE_IMM16_V8_V8:
case EcmaOpcode::STOWNBYINDEX_IMM16_V8_IMM16:
case EcmaOpcode::STTHISBYVALUE_IMM16_V8: {
uint16_t slotId = READ_INST_16_0();
DumpICByValue(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, BCType::STORE);
break;
}
case EcmaOpcode::ADD2_IMM8_V8:
case EcmaOpcode::SUB2_IMM8_V8:
case EcmaOpcode::MUL2_IMM8_V8:
case EcmaOpcode::DIV2_IMM8_V8:
case EcmaOpcode::MOD2_IMM8_V8:
case EcmaOpcode::SHL2_IMM8_V8:
case EcmaOpcode::SHR2_IMM8_V8:
case EcmaOpcode::AND2_IMM8_V8:
case EcmaOpcode::OR2_IMM8_V8:
case EcmaOpcode::XOR2_IMM8_V8:
case EcmaOpcode::ASHR2_IMM8_V8:
case EcmaOpcode::EXP_IMM8_V8:
case EcmaOpcode::NEG_IMM8:
case EcmaOpcode::NOT_IMM8:
case EcmaOpcode::INC_IMM8:
case EcmaOpcode::DEC_IMM8:
case EcmaOpcode::EQ_IMM8_V8:
case EcmaOpcode::NOTEQ_IMM8_V8:
case EcmaOpcode::LESS_IMM8_V8:
case EcmaOpcode::LESSEQ_IMM8_V8:
case EcmaOpcode::GREATER_IMM8_V8:
case EcmaOpcode::GREATEREQ_IMM8_V8:
case EcmaOpcode::STRICTNOTEQ_IMM8_V8:
case EcmaOpcode::STRICTEQ_IMM8_V8:
case EcmaOpcode::TONUMERIC_IMM8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpOpType(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::CALLRUNTIME_ISTRUE_PREF_IMM8:
case EcmaOpcode::CALLRUNTIME_ISFALSE_PREF_IMM8: {
uint8_t slotId = READ_INST_8_1();
CHECK_SLOTID_BREAK(slotId);
DumpOpType(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::CALLARG0_IMM8:
case EcmaOpcode::CALLARG1_IMM8_V8:
case EcmaOpcode::CALLARGS2_IMM8_V8_V8:
case EcmaOpcode::CALLARGS3_IMM8_V8_V8_V8:
case EcmaOpcode::CALLRANGE_IMM8_IMM8_V8:
case EcmaOpcode::CALLTHIS0_IMM8_V8:
case EcmaOpcode::CALLTHIS1_IMM8_V8_V8:
case EcmaOpcode::CALLTHIS2_IMM8_V8_V8_V8:
case EcmaOpcode::CALLTHIS3_IMM8_V8_V8_V8_V8:
case EcmaOpcode::CALLTHISRANGE_IMM8_IMM8_V8:
case EcmaOpcode::CALLTHIS0WITHNAME_IMM8_ID16_V8:
case EcmaOpcode::CALLTHIS1WITHNAME_IMM8_ID16_V8_V8:
case EcmaOpcode::CALLTHIS2WITHNAME_IMM8_ID16_V8_V8_V8:
case EcmaOpcode::CALLTHIS3WITHNAME_IMM8_ID16_V8_V8_V8_V8:
case EcmaOpcode::CALLTHISRANGEWITHNAME_IMM8_IMM8_ID16_V8:
case EcmaOpcode::SUPERCALLTHISRANGE_IMM8_IMM8_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpCall(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::CALLRUNTIME_CALLINIT_PREF_IMM8_V8: {
uint8_t slotId = READ_INST_8_1();
CHECK_SLOTID_BREAK(slotId);
DumpCall(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::WIDE_CALLRANGE_PREF_IMM16_V8:
case EcmaOpcode::WIDE_CALLTHISRANGE_PREF_IMM16_V8:
case EcmaOpcode::WIDE_CALLTHISRANGEWITHNAME_PREF_IMM16_ID16_V8: {
break;
}
case EcmaOpcode::NEWOBJRANGE_IMM8_IMM8_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpNewObjRange(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::NEWOBJRANGE_IMM16_IMM8_V8: {
uint16_t slotId = READ_INST_16_0();
DumpNewObjRange(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::WIDE_NEWOBJRANGE_PREF_IMM16_V8: {
break;
}
case EcmaOpcode::DEFINECLASSWITHBUFFER_IMM8_ID16_ID16_IMM16_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpDefineClass(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::DEFINECLASSWITHBUFFER_IMM16_ID16_ID16_IMM16_V8: {
uint16_t slotId = READ_INST_16_0();
DumpDefineClass(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::DEFINEFUNC_IMM8_ID16_IMM8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpDefineClass(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::DEFINEFUNC_IMM16_ID16_IMM8: {
uint16_t slotId = READ_INST_16_0();
DumpDefineClass(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::CREATEOBJECTWITHBUFFER_IMM8_ID16:
case EcmaOpcode::CREATEARRAYWITHBUFFER_IMM8_ID16:
case EcmaOpcode::CREATEEMPTYARRAY_IMM8: {
if (method->GetJSPandaFile(thread) == nullptr) {
break;
}
auto header = method->GetJSPandaFile(thread)->GetPandaFile()->GetHeader();
auto traceId =
static_cast<int32_t>(reinterpret_cast<uintptr_t>(pc) - reinterpret_cast<uintptr_t>(header));
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpCreateObject(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, traceId);
break;
}
case EcmaOpcode::CREATEOBJECTWITHBUFFER_IMM16_ID16:
case EcmaOpcode::CREATEARRAYWITHBUFFER_IMM16_ID16:
case EcmaOpcode::CREATEEMPTYARRAY_IMM16: {
if (method->GetJSPandaFile(thread) == nullptr) {
break;
}
auto header = method->GetJSPandaFile(thread)->GetPandaFile()->GetHeader();
auto traceId =
static_cast<int32_t>(reinterpret_cast<uintptr_t>(pc) - reinterpret_cast<uintptr_t>(header));
uint16_t slotId = READ_INST_16_0();
DumpCreateObject(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo, traceId);
break;
}
case EcmaOpcode::GETITERATOR_IMM8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpGetIterator(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::GETITERATOR_IMM16: {
uint16_t slotId = READ_INST_16_0();
DumpGetIterator(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::INSTANCEOF_IMM8_V8: {
uint8_t slotId = READ_INST_8_0();
CHECK_SLOTID_BREAK(slotId);
DumpInstanceof(abcId, recordName, methodId, bcOffset, slotId, profileTypeInfo);
break;
}
case EcmaOpcode::DEFINEGETTERSETTERBYVALUE_V8_V8_V8_V8:
default:
break;
}
bcIns = bcIns.GetNext();
}
if (PGOTrace::GetInstance()->IsEnable()) {
auto methodData = PGOTrace::GetInstance()->TryGetMethodData(thread, function->GetMethod(thread));
methodData->SetProfileBytecodeTime(start.TotalSpentTime());
}
}
void PGOProfiler::DumpICByName(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo, BCType type)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue firstValue = profileTypeInfo->Get(thread, slotId);
if (!firstValue.IsHeapObject()) {
if (firstValue.IsHole()) {
AddObjectInfoWithMega(abcId, recordName, methodId, bcOffset);
}
return;
}
if (firstValue.IsWeak()) {
TaggedObject *object = firstValue.GetWeakReferentUnChecked();
if (object->GetClass()->IsHClass()) {
JSTaggedValue secondValue = profileTypeInfo->Get(thread, slotId + 1);
JSHClass *hclass = JSHClass::Cast(object);
DumpICByNameWithHandler(abcId, recordName, methodId, bcOffset, hclass, secondValue, type);
}
return;
}
DumpICByNameWithPoly(abcId, recordName, methodId, bcOffset, firstValue, type);
}
void PGOProfiler::DumpICByValue(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo, BCType type)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue firstValue = profileTypeInfo->Get(thread, slotId);
if (!firstValue.IsHeapObject()) {
if (firstValue.IsHole()) {
AddObjectInfoWithMega(abcId, recordName, methodId, bcOffset);
}
return;
}
if (firstValue.IsWeak()) {
TaggedObject *object = firstValue.GetWeakReferentUnChecked();
if (object->GetClass()->IsHClass()) {
JSTaggedValue secondValue = profileTypeInfo->Get(thread, slotId + 1);
JSHClass *hclass = JSHClass::Cast(object);
DumpICByValueWithHandler(abcId, recordName, methodId, bcOffset, hclass, secondValue, type);
}
return;
}
if ((firstValue.IsString() || firstValue.IsSymbol())) {
JSTaggedValue secondValue = profileTypeInfo->Get(thread, slotId + 1);
DumpICByValueWithPoly(abcId, recordName, methodId, bcOffset, secondValue, type);
return;
}
DumpICByValueWithPoly(abcId, recordName, methodId, bcOffset, firstValue, type);
}
void PGOProfiler::DumpICByNameWithPoly(ApEntityId abcId,
const CString &recordName, EntityId methodId, int32_t bcOffset, JSTaggedValue cacheValue, BCType type)
{
if (cacheValue.IsWeak()) {
return;
}
ASSERT(cacheValue.IsTaggedArray());
const JSThread *thread = vm_->GetJSThread();
auto array = TaggedArray::Cast(cacheValue);
uint32_t length = array->GetLength();
for (uint32_t i = 0; i < length; i += 2) {
auto result = array->Get(thread, i);
auto handler = array->Get(thread, i + 1);
if (!result.IsHeapObject() || !result.IsWeak()) {
continue;
}
TaggedObject *object = result.GetWeakReferentUnChecked();
if (!object->GetClass()->IsHClass()) {
continue;
}
JSHClass *hclass = JSHClass::Cast(object);
if (!DumpICByNameWithHandler(abcId, recordName, methodId, bcOffset, hclass, handler, type)) {
AddObjectInfoWithMega(abcId, recordName, methodId, bcOffset);
break;
}
}
}
void PGOProfiler::DumpICByValueWithPoly(ApEntityId abcId,
const CString &recordName, EntityId methodId, int32_t bcOffset, JSTaggedValue cacheValue, BCType type)
{
if (cacheValue.IsWeak()) {
return;
}
if (!cacheValue.IsTaggedArray()) {
return;
}
auto array = TaggedArray::Cast(cacheValue);
const JSThread *thread = vm_->GetJSThread();
uint32_t length = array->GetLength();
for (uint32_t i = 0; i < length; i += 2) {
auto result = array->Get(thread, i);
auto handler = array->Get(thread, i + 1);
if (!result.IsHeapObject() || !result.IsWeak()) {
continue;
}
TaggedObject *object = result.GetWeakReferentUnChecked();
if (!object->GetClass()->IsHClass()) {
continue;
}
JSHClass *hclass = JSHClass::Cast(object);
DumpICByValueWithHandler(abcId, recordName, methodId, bcOffset, hclass, handler, type);
}
}
bool PGOProfiler::DumpICByNameWithHandler(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, JSHClass *hclass, JSTaggedValue secondValue, BCType type)
{
TryDumpProtoTransitionType(hclass);
if (type == BCType::LOAD) {
return DumpICLoadByNameWithHandler(abcId, recordName, methodId, bcOffset, hclass, secondValue);
}
if (secondValue.IsInt()) {
return AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, hclass);
} else if (secondValue.IsTransitionHandler()) {
if (!hclass->GetPrototype(vm_->GetJSThread()).IsHeapObject()) {
return false;
}
auto transitionHandler = TransitionHandler::Cast(secondValue.GetTaggedObject());
auto transitionHClassVal = transitionHandler->GetTransitionHClass(vm_->GetJSThread());
if (transitionHClassVal.IsJSHClass()) {
auto transitionHClass = JSHClass::Cast(transitionHClassVal.GetTaggedObject());
return AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, transitionHClass);
}
} else if (secondValue.IsTransWithProtoHandler()) {
auto transWithProtoHandler = TransWithProtoHandler::Cast(secondValue.GetTaggedObject());
auto cellValue = transWithProtoHandler->GetProtoCell(vm_->GetJSThread());
if (CheckProtoChangeMarker(cellValue)) {
return false;
}
auto transitionHClassVal = transWithProtoHandler->GetTransitionHClass(vm_->GetJSThread());
if (transitionHClassVal.IsJSHClass()) {
auto transitionHClass = JSHClass::Cast(transitionHClassVal.GetTaggedObject());
return AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, transitionHClass);
}
} else if (secondValue.IsPrototypeHandler()) {
auto prototypeHandler = PrototypeHandler::Cast(secondValue.GetTaggedObject());
auto cellValue = prototypeHandler->GetProtoCell(vm_->GetJSThread());
if (CheckProtoChangeMarker(cellValue)) {
return false;
}
auto holder = prototypeHandler->GetHolder(vm_->GetJSThread());
auto holderHClass = holder.GetTaggedObject()->GetClass();
auto accessorMethodId = prototypeHandler->GetAccessorMethodId();
return AddObjectInfo(
abcId, recordName, methodId, bcOffset, hclass, holderHClass, holderHClass, accessorMethodId);
} else if (secondValue.IsStoreAOTHandler()) {
StoreAOTHandler *storeAOTHandler = StoreAOTHandler::Cast(secondValue.GetTaggedObject());
auto cellValue = storeAOTHandler->GetProtoCell(vm_->GetJSThread());
if (CheckProtoChangeMarker(cellValue)) {
return false;
}
auto holder = storeAOTHandler->GetHolder(vm_->GetJSThread());
auto holderHClass = holder.GetTaggedObject()->GetClass();
return AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, holderHClass, holderHClass);
}
return false;
}
bool PGOProfiler::DumpICLoadByNameWithHandler(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, JSHClass *hclass, JSTaggedValue secondValue)
{
bool ret = false;
if (secondValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(secondValue.GetInt());
if (HandlerBase::IsNonExist(handlerInfo)) {
return ret;
}
if (HandlerBase::IsField(handlerInfo) || HandlerBase::IsAccessor(handlerInfo)) {
if (AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, hclass)) {
return true;
}
}
return AddBuiltinsInfoByNameInInstance(abcId, recordName, methodId, bcOffset, hclass);
} else if (secondValue.IsPrototypeHandler()) {
auto prototypeHandler = PrototypeHandler::Cast(secondValue.GetTaggedObject());
auto cellValue = prototypeHandler->GetProtoCell(vm_->GetJSThread());
if (CheckProtoChangeMarker(cellValue)) {
return ret;
}
JSTaggedValue handlerInfoVal = prototypeHandler->GetHandlerInfo(vm_->GetJSThread());
if (!handlerInfoVal.IsInt()) {
return ret;
}
auto handlerInfo = static_cast<uint32_t>(handlerInfoVal.GetInt());
if (HandlerBase::IsNonExist(handlerInfo)) {
return ret;
}
auto holder = prototypeHandler->GetHolder(vm_->GetJSThread());
auto holderHClass = holder.GetTaggedObject()->GetClass();
auto accessorMethodId = prototypeHandler->GetAccessorMethodId();
if (!AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, holderHClass,
holderHClass, accessorMethodId)) {
return AddBuiltinsInfoByNameInProt(abcId, recordName, methodId, bcOffset, hclass, holderHClass);
}
return true;
}
return false;
}
void PGOProfiler::DumpICByValueWithHandler(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, JSHClass *hclass, JSTaggedValue secondValue, BCType type)
{
TryDumpProtoTransitionType(hclass);
if (type == BCType::LOAD) {
if (secondValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(secondValue.GetInt());
if (HandlerBase::IsNormalElement(handlerInfo) || HandlerBase::IsStringElement(handlerInfo)) {
if (HandlerBase::NeedSkipInPGODump(handlerInfo)) {
return;
}
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, hclass);
return;
}
if (HandlerBase::IsTypedArrayElement(handlerInfo)) {
OnHeapMode onHeap = HandlerBase::IsOnHeap(handlerInfo) ? OnHeapMode::ON_HEAP : OnHeapMode::NOT_ON_HEAP;
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, onHeap);
return;
}
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, hclass);
}
return;
}
if (secondValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(secondValue.GetInt());
if (HandlerBase::IsNormalElement(handlerInfo) || HandlerBase::IsStringElement(handlerInfo)) {
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, hclass,
OnHeapMode::NONE, HandlerBase::IsStoreOutOfBounds(handlerInfo));
return;
}
if (HandlerBase::IsTypedArrayElement(handlerInfo)) {
OnHeapMode onHeap = HandlerBase::IsOnHeap(handlerInfo) ? OnHeapMode::ON_HEAP : OnHeapMode::NOT_ON_HEAP;
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, onHeap,
HandlerBase::IsStoreOutOfBounds(handlerInfo));
return;
}
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, hclass);
} else if (secondValue.IsTransitionHandler()) {
auto transitionHandler = TransitionHandler::Cast(secondValue.GetTaggedObject());
auto transitionHClassVal = transitionHandler->GetTransitionHClass(vm_->GetJSThread());
if (!transitionHClassVal.IsJSHClass()) {
return ;
}
auto handlerInfoValue = transitionHandler->GetHandlerInfo(vm_->GetJSThread());
auto transitionHClass = JSHClass::Cast(transitionHClassVal.GetTaggedObject());
if (handlerInfoValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(handlerInfoValue.GetInt());
if (HandlerBase::IsElement(handlerInfo)) {
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, transitionHClass,
OnHeapMode::NONE, HandlerBase::IsStoreOutOfBounds(handlerInfo));
return;
}
}
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, transitionHClass);
} else if (secondValue.IsTransWithProtoHandler()) {
auto transWithProtoHandler = TransWithProtoHandler::Cast(secondValue.GetTaggedObject());
auto transitionHClassVal = transWithProtoHandler->GetTransitionHClass(vm_->GetJSThread());
if (!transitionHClassVal.IsJSHClass()) {
return ;
}
auto handlerInfoValue = transWithProtoHandler->GetHandlerInfo(vm_->GetJSThread());
auto transitionHClass = JSHClass::Cast(transitionHClassVal.GetTaggedObject());
if (handlerInfoValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(handlerInfoValue.GetInt());
if (HandlerBase::IsElement(handlerInfo)) {
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, transitionHClass,
OnHeapMode::NONE, HandlerBase::IsStoreOutOfBounds(handlerInfo));
return;
}
}
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, transitionHClass);
} else if (secondValue.IsPrototypeHandler()) {
PrototypeHandler *prototypeHandler = PrototypeHandler::Cast(secondValue.GetTaggedObject());
auto cellValue = prototypeHandler->GetProtoCell(vm_->GetJSThread());
if (!cellValue.IsProtoChangeMarker()) {
return;
}
ASSERT(cellValue.IsProtoChangeMarker());
ProtoChangeMarker *cell = ProtoChangeMarker::Cast(cellValue.GetTaggedObject());
if (cell->GetHasChanged()) {
return;
}
JSTaggedValue handlerInfoValue = prototypeHandler->GetHandlerInfo(vm_->GetJSThread());
if (handlerInfoValue.IsInt()) {
auto handlerInfo = static_cast<uint32_t>(handlerInfoValue.GetInt());
if (HandlerBase::IsElement(handlerInfo)) {
AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, hclass, hclass,
OnHeapMode::NONE, HandlerBase::IsStoreOutOfBounds(handlerInfo));
return;
}
}
auto holder = prototypeHandler->GetHolder(vm_->GetJSThread());
auto holderHClass = holder.GetTaggedObject()->GetClass();
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, holderHClass, holderHClass);
}
}
void PGOProfiler::TryDumpProtoTransitionType(JSHClass *hclass)
{
if (hclass->IsCompositeHClass()) {
return;
}
auto thread = vm_->GetJSThread();
JSHClass *ihc1 = JSHClass::FindRootHClass(thread, hclass);
auto transitionType = GetProfileType(ihc1, true);
if (!transitionType.IsRootType() || !transitionType.IsTransitionClassType()) {
return;
}
JSTaggedValue phc1Root = JSHClass::FindProtoRootHClass(thread, ihc1);
if (!phc1Root.IsJSHClass()) {
LOG_PGO(DEBUG) << "Phc1Root is not a JSHclass!";
return;
}
auto transitionPrototype = GetProfileType(JSHClass::Cast(phc1Root.GetTaggedObject()), true);
if (!transitionPrototype.IsRootType()) {
LOG_PGO(DEBUG) << "Set as the prototype of a function again after transition happened for this prototype!";
return;
}
auto* transitionTable = vm_->GetFunctionProtoTransitionTable();
JSTaggedType ihc0 = transitionTable->GetFakeParent(JSTaggedType(ihc1));
JSTaggedType baseIhc = transitionTable->GetFakeParent(phc1Root.GetRawData());
if ((ihc0 == 0) || (baseIhc == 0)) {
return;
}
auto ihc0Obj = JSHClass::Cast(JSTaggedValue(ihc0).GetTaggedObject());
auto baseIhcObj = JSHClass::Cast(JSTaggedValue(baseIhc).GetTaggedObject());
UpdateLayout(ihc0Obj);
UpdateLayout(ihc1);
UpdateLayout(baseIhcObj);
auto ihc0RootType = GetProfileType(ihc0Obj);
ASSERT(ihc0RootType.IsRootType());
auto baseRootHClass = JSHClass::FindRootHClass(thread, baseIhcObj);
auto baseRootType = GetProfileType(baseRootHClass, true);
if (!baseRootType.IsRootType()) {
LOG_PGO(DEBUG) << "Unsupported prototypes which cannot be recorded!";
return;
}
auto baseType = GetProfileType(baseIhcObj);
ASSERT(!baseType.IsNone());
PGOProtoTransitionType protoTransitionType(ihc0RootType);
protoTransitionType.SetBaseType(baseRootType, baseType);
protoTransitionType.SetTransitionType(transitionType);
protoTransitionType.SetTransitionProtoPt(transitionPrototype);
recordInfos_->GetProtoTransitionPool()->Add(protoTransitionType);
}
void PGOProfiler::DumpOpType(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
JSTaggedValue slotValue = profileTypeInfo->Get(vm_->GetJSThread(), slotId);
if (slotValue.IsInt()) {
auto type = slotValue.GetInt();
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddType(recordType, methodId, bcOffset, PGOSampleType(type));
}
}
bool PGOProfiler::FunctionKindVerify(const JSFunction *ctorFunction)
{
FunctionKind kind = Method::Cast(ctorFunction->GetMethod(vm_->GetJSThread()))->GetFunctionKind();
return kind == FunctionKind::BASE_CONSTRUCTOR ||
kind == FunctionKind::CLASS_CONSTRUCTOR ||
kind == FunctionKind::DERIVED_CONSTRUCTOR;
}
void PGOProfiler::DumpDefineClass(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue slotValue = profileTypeInfo->Get(thread, slotId);
if (!slotValue.IsProfileTypeInfoCell0()) {
return;
}
JSTaggedValue handle = ProfileTypeInfoCell::Cast(slotValue)->GetHandle(thread);
if (!handle.IsHeapObject() || !handle.IsWeak()) {
return;
}
auto object = handle.GetWeakReferentUnChecked();
if (object->GetClass()->IsJSFunction()) {
JSFunction *ctorFunction = JSFunction::Cast(object);
auto ctorMethod = ctorFunction->GetMethod(thread);
if (!ctorMethod.IsMethod() || !FunctionKindVerify(ctorFunction)) {
return;
}
ApEntityId ctorAbcId = GetMethodAbcId(thread, ctorFunction);
auto ctorJSMethod = Method::Cast(ctorMethod);
auto ctorMethodId = ctorJSMethod->GetMethodId().GetOffset();
auto localType = ProfileType(ctorAbcId, ctorMethodId, ProfileType::Kind::ClassId, true);
if (IsSkippableObjectTypeSafe(localType)) {
return;
}
auto protoOrHClass = ctorFunction->GetProtoOrHClass(thread);
if (protoOrHClass.IsJSHClass() &&
!JSHClass::Cast(protoOrHClass.GetTaggedObject())->IsOnlyJSObject()) {
return;
}
PGODefineOpType objDefType(localType);
if (protoOrHClass.IsJSHClass()) {
auto ihc = JSHClass::Cast(protoOrHClass.GetTaggedObject());
SetRootProfileType(ihc, ctorAbcId, ctorMethodId, ProfileType::Kind::ClassId);
recordInfos_->AddRootLayout(thread, JSTaggedType(ihc), localType);
protoOrHClass = ihc->GetProto(thread);
}
auto ctorRootHClass = JSHClass::FindRootHClass(thread, ctorFunction->GetJSHClass());
auto ctorType = GetProfileType(ctorRootHClass);
if (!ctorType.IsRootType()) {
LOG_PGO(DEBUG) << "The profileType of constructor root hclass was not found.";
} else {
objDefType.SetCtorPt(ctorType);
recordInfos_->AddRootLayout(thread, JSTaggedType(ctorRootHClass), ctorType);
}
if (protoOrHClass.IsJSObject()) {
auto prototypeHClass = JSObject::Cast(protoOrHClass)->GetClass();
auto prototypeRootHClass = JSHClass::FindRootHClass(thread, prototypeHClass);
ProfileType prototypeType = GetProfileType(prototypeRootHClass);
if (!prototypeType.IsRootType()) {
LOG_PGO(DEBUG) << "The profileType of prototype root hclass was not found.";
} else {
objDefType.SetPrototypePt(prototypeType);
recordInfos_->AddRootLayout(thread, JSTaggedType(prototypeRootHClass), prototypeType);
}
}
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddDefine(recordType, methodId, bcOffset, objDefType);
}
}
void PGOProfiler::DumpCreateObject(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo, int32_t traceId)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue slotValue = profileTypeInfo->Get(thread, slotId);
if (!slotValue.IsHeapObject()) {
return;
}
ProfileType recordType = GetRecordProfileType(abcId, recordName);
if (slotValue.IsWeak()) {
auto object = slotValue.GetWeakReferentUnChecked();
if (object->GetClass()->IsHClass()) {
auto newHClass = JSHClass::Cast(object);
auto rootHClass = JSHClass::FindRootHClass(thread, newHClass);
ProfileType profileType = GetProfileType(rootHClass);
if (!profileType.IsRootType()) {
return;
}
ASSERT(profileType.GetKind() == ProfileType::Kind::ObjectLiteralId);
PGOSampleType currentType(profileType);
PGODefineOpType objDefType(profileType);
recordInfos_->AddDefine(recordType, methodId, bcOffset, objDefType);
recordInfos_->AddRootLayout(thread, JSTaggedType(rootHClass), profileType);
}
} else if (slotValue.IsTrackInfoObject()) {
auto currentType = PGOSampleType::CreateProfileType(abcId, traceId, ProfileType::Kind::ArrayLiteralId, true);
auto profileType = currentType.GetProfileType();
PGODefineOpType objDefType(profileType);
TrackInfo *trackInfo = TrackInfo::Cast(slotValue.GetTaggedObject());
auto elementsKind = trackInfo->GetElementsKind();
objDefType.SetElementsKind(elementsKind);
objDefType.SetElementsLength(trackInfo->GetArrayLength());
objDefType.SetSpaceFlag(trackInfo->GetSpaceFlag());
recordInfos_->AddDefine(recordType, methodId, bcOffset, objDefType);
auto cachedHClass = trackInfo->GetCachedHClass(thread);
if (cachedHClass.IsJSHClass()) {
auto hclass = JSHClass::Cast(cachedHClass.GetTaggedObject());
recordInfos_->AddRootLayout(thread, JSTaggedType(hclass), profileType);
}
}
}
void PGOProfiler::DumpCall(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue slotValue = profileTypeInfo->Get(thread, slotId);
ProfileType::Kind kind;
int calleeMethodId = 0;
ApEntityId calleeAbcId = 0;
if (slotValue.IsFuncSlot()) {
auto funcSlot = FuncSlot::Cast(slotValue);
slotValue = funcSlot->GetFunction(thread);
}
if (slotValue.IsInt()) {
calleeMethodId = slotValue.GetInt();
calleeAbcId = abcId;
ASSERT(calleeMethodId <= 0);
if (calleeMethodId == 0) {
kind = ProfileType::Kind::MethodId;
} else {
kind = ProfileType::Kind::BuiltinFunctionId;
}
} else if (slotValue.IsJSFunction()) {
JSFunction *callee = JSFunction::Cast(slotValue);
Method *calleeMethod = Method::Cast(callee->GetMethod(thread));
calleeMethodId = static_cast<int>(calleeMethod->GetMethodId().GetOffset());
calleeAbcId = GetMethodAbcId(thread, callee->GetMethod(thread));
kind = ProfileType::Kind::MethodId;
} else {
return;
}
PGOSampleType type = PGOSampleType::CreateProfileType(calleeAbcId, std::abs(calleeMethodId), kind);
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddCallTargetType(recordType, methodId, bcOffset, type);
}
void PGOProfiler::DumpGetIterator(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
const JSThread *thread = vm_->GetJSThread();
if (thread->GetEnableLazyBuiltins()) {
return;
}
JSTaggedValue value = profileTypeInfo->Get(thread, slotId);
if (!value.IsInt()) {
return;
}
int iterKind = value.GetInt();
ASSERT(iterKind <= 0);
ProfileType::Kind pgoKind = ProfileType::Kind::BuiltinFunctionId;
PGOSampleType type = PGOSampleType::CreateProfileType(abcId, std::abs(iterKind), pgoKind);
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddCallTargetType(recordType, methodId, bcOffset, type);
}
void PGOProfiler::DumpNewObjRange(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue slotValue = profileTypeInfo->Get(thread, slotId);
int ctorMethodId = 0;
if (slotValue.IsFuncSlot()) {
auto funcSlot = FuncSlot::Cast(slotValue);
slotValue = funcSlot->GetFunction(thread);
}
if (slotValue.IsInt()) {
ctorMethodId = slotValue.GetInt();
} else if (slotValue.IsJSFunction()) {
JSFunction *callee = JSFunction::Cast(slotValue);
Method *calleeMethod = Method::Cast(callee->GetMethod(thread));
ctorMethodId = static_cast<int>(calleeMethod->GetMethodId().GetOffset());
} else {
return;
}
PGOSampleType type;
if (ctorMethodId > 0) {
type = PGOSampleType::CreateProfileType(abcId, ctorMethodId, ProfileType::Kind::ClassId, true);
} else {
auto kind = ProfileType::Kind::BuiltinFunctionId;
type = PGOSampleType::CreateProfileType(abcId, std::abs(ctorMethodId), kind);
}
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddCallTargetType(recordType, methodId, bcOffset, type);
}
void PGOProfiler::DumpInstanceof(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
uint32_t slotId, ProfileTypeInfo *profileTypeInfo)
{
const JSThread *thread = vm_->GetJSThread();
JSTaggedValue firstValue = profileTypeInfo->Get(thread, slotId);
if (!firstValue.IsHeapObject()) {
if (firstValue.IsHole()) {
AddObjectInfoWithMega(abcId, recordName, methodId, bcOffset);
}
return;
}
if (firstValue.IsWeak()) {
TaggedObject *object = firstValue.GetWeakReferentUnChecked();
if (object->GetClass()->IsHClass()) {
JSHClass *hclass = JSHClass::Cast(object);
JSHandle<GlobalEnv> env = GetCurrentGlobalEnv();
JSTaggedValue key = thread->GlobalConstants()->GetHasInstanceSymbol();
JSHClass *functionPrototypeHC = JSObject::Cast(env->GetFunctionPrototype().GetTaggedValue())->GetClass();
JSTaggedValue foundHClass = TryFindKeyInPrototypeChain(object, hclass, key);
if (!foundHClass.IsUndefined() && JSHClass::Cast(foundHClass.GetTaggedObject()) != functionPrototypeHC) {
return;
}
AddObjectInfo(abcId, recordName, methodId, bcOffset, hclass, hclass, hclass);
}
return;
}
return;
}
void PGOProfiler::UpdateLayout(JSHClass *hclass)
{
JSThread *thread = vm_->GetJSThread();
auto parentHClass = hclass->GetParent(thread);
if (!GetProfileType(hclass).IsRootType() && parentHClass.IsJSHClass()) {
UpdateTransitionLayout(JSHClass::Cast(parentHClass.GetTaggedObject()), hclass);
} else {
auto rootHClass = JSHClass::FindRootHClass(thread, hclass);
ProfileType rootType = GetProfileType(rootHClass, true);
if (!rootType.IsRootType()) {
return;
}
auto prototypeHClass = JSHClass::FindProtoRootHClass(thread, rootHClass);
if (prototypeHClass.IsJSHClass()) {
auto prototypeObject = JSHClass::Cast(prototypeHClass.GetTaggedObject());
ProfileType prototypeType = GetProfileType(prototypeObject, true);
if (prototypeType.IsRootType()) {
recordInfos_->AddRootPtType(rootType, prototypeType);
UpdateLayout(JSHClass::Cast(prototypeHClass.GetTaggedObject()));
}
}
auto curType = GetOrInsertProfileType(hclass, rootType);
recordInfos_->UpdateLayout(thread, rootType, JSTaggedType(hclass), curType);
}
}
void PGOProfiler::UpdateTransitionLayout(JSHClass* parent, JSHClass* child)
{
JSThread *thread = vm_->GetJSThread();
auto rootHClass = JSHClass::FindRootHClass(thread, parent);
auto rootType = GetProfileType(rootHClass, true);
if (!rootType.IsRootType()) {
return;
}
if (GetProfileType(child).IsRootType()) {
return;
}
CStack<JSHClass *> hclassVec;
hclassVec.emplace(child);
hclassVec.emplace(parent);
while (!GetProfileType(parent).IsRootType()) {
auto parentHCValue = parent->GetParent(thread);
if (!parentHCValue.IsJSHClass()) {
break;
}
parent = JSHClass::Cast(parentHCValue.GetTaggedObject());
hclassVec.emplace(parent);
}
auto prototypeRootHClassVal = JSHClass::FindProtoRootHClass(thread, rootHClass);
if (prototypeRootHClassVal.IsJSHClass()) {
auto prototypeRootHClass = JSHClass::Cast(prototypeRootHClassVal.GetTaggedObject());
auto prototypeType = GetProfileType(prototypeRootHClass);
if (prototypeType.IsRootType()) {
recordInfos_->AddRootPtType(rootType, prototypeType);
UpdateLayout(prototypeRootHClass);
}
}
parent = hclassVec.top();
hclassVec.pop();
auto parentType = GetProfileType(parent);
while (!hclassVec.empty()) {
child = hclassVec.top();
hclassVec.pop();
auto childType = GetOrInsertProfileType(child, rootType);
recordInfos_->UpdateTransitionLayout(thread, rootType, JSTaggedType(parent), parentType, JSTaggedType(child),
childType);
parentType = childType;
parent = child;
}
}
bool PGOProfiler::AddTransitionObjectInfo(ProfileType recordType,
EntityId methodId,
int32_t bcOffset,
JSHClass* receiver,
JSHClass* hold,
JSHClass* holdTra,
PGOSampleType accessorMethod)
{
if (receiver->IsCompositeHClass()) {
return false;
}
auto receiverRootType = FindRootProfileType(receiver);
if (!receiverRootType.IsRootType()) {
return false;
}
auto holdRootType = FindRootProfileType(hold);
if (!holdRootType.IsRootType()) {
return true;
}
auto receiverType = GetOrInsertProfileType(receiver, receiverRootType);
auto holdType = GetOrInsertProfileType(hold, holdRootType);
auto holdTraType = GetOrInsertProfileType(holdTra, holdRootType);
if (receiver != hold) {
UpdateLayout(receiver);
}
if (holdType == holdTraType) {
UpdateLayout(hold);
} else {
UpdateTransitionLayout(hold, holdTra);
}
PGOObjectInfo info(receiverRootType, receiverType, holdRootType, holdType, holdRootType, holdTraType,
accessorMethod);
UpdatePrototypeChainInfo(receiver, hold, info);
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
return true;
}
bool PGOProfiler::AddObjectInfo(ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset,
JSHClass *receiver, JSHClass *hold, JSHClass *holdTra, uint32_t accessorMethodId)
{
PGOSampleType accessor = PGOSampleType::CreateProfileType(abcId, accessorMethodId, ProfileType::Kind::MethodId);
ProfileType recordType = GetRecordProfileType(abcId, recordName);
return AddTransitionObjectInfo(recordType, methodId, bcOffset, receiver, hold, holdTra, accessor);
}
void PGOProfiler::UpdatePrototypeChainInfo(JSHClass *receiver, JSHClass *holder, PGOObjectInfo &info)
{
if (receiver == holder) {
return;
}
std::vector<std::pair<ProfileType, ProfileType>> protoChain;
JSTaggedValue proto = JSHClass::FindProtoHClass(vm_->GetJSThread(), receiver);
while (proto.IsJSHClass()) {
auto protoHClass = JSHClass::Cast(proto.GetTaggedObject());
if (protoHClass == holder) {
break;
}
auto protoRootType = FindRootProfileType(protoHClass);
if (!protoRootType.IsRootType()) {
break;
}
auto protoType = GetOrInsertProfileType(protoHClass, protoRootType);
protoChain.emplace_back(protoRootType, protoType);
proto = JSHClass::FindProtoHClass(vm_->GetJSThread(), protoHClass);
}
if (!protoChain.empty()) {
info.AddPrototypePt(protoChain);
}
}
void PGOProfiler::AddObjectInfoWithMega(
ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset)
{
auto megaType = ProfileType::CreateMegaType();
PGOObjectInfo info(megaType, megaType, megaType, megaType, megaType, megaType, PGOSampleType());
ProfileType recordType = GetRecordProfileType(abcId, recordName);
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
}
bool PGOProfiler::AddBuiltinsInfoByNameInInstance(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, JSHClass *receiver)
{
auto thread = vm_->GetJSThread();
auto type = receiver->GetObjectType();
const auto &ctorEntries = thread->GetCtorHclassEntries();
auto entry = ctorEntries.find(receiver);
if (entry != ctorEntries.end()) {
AddBuiltinsGlobalInfo(abcId, recordName, methodId, bcOffset, entry->second);
return true;
}
auto builtinsId = ToBuiltinsTypeId(type);
if (!builtinsId.has_value()) {
return false;
}
JSHClass *exceptRecvHClass = nullptr;
if (builtinsId == BuiltinTypeId::ARRAY) {
bool receiverIsPrototype = receiver->IsPrototype();
exceptRecvHClass = thread->GetArrayInstanceHClass(GetCurrentGlobalEnv(), receiver->GetElementsKind(),
receiverIsPrototype, JSThread::ThreadKind::OtherThread);
} else if (builtinsId == BuiltinTypeId::STRING) {
exceptRecvHClass = receiver;
} else {
exceptRecvHClass = thread->GetBuiltinInstanceHClass(builtinsId.value());
}
if (exceptRecvHClass != receiver) {
if (builtinsId == BuiltinTypeId::OBJECT) {
JSHandle<GlobalEnv> env = GetCurrentGlobalEnv();
exceptRecvHClass = JSHClass::Cast(env->GetIteratorResultClass().GetTaggedValue().GetTaggedObject());
if (exceptRecvHClass == receiver) {
GlobalIndex globalsId;
globalsId.UpdateGlobalEnvId(static_cast<size_t>(GlobalEnvField::ITERATOR_RESULT_CLASS_INDEX));
AddBuiltinsGlobalInfo(abcId, recordName, methodId, bcOffset, globalsId);
return true;
}
}
return false;
}
return AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, receiver, receiver);
}
bool PGOProfiler::AddBuiltinsInfoByNameInProt(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, JSHClass *receiver, JSHClass *hold)
{
auto type = receiver->GetObjectType();
auto builtinsId = ToBuiltinsTypeId(type);
if (!builtinsId.has_value()) {
return false;
}
auto thread = vm_->GetJSThread();
JSHClass *exceptRecvHClass = nullptr;
if (builtinsId == BuiltinTypeId::ARRAY) {
bool receiverIsPrototype = receiver->IsPrototype();
exceptRecvHClass = thread->GetArrayInstanceHClass(GetCurrentGlobalEnv(), receiver->GetElementsKind(),
receiverIsPrototype, JSThread::ThreadKind::OtherThread);
} else if (builtinsId == BuiltinTypeId::STRING) {
exceptRecvHClass = receiver;
} else {
exceptRecvHClass = thread->GetBuiltinInstanceHClass(builtinsId.value());
}
auto exceptHoldHClass = thread->GetBuiltinPrototypeHClass(builtinsId.value());
auto exceptPrototypeOfPrototypeHClass =
thread->GetBuiltinPrototypeOfPrototypeHClass(builtinsId.value());
if (builtinsId == BuiltinTypeId::ARRAY_ITERATOR) {
if ((exceptRecvHClass != receiver) ||
(exceptHoldHClass != hold && exceptPrototypeOfPrototypeHClass != hold)) {
return false;
}
} else if (IsTypedArrayType(builtinsId.value())) {
auto exceptRecvHClassOnHeap = thread->GetBuiltinExtraHClass(builtinsId.value());
ASSERT_PRINT(exceptRecvHClassOnHeap == nullptr || exceptRecvHClassOnHeap->IsOnHeapFromBitField(),
"must be on heap");
if (IsJSHClassNotEqual(receiver, hold, exceptRecvHClass, exceptRecvHClassOnHeap,
exceptHoldHClass, exceptPrototypeOfPrototypeHClass)) {
return false;
}
} else if (exceptRecvHClass != receiver || exceptHoldHClass != hold) {
return false;
}
return AddBuiltinsInfo(abcId, recordName, methodId, bcOffset, receiver, receiver);
}
bool PGOProfiler::IsJSHClassNotEqual(JSHClass *receiver, JSHClass *hold, JSHClass *exceptRecvHClass,
JSHClass *exceptRecvHClassOnHeap, JSHClass *exceptHoldHClass,
JSHClass *exceptPrototypeOfPrototypeHClass)
{
return ((exceptRecvHClass != receiver && exceptRecvHClassOnHeap != receiver) ||
(exceptHoldHClass != hold && exceptPrototypeOfPrototypeHClass != hold));
}
bool PGOProfiler::CheckProtoChangeMarker(JSTaggedValue cellValue) const
{
if (!cellValue.IsProtoChangeMarker()) {
return true;
}
ProtoChangeMarker *cell = ProtoChangeMarker::Cast(cellValue.GetTaggedObject());
return cell->GetHasChanged();
}
void PGOProfiler::AddBuiltinsGlobalInfo(ApEntityId abcId, const CString &recordName, EntityId methodId,
int32_t bcOffset, GlobalIndex globalsId)
{
ProfileType recordType = GetRecordProfileType(abcId, recordName);
PGOObjectInfo info(ProfileType::CreateGlobals(abcId, globalsId));
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
}
bool PGOProfiler::IsTypedArrayRootHClass(JSType jsType, OnHeapMode mode, JSHClass *receiver)
{
if (OnHeap::IsOnHeap(mode) || OnHeap::IsNone(mode)) {
JSHClass* rootHClass = GetCurrentGlobalEnv()->GetBuildinTypedArrayHClassByJSType(jsType, OnHeapMode::ON_HEAP);
if (rootHClass != nullptr && rootHClass == receiver) {
return true;
}
}
if (OnHeap::IsNotOnHeap(mode) || OnHeap::IsNone(mode)) {
JSHClass* rootHClass =
GetCurrentGlobalEnv()->GetBuildinTypedArrayHClassByJSType(jsType, OnHeapMode::NOT_ON_HEAP);
if (rootHClass != nullptr && rootHClass == receiver) {
return true;
}
}
return false;
}
bool PGOProfiler::AddBuiltinsInfo(
ApEntityId abcId, const CString &recordName, EntityId methodId, int32_t bcOffset, JSHClass *receiver,
JSHClass *transitionHClass, OnHeapMode onHeap, bool everOutOfBounds)
{
ProfileType recordType = GetRecordProfileType(abcId, recordName);
if (receiver->IsJSArray()) {
auto type = receiver->GetObjectType();
auto elementsKind = receiver->GetElementsKind();
auto transitionElementsKind = transitionHClass->GetElementsKind();
auto profileType = ProfileType::CreateBuiltinsArray(abcId, type, elementsKind, transitionElementsKind,
everOutOfBounds);
PGOObjectInfo info(profileType);
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
} else if (receiver->IsTypedArray()) {
JSType jsType = receiver->GetObjectType();
auto profileType = IsTypedArrayRootHClass(jsType, onHeap, receiver) ?
ProfileType::CreateBuiltinsTypedArray(abcId, jsType, onHeap, everOutOfBounds) :
ProfileType::CreateInvalid(abcId);
PGOObjectInfo info(profileType);
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
} else {
auto type = receiver->GetObjectType();
PGOObjectInfo info(ProfileType::CreateBuiltins(abcId, type));
recordInfos_->AddObjectInfo(recordType, methodId, bcOffset, info);
}
return true;
}
bool PGOProfiler::IsRecoredTransRootType(ProfileType type)
{
if (!type.IsRootType() || !type.IsTransitionType()) {
return false;
}
if (std::find(recordedTransRootType_.begin(), recordedTransRootType_.end(), type) != recordedTransRootType_.end()) {
LOG_PGO(DEBUG) << "forbide to add more than 1 hclass for a root type!";
return true;
}
recordedTransRootType_.emplace_back(type);
return false;
}
void PGOProfiler::SetRootProfileType(JSHClass *root, ApEntityId abcId, uint32_t type, ProfileType::Kind kind)
{
ProfileType traceType(root->GetPGOProfileType());
if (traceType.IsNone()) {
traceType = ProfileType(abcId, type, kind, true);
if (IsRecoredTransRootType(traceType)) {
return;
}
root->SetPGOProfileType(traceType.GetRaw());
}
}
ProfileType PGOProfiler::FindRootProfileType(JSHClass *hclass)
{
auto rootHClass = JSHClass::FindRootHClass(vm_->GetJSThread(), hclass);
return GetProfileType(rootHClass, true);
}
ProfileType PGOProfiler::GetOrInsertProfileType(JSHClass *child, ProfileType rootType)
{
ProfileType childType = GetProfileType(child);
if (childType.IsNone()) {
ASSERT(rootType.IsRootType());
childType = PGOTypeGenerator::GenerateProfileType(vm_->GetJSThread(), JSTaggedType(child), rootType);
child->SetPGOProfileType(childType.GetRaw());
}
return childType;
}
ProfileType PGOProfiler::GetProfileType(JSHClass *hclass, bool check)
{
auto result = ProfileType(hclass->GetPGOProfileType());
if (check) {
if (IsSkippableObjectTypeSafe(result)) {
result = ProfileType::PROFILE_TYPE_NONE;
}
}
return result;
}
void PGOProfiler::IteratePGOPreFuncList(RootVisitor& visitor) const
{
if (!isEnable_) {
return;
}
preDumpWorkList_.Iterate([this, &visitor](WorkNode *node) {
visitor.VisitRoot(Root::ROOT_VM, ObjectSlot(node->GetValueAddr()));
});
}
void PGOProfiler::ProcessReferences(const WeakRootVisitor &visitor)
{
if (!isEnable_) {
return;
}
preDumpWorkList_.Iterate([this, &visitor](WorkNode *node) {
auto object = reinterpret_cast<TaggedObject *>(node->GetValue());
auto fwd = visitor(object);
if (fwd == nullptr) {
preDumpWorkList_.Remove(node);
nativeAreaAllocator_->Delete(node);
return;
}
if (fwd != object) {
node->SetValue(JSTaggedType(fwd));
}
});
}
void PGOProfiler::Iterate(RootVisitor &visitor)
{
if (!isEnable_) {
return;
}
dumpWorkList_.Iterate([&visitor](WorkNode* node) {
visitor.VisitRoot(Root::ROOT_VM, ObjectSlot(node->GetValueAddr()));
});
}
PGOProfiler::PGOProfiler(EcmaVM* vm, bool isEnable)
: nativeAreaAllocator_(std::make_unique<NativeAreaAllocator>()), vm_(vm), isEnable_(isEnable)
{
if (isEnable_) {
manager_ = PGOProfilerManager::GetInstance();
state_ = std::make_unique<PGOState>();
recordInfos_ = manager_->GetPGOInfo()->GetRecordDetailInfosPtr();
SetSaveTimestamp(std::chrono::system_clock::now());
if (g_isEnableCMCGC) {
holder_ = ThreadHolder::CreateAndRegisterNewThreadHolder(nullptr);
}
LOG_PGO(INFO) << "constructing pgo profiler, pgo is enabled";
} else {
LOG_PGO(DEBUG) << "skipping pgo profiler construction, pgo is disabled";
}
};
PGOProfiler::~PGOProfiler()
{
Reset(false);
}
void PGOProfiler::Reset(bool isEnable)
{
isEnable_ = isEnable;
methodCount_ = 0;
SetSaveTimestamp(std::chrono::system_clock::now());
LockHolder lock(PGOProfilerManager::GetPGOInfoMutex());
if (isEnable_) {
manager_ = PGOProfilerManager::GetInstance();
recordInfos_ = manager_->GetPGOInfo()->GetRecordDetailInfosPtr();
state_ = std::make_unique<PGOState>();
if (g_isEnableCMCGC) {
if (holder_ == nullptr) {
holder_ = ThreadHolder::CreateAndRegisterNewThreadHolder(nullptr);
}
}
} else {
state_.reset();
recordInfos_.reset();
manager_ = nullptr;
if (g_isEnableCMCGC) {
if (holder_ != nullptr) {
ThreadHolder::DestroyThreadHolder(holder_);
holder_ = nullptr;
}
}
}
LOG_PGO(INFO) << "reset pgo profiler, pgo profiler is " << (isEnable_ ? "enabled" : "disabled");
}
ApEntityId PGOProfiler::GetMethodAbcId(const JSThread *thread, JSTaggedValue jsMethod)
{
ASSERT(jsMethod.IsMethod());
CString pfName;
const auto *pf = Method::Cast(jsMethod)->GetJSPandaFile(thread);
if (pf != nullptr) {
pfName = pf->GetJSPandaFileDesc();
}
ApEntityId abcId(0);
if (!PGOProfilerManager::GetInstance()->GetPandaFileId(pfName, abcId) && !pfName.empty()) {
LOG_PGO(ERROR) << "Get method abc id failed. abcName: " << pfName;
}
return abcId;
}
ApEntityId PGOProfiler::GetMethodAbcId(const JSThread *thread, JSFunction *jsFunction)
{
CString pfName;
auto jsMethod = jsFunction->GetMethod(thread);
if (jsMethod.IsMethod()) {
return GetMethodAbcId(thread, jsMethod);
}
LOG_PGO(ERROR) << "Get method abc id failed. Not a method.";
UNREACHABLE();
}
ProfileType PGOProfiler::GetRecordProfileType(JSFunction *jsFunction, const CString &recordName)
{
CString pfName;
JSThread *thread = vm_->GetJSThread();
auto jsMethod = jsFunction->GetMethod(thread);
if (jsMethod.IsMethod()) {
const auto *pf = Method::Cast(jsMethod)->GetJSPandaFile(thread);
if (pf != nullptr) {
pfName = pf->GetJSPandaFileDesc();
}
}
const auto &pf = JSPandaFileManager::GetInstance()->FindJSPandaFile(pfName);
if (pf == nullptr) {
LOG_PGO(ERROR) << "Get record profile type failed. pf is null, pfName: " << pfName
<< ", recordName: " << recordName;
return ProfileType::PROFILE_TYPE_NONE;
}
return GetRecordProfileType(pf, GetMethodAbcId(thread, jsFunction), recordName);
}
ProfileType PGOProfiler::GetRecordProfileType(ApEntityId abcId, const CString &recordName)
{
CString pfDesc;
PGOProfilerManager::GetInstance()->GetPandaFileDesc(abcId, pfDesc);
const auto &pf = JSPandaFileManager::GetInstance()->FindJSPandaFile(pfDesc);
if (pf == nullptr) {
LOG_PGO(ERROR) << "Get record profile type failed. pf is null, pfDesc: " << pfDesc
<< ", recordName: " << recordName;
return ProfileType::PROFILE_TYPE_NONE;
}
return GetRecordProfileType(pf, abcId, recordName);
}
ProfileType PGOProfiler::GetRecordProfileType(const std::shared_ptr<JSPandaFile> &pf, ApEntityId abcId,
const CString &recordName)
{
ASSERT(pf != nullptr);
JSRecordInfo *recordInfo = pf->CheckAndGetRecordInfo(recordName);
if (recordInfo == nullptr) {
LOG_PGO(ERROR) << "Get recordInfo failed. recordName: " << recordName;
return ProfileType::PROFILE_TYPE_NONE;
}
ProfileType recordType {0};
if (pf->IsBundlePack()) {
recordType = CreateRecordProfileType(abcId, ProfileType::RECORD_ID_FOR_BUNDLE);
recordInfos_->GetRecordPool()->Add(recordType, recordName);
return recordType;
}
if (recordInfo->classId != JSPandaFile::CLASSID_OFFSET_NOT_FOUND) {
recordType = CreateRecordProfileType(abcId, recordInfo->classId);
recordInfos_->GetRecordPool()->Add(recordType, recordName);
return recordType;
}
LOG_PGO(ERROR) << "Invalid classId, skip it. recordName: " << recordName << ", isCjs: " << recordInfo->isCjs
<< ", isJson: " << recordInfo->isJson;
return ProfileType::PROFILE_TYPE_NONE;
}
bool PGOProfiler::WorkList::IsEmpty()
{
LockHolder lock(workListMutex_);
return first_ == nullptr;
}
void PGOProfiler::WorkList::PushBack(WorkNode *node)
{
LockHolder lock(workListMutex_);
if (node == nullptr) {
LOG_PGO(FATAL) << "PGOProfiler::WorkList::PushBack:node is nullptr";
UNREACHABLE();
}
if (last_ == nullptr) {
first_ = node;
last_ = node;
} else {
last_->SetNext(node);
node->SetPrev(last_);
last_ = node;
}
node->SetWorkList(this);
}
PGOProfiler::WorkNode *PGOProfiler::WorkList::PopFront()
{
LockHolder lock(workListMutex_);
WorkNode *result = nullptr;
if (first_ != nullptr) {
result = first_;
if (first_->GetNext() != nullptr) {
first_ = first_->GetNext();
first_->SetPrev(nullptr);
} else {
first_ = nullptr;
last_ = nullptr;
}
result->SetNext(nullptr);
result->SetWorkList(nullptr);
}
return result;
}
void PGOProfiler::WorkList::Remove(WorkNode *node)
{
LockHolder lock(workListMutex_);
if (node->GetPrev() != nullptr) {
node->GetPrev()->SetNext(node->GetNext());
}
if (node->GetNext() != nullptr) {
node->GetNext()->SetPrev(node->GetPrev());
}
if (node == first_) {
first_ = node->GetNext();
}
if (node == last_) {
last_ = node->GetPrev();
}
node->SetPrev(nullptr);
node->SetNext(nullptr);
node->SetWorkList(nullptr);
}
void PGOProfiler::WorkList::Iterate(Callback callback) const
{
auto current = first_;
while (current != nullptr) {
auto next = current->GetNext();
callback(current);
current = next;
}
}
void PGOProfiler::WorkList::Reset()
{
LockHolder lock(workListMutex_);
first_ = nullptr;
last_ = nullptr;
}
ProfileType PGOProfiler::CreateRecordProfileType(ApEntityId abcId, ApEntityId classId)
{
return {abcId, classId, ProfileType::Kind::RecordClassId};
}
JSTaggedValue PGOProfiler::TryFindKeyInPrototypeChain(TaggedObject *currObj, JSHClass *currHC, JSTaggedValue key)
{
if (currHC->IsDictionaryMode()) {
return JSTaggedValue(currHC);
}
JSThread *thread = vm_->GetJSThread();
while (!JSTaggedValue(currHC).IsUndefinedOrNull()) {
if (LIKELY(!currHC->IsDictionaryMode())) {
int entry = JSHClass::FindPropertyEntry(thread, currHC, key);
if (entry != -1) {
return JSTaggedValue(currHC);
}
} else {
TaggedArray *array = TaggedArray::Cast(JSObject::Cast(currObj)->GetProperties(thread).GetTaggedObject());
ASSERT(array->IsDictionaryMode());
NameDictionary *dict = NameDictionary::Cast(array);
int entry = dict->FindEntry(thread, key);
if (entry != -1) {
return JSTaggedValue(currHC);
}
}
auto proto = currHC->GetProto(thread);
if (!proto.IsHeapObject()) {
return JSTaggedValue::Undefined();
}
currObj = proto.GetTaggedObject();
if (JSTaggedValue(currObj).IsUndefinedOrNull()) {
break;
}
currHC = currObj->GetClass();
}
return JSTaggedValue::Undefined();
}
void PGOProfiler::InitJITProfiler()
{
jitProfiler_ = new JITProfiler(vm_);
jitProfiler_->InitJITProfiler();
}
void PGOProfiler::InsertSkipCtorMethodIdSafe(EntityId ctorMethodId)
{
LockHolder lock(skipCtorMethodIdMutex_);
skipCtorMethodId_.insert(ctorMethodId.GetOffset());
}
void PGOProfiler::SetSaveTimestamp(std::chrono::system_clock::time_point timestamp)
{
saveTimestamp_ = timestamp;
}
JITProfiler* PGOProfiler::GetJITProfile()
{
return jitProfiler_;
}
bool PGOProfiler::IsSkippableObjectTypeSafe(ProfileType type)
{
if (type.IsGeneralizedClassType() || type.IsConstructor() || type.IsGeneralizedPrototype()) {
uint32_t ctorId = type.GetId();
LockHolder lock(skipCtorMethodIdMutex_);
return skipCtorMethodId_.find(ctorId) != skipCtorMethodId_.end();
}
return false;
}
bool PGOProfiler::IsSkippableCtor(uint32_t entityId)
{
return entityId == 0 || skipCtorMethodId_.find(entityId) != skipCtorMethodId_.end();
}
bool PGOProfiler::InsertDefinedCtor(uint32_t entityId)
{
if (definedCtorMethodId_.find(entityId) == definedCtorMethodId_.end()) {
definedCtorMethodId_.insert(entityId);
return true;
}
return false;
}
bool PGOProfiler::IsProfileTypeInfoDumped(JSThread *thread, JSFunction *function)
{
auto profileTypeInfoVal = function->GetProfileTypeInfo(thread);
if (profileTypeInfoVal.IsUndefined() || !profileTypeInfoVal.IsTaggedArray()) {
return false;
}
auto profileTypeInfo = ProfileTypeInfo::Cast(profileTypeInfoVal.GetTaggedObject());
return profileTypeInfo->IsProfileTypeInfoDumped();
}
void PGOProfiler::SetCurrentGlobalEnv(JSTaggedValue globalEnv)
{
ASSERT(globalEnv.IsJSGlobalEnv());
globalEnv_ = globalEnv;
}
JSHandle<GlobalEnv> PGOProfiler::GetCurrentGlobalEnv() const
{
ASSERT(globalEnv_.IsJSGlobalEnv());
return JSHandle<GlobalEnv>(ToUintPtr(&globalEnv_));
}
}