* Copyright (c) 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/ic/ic_info.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ic/ic_handler.h"
#include "ecmascript/ic/ic_runtime.h"
#include "ecmascript/ic/ic_runtime_stub-inl.h"
#include "ecmascript/ic/profile_type_info.h"
#include "ecmascript/js_function.h"
#include "ecmascript/tagged_array-inl.h"
namespace panda::ecmascript {
IcAccessorLockScope::IcAccessorLockScope(JSThread *thread)
{
if (thread->GetEcmaVM()->IsEnableFastJit() || thread->GetEcmaVM()->IsEnableBaselineJit()) {
lockHolder_.emplace(thread->GetIcAccessorLock());
}
}
IcAccessor::IcAccessor(JSThread* thread, JSHandle<ProfileTypeInfo> profileTypeInfo, uint32_t slotId, ICKind kind)
: IcAccessor(thread, JSHandle<ICInfo>(profileTypeInfo), slotId, kind)
{
}
IcAccessor::IcAccessor(JSThread* thread, JSHandle<ICInfo> icInfo, uint32_t slotId, ICKind kind)
: thread_(thread), icInfo_(icInfo), slotId_(slotId), kind_(kind)
{
enableICMega_ = thread_->GetEcmaVM()->GetJSOptions().IsEnableMegaIC();
}
void ICInfo::SetMultiIcSlotLocked(JSThread *thread, uint32_t firstIdx, const JSTaggedValue &firstValue,
uint32_t secondIdx, const JSTaggedValue &secondValue)
{
IcAccessorLockScope accessorLockScope(thread);
ASSERT(firstIdx < GetLength());
ASSERT(secondIdx < GetLength());
Set(thread, firstIdx, firstValue);
Set(thread, secondIdx, secondValue);
}
void IcAccessor::AddElementHandler(JSHandle<JSTaggedValue> hclass, JSHandle<JSTaggedValue> handler) const
{
ALLOW_LOCAL_TO_SHARE_WEAK_REF_HANDLE;
auto profileData = icInfo_->GetIcSlot(thread_, slotId_);
ASSERT(!profileData.IsHole());
auto index = slotId_;
if (profileData.IsUndefined()) {
icInfo_->SetMultiIcSlotLocked(thread_, index, GetWeakRef(hclass.GetTaggedValue()),
index + 1, handler.GetTaggedValue());
return;
}
if (!profileData.IsWeak() && (profileData.IsString() || profileData.IsSymbol())) {
icInfo_->SetMultiIcSlotLocked(thread_, index, GetWeakRef(hclass.GetTaggedValue()),
index + 1, handler.GetTaggedValue());
return;
}
AddHandlerWithoutKey(hclass, handler);
}
void IcAccessor::AddWithoutKeyPoly(JSHandle<JSTaggedValue> hclass, JSHandle<JSTaggedValue> handler,
uint32_t index, JSTaggedValue profileData,
JSHandle<JSTaggedValue> keyForMegaIC, MegaICCache::MegaICKind kind) const
{
ASSERT(icInfo_->GetIcSlot(thread_, index + 1).IsHole());
JSHandle<TaggedArray> arr(thread_, profileData);
const uint32_t step = 2;
uint32_t newLen = arr->GetLength() + step;
if (newLen > CACHE_MAX_LEN) {
if (!enableICMega_ || keyForMegaIC.IsEmpty() || !keyForMegaIC->IsString()) {
icInfo_->SetMultiIcSlotLocked(thread_, index, JSTaggedValue::Hole(), index + 1,
JSTaggedValue::Hole());
return;
}
ASSERT(keyForMegaIC->IsString());
ASSERT(kind != MegaICCache::None);
MegaICCache *cache = nullptr;
if (kind == MegaICCache::Load) {
cache = thread_->GetLoadMegaICCache();
} else {
cache = thread_->GetStoreMegaICCache();
}
uint32_t i = 0;
for (; i < arr->GetLength(); i += step) {
if (arr->Get(thread_, i) == JSTaggedValue::Undefined()) {
continue;
}
cache->Set(JSHClass::Cast(arr->Get(thread_, i).GetWeakReferentUnChecked()), keyForMegaIC.GetTaggedValue(),
arr->Get(thread_, i + 1), thread_);
}
icInfo_->SetMultiIcSlotLocked(thread_, index, JSTaggedValue::Hole(), index + 1,
keyForMegaIC.GetTaggedValue());
return;
}
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(newLen);
uint32_t newArrPos = 0;
for (uint32_t pos = 0; pos < arr->GetLength(); pos += step) {
if (arr->Get(thread_, pos).IsUndefined()) {
continue;
}
newArr->Set(thread_, newArrPos, arr->Get(thread_, pos));
newArr->Set(thread_, newArrPos + 1, arr->Get(thread_, pos + 1));
newArrPos += step;
}
newArr->Set(thread_, newArrPos++, GetWeakRef(hclass.GetTaggedValue()));
newArr->Set(thread_, newArrPos++, handler.GetTaggedValue());
if (newLen > newArrPos) {
newArr->Trim(thread_, newArrPos);
}
icInfo_->SetMultiIcSlotLocked(thread_, index, newArr.GetTaggedValue(), index + 1, JSTaggedValue::Hole());
}
void IcAccessor::AddHandlerWithoutKey(JSHandle<JSTaggedValue> hclass, JSHandle<JSTaggedValue> handler,
JSHandle<JSTaggedValue> keyForMegaIC,
MegaICCache::MegaICKind kind) const
{
ALLOW_LOCAL_TO_SHARE_WEAK_REF_HANDLE;
auto index = slotId_;
if (IsNamedGlobalIC(GetKind())) {
icInfo_->SetIcSlot(thread_, index, handler.GetTaggedValue());
return;
}
auto profileData = icInfo_->GetIcSlot(thread_, slotId_);
ASSERT(!profileData.IsHole());
if (profileData.IsUndefined()) {
icInfo_->SetMultiIcSlotLocked(thread_, index, GetWeakRef(hclass.GetTaggedValue()),
index + 1, handler.GetTaggedValue());
return;
}
if (!profileData.IsWeak() && profileData.IsTaggedArray()) {
AddWithoutKeyPoly(hclass, handler, index, profileData, keyForMegaIC, kind);
return;
}
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(POLY_CASE_NUM);
uint32_t arrIndex = 0;
newArr->Set(thread_, arrIndex++, icInfo_->GetIcSlot(thread_, index));
newArr->Set(thread_, arrIndex++, icInfo_->GetIcSlot(thread_, index + 1));
newArr->Set(thread_, arrIndex++, GetWeakRef(hclass.GetTaggedValue()));
newArr->Set(thread_, arrIndex, handler.GetTaggedValue());
icInfo_->SetMultiIcSlotLocked(thread_, index, newArr.GetTaggedValue(), index + 1, JSTaggedValue::Hole());
}
void IcAccessor::AddHandlerWithKey(JSHandle<JSTaggedValue> key, JSHandle<JSTaggedValue> hclass,
JSHandle<JSTaggedValue> handler) const
{
ALLOW_LOCAL_TO_SHARE_WEAK_REF_HANDLE;
if (IsValueGlobalIC(GetKind())) {
AddGlobalHandlerKey(key, handler);
return;
}
auto profileData = icInfo_->GetIcSlot(thread_, slotId_);
ASSERT(!profileData.IsHole());
auto index = slotId_;
if (profileData.IsUndefined()) {
const int arrayLength = 2;
JSHandle<TaggedArray> newArr = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arrayLength);
newArr->Set(thread_, 0, GetWeakRef(hclass.GetTaggedValue()));
newArr->Set(thread_, 1, handler.GetTaggedValue());
icInfo_->SetMultiIcSlotLocked(thread_, index,
key.GetTaggedValue(), index + 1, newArr.GetTaggedValue());
return;
}
if (key.GetTaggedValue() != profileData) {
icInfo_->SetMultiIcSlotLocked(thread_, index, JSTaggedValue::Hole(), index + 1, JSTaggedValue::Hole());
return;
}
JSTaggedValue patchValue = icInfo_->GetIcSlot(thread_, index + 1);
ASSERT(patchValue.IsTaggedArray());
JSHandle<TaggedArray> arr(thread_, patchValue);
const uint32_t step = 2;
if (arr->GetLength() > step) {
uint32_t newLen = arr->GetLength() + step;
if (newLen > CACHE_MAX_LEN) {
icInfo_->SetMultiIcSlotLocked(thread_, index,
JSTaggedValue::Hole(), index + 1, JSTaggedValue::Hole());
return;
}
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(newLen);
newArr->Set(thread_, 0, GetWeakRef(hclass.GetTaggedValue()));
newArr->Set(thread_, 1, handler.GetTaggedValue());
for (uint32_t i = 0; i < arr->GetLength(); i += step) {
newArr->Set(thread_, i + step, arr->Get(thread_, i));
newArr->Set(thread_, i + step + 1, arr->Get(thread_, i + 1));
}
icInfo_->SetIcSlot(thread_, index + 1, newArr.GetTaggedValue());
return;
}
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(POLY_CASE_NUM);
uint32_t arrIndex = 0;
newArr->Set(thread_, arrIndex++, arr->Get(thread_, 0));
newArr->Set(thread_, arrIndex++, arr->Get(thread_, 1));
newArr->Set(thread_, arrIndex++, GetWeakRef(hclass.GetTaggedValue()));
newArr->Set(thread_, arrIndex++, handler.GetTaggedValue());
icInfo_->SetIcSlot(thread_, index + 1, newArr.GetTaggedValue());
}
void IcAccessor::AddGlobalHandlerKey(JSHandle<JSTaggedValue> key, JSHandle<JSTaggedValue> handler) const
{
ALLOW_LOCAL_TO_SHARE_WEAK_REF_HANDLE;
auto index = slotId_;
const uint8_t step = 2;
JSTaggedValue indexVal = icInfo_->GetIcSlot(thread_, index);
if (indexVal.IsUndefined()) {
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(step);
newArr->Set(thread_, 0, GetWeakRef(key.GetTaggedValue()));
newArr->Set(thread_, 1, handler.GetTaggedValue());
icInfo_->SetIcSlot(thread_, index, newArr.GetTaggedValue());
return;
}
ASSERT(indexVal.IsTaggedArray());
JSHandle<TaggedArray> arr(thread_, indexVal);
uint32_t newLen = arr->GetLength() + step;
if (newLen > CACHE_MAX_LEN) {
icInfo_->SetIcSlot(thread_, index, JSTaggedValue::Hole());
return;
}
auto factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(newLen);
newArr->Set(thread_, 0, GetWeakRef(key.GetTaggedValue()));
newArr->Set(thread_, 1, handler.GetTaggedValue());
for (uint32_t i = 0; i < arr->GetLength(); i += step) {
newArr->Set(thread_, i + step, arr->Get(thread_, i));
newArr->Set(thread_, i + step + 1, arr->Get(thread_, i + 1));
}
icInfo_->SetIcSlot(thread_, index, newArr.GetTaggedValue());
}
void IcAccessor::AddGlobalRecordHandler(JSHandle<JSTaggedValue> handler) const
{
uint32_t index = slotId_;
icInfo_->SetIcSlot(thread_, index, handler.GetTaggedValue());
}
void IcAccessor::SetAsMegaForTraceSlowMode([[maybe_unused]] ObjectOperator& op) const
{
#if ECMASCRIPT_ENABLE_TRACE_LOAD
if (op.IsFoundDict()) {
SetAsMegaForTrace(JSTaggedValue(IcAccessor::MegaState::DICT_MEGA));
} else if (!op.IsFound()) {
SetAsMegaForTrace(JSTaggedValue(IcAccessor::MegaState::NOTFOUND_MEGA));
} else {
SetAsMega();
}
#else
SetAsMega();
#endif
}
void IcAccessor::SetAsMega() const
{
if (IsGlobalIC(kind_)) {
icInfo_->SetIcSlot(thread_, slotId_, JSTaggedValue::Hole());
} else {
icInfo_->SetMultiIcSlotLocked(thread_, slotId_,
JSTaggedValue::Hole(), slotId_ + 1, JSTaggedValue::Hole());
}
}
void IcAccessor::SetAsMegaIfUndefined() const
{
if (icInfo_->GetIcSlot(thread_, slotId_).IsUndefined()) {
SetAsMega();
}
}
void IcAccessor::SetAsMegaForTrace(JSTaggedValue value) const
{
if (IsGlobalIC(kind_)) {
icInfo_->SetIcSlot(thread_, slotId_, JSTaggedValue::Hole());
} else {
icInfo_->SetMultiIcSlotLocked(thread_, slotId_,
JSTaggedValue::Hole(), slotId_ + 1, value);
}
}
std::string ICKindToString(ICKind kind)
{
switch (kind) {
case ICKind::NamedLoadIC:
return "NamedLoadIC";
case ICKind::NamedStoreIC:
return "NamedStoreIC";
case ICKind::LoadIC:
return "LoadIC";
case ICKind::StoreIC:
return "StoreIC";
case ICKind::NamedGlobalLoadIC:
return "NamedGlobalLoadIC";
case ICKind::NamedGlobalStoreIC:
return "NamedGlobalStoreIC";
case ICKind::NamedGlobalTryLoadIC:
return "NamedGlobalTryLoadIC";
case ICKind::NamedGlobalTryStoreIC:
return "NamedGlobalTryStoreIC";
case ICKind::GlobalLoadIC:
return "GlobalLoadIC";
case ICKind::GlobalStoreIC:
return "GlobalStoreIC";
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
break;
}
}
std::string IcAccessor::ICStateToString(IcAccessor::ICState state)
{
switch (state) {
case ICState::UNINIT:
return "uninit";
case ICState::MONO:
return "mono";
case ICState::POLY:
return "poly";
case ICState::MEGA:
return "mega";
case ICState::IC_MEGA:
return "ic_mega";
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
break;
}
}
IcAccessor::ICState IcAccessor::GetMegaState() const
{
if (IsGlobalIC(kind_)) {
return ICState::MEGA;
}
auto profileDataSecond = icInfo_->Get(thread_, slotId_ + 1);
if (profileDataSecond.IsString()) {
return ICState::IC_MEGA;
} else {
return ICState::MEGA;
}
}
IcAccessor::ICState IcAccessor::GetICState() const
{
auto profileData = icInfo_->GetIcSlot(thread_, slotId_);
if (profileData.IsUndefined()) {
return ICState::UNINIT;
}
if (profileData.IsHole()) {
return GetMegaState();
}
switch (kind_) {
case ICKind::NamedLoadIC:
case ICKind::NamedStoreIC:
if (profileData.IsWeak()) {
return ICState::MONO;
}
ASSERT(profileData.IsTaggedArray());
return ICState::POLY;
case ICKind::LoadIC:
case ICKind::StoreIC: {
if (profileData.IsWeak()) {
return ICState::MONO;
}
if (profileData.IsTaggedArray()) {
TaggedArray *array = TaggedArray::Cast(profileData.GetTaggedObject());
return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;
}
profileData = icInfo_->GetIcSlot(thread_, slotId_ + 1);
TaggedArray *array = TaggedArray::Cast(profileData.GetTaggedObject());
return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;
}
case ICKind::NamedGlobalLoadIC:
case ICKind::NamedGlobalStoreIC:
case ICKind::NamedGlobalTryLoadIC:
case ICKind::NamedGlobalTryStoreIC:
ASSERT(profileData.IsPropertyBox());
return ICState::MONO;
case ICKind::GlobalLoadIC:
case ICKind::GlobalStoreIC: {
ASSERT(profileData.IsTaggedArray());
TaggedArray *array = TaggedArray::Cast(profileData.GetTaggedObject());
return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;
}
default:
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
break;
}
return ICState::UNINIT;
}
JSTaggedValue NapiICInfo::TryLoadKeyIC(JSThread *thread, JSHandle<NapiICInfo> icInfo,
JSTaggedValue obj, JSTaggedValue key)
{
JSTaggedValue slot0 = icInfo->GetIcSlot(thread, 0);
JSTaggedValue slot1 = icInfo->GetIcSlot(thread, 1);
if (IsKeyIC(slot0)) {
return ICRuntimeStub::TryLoadICByValue(thread, obj, key, slot0, slot1);
}
return JSTaggedValue::Hole();
}
JSTaggedValue NapiICInfo::TryLoadICOrMiss(JSThread *thread, JSHandle<NapiICInfo> icInfo,
JSHandle<JSTaggedValue> obj,
JSMutableHandle<JSTaggedValue> &key, bool *hit)
{
if (key->IsString() && !EcmaStringAccessor(key.GetTaggedValue()).IsInternString()) {
key.Update(JSTaggedValue(thread->GetEcmaVM()->GetFactory()->InternString(key)));
}
JSTaggedValue slot0 = icInfo->GetIcSlot(thread, 0);
JSTaggedValue slot1 = icInfo->GetIcSlot(thread, 1);
JSTaggedValue res = JSTaggedValue::Hole();
if (LIKELY(!slot0.IsUndefined())) {
res = ICRuntimeStub::TryLoadICByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue(), slot0, slot1);
}
if (res.IsHole()) {
if (IsKeyIC(slot0) && slot0 != key.GetTaggedValue()) {
icInfo->SetIcSlot(thread, 0, JSTaggedValue::Undefined());
icInfo->SetIcSlot(thread, 1, JSTaggedValue::Undefined());
icInfo->ClearICKind(thread);
}
LoadICRuntime icRT(thread, JSHandle<ICInfo>(icInfo), 0, ICKind::LoadIC);
res = icRT.LoadValueMiss(obj, key);
if (!res.IsHole()) {
icInfo->MarkAsGetIC(thread);
}
} else if (hit != nullptr) {
*hit = true;
}
return res;
}
JSTaggedValue NapiICInfo::TryStoreICOrMiss(JSThread *thread, JSHandle<NapiICInfo> icInfo,
JSHandle<JSTaggedValue> obj,
JSMutableHandle<JSTaggedValue> &key,
JSHandle<JSTaggedValue> value, bool *hit)
{
if (key->IsString() && !EcmaStringAccessor(key.GetTaggedValue()).IsInternString()) {
key.Update(JSTaggedValue(thread->GetEcmaVM()->GetFactory()->InternString(key)));
}
JSTaggedValue slot0 = icInfo->GetIcSlot(thread, 0);
JSTaggedValue slot1 = icInfo->GetIcSlot(thread, 1);
JSTaggedValue res = JSTaggedValue::Hole();
if (LIKELY(!slot0.IsUndefined())) {
res = ICRuntimeStub::TryStoreICByValue(thread, obj.GetTaggedValue(),
key.GetTaggedValue(), slot0, slot1,
value.GetTaggedValue());
}
if (res.IsHole()) {
if (IsKeyIC(slot0) && slot0 != key.GetTaggedValue()) {
icInfo->SetIcSlot(thread, 0, JSTaggedValue::Undefined());
icInfo->SetIcSlot(thread, 1, JSTaggedValue::Undefined());
icInfo->ClearICKind(thread);
}
StoreICRuntime icRT(thread, JSHandle<ICInfo>(icInfo), 0, ICKind::StoreIC);
res = icRT.StoreMiss(obj, key, value);
if (!res.IsHole()) {
icInfo->MarkAsSetIC(thread);
}
} else if (hit != nullptr) {
*hit = true;
}
return res;
}
}