/* * Copyright (c) 2021-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ECMASCRIPT_IC_IC_HANDLER_H #define ECMASCRIPT_IC_IC_HANDLER_H #include "ecmascript/ecma_macros.h" #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/js_typed_array.h" #include "ecmascript/mem/tagged_object.h" #include "ecmascript/object_operator.h" #include "ecmascript/js_function.h" namespace panda::ecmascript { class HandlerBase { public: static constexpr uint32_t KIND_BIT_LENGTH = 4; static constexpr uint32_t STORE_KIND_BIT_LENGTH = 2; static constexpr uint32_t MAX_BIT_SIZE = 48; enum HandlerKind { NONE = 0, FIELD, ELEMENT, DICTIONARY, STRING, STRING_LENGTH, TYPED_ARRAY, NUMBER, NON_EXIST, TOTAL_KINDS, }; static_assert(static_cast(HandlerKind::TOTAL_KINDS) <= (1 << KIND_BIT_LENGTH)); // Store Handler kind combined with KindBit called SWholeKindBit. Which used to quickly check S_FIELD kind enum StoreHandlerKind { S_NONE = HandlerKind::NONE, S_FIELD, S_ELEMENT, S_TOTAL_KINDS, }; static_assert(static_cast(StoreHandlerKind::S_TOTAL_KINDS) <= (1 << STORE_KIND_BIT_LENGTH)); // For Load using KindBit = BitField; // [0, 4) using InlinedPropsBit = KindBit::NextFlag; // [4, 5) using AccessorBit = InlinedPropsBit::NextFlag; // [5, 6) using IsJSArrayBit = AccessorBit::NextFlag; // [6, 7) using OffsetBit = IsJSArrayBit::NextField; // [7, 17) using RepresentationBit = OffsetBit::NextField; // [17, 19) using AttrIndexBit = RepresentationBit::NextField; // [19, 29) using IsOnHeapBit = AttrIndexBit::NextFlag; // [29, 30) using NeedSkipInPGODumpBit = IsOnHeapBit::NextFlag; // [30, 31) static_assert(NeedSkipInPGODumpBit::END_BIT <= MAX_BIT_SIZE, "load handler overflow"); // For Store using SWholeKindBit = KindBit; using SKindBit = BitField; // [0, 2) static_assert(SKindBit::START_BIT == KindBit::START_BIT); using SSharedBit = SKindBit::NextFlag; // [2, 4) static_assert((SKindBit::SIZE + SSharedBit::SIZE) <= KindBit::SIZE); // reuse: [0, 4) bits // shared with Load bits: [4, 30) using SOutOfBoundsBit = IsOnHeapBit::NextFlag; // reuse: [30, 31) bit using SFieldTypeBit = SOutOfBoundsBit::NextField; // [31, 39) static_assert(SFieldTypeBit::END_BIT <= MAX_BIT_SIZE, "store handler overflow"); using Type = uint64_t; static_assert(sizeof(Type) <= JSTaggedValue::TaggedTypeSize()); HandlerBase() = default; virtual ~HandlerBase() = default; static inline bool IsAccessor(Type handler) { return AccessorBit::Get(handler); } static inline SharedFieldType GetFieldType(Type handler) { return static_cast(SFieldTypeBit::Get(handler)); } static inline bool IsNonExist(Type handler) { return GetKind(handler) == HandlerKind::NON_EXIST; } static inline bool IsField(Type handler) { return GetKind(handler) == HandlerKind::FIELD; } static inline bool IsNonSharedStoreField(Type handler) { return static_cast(GetKind(handler)) == StoreHandlerKind::S_FIELD; } static inline bool IsStoreShared(Type handler) { return SSharedBit::Get(handler); } static inline void ClearSharedStoreKind(Type &handler) { SSharedBit::Set(false, &handler); } static inline bool IsStoreOutOfBounds(Type handler) { return SOutOfBoundsBit::Get(handler); } static inline void ClearStoreOutOfBounds(Type &handler) { SOutOfBoundsBit::Set(false, &handler); } static inline bool IsString(Type handler) { return GetKind(handler) == HandlerKind::STRING; } static inline bool IsNumber(Type handler) { return GetKind(handler) == HandlerKind::NUMBER; } static inline bool IsStringLength(Type handler) { return GetKind(handler) == HandlerKind::STRING_LENGTH; } static inline bool IsElement(Type handler) { return IsNormalElement(handler) || IsStringElement(handler) || IsTypedArrayElement(handler); } static inline bool IsNormalElement(Type handler) { return GetKind(handler) == HandlerKind::ELEMENT; } static inline bool IsStringElement(Type handler) { return GetKind(handler) == HandlerKind::STRING; } static inline bool IsTypedArrayElement(Type handler) { return GetKind(handler) == HandlerKind::TYPED_ARRAY; } static inline bool IsDictionary(Type handler) { return GetKind(handler) == HandlerKind::DICTIONARY; } static inline bool IsInlinedProps(Type handler) { return InlinedPropsBit::Get(handler); } static inline HandlerKind GetKind(Type handler) { return KindBit::Get(handler); } static inline bool IsJSArray(Type handler) { return IsJSArrayBit::Get(handler); } static inline bool NeedSkipInPGODump(Type handler) { return NeedSkipInPGODumpBit::Get(handler); } static inline int GetOffset(Type handler) { return OffsetBit::Get(handler); } static inline bool IsOnHeap(Type handler) { return IsOnHeapBit::Get(handler); } }; class LoadHandler final : public HandlerBase { public: static inline JSHandle LoadProperty(const JSThread *thread, const ObjectOperator &op) { uint64_t handler = 0; ASSERT(!op.IsElement()); if (!op.IsFound()) { KindBit::Set(HandlerKind::NON_EXIST, &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } ASSERT(op.IsFastMode()); JSTaggedValue val = op.GetValue(); if (val.IsPropertyBox()) { return JSHandle(thread, val); } bool hasAccessor = op.IsAccessorDescriptor(); AccessorBit::Set(hasAccessor, &handler); if (!hasAccessor) { if (op.GetReceiver()->IsString()) { JSTaggedValue lenKey = thread->GlobalConstants()->GetLengthString(); EcmaString *proKey = nullptr; if (op.GetKey()->IsString()) { proKey = EcmaString::Cast(op.GetKey()->GetTaggedObject()); } if (EcmaStringAccessor::StringsAreEqual(proKey, EcmaString::Cast(lenKey.GetTaggedObject()))) { KindBit::Set(HandlerKind::STRING_LENGTH, &handler); } else { KindBit::Set(HandlerKind::STRING, &handler); } } else if (op.GetReceiver()->IsNumber()) { KindBit::Set(HandlerKind::NUMBER, &handler); } else { KindBit::Set(HandlerKind::FIELD, &handler); } } if (op.IsInlinedProps()) { InlinedPropsBit::Set(true, &handler); JSHandle holder = JSHandle::Cast(op.GetHolder()); auto index = holder->GetJSHClass()->GetInlinedPropertiesIndex(op.GetIndex()); OffsetBit::Set(index, &handler); AttrIndexBit::Set(op.GetIndex(), &handler); RepresentationBit::Set(op.GetRepresentation(), &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } if (op.IsFastMode()) { JSHandle holder = JSHandle::Cast(op.GetHolder()); uint32_t inlinePropNum = holder->GetJSHClass()->GetInlinedProperties(); AttrIndexBit::Set(op.GetIndex() + inlinePropNum, &handler); OffsetBit::Set(op.GetIndex(), &handler); RepresentationBit::Set(Representation::TAGGED, &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } static inline JSHandle LoadElement(const JSThread *thread, const ObjectOperator &op) { uint64_t handler = 0; KindBit::Set(HandlerKind::ELEMENT, &handler); // To avoid logical errors and Deopt, temporarily skipping PGO Profiling. // logical errors: // When accessing an element of an object, AOT does not have a chain-climbing operation, // so if the element is on a prototype, it will not be able to get the correct element. // deopt: // Currently there is no way to save the type of the key in pgo file, even if the type of the key // is string, it will be treated as a number type by the AOT, leading to deopt at runtime. if (op.GetReceiver() != op.GetHolder() || op.KeyFromStringType()) { NeedSkipInPGODumpBit::Set(true, &handler); } if (op.GetReceiver()->IsJSArray()) { IsJSArrayBit::Set(true, &handler); } return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } static inline JSHandle LoadStringElement(const JSThread *thread) { uint64_t handler = 0; KindBit::Set(HandlerKind::STRING, &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } static inline JSHandle LoadTypedArrayElement(const JSThread *thread, JSHandle typedArray) { uint64_t handler = 0; KindBit::Set(HandlerKind::TYPED_ARRAY, &handler); IsOnHeapBit::Set(JSHandle(typedArray)->GetClass()->IsOnHeapFromBitField(), &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } }; class StoreHandler final : public HandlerBase { public: static inline JSHandle StoreProperty(const JSThread *thread, const ObjectOperator &op) { uint64_t handler = 0; JSHandle receiver = JSHandle::Cast(op.GetReceiver()); SSharedBit::Set(op.GetReceiver()->IsJSShared(), &handler); TaggedArray *array = TaggedArray::Cast(receiver->GetProperties().GetTaggedObject()); if (!array->IsDictionaryMode()) { SFieldTypeBit::Set(op.GetAttr().GetSharedFieldType(), &handler); } else { SFieldTypeBit::Set(op.GetAttr().GetDictSharedFieldType(), &handler); } if (op.IsElement()) { SOutOfBoundsBit::Set(op.GetElementOutOfBounds(), &handler); return StoreElement(thread, op.GetReceiver(), handler); } JSTaggedValue val = op.GetValue(); if (val.IsPropertyBox()) { return JSHandle(thread, val); } bool hasSetter = op.IsAccessorDescriptor(); AccessorBit::Set(hasSetter, &handler); if (!hasSetter) { SKindBit::Set(StoreHandlerKind::S_FIELD, &handler); } if (op.IsInlinedProps()) { InlinedPropsBit::Set(true, &handler); uint32_t index = 0; if (!hasSetter) { index = receiver->GetJSHClass()->GetInlinedPropertiesIndex(op.GetIndex()); } else { JSHandle holder = JSHandle::Cast(op.GetHolder()); index = holder->GetJSHClass()->GetInlinedPropertiesIndex(op.GetIndex()); } AttrIndexBit::Set(op.GetIndex(), &handler); OffsetBit::Set(index, &handler); RepresentationBit::Set(op.GetRepresentation(), &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } ASSERT(op.IsFastMode()); uint32_t inlinePropNum = receiver->GetJSHClass()->GetInlinedProperties(); AttrIndexBit::Set(op.GetIndex() + inlinePropNum, &handler); OffsetBit::Set(op.GetIndex(), &handler); RepresentationBit::Set(Representation::TAGGED, &handler); return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } static inline JSHandle StoreElement(const JSThread *thread, JSHandle receiver, uint64_t handler) { SKindBit::Set(StoreHandlerKind::S_ELEMENT, &handler); if (receiver->IsJSArray()) { IsJSArrayBit::Set(true, &handler); } return JSHandle(thread, JSTaggedValue::WrapUint64(handler)); } }; class TransitionHandler : public TaggedObject { public: static TransitionHandler *Cast(TaggedObject *object) { ASSERT(JSTaggedValue(object).IsTransitionHandler()); return static_cast(object); } static inline JSHandle StoreTransition(const JSThread *thread, const ObjectOperator &op) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle handler = factory->NewTransitionHandler(); JSHandle handlerInfo = StoreHandler::StoreProperty(thread, op); handler->SetHandlerInfo(thread, handlerInfo); auto hclass = JSObject::Cast(op.GetReceiver()->GetTaggedObject())->GetJSHClass(); handler->SetTransitionHClass(thread, JSTaggedValue(hclass)); return JSHandle::Cast(handler); } static constexpr size_t HANDLER_INFO_OFFSET = TaggedObjectSize(); ACCESSORS(HandlerInfo, HANDLER_INFO_OFFSET, TRANSITION_HCLASS_OFFSET) ACCESSORS(TransitionHClass, TRANSITION_HCLASS_OFFSET, SIZE) DECL_VISIT_OBJECT(HANDLER_INFO_OFFSET, SIZE) DECL_DUMP() }; class PrototypeHandler : public TaggedObject { public: static PrototypeHandler *Cast(TaggedObject *object) { ASSERT(JSTaggedValue(object).IsPrototypeHandler()); return static_cast(object); } static inline JSHandle LoadPrototype(const JSThread *thread, const ObjectOperator &op, const JSHandle &hclass) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle handlerInfo = LoadHandler::LoadProperty(thread, op); JSHandle handler = factory->NewPrototypeHandler(); handler->SetHandlerInfo(thread, handlerInfo); if (op.IsFound()) { handler->SetHolder(thread, op.GetHolder()); } if (op.IsAccessorDescriptor()) { JSTaggedValue result = op.GetValue(); if (result.IsPropertyBox()) { result = PropertyBox::Cast(result.GetTaggedObject())->GetValue(); } AccessorData *accessor = AccessorData::Cast(result.GetTaggedObject()); if (!accessor->IsInternal()) { JSTaggedValue getter = accessor->GetGetter(); if (!getter.IsUndefined()) { JSHandle func(thread, getter); uint32_t methodOffset = Method::Cast(func->GetMethod())->GetMethodId().GetOffset(); handler->SetAccessorMethodId(methodOffset); handler->SetAccessorJSFunction(thread, getter); } } } // ShareToLocal is prohibited if (!hclass->IsJSShared()) { auto result = JSHClass::EnableProtoChangeMarker(thread, hclass); handler->SetProtoCell(thread, result); } return JSHandle::Cast(handler); } static inline JSHandle StorePrototype(const JSThread *thread, const ObjectOperator &op, const JSHandle &hclass) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle handler = factory->NewPrototypeHandler(); JSHandle handlerInfo = StoreHandler::StoreProperty(thread, op); handler->SetHandlerInfo(thread, handlerInfo); handler->SetHolder(thread, op.GetHolder()); if (op.IsAccessorDescriptor()) { JSTaggedValue result = op.GetValue(); if (result.IsPropertyBox()) { result = PropertyBox::Cast(result.GetTaggedObject())->GetValue(); } AccessorData *accessor = AccessorData::Cast(result.GetTaggedObject()); if (!accessor->IsInternal() && accessor->HasSetter()) { JSTaggedValue setter = accessor->GetSetter(); JSHandle func(thread, setter); handler->SetAccessorMethodId( Method::Cast(func->GetMethod())->GetMethodId().GetOffset()); handler->SetAccessorJSFunction(thread, setter); } } // ShareToLocal is prohibited if (!hclass->IsJSShared()) { auto result = JSHClass::EnableProtoChangeMarker(thread, hclass); handler->SetProtoCell(thread, result); } return JSHandle::Cast(handler); } static constexpr size_t HANDLER_INFO_OFFSET = TaggedObjectSize(); ACCESSORS(HandlerInfo, HANDLER_INFO_OFFSET, PROTO_CELL_OFFSET) ACCESSORS(ProtoCell, PROTO_CELL_OFFSET, HOLDER_OFFSET) ACCESSORS(Holder, HOLDER_OFFSET, ACCESSOR_JSFUNCTION_OFFSET) ACCESSORS(AccessorJSFunction, ACCESSOR_JSFUNCTION_OFFSET, ACCESSOR_METHOD_ID_OFFSET) ACCESSORS_PRIMITIVE_FIELD(AccessorMethodId, uint32_t, ACCESSOR_METHOD_ID_OFFSET, LAST_OFFSET) DEFINE_ALIGN_SIZE(LAST_OFFSET); DECL_VISIT_OBJECT(HANDLER_INFO_OFFSET, ACCESSOR_METHOD_ID_OFFSET) DECL_DUMP() }; class TransWithProtoHandler : public TaggedObject { public: static TransWithProtoHandler *Cast(TaggedObject *object) { ASSERT(JSTaggedValue(object).IsTransWithProtoHandler()); return static_cast(object); } static inline JSHandle StoreTransition(const JSThread *thread, const ObjectOperator &op, const JSHandle &hclass) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle handler = factory->NewTransWithProtoHandler(); JSHandle handlerInfo = StoreHandler::StoreProperty(thread, op); handler->SetHandlerInfo(thread, handlerInfo); auto result = JSHClass::EnableProtoChangeMarker(thread, hclass); handler->SetProtoCell(thread, result); handler->SetTransitionHClass(thread, hclass.GetTaggedValue()); return JSHandle::Cast(handler); } static constexpr size_t HANDLER_INFO_OFFSET = TaggedObjectSize(); ACCESSORS(HandlerInfo, HANDLER_INFO_OFFSET, TRANSITION_HCLASS_OFFSET) ACCESSORS(TransitionHClass, TRANSITION_HCLASS_OFFSET, PROTO_CELL_OFFSET) ACCESSORS(ProtoCell, PROTO_CELL_OFFSET, SIZE) DECL_VISIT_OBJECT(HANDLER_INFO_OFFSET, SIZE) DECL_DUMP() }; class StoreTSHandler : public TaggedObject { public: static StoreTSHandler *Cast(TaggedObject *object) { ASSERT(JSTaggedValue(object).IsStoreTSHandler()); return static_cast(object); } static inline JSHandle StoreAOT(const JSThread *thread, const ObjectOperator &op, const JSHandle &hclass) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle handler = factory->NewStoreTSHandler(); JSHandle handlerInfo = StoreHandler::StoreProperty(thread, op); handler->SetHandlerInfo(thread, handlerInfo); handler->SetHolder(thread, op.GetHolder()); auto result = JSHClass::EnableProtoChangeMarker(thread, hclass); handler->SetProtoCell(thread, result); return JSHandle::Cast(handler); } static constexpr size_t HANDLER_INFO_OFFSET = TaggedObjectSize(); ACCESSORS(HandlerInfo, HANDLER_INFO_OFFSET, PROTO_CELL_OFFSET) ACCESSORS(ProtoCell, PROTO_CELL_OFFSET, HOLDER_OFFSET) ACCESSORS(Holder, HOLDER_OFFSET, SIZE) DECL_VISIT_OBJECT(HANDLER_INFO_OFFSET, SIZE) DECL_DUMP() }; } // namespace panda::ecmascript #endif // ECMASCRIPT_IC_IC_HANDLER_H