/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/jspandafile/class_info_extractor.h" #include "ecmascript/global_env.h" #include "ecmascript/js_function.h" #include "ecmascript/jspandafile/program_object.h" #include "ecmascript/jspandafile/method_literal.h" #include "ecmascript/tagged_dictionary.h" namespace panda::ecmascript { void ClassInfoExtractor::BuildClassInfoExtractorFromLiteral(JSThread *thread, JSHandle &extractor, const JSHandle &literal) { [[maybe_unused]] EcmaHandleScope handleScope(thread); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t literalBufferLength = literal->GetLength(); // non static properties number is hidden in the last index of Literal buffer uint32_t nonStaticNum = 0; if (literalBufferLength != 0) { nonStaticNum = static_cast(literal->Get(thread, literalBufferLength - 1).GetInt()); } // Reserve sufficient length to prevent frequent creation. JSHandle nonStaticKeys = factory->NewOldSpaceTaggedArray(nonStaticNum + NON_STATIC_RESERVED_LENGTH); JSHandle nonStaticProperties = factory->NewOldSpaceTaggedArray(nonStaticNum + NON_STATIC_RESERVED_LENGTH); nonStaticKeys->Set(thread, CONSTRUCTOR_INDEX, globalConst->GetConstructorString()); Method *method = Method::Cast(extractor->GetConstructorMethod().GetTaggedObject()); MethodLiteral *methodLiteral = method->GetMethodLiteral(); const JSPandaFile *jsPandaFile = method->GetJSPandaFile(); EntityId methodId = method->GetMethodId(); if (nonStaticNum) { ExtractContentsDetail nonStaticDetail {0, nonStaticNum * 2, NON_STATIC_RESERVED_LENGTH, nullptr}; JSHandle nonStaticElements = factory->EmptyArray(); if (UNLIKELY(ExtractAndReturnWhetherWithElements(thread, literal, nonStaticDetail, nonStaticKeys, nonStaticProperties, nonStaticElements, jsPandaFile))) { extractor->SetNonStaticWithElements(true); extractor->SetNonStaticElements(thread, nonStaticElements); } } extractor->SetNonStaticKeys(thread, nonStaticKeys); extractor->SetNonStaticProperties(thread, nonStaticProperties); uint32_t staticNum = literalBufferLength == 0 ? 0 : (literalBufferLength - 1) / 2 - nonStaticNum; // Reserve sufficient length to prevent frequent creation. JSHandle staticKeys = factory->NewOldSpaceTaggedArray(staticNum + STATIC_RESERVED_LENGTH); JSHandle staticProperties = factory->NewOldSpaceTaggedArray(staticNum + STATIC_RESERVED_LENGTH); staticKeys->Set(thread, LENGTH_INDEX, globalConst->GetLengthString()); staticKeys->Set(thread, NAME_INDEX, globalConst->GetNameString()); staticKeys->Set(thread, PROTOTYPE_INDEX, globalConst->GetPrototypeString()); JSHandle staticElements = factory->EmptyArray(); if (staticNum) { ExtractContentsDetail staticDetail { nonStaticNum * 2, literalBufferLength - 1, STATIC_RESERVED_LENGTH, methodLiteral }; if (UNLIKELY(ExtractAndReturnWhetherWithElements(thread, literal, staticDetail, staticKeys, staticProperties, staticElements, jsPandaFile))) { extractor->SetStaticWithElements(true); extractor->SetStaticElements(thread, staticElements); } } else { // without static properties, set class name std::string clsName = MethodLiteral::ParseFunctionName(jsPandaFile, methodId); JSHandle clsNameHandle = factory->NewFromStdString(clsName); staticProperties->Set(thread, NAME_INDEX, clsNameHandle); } // set prototype internal accessor JSHandle prototypeAccessor = globalConst->GetHandledFunctionPrototypeAccessor(); staticProperties->Set(thread, PROTOTYPE_INDEX, prototypeAccessor); extractor->SetStaticKeys(thread, staticKeys); extractor->SetStaticProperties(thread, staticProperties); } bool ClassInfoExtractor::ExtractAndReturnWhetherWithElements(JSThread *thread, const JSHandle &literal, const ExtractContentsDetail &detail, JSHandle &keys, JSHandle &properties, JSHandle &elements, const JSPandaFile *jsPandaFile) { const GlobalEnvConstants *globalConst = thread->GlobalConstants(); ASSERT(keys->GetLength() == properties->GetLength() && elements->GetLength() == 0); uint32_t pos = detail.fillStartLoc; bool withElementsFlag = false; bool isStaticFlag = (detail.methodLiteral != nullptr); bool keysHasNameFlag = false; JSHandle nameString = globalConst->GetHandledNameString(); JSMutableHandle firstValue(thread, JSTaggedValue::Undefined()); JSMutableHandle secondValue(thread, JSTaggedValue::Undefined()); for (uint32_t index = detail.extractBegin; index < detail.extractEnd; index += 2) { // 2: key-value pair firstValue.Update(literal->Get(index)); secondValue.Update(literal->Get(index + 1)); ASSERT_PRINT(JSTaggedValue::IsPropertyKey(firstValue), "Key is not a property key"); if (LIKELY(firstValue->IsString())) { if (isStaticFlag && !keysHasNameFlag && JSTaggedValue::SameValue(firstValue, nameString)) { properties->Set(thread, NAME_INDEX, secondValue); keysHasNameFlag = true; continue; } // front-end can do better: write index in class literal directly. uint32_t elementIndex = 0; if (JSTaggedValue::StringToElementIndex(firstValue.GetTaggedValue(), &elementIndex)) { ASSERT(elementIndex < JSObject::MAX_ELEMENT_INDEX); uint32_t elementsLength = elements->GetLength(); elements = TaggedArray::SetCapacityInOldSpace(thread, elements, elementsLength + 2); // 2: key-value pair elements->Set(thread, elementsLength, firstValue); elements->Set(thread, elementsLength + 1, secondValue); withElementsFlag = true; continue; } } keys->Set(thread, pos, firstValue); properties->Set(thread, pos, secondValue); pos++; } if (isStaticFlag) { if (LIKELY(!keysHasNameFlag)) { [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); EntityId methodId = detail.methodLiteral->GetMethodId(); std::string clsName = MethodLiteral::ParseFunctionName(jsPandaFile, methodId); JSHandle clsNameHandle = factory->NewFromStdString(clsName); properties->Set(thread, NAME_INDEX, clsNameHandle); } else { // class has static name property, reserved length bigger 1 than actual, need trim uint32_t trimOneLength = keys->GetLength() - 1; keys->Trim(thread, trimOneLength); properties->Trim(thread, trimOneLength); } } if (UNLIKELY(withElementsFlag)) { ASSERT(pos + elements->GetLength() / 2 == properties->GetLength()); // 2: half keys->Trim(thread, pos); properties->Trim(thread, pos); } return withElementsFlag; } JSHandle ClassInfoExtractor::CreatePrototypeHClass(JSThread *thread, JSHandle &keys, JSHandle &properties) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); JSHandle hclass; if (LIKELY(length <= PropertyAttributes::MAX_FAST_PROPS_CAPACITY)) { JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSHandle layout = factory->CreateLayoutInfo(length, MemSpaceType::OLD_SPACE, GrowMode::KEEP); for (uint32_t index = 0; index < length; ++index) { key.Update(keys->Get(index)); ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key"); PropertyAttributes attributes = PropertyAttributes::Default(true, false, true); // non-enumerable if (UNLIKELY(properties->Get(index).IsAccessor())) { attributes.SetIsAccessor(true); } attributes.SetIsInlinedProps(true); attributes.SetRepresentation(Representation::TAGGED); attributes.SetOffset(index); layout->AddKey(thread, index, key.GetTaggedValue(), attributes); } hclass = factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT, length); // Not need set proto here hclass->SetLayout(thread, layout); hclass->SetNumberOfProps(length); } else { // dictionary mode hclass = factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT, 0); // without in-obj hclass->SetIsDictionaryMode(true); hclass->SetNumberOfProps(0); } hclass->SetClassPrototype(true); hclass->SetIsPrototype(true); return hclass; } JSHandle ClassInfoExtractor::CreateConstructorHClass(JSThread *thread, const JSHandle &base, JSHandle &keys, JSHandle &properties) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); if (!thread->GetEcmaVM()->IsEnablePGOProfiler()) { // The class constructor of AOT is not shared, and PGO collect cannot be shared. if (length == ClassInfoExtractor::STATIC_RESERVED_LENGTH && base->IsHole() && properties->Get(NAME_INDEX).IsString()) { const GlobalEnvConstants *globalConst = thread->GlobalConstants(); return JSHandle(globalConst->GetHandledClassConstructorClass()); } } JSHandle hclass; if (LIKELY(length <= PropertyAttributes::MAX_FAST_PROPS_CAPACITY)) { JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSHandle layout = factory->CreateLayoutInfo(length, MemSpaceType::OLD_SPACE, GrowMode::KEEP); for (uint32_t index = 0; index < length; ++index) { key.Update(keys->Get(index)); ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key"); PropertyAttributes attributes; switch (index) { case LENGTH_INDEX: attributes = PropertyAttributes::Default(false, false, true); break; case NAME_INDEX: if (LIKELY(properties->Get(NAME_INDEX).IsString())) { attributes = PropertyAttributes::Default(false, false, true); } else { ASSERT(properties->Get(NAME_INDEX).IsJSFunction()); attributes = PropertyAttributes::Default(true, false, true); } break; case PROTOTYPE_INDEX: attributes = PropertyAttributes::DefaultAccessor(false, false, false); break; default: attributes = PropertyAttributes::Default(true, false, true); break; } if (UNLIKELY(properties->Get(index).IsAccessor())) { attributes.SetIsAccessor(true); } attributes.SetIsInlinedProps(true); attributes.SetRepresentation(Representation::TAGGED); attributes.SetOffset(index); layout->AddKey(thread, index, key.GetTaggedValue(), attributes); } hclass = factory->NewEcmaHClass(JSFunction::SIZE, JSType::JS_FUNCTION, length); // Not need set proto here hclass->SetLayout(thread, layout); hclass->SetNumberOfProps(length); } else { // dictionary mode hclass = factory->NewEcmaHClass(JSFunction::SIZE, JSType::JS_FUNCTION, 0); // without in-obj hclass->SetIsDictionaryMode(true); hclass->SetNumberOfProps(0); } hclass->SetClassConstructor(true); hclass->SetConstructor(true); return hclass; } void ClassInfoExtractor::CorrectConstructorHClass(JSThread *thread, JSHandle &properties, JSHClass *constructorHClass) { if (LIKELY(!constructorHClass->IsDictionaryMode())) { JSHandle layout(thread, constructorHClass->GetLayout()); for (uint32_t index = 0; index < ClassInfoExtractor::STATIC_RESERVED_LENGTH; ++index) { switch (index) { case NAME_INDEX: if (UNLIKELY(properties->Get(NAME_INDEX).IsJSFunction())) { PropertyAttributes attr = layout->GetAttr(index); attr.SetWritable(true); layout->SetNormalAttr(thread, index, attr); } if (UNLIKELY(properties->Get(index).IsAccessor())) { PropertyAttributes attr = layout->GetAttr(index); attr.SetIsAccessor(true); layout->SetNormalAttr(thread, index, attr); } break; default: if (UNLIKELY(properties->Get(index).IsAccessor())) { PropertyAttributes attr = layout->GetAttr(index); attr.SetIsAccessor(true); layout->SetNormalAttr(thread, index, attr); } break; } } } } JSHandle ClassInfoExtractor::CreateSendableHClass(JSThread *thread, JSHandle &keys, JSHandle &properties, bool isProtoClass, uint32_t extraLength) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); JSHandle hclass; uint32_t maxInline = isProtoClass ? JSSharedObject::MAX_INLINE : JSSharedFunction::MAX_INLINE; if (LIKELY(length + extraLength <= maxInline)) { JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSHandle layout = factory->CreateLayoutInfo(length + extraLength, MemSpaceType::OLD_SPACE, GrowMode::KEEP); for (uint32_t index = 0; index < length; ++index) { key.Update(keys->Get(index)); ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key"); PropertyAttributes attributes = PropertyAttributes::Default(false, false, false); if (UNLIKELY(properties->Get(index).IsAccessor())) { attributes.SetIsAccessor(true); } attributes.SetIsInlinedProps(true); attributes.SetRepresentation(Representation::TAGGED); attributes.SetOffset(index); layout->AddKey(thread, index, key.GetTaggedValue(), attributes); } hclass = isProtoClass ? factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, length) : factory->NewEcmaHClass(JSSharedFunction::SIZE, JSType::JS_SHARED_FUNCTION, length + extraLength); hclass->SetLayout(thread, layout); hclass->SetNumberOfProps(length); } else { // dictionary mode hclass = isProtoClass ? factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, 0) : factory->NewEcmaHClass(JSSharedFunction::SIZE, JSType::JS_SHARED_FUNCTION, 0); hclass->SetIsDictionaryMode(true); hclass->SetNumberOfProps(0); } if (isProtoClass) { hclass->SetClassPrototype(true); hclass->SetIsPrototype(true); } else { hclass->SetClassConstructor(true); hclass->SetConstructor(true); } return hclass; } JSHandle ClassHelper::DefineClassFromExtractor(JSThread *thread, const JSHandle &base, JSHandle &extractor, const JSHandle &lexenv) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle staticKeys(thread, extractor->GetStaticKeys()); JSHandle staticProperties(thread, extractor->GetStaticProperties()); JSHandle nonStaticKeys(thread, extractor->GetNonStaticKeys()); JSHandle nonStaticProperties(thread, extractor->GetNonStaticProperties()); JSHandle prototypeHClass = ClassInfoExtractor::CreatePrototypeHClass(thread, nonStaticKeys, nonStaticProperties); JSHandle prototype = factory->NewOldSpaceJSObject(prototypeHClass); JSHandle method(thread, Method::Cast(extractor->GetConstructorMethod().GetTaggedObject())); JSHandle constructorHClass = ClassInfoExtractor::CreateConstructorHClass(thread, base, staticKeys, staticProperties); // Allocate to non-movable space for PGO JSHandle constructor = factory->NewJSFunctionByHClass(method, constructorHClass, MemSpaceType::NON_MOVABLE); // non-static nonStaticProperties->Set(thread, 0, constructor); uint32_t nonStaticLength = nonStaticProperties->GetLength(); JSMutableHandle propValue(thread, JSTaggedValue::Undefined()); if (LIKELY(!prototypeHClass->IsDictionaryMode())) { for (uint32_t index = 0; index < nonStaticLength; ++index) { propValue.Update(nonStaticProperties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneJSFuction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, prototype); propFunc->SetLexicalEnv(thread, lexenv); propValue.Update(propFunc); } prototype->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildDictionaryProperties(thread, prototype, nonStaticKeys, nonStaticProperties, ClassPropertyType::NON_STATIC, lexenv); prototype->SetProperties(thread, dict); } // non-static elements if (UNLIKELY(extractor->GetNonStaticWithElements())) { JSHandle nonStaticElements(thread, extractor->GetNonStaticElements()); ClassHelper::HandleElementsProperties(thread, prototype, nonStaticElements); } // static uint32_t staticLength = staticProperties->GetLength(); if (LIKELY(!constructorHClass->IsDictionaryMode())) { for (uint32_t index = 0; index < staticLength; ++index) { propValue.Update(staticProperties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneJSFuction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, constructor); propFunc->SetLexicalEnv(thread, lexenv); propValue.Update(propFunc); } JSHandle::Cast(constructor)->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildDictionaryProperties(thread, JSHandle(constructor), staticKeys, staticProperties, ClassPropertyType::STATIC, lexenv); constructor->SetProperties(thread, dict); } // static elements if (UNLIKELY(extractor->GetStaticWithElements())) { JSHandle staticElements(thread, extractor->GetStaticElements()); ClassHelper::HandleElementsProperties(thread, JSHandle(constructor), staticElements); } PropertyDescriptor ctorDesc(thread, JSHandle(constructor), true, false, true); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSTaggedValue::DefinePropertyOrThrow(thread, JSHandle(prototype), globalConst->GetHandledConstructorString(), ctorDesc); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSFunction, thread); constructor->SetHomeObject(thread, prototype); constructor->SetProtoOrHClass(thread, prototype); if (thread->GetEcmaVM()->IsEnablePGOProfiler()) { thread->GetEcmaVM()->GetPGOProfiler()->ProfileDefineClass(constructor.GetTaggedType()); } return constructor; } JSHandle ClassHelper::DefineClassWithIHClass(JSThread *thread, JSHandle &extractor, const JSHandle &lexenv, const JSHandle &prototypeOrHClass, const JSHandle &constructorHClass) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle staticKeys(thread, extractor->GetStaticKeys()); JSHandle staticProperties(thread, extractor->GetStaticProperties()); ClassInfoExtractor::CorrectConstructorHClass(thread, staticProperties, *constructorHClass); JSHandle nonStaticKeys(thread, extractor->GetNonStaticKeys()); JSHandle nonStaticProperties(thread, extractor->GetNonStaticProperties()); JSHandle prototype; if (prototypeOrHClass->IsJSHClass()) { JSHandle ihclass(prototypeOrHClass); prototype = JSHandle(thread, ihclass->GetProto()); } else { prototype = JSHandle(prototypeOrHClass); } JSHandle method(thread, Method::Cast(extractor->GetConstructorMethod().GetTaggedObject())); JSHandle constructor = factory->NewJSFunctionByHClass(method, constructorHClass, MemSpaceType::NON_MOVABLE); // non-static nonStaticProperties->Set(thread, 0, constructor); uint32_t nonStaticLength = nonStaticProperties->GetLength(); JSMutableHandle propValue(thread, JSTaggedValue::Undefined()); if (LIKELY(!prototype->GetJSHClass()->IsDictionaryMode())) { for (uint32_t index = 0; index < nonStaticLength; ++index) { propValue.Update(nonStaticProperties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneJSFuction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, prototype); propFunc->SetLexicalEnv(thread, lexenv); propValue.Update(propFunc); } prototype->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildDictionaryProperties(thread, prototype, nonStaticKeys, nonStaticProperties, ClassPropertyType::NON_STATIC, lexenv); prototype->SetProperties(thread, dict); } // non-static elements if (UNLIKELY(extractor->GetNonStaticWithElements())) { JSHandle nonStaticElements(thread, extractor->GetNonStaticElements()); ClassHelper::HandleElementsProperties(thread, prototype, nonStaticElements); } // static uint32_t staticLength = staticProperties->GetLength(); JSMutableHandle key(thread, JSTaggedValue::Undefined()); int correntIndex = 0; if (LIKELY(!constructorHClass->IsDictionaryMode())) { for (uint32_t index = 0; index < staticLength; ++index) { propValue.Update(staticProperties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneJSFuction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, constructor); propFunc->SetLexicalEnv(thread, lexenv); propValue.Update(propFunc); } bool needCorrentIndex = index >= ClassInfoExtractor::STATIC_RESERVED_LENGTH; if (needCorrentIndex) { key.Update(staticKeys->Get(index)); correntIndex = JSHClass::FindPropertyEntry(thread, *constructorHClass, key.GetTaggedValue()); } JSHandle::Cast(constructor)->SetPropertyInlinedProps(thread, needCorrentIndex ? static_cast(correntIndex) : index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildDictionaryProperties(thread, JSHandle(constructor), staticKeys, staticProperties, ClassPropertyType::STATIC, lexenv); constructor->SetProperties(thread, dict); } // static elements if (UNLIKELY(extractor->GetStaticWithElements())) { JSHandle staticElements(thread, extractor->GetStaticElements()); ClassHelper::HandleElementsProperties(thread, JSHandle(constructor), staticElements); } PropertyDescriptor ctorDesc(thread, JSHandle(constructor), true, false, true); const GlobalEnvConstants *globalConst = thread->GlobalConstants(); JSTaggedValue::DefinePropertyOrThrow(thread, JSHandle(prototype), globalConst->GetHandledConstructorString(), ctorDesc); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSFunction, thread); constructor->SetHomeObject(thread, prototype); constructor->SetProtoOrHClass(thread, prototypeOrHClass); if (thread->GetEcmaVM()->IsEnablePGOProfiler()) { thread->GetEcmaVM()->GetPGOProfiler()->ProfileDefineClass(constructor.GetTaggedType()); } return constructor; } JSHandle ClassHelper::BuildDictionaryProperties(JSThread *thread, const JSHandle &object, JSHandle &keys, JSHandle &properties, ClassPropertyType type, const JSHandle &lexenv) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); ASSERT(length > PropertyAttributes::MAX_FAST_PROPS_CAPACITY); ASSERT(keys->GetLength() == properties->GetLength()); JSMutableHandle dict( thread, NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(length))); JSMutableHandle propKey(thread, JSTaggedValue::Undefined()); JSMutableHandle propValue(thread, JSTaggedValue::Undefined()); for (uint32_t index = 0; index < length; index++) { PropertyAttributes attributes; if (type == ClassPropertyType::STATIC) { switch (index) { case ClassInfoExtractor::LENGTH_INDEX: attributes = PropertyAttributes::Default(false, false, true); break; case ClassInfoExtractor::NAME_INDEX: if (LIKELY(properties->Get(ClassInfoExtractor::NAME_INDEX).IsString())) { attributes = PropertyAttributes::Default(false, false, true); } else { ASSERT(properties->Get(ClassInfoExtractor::NAME_INDEX).IsJSFunction()); attributes = PropertyAttributes::Default(true, false, true); } break; case ClassInfoExtractor::PROTOTYPE_INDEX: attributes = PropertyAttributes::DefaultAccessor(false, false, false); break; default: attributes = PropertyAttributes::Default(true, false, true); break; } } else { attributes = PropertyAttributes::Default(true, false, true); // non-enumerable } propKey.Update(keys->Get(index)); propValue.Update(properties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneJSFuction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, object); propFunc->SetLexicalEnv(thread, lexenv); propValue.Update(propFunc); } JSHandle newDict = NameDictionary::PutIfAbsent(thread, dict, propKey, propValue, attributes); dict.Update(newDict); } return dict; } bool ClassHelper::MatchTrackType(TrackType trackType, JSTaggedValue value) { bool checkRet = false; switch (trackType) { case TrackType::NUMBER: { checkRet = value.IsNumber(); break; } case TrackType::INT: { checkRet = value.IsInt(); break; } case TrackType::DOUBLE: { checkRet = value.IsDouble(); break; } case TrackType::BOOLEAN: { checkRet = value.IsBoolean(); break; } case TrackType::STRING: { checkRet = value.IsString() || value.IsNull(); break; } case TrackType::SENDABLE: { checkRet = value.IsJSShared() || value.IsNull(); break; } case TrackType::NONE: { checkRet = true; break; } case TrackType::TAGGED: default: break; } return checkRet; } void ClassHelper::HandleElementsProperties(JSThread *thread, const JSHandle &object, JSHandle &elements) { JSMutableHandle elementsKey(thread, JSTaggedValue::Undefined()); JSMutableHandle elementsValue(thread, JSTaggedValue::Undefined()); for (uint32_t index = 0; index < elements->GetLength(); index += 2) { // 2: key-value pair elementsKey.Update(elements->Get(index)); elementsValue.Update(elements->Get(index + 1)); // class property attribute is not default, will transition to dictionary directly. JSObject::DefinePropertyByLiteral(thread, object, elementsKey, elementsValue, true); if (elementsValue->IsJSFunction()) { JSHandle elementsFunc = JSHandle::Cast(elementsValue); elementsFunc->SetHomeObject(thread, object); } } } JSHandle SendableClassDefiner::DefineSendableClassFromExtractor(JSThread *thread, JSHandle &extractor, const JSHandle &staticFieldArray) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle staticKeys(thread, extractor->GetStaticKeys()); JSHandle staticProperties(thread, extractor->GetStaticProperties()); SendableClassDefiner::FilterDuplicatedKeys(thread, staticKeys, staticProperties); JSHandle nonStaticKeys(thread, extractor->GetNonStaticKeys()); JSHandle nonStaticProperties(thread, extractor->GetNonStaticProperties()); SendableClassDefiner::FilterDuplicatedKeys(thread, nonStaticKeys, nonStaticProperties); JSHandle prototypeHClass = ClassInfoExtractor::CreateSendableHClass(thread, nonStaticKeys, nonStaticProperties, true); JSHandle prototype = factory->NewOldSpaceJSObject(prototypeHClass); uint32_t length = staticFieldArray->GetLength(); uint32_t staticFields = length / 2; // 2: key-type JSHandle constructorHClass = ClassInfoExtractor::CreateSendableHClass(thread, staticKeys, staticProperties, false, staticFields); JSHandle method(thread, Method::Cast(extractor->GetConstructorMethod().GetTaggedObject())); method->SetFunctionKind(FunctionKind::CLASS_CONSTRUCTOR); if (!constructorHClass->IsDictionaryMode() && staticFields > 0) { auto layout = JSHandle(thread, constructorHClass->GetLayout()); AddFieldTypeToHClass(thread, staticFieldArray, layout, constructorHClass); } JSHandle constructor = factory->NewJSFunctionByHClass(method, constructorHClass, MemSpaceType::NON_MOVABLE); // non-static nonStaticProperties->Set(thread, 0, constructor); uint32_t nonStaticLength = nonStaticProperties->GetLength(); JSMutableHandle propValue(thread, JSTaggedValue::Undefined()); if (LIKELY(!prototypeHClass->IsDictionaryMode())) { for (uint32_t index = 0; index < nonStaticLength; ++index) { propValue.Update(nonStaticProperties->Get(index)); // constructor don't need to clone if (propValue->IsJSFunction() && index != ClassInfoExtractor::CONSTRUCTOR_INDEX) { JSHandle propFunc = factory->CloneSFunction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, prototype); propFunc->SetLexicalEnv(thread, constructor); ASSERT(!propFunc->GetClass()->IsExtensible()); propValue.Update(propFunc); } else if (propValue->IsAccessorData()) { UpdateAccessorFunction(thread, propValue, JSHandle(prototype), constructor); } prototype->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildSendableDictionaryProperties(thread, prototype, nonStaticKeys, nonStaticProperties, ClassPropertyType::NON_STATIC, constructor); prototype->SetProperties(thread, dict); } // non-static elements if (UNLIKELY(extractor->GetNonStaticWithElements())) { THROW_TYPE_ERROR_AND_RETURN(thread, "Concurrent class don't support members with numerical key", JSHandle(thread, JSTaggedValue::Exception())); } // static uint32_t staticLength = staticProperties->GetLength(); if (LIKELY(!constructorHClass->IsDictionaryMode())) { for (uint32_t index = 0; index < staticLength; ++index) { propValue.Update(staticProperties->Get(index)); if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneSFunction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, constructor); propFunc->SetLexicalEnv(thread, constructor); ASSERT(!propFunc->GetClass()->IsExtensible()); propValue.Update(propFunc); } else if (propValue->IsAccessorData()) { UpdateAccessorFunction(thread, propValue, JSHandle(constructor), constructor); } JSHandle::Cast(constructor)->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue()); } } else { JSHandle dict = BuildSendableDictionaryProperties(thread, JSHandle(constructor), staticKeys, staticProperties, ClassPropertyType::STATIC, constructor); JSMutableHandle nameDict(thread, dict); if (staticFields > 0) { AddFieldTypeToDict(thread, staticFieldArray, nameDict, PropertyAttributes::Default(false, true, false)); } constructor->SetProperties(thread, nameDict); } // static elements if (UNLIKELY(extractor->GetStaticWithElements())) { THROW_TYPE_ERROR_AND_RETURN(thread, "Concurrent class don't support static members with numerical key", JSHandle(thread, JSTaggedValue::Exception())); } prototype->GetJSHClass()->SetExtensible(false); constructor->SetHomeObject(thread, prototype); constructor->SetProtoOrHClass(thread, prototype); constructor->SetLexicalEnv(thread, constructor); return constructor; } // Process duplicated key due to getter/setter. void SendableClassDefiner::FilterDuplicatedKeys(JSThread *thread, const JSHandle &keys, const JSHandle &properties) { auto attr = PropertyAttributes::Default(); uint32_t length = keys->GetLength(); uint32_t left = 0; uint32_t right = 0; JSMutableHandle dict( thread, NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(length))); JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSMutableHandle value(thread, JSTaggedValue::Undefined()); JSMutableHandle existValue(thread, JSTaggedValue::Undefined()); JSMutableHandle index(thread, JSTaggedValue::Undefined()); for (; right < length; right++) { key.Update(keys->Get(right)); value.Update(properties->Get(right)); int entry = dict->FindEntry(key.GetTaggedValue()); if (entry == -1) { TryUpdateValue(thread, value); index.Update(JSTaggedValue(left)); JSHandle newDict = NameDictionary::PutIfAbsent(thread, dict, key, index, attr); dict.Update(newDict); if (left < right) { keys->Set(thread, left, key); } properties->Set(thread, left, value); left++; continue; } auto existIndex = static_cast(dict->GetValue(entry).GetNumber()); existValue.Update(properties->Get(existIndex)); bool needUpdateValue = TryUpdateExistValue(thread, existValue, value); if (needUpdateValue) { properties->Set(thread, existIndex, value); } } if (left < right) { keys->Trim(thread, left); properties->Trim(thread, left); } } JSHandle SendableClassDefiner::BuildSendableDictionaryProperties(JSThread *thread, const JSHandle &object, JSHandle &keys, JSHandle &properties, ClassPropertyType type, const JSHandle &ctor) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); ASSERT(keys->GetLength() == properties->GetLength()); JSMutableHandle dict( thread, NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(length))); JSMutableHandle propKey(thread, JSTaggedValue::Undefined()); JSMutableHandle propValue(thread, JSTaggedValue::Undefined()); for (uint32_t index = 0; index < length; index++) { PropertyAttributes attributes = PropertyAttributes::Default(false, false, false); propKey.Update(keys->Get(index)); propValue.Update(properties->Get(index)); // constructor don't need to clone if (index == ClassInfoExtractor::CONSTRUCTOR_INDEX && type == ClassPropertyType::NON_STATIC) { JSHandle newDict = NameDictionary::PutIfAbsent(thread, dict, propKey, propValue, attributes); dict.Update(newDict); continue; } if (propValue->IsJSFunction()) { JSHandle propFunc = factory->CloneSFunction(JSHandle::Cast(propValue)); propFunc->SetHomeObject(thread, object); propFunc->SetLexicalEnv(thread, ctor); ASSERT(!propFunc->GetClass()->IsExtensible()); propValue.Update(propFunc); } else if (propValue->IsAccessorData()) { UpdateAccessorFunction(thread, propValue, JSHandle(object), ctor); } JSHandle newDict = NameDictionary::PutIfAbsent(thread, dict, propKey, propValue, attributes); dict.Update(newDict); } return dict; } void SendableClassDefiner::AddFieldTypeToHClass(JSThread *thread, const JSHandle &fieldTypeArray, const JSHandle &layout, const JSHandle &hclass) { uint32_t length = fieldTypeArray->GetLength(); JSMutableHandle key(thread, JSTaggedValue::Undefined()); uint32_t index = static_cast(layout->NumberOfElements()); PropertyAttributes attributes = PropertyAttributes::Default(true, true, false); attributes.SetIsInlinedProps(true); attributes.SetRepresentation(Representation::TAGGED); for (uint32_t i = 0; i < length; i += 2) { // 2: key-value pair; key.Update(fieldTypeArray->Get(i)); ASSERT(key->IsString()); TrackType type = FromFieldType(FieldType(fieldTypeArray->Get(i + 1).GetInt())); int entry = layout->FindElementWithCache(thread, *hclass, key.GetTaggedValue(), index); if (entry != -1) { attributes = layout->GetAttr(entry); attributes.SetTrackType(type); layout->SetNormalAttr(thread, entry, attributes); } else { attributes.SetTrackType(type); attributes.SetOffset(index); layout->AddKey(thread, index++, key.GetTaggedValue(), attributes); } } hclass->SetLayout(thread, layout); hclass->SetNumberOfProps(index); auto inlinedProps = hclass->GetInlinedProperties(); if (inlinedProps > index) { // resize hclass due to duplicated key. uint32_t duplicatedSize = (inlinedProps - index) * JSTaggedValue::TaggedTypeSize(); hclass->SetObjectSize(hclass->GetObjectSize() - duplicatedSize); } } void SendableClassDefiner::AddFieldTypeToDict(JSThread *thread, const JSHandle &fieldTypeArray, JSMutableHandle &dict, PropertyAttributes attributes) { uint32_t length = fieldTypeArray->GetLength(); JSMutableHandle key(thread, JSTaggedValue::Undefined()); auto globalConst = const_cast(thread->GlobalConstants()); JSHandle value = globalConst->GetHandledUndefined(); for (uint32_t i = 0; i < length; i += 2) { // 2: key-value pair; key.Update(fieldTypeArray->Get(i)); ASSERT(key->IsString()); TrackType type = FromFieldType(FieldType(fieldTypeArray->Get(i + 1).GetInt())); attributes.SetTrackType(type); attributes.SetBoxType(PropertyBoxType::UNDEFINED); JSHandle newDict = NameDictionary::Put(thread, dict, key, value, attributes); dict.Update(newDict); } } void SendableClassDefiner::AddFieldTypeToHClass(JSThread *thread, const JSHandle &fieldTypeArray, const JSHandle &nameDict, const JSHandle &hclass) { JSMutableHandle dict(thread, nameDict); AddFieldTypeToDict(thread, fieldTypeArray, dict); hclass->SetLayout(thread, dict); hclass->SetNumberOfProps(0); hclass->SetIsDictionaryMode(true); } void SendableClassDefiner::DefineSendableInstanceHClass(JSThread *thread, const JSHandle &fieldTypeArray, const JSHandle &ctor, const JSHandle &base) { ASSERT(ctor->GetClass()->IsJSSharedFunction()); JSHandle clsPrototype(thread, JSHandle(ctor)->GetFunctionPrototype()); ASSERT(clsPrototype->GetClass()->IsJSSharedObject()); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = fieldTypeArray->GetLength(); uint32_t fieldNum = length / 2; // 2: key-value pair; JSHandle iHClass; if (base->IsHole() || base->IsNull()) { if (fieldNum == 0) { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, fieldNum); } else if (LIKELY(fieldNum <= JSSharedObject::MAX_INLINE)) { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, fieldNum); JSHandle layout = factory->CreateLayoutInfo(fieldNum, MemSpaceType::OLD_SPACE, GrowMode::KEEP); AddFieldTypeToHClass(thread, fieldTypeArray, layout, iHClass); } else { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, 0); JSHandle dict = NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(fieldNum)); AddFieldTypeToHClass(thread, fieldTypeArray, dict, iHClass); } } else { ASSERT(base->IsJSSharedFunction()); JSHandle baseCtor = JSHandle::Cast(base); JSHandle baseIHClass(thread, baseCtor->GetProtoOrHClass()); ASSERT(baseIHClass->IsJSSharedObject()); if (LIKELY(!baseIHClass->IsDictionaryMode())) { auto baseLength = baseIHClass->NumberOfProps(); JSHandle baseLayout(thread, baseIHClass->GetLayout()); auto newLength = baseLength + fieldNum; if (fieldNum == 0) { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, fieldNum); } else if (LIKELY(newLength <= JSSharedObject::MAX_INLINE)) { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, newLength); JSHandle layout = factory->CopyAndReSort(baseLayout, baseLength, newLength); AddFieldTypeToHClass(thread, fieldTypeArray, layout, iHClass); } else { iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, 0); JSHandle dict = NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(newLength)); auto globalConst = const_cast(thread->GlobalConstants()); JSHandle value = globalConst->GetHandledUndefined(); JSMutableHandle key(thread, JSTaggedValue::Undefined()); for (uint32_t i = 0; i < baseLength; i++) { key.Update(baseLayout->GetKey(i)); PropertyAttributes attr = baseLayout->GetAttr(i); attr.SetIsInlinedProps(false); attr.SetBoxType(PropertyBoxType::UNDEFINED); dict = NameDictionary::Put(thread, dict, key, value, attr); } AddFieldTypeToHClass(thread, fieldTypeArray, dict, iHClass); } } else { JSHandle baseDict(thread, baseIHClass->GetLayout()); auto baseLength = baseDict->EntriesCount(); JSHandle dict = NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(fieldNum + baseLength)); baseDict->Rehash(thread, *dict); dict->SetNextEnumerationIndex(thread, baseDict->GetNextEnumerationIndex()); iHClass = factory->NewEcmaHClass(JSSharedObject::SIZE, JSType::JS_SHARED_OBJECT, 0); AddFieldTypeToHClass(thread, fieldTypeArray, dict, iHClass); } } iHClass->SetPrototype(thread, JSHandle(clsPrototype)); iHClass->SetExtensible(false); ctor->SetProtoOrHClass(thread, iHClass); ctor->GetJSHClass()->SetExtensible(false); } JSHandle SendableClassDefiner::ExtractStaticFieldTypeArray(JSThread *thread, const JSHandle &fieldTypeArray) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = fieldTypeArray->GetLength(); auto instanceFieldNums = static_cast(fieldTypeArray->Get(arrayLength - 1).GetInt()); uint32_t staticFieldBegin = instanceFieldNums * 2; // 2: key-type if (staticFieldBegin >= arrayLength) { LOG_ECMA(ERROR) << "ExtractStaticFieldTypeArray Failed, staticFieldBegin:" << staticFieldBegin << " should be less than totalLength:" << arrayLength; return factory->EmptyArray(); } uint32_t staticFieldLength = arrayLength - staticFieldBegin - 1; JSHandle staticFieldArray = factory->NewTaggedArray(staticFieldLength); for (uint32_t i = 0; i < staticFieldLength; i += 2) { // 2: key-type staticFieldArray->Set(thread, i, fieldTypeArray->Get(staticFieldBegin + i)); staticFieldArray->Set(thread, i + 1, fieldTypeArray->Get(staticFieldBegin + i + 1)); } return staticFieldArray; } void SendableClassDefiner::UpdateAccessorFunction(JSThread *thread, const JSMutableHandle &value, const JSHandle &homeObject, const JSHandle &ctor) { ASSERT(value->IsAccessorData()); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle accessor(value); auto getter = accessor->GetGetter(); if (getter.IsJSFunction()) { JSHandle func(thread, getter); JSHandle propFunc = factory->CloneSFunction(func); propFunc->SetHomeObject(thread, homeObject); propFunc->SetLexicalEnv(thread, ctor); ASSERT(!propFunc->GetClass()->IsExtensible()); accessor->SetGetter(thread, propFunc); } auto setter = accessor->GetSetter(); if (setter.IsJSFunction()) { JSHandle func(thread, setter); JSHandle propFunc = factory->CloneSFunction(func); propFunc->SetHomeObject(thread, homeObject); propFunc->SetLexicalEnv(thread, ctor); ASSERT(!propFunc->GetClass()->IsExtensible()); accessor->SetSetter(thread, propFunc); } } bool SendableClassDefiner::TryUpdateExistValue(JSThread *thread, JSMutableHandle &existValue, JSMutableHandle &value) { bool needUpdateValue = true; if (existValue->IsAccessorData()) { if (value->IsJSFunction() && JSHandle(value)->IsGetterOrSetter()) { JSHandle accessor(existValue); UpdateValueToAccessor(thread, value, accessor); needUpdateValue = false; } } else { if (value->IsJSFunction() && JSHandle(value)->IsGetterOrSetter()) { JSHandle accessor = thread->GetEcmaVM()->GetFactory()->NewAccessorData(); UpdateValueToAccessor(thread, value, accessor); } } return needUpdateValue; } void SendableClassDefiner::TryUpdateValue(JSThread *thread, JSMutableHandle &value) { if (value->IsJSFunction() && JSHandle(value)->IsGetterOrSetter()) { JSHandle accessor = thread->GetEcmaVM()->GetFactory()->NewAccessorData(); UpdateValueToAccessor(thread, value, accessor); } } void SendableClassDefiner::UpdateValueToAccessor(JSThread *thread, JSMutableHandle &value, JSHandle &accessor) { ASSERT(value->IsJSFunction() && JSHandle(value)->IsGetterOrSetter()); if (JSHandle(value)->IsGetter()) { accessor->SetGetter(thread, value); } else { accessor->SetSetter(thread, value); } value.Update(accessor); } } // namespace panda::ecmascript