/* * 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, const JSHandle &base, JSHandle &keys, JSHandle &properties) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); if (length == ClassInfoExtractor::NON_STATIC_RESERVED_LENGTH && base->IsHole()) { const GlobalEnvConstants *globalConst = thread->GlobalConstants(); return JSHandle(globalConst->GetHandledClassPrototypeClass()); } JSHandle hclass; if (LIKELY(length <= PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES)) { 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, JSHandle &method) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t length = keys->GetLength(); if (length == ClassInfoExtractor::STATIC_RESERVED_LENGTH && base->IsHole() && properties->Get(NAME_INDEX).IsString()) { const GlobalEnvConstants *globalConst = thread->GlobalConstants(); if (method->IsAotWithCallField()) { if (method->IsFastCall()) { return JSHandle(globalConst->GetHandledClassConstructorOptimizedWithFastCallClass()); } else { return JSHandle(globalConst->GetHandledClassConstructorOptimizedClass()); } } else { return JSHandle(globalConst->GetHandledClassConstructorClass()); } } JSHandle hclass; if (LIKELY(length <= PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES)) { 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 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, base, nonStaticKeys, nonStaticProperties); JSHandle prototype = factory->NewOldSpaceJSObject(prototypeHClass); JSHandle method(thread, Method::Cast(extractor->GetConstructorMethod().GetTaggedObject())); JSHandle constructorHClass = ClassInfoExtractor::CreateConstructorHClass(thread, base, staticKeys, staticProperties, method); // 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); return constructor; } JSHandle ClassHelper::DefineClassWithIHClass(JSThread *thread, JSHandle &extractor, const JSHandle &lexenv, const JSHandle &ihclass, 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(thread, ihclass->GetProto()); JSHandle method(thread, Method::Cast(extractor->GetConstructorMethod().GetTaggedObject())); constructorHClass->SetIsOptimized(method->IsAotWithCallField()); constructorHClass->SetCanFastCall(method->IsFastCall()); 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, ihclass); 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_CAPACITY_OF_PROPERTIES); 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; } 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); } } } } // namespace panda::ecmascript