1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#ifndef ECMASCRIPT_JS_HCLASS_INL_H
17#define ECMASCRIPT_JS_HCLASS_INL_H
18
19#include "ecmascript/js_hclass.h"
20
21#include "ecmascript/js_bigint.h"
22#include "ecmascript/layout_info.h"
23#include "ecmascript/layout_info-inl.h"
24#include "ecmascript/byte_array.h"
25#include "ecmascript/mem/assert_scope.h"
26#include "ecmascript/transitions_dictionary.h"
27
28namespace panda::ecmascript {
29
30bool JSHClass::ProtoIsFastJSArray(const JSThread *thread, const JSHandle<JSTaggedValue> proto,
31                                  const JSHandle<JSHClass> hclass)
32{
33    // Since we currently only support ElementsKind for JSArray initial hclass,
34    // if an object's hclass has a non-generic ElementsKind, it must be one of the JSArray initial hclass.
35    // if an object's hclass has a Generic ElementsKind, it might be the JSArray initial generic elementskind hclass,
36    // which therefore needs further hclass comparison.
37    if (proto->IsJSArray()) {
38        JSTaggedValue genericArrayHClass = thread->GlobalConstants()->GetElementHoleTaggedClass();
39        if (!Elements::IsGeneric(hclass->GetElementsKind()) || hclass.GetTaggedValue() == genericArrayHClass) {
40            return true;
41        }
42    }
43    return false;
44}
45
46void JSHClass::AddTransitions(const JSThread *thread, const JSHandle<JSHClass> &parent, const JSHandle<JSHClass> &child,
47                              const JSHandle<JSTaggedValue> &key, PropertyAttributes attributes)
48{
49    UpdateRootHClass(thread, parent, child);
50    JSTaggedValue transitions = parent->GetTransitions();
51    if (transitions.IsUndefined()) {
52        JSTaggedValue weakChild = JSTaggedValue(child.GetTaggedValue().CreateAndGetWeakRef());
53        parent->SetTransitions(thread, weakChild);
54        return;
55    }
56    JSMutableHandle<TransitionsDictionary> dict(thread, JSTaggedValue::Undefined());
57    if (transitions.IsWeak()) {
58        auto cachedHClass = JSHClass::Cast(transitions.GetTaggedWeakRef());
59        if (cachedHClass->HasProps()) {
60            uint32_t last = cachedHClass->LastPropIndex();
61            LayoutInfo* layoutInfo = LayoutInfo::Cast(cachedHClass->GetLayout().GetTaggedObject());
62            auto metaData = JSHandle<JSTaggedValue>(thread,
63                JSTaggedValue(layoutInfo->GetAttr(last).GetPropertyMetaData()));
64            auto lastKey = JSHandle<JSTaggedValue>(thread, layoutInfo->GetKey(last));
65            auto lastHClass = JSHandle<JSTaggedValue>(thread, cachedHClass);
66            dict.Update(TransitionsDictionary::Create(thread));
67            transitions = TransitionsDictionary::PutIfAbsent(thread, dict, lastKey, lastHClass,
68                metaData).GetTaggedValue();
69        }
70    }
71    auto metaData = JSHandle<JSTaggedValue>(thread, JSTaggedValue(attributes.GetPropertyMetaData()));
72    dict.Update(transitions);
73    transitions = TransitionsDictionary::PutIfAbsent(thread, dict, key, JSHandle<JSTaggedValue>(child),
74        metaData).GetTaggedValue();
75    parent->SetTransitions(thread, transitions);
76}
77
78void JSHClass::AddExtensionTransitions(const JSThread *thread, const JSHandle<JSHClass> &parent,
79                                       const JSHandle<JSHClass> &child, const JSHandle<JSTaggedValue> &key)
80{
81    auto attr = JSHandle<JSTaggedValue>(thread, PropertyAttributes(0).GetTaggedValue());
82    AddProtoTransitions(thread, parent, child, key, attr);
83}
84
85void JSHClass::AddProtoTransitions(const JSThread *thread, const JSHandle<JSHClass> &parent,
86                                   const JSHandle<JSHClass> &child, const JSHandle<JSTaggedValue> &key,
87                                   const JSHandle<JSTaggedValue> &proto)
88{
89    ALLOW_LOCAL_TO_SHARE_WEAK_REF_HANDLE;
90    UpdateRootHClass(thread, parent, child);
91    JSTaggedValue transitions = parent->GetTransitions();
92    JSMutableHandle<TransitionsDictionary> dict(thread, JSTaggedValue::Undefined());
93    if (transitions.IsUndefined()) {
94        transitions = TransitionsDictionary::Create(thread).GetTaggedValue();
95    } else if (transitions.IsWeak()) {
96        auto cachedHClass = JSHClass::Cast(transitions.GetTaggedWeakRef());
97        if (cachedHClass->HasProps()) {
98            uint32_t last = cachedHClass->LastPropIndex();
99            LayoutInfo* layoutInfo = LayoutInfo::Cast(cachedHClass->GetLayout().GetTaggedObject());
100            auto metaData = JSHandle<JSTaggedValue>(thread,
101                JSTaggedValue(layoutInfo->GetAttr(last).GetPropertyMetaData()));
102            auto lastKey = JSHandle<JSTaggedValue>(thread, layoutInfo->GetKey(last));
103            auto lastHClass = JSHandle<JSTaggedValue>(thread, cachedHClass);
104            dict.Update(TransitionsDictionary::Create(thread));
105            transitions = TransitionsDictionary::PutIfAbsent(thread, dict, lastKey, lastHClass,
106                metaData).GetTaggedValue();
107        }
108    }
109    dict.Update(transitions);
110    transitions =
111        TransitionsDictionary::PutIfAbsent(thread, dict, key, JSHandle<JSTaggedValue>(child), proto).GetTaggedValue();
112    parent->SetTransitions(thread, transitions);
113}
114
115inline JSHClass *JSHClass::FindTransitions(const JSTaggedValue &key, const JSTaggedValue &metaData,
116                                           const Representation &rep)
117{
118    DISALLOW_GARBAGE_COLLECTION;
119    JSTaggedValue transitions = GetTransitions();
120    if (transitions.IsUndefined()) {
121        return nullptr;
122    }
123    if (transitions.IsWeak()) {
124        auto cachedHClass = JSHClass::Cast(transitions.GetTaggedWeakRef());
125        if (cachedHClass->PropsIsEmpty()) {
126            return nullptr;
127        }
128        int last = static_cast<int>(cachedHClass->LastPropIndex());
129        LayoutInfo *layoutInfo = LayoutInfo::Cast(cachedHClass->GetLayout().GetTaggedObject());
130        auto lastMetaData = layoutInfo->GetAttr(last).GetPropertyMetaData();
131        auto lastKey = layoutInfo->GetKey(last);
132        if (lastMetaData == metaData.GetInt() && key == lastKey) {
133            return CheckHClassForRep(cachedHClass, rep);
134        }
135        return nullptr;
136    }
137
138    ASSERT(transitions.IsTaggedArray());
139    TransitionsDictionary *dict = TransitionsDictionary::Cast(transitions.GetTaggedObject());
140    auto entry = dict->FindEntry(key, metaData);
141    if (entry == -1) {
142        return nullptr;
143    }
144
145    JSTaggedValue ret = dict->GetValue(entry);
146    if (ret.IsUndefined()) {
147        return nullptr;
148    }
149
150    return CheckHClassForRep(JSHClass::Cast(ret.GetTaggedWeakRef()), rep);
151}
152
153inline JSHClass *JSHClass::FindProtoTransitions(const JSTaggedValue &key, const JSTaggedValue &proto)
154{
155    DISALLOW_GARBAGE_COLLECTION;
156    JSTaggedValue transitions = GetTransitions();
157    if (transitions.IsWeak() || !transitions.IsTaggedArray()) {
158        ASSERT(transitions.IsUndefined() || transitions.IsWeak());
159        return nullptr;
160    }
161    ASSERT(transitions.IsTaggedArray());
162    TransitionsDictionary *dict = TransitionsDictionary::Cast(transitions.GetTaggedObject());
163    auto entry = dict->FindEntry(key, proto);
164    if (entry == -1) {
165        return nullptr;
166    }
167
168    JSTaggedValue ret = dict->GetValue(entry);
169    if (ret.IsUndefined()) {
170        return nullptr;
171    }
172
173    return JSHClass::Cast(ret.GetTaggedWeakRef());
174}
175
176inline void JSHClass::RestoreElementsKindToGeneric(JSHClass *newJsHClass)
177{
178    newJsHClass->SetElementsKind(ElementsKind::GENERIC);
179}
180
181inline JSHClass *JSHClass::CheckHClassForRep(JSHClass *hclass, const Representation &rep)
182{
183    if (!hclass->IsTS()) {
184        return hclass;
185    }
186    if (rep == Representation::NONE) {
187        return hclass;
188    }
189
190    int last = static_cast<int>(hclass->LastPropIndex());
191    LayoutInfo *layoutInfo = LayoutInfo::Cast(hclass->GetLayout().GetTaggedObject());
192    auto lastRep = layoutInfo->GetAttr(last).GetRepresentation();
193    auto result = hclass;
194    if (lastRep == Representation::INT) {
195        if (rep != Representation::INT) {
196            result = nullptr;
197        }
198    } else if (lastRep == Representation::DOUBLE) {
199        if (rep != Representation::INT && rep != Representation::DOUBLE) {
200            result = nullptr;
201        }
202    }
203    return result;
204}
205
206inline void JSHClass::UpdatePropertyMetaData(const JSThread *thread, [[maybe_unused]] const JSTaggedValue &key,
207                                             const PropertyAttributes &metaData)
208{
209    DISALLOW_GARBAGE_COLLECTION;
210    ASSERT(!GetLayout().IsNull());
211    LayoutInfo *layoutInfo = LayoutInfo::Cast(GetLayout().GetTaggedObject());
212    ASSERT(layoutInfo->GetLength() != 0);
213    uint32_t entry = metaData.GetOffset();
214
215    layoutInfo->SetNormalAttr(thread, entry, metaData);
216}
217
218inline bool JSHClass::HasReferenceField()
219{
220    auto type = GetObjectType();
221    switch (type) {
222        case JSType::LINE_STRING:
223        case JSType::CONSTANT_STRING:
224        case JSType::JS_NATIVE_POINTER:
225            return false;
226        default:
227            return true;
228    }
229}
230
231inline size_t JSHClass::SizeFromJSHClass(TaggedObject *header)
232{
233    // CAUTION! Never use T::Cast(header) in this function
234    // it would cause issue during GC because hclass may forward to a new addres
235    // and the casting method would still use the old address.
236    auto type = GetObjectType();
237    size_t size = 0;
238    switch (type) {
239        case JSType::TAGGED_ARRAY:
240        case JSType::TAGGED_DICTIONARY:
241        case JSType::LEXICAL_ENV:
242        case JSType::SENDABLE_ENV:
243        case JSType::CONSTANT_POOL:
244        case JSType::AOT_LITERAL_INFO:
245        case JSType::VTABLE:
246        case JSType::COW_TAGGED_ARRAY:
247        case JSType::MUTANT_TAGGED_ARRAY:
248        case JSType::COW_MUTANT_TAGGED_ARRAY:
249        case JSType::PROFILE_TYPE_INFO:
250            size = TaggedArray::ComputeSize(JSTaggedValue::TaggedTypeSize(),
251                reinterpret_cast<TaggedArray *>(header)->GetLength());
252            break;
253        case JSType::BYTE_ARRAY:
254            size = ByteArray::ComputeSize(reinterpret_cast<ByteArray *>(header)->GetByteLength(),
255                                          reinterpret_cast<ByteArray *>(header)->GetArrayLength());
256            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
257            break;
258        case JSType::LINE_STRING:
259            size = LineEcmaString::ObjectSize(reinterpret_cast<EcmaString* >(header));
260            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
261            break;
262        case JSType::CONSTANT_STRING:
263            size = ConstantString::SIZE;
264            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
265            break;
266        case JSType::TREE_STRING:
267            size = TreeEcmaString::SIZE;
268            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
269            break;
270        case JSType::SLICED_STRING:
271            size = SlicedString::SIZE;
272            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
273            break;
274        case JSType::MACHINE_CODE_OBJECT:
275            size = reinterpret_cast<MachineCode *>(header)->GetMachineCodeObjectSize();
276            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
277            break;
278        case JSType::BIGINT:
279            size = BigInt::ComputeSize(reinterpret_cast<BigInt *>(header)->GetLength());
280            size = AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
281            break;
282        default:
283            ASSERT(GetObjectSize() != 0);
284            size = GetObjectSize();
285            break;
286    }
287    ASSERT(AlignUp(size, static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT)) == size);
288    return size;
289}
290
291inline void JSHClass::Copy(const JSThread *thread, const JSHClass *jshclass)
292{
293    DISALLOW_GARBAGE_COLLECTION;
294
295    // copy jshclass
296    SetPrototype(thread, jshclass->GetPrototype());
297    SetBitField(jshclass->GetBitField());
298    SetIsAllTaggedProp(jshclass->IsAllTaggedProp());
299    SetNumberOfProps(jshclass->NumberOfProps());
300}
301
302inline JSHClass *JSHClass::FindRootHClass(JSHClass *hclass)
303{
304    auto root = hclass;
305    while (!ProfileType(root->GetProfileType()).IsRootType()) {
306        auto parent = root->GetParent();
307        if (!parent.IsJSHClass()) {
308            break;
309        }
310        root = JSHClass::Cast(parent.GetTaggedObject());
311    }
312    return root;
313}
314
315inline JSTaggedValue JSHClass::FindProtoHClass(JSHClass *hclass)
316{
317    auto proto = hclass->GetProto();
318    if (proto.IsJSObject()) {
319        auto prototypeObj = JSObject::Cast(proto);
320        return JSTaggedValue(prototypeObj->GetClass());
321    }
322    return JSTaggedValue::Undefined();
323}
324
325inline JSTaggedValue JSHClass::FindProtoRootHClass(JSHClass *hclass)
326{
327    auto proto = hclass->GetProto();
328    if (proto.IsJSObject()) {
329        auto prototypeObj = JSObject::Cast(proto);
330        auto prototypeHClass = prototypeObj->GetClass();
331        return JSTaggedValue(JSHClass::FindRootHClass(prototypeHClass));
332    }
333    return JSTaggedValue::Undefined();
334}
335
336inline void JSHClass::UpdateRootHClass(const JSThread *thread, const JSHandle<JSHClass> &parent,
337                                       const JSHandle<JSHClass> &child)
338{
339    if (thread->GetEcmaVM()->IsEnablePGOProfiler()) {
340        child->SetParent(thread, parent);
341    }
342}
343
344inline int JSHClass::FindPropertyEntry(const JSThread *thread, JSHClass *hclass, JSTaggedValue key)
345{
346    DISALLOW_GARBAGE_COLLECTION;
347    LayoutInfo *layout = LayoutInfo::Cast(hclass->GetLayout().GetTaggedObject());
348    uint32_t propsNumber = hclass->NumberOfProps();
349    int entry = layout->FindElementWithCache(thread, hclass, key, propsNumber);
350    return entry;
351}
352
353template<bool checkDuplicateKeys /* = false*/>
354void JSHClass::AddPropertyToNewHClass(const JSThread *thread, JSHandle<JSHClass> &jshclass,
355                                      JSHandle<JSHClass> &newJsHClass,
356                                      const JSHandle<JSTaggedValue> &key,
357                                      const PropertyAttributes &attr)
358{
359    ASSERT(!jshclass->IsDictionaryMode());
360    ASSERT(!newJsHClass->IsDictionaryMode());
361    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
362    // Add Property and metaData
363    uint32_t offset = attr.GetOffset();
364    newJsHClass->IncNumberOfProps();
365
366    {
367        JSMutableHandle<LayoutInfo> layoutInfoHandle(thread, newJsHClass->GetLayout());
368
369        if (layoutInfoHandle->NumberOfElements() != static_cast<int>(offset)) {
370            layoutInfoHandle.Update(factory->CopyAndReSort(layoutInfoHandle, offset, offset + 1));
371        } else if (layoutInfoHandle->GetPropertiesCapacity() <= static_cast<int>(offset)) {  // need to Grow
372            layoutInfoHandle.Update(
373                factory->ExtendLayoutInfo(layoutInfoHandle, offset));
374        }
375        newJsHClass->SetLayout(thread, layoutInfoHandle);
376        layoutInfoHandle->AddKey<checkDuplicateKeys>(thread, offset, key.GetTaggedValue(), attr);
377    }
378
379    // Add newClass to old hclass's transitions.
380    AddTransitions(thread, jshclass, newJsHClass, key, attr);
381}
382
383template<bool checkDuplicateKeys /* = false*/>
384JSHandle<JSHClass> JSHClass::SetPropertyOfObjHClass(const JSThread *thread, JSHandle<JSHClass> &jshclass,
385                                                    const JSHandle<JSTaggedValue> &key,
386                                                    const PropertyAttributes &attr, const Representation &rep)
387{
388    JSHClass *newClass = jshclass->FindTransitions(
389        key.GetTaggedValue(), JSTaggedValue(attr.GetPropertyMetaData()), rep);
390    if (newClass != nullptr) {
391        newClass->SetPrototype(thread, jshclass->GetPrototype());
392        return JSHandle<JSHClass>(thread, newClass);
393    }
394
395    JSHandle<JSHClass> newJsHClass = JSHClass::Clone(thread, jshclass);
396    AddPropertyToNewHClass<checkDuplicateKeys>(thread, jshclass, newJsHClass, key, attr);
397    return newJsHClass;
398}
399}  // namespace panda::ecmascript
400
401#endif  // ECMASCRIPT_JS_HCLASS_INL_H
402