1/*
2 * Copyright (c) 2023 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#include "ecmascript/elements.h"
17#include "ecmascript/js_tagged_value-inl.h"
18#include "ecmascript/tagged_array-inl.h"
19
20namespace panda::ecmascript {
21CMap<ElementsKind, std::pair<ConstantIndex, ConstantIndex>> Elements::InitializeHClassMap()
22{
23    CMap<ElementsKind, std::pair<ConstantIndex, ConstantIndex>> result;
24#define INIT_ARRAY_HCLASS_INDEX_MAPS(name)                                                                       \
25    result.emplace(ElementsKind::name, std::make_pair(ConstantIndex::ELEMENT_##name##_HCLASS_INDEX,              \
26                                                      ConstantIndex::ELEMENT_##name##_PROTO_HCLASS_INDEX));
27    ELEMENTS_KIND_INIT_HCLASS_LIST(INIT_ARRAY_HCLASS_INDEX_MAPS)
28#undef INIT_ARRAY_HCLASS_INDEX_MAPS
29    return result;
30}
31
32std::string Elements::GetString(ElementsKind kind)
33{
34    return std::to_string(static_cast<uint32_t>(kind));
35}
36
37bool Elements::IsInt(ElementsKind kind)
38{
39    return kind == ElementsKind::INT;
40}
41
42bool Elements::IsNumber(ElementsKind kind)
43{
44    return kind == ElementsKind::NUMBER;
45}
46
47bool Elements::IsTagged(ElementsKind kind)
48{
49    return kind == ElementsKind::TAGGED;
50}
51
52bool Elements::IsObject(ElementsKind kind)
53{
54    return kind == ElementsKind::OBJECT;
55}
56
57bool Elements::IsHole(ElementsKind kind)
58{
59    static constexpr uint8_t EVEN_NUMBER = 2;
60    return static_cast<uint8_t>(kind) % EVEN_NUMBER == 1;
61}
62
63ConstantIndex Elements::GetGlobalContantIndexByKind(ElementsKind kind)
64{
65    switch (kind) {
66        case ElementsKind::NONE:
67            return ConstantIndex::ELEMENT_NONE_HCLASS_INDEX;
68        case ElementsKind::INT:
69            return ConstantIndex::ELEMENT_INT_HCLASS_INDEX;
70        case ElementsKind::NUMBER:
71            return ConstantIndex::ELEMENT_NUMBER_HCLASS_INDEX;
72        case ElementsKind::STRING:
73            return ConstantIndex::ELEMENT_STRING_HCLASS_INDEX;
74        case ElementsKind::OBJECT:
75            return ConstantIndex::ELEMENT_OBJECT_HCLASS_INDEX;
76        case ElementsKind::TAGGED:
77            return ConstantIndex::ELEMENT_TAGGED_HCLASS_INDEX;
78        case ElementsKind::HOLE:
79            return ConstantIndex::ELEMENT_HOLE_HCLASS_INDEX;
80        case ElementsKind::HOLE_INT:
81            return ConstantIndex::ELEMENT_HOLE_INT_HCLASS_INDEX;
82        case ElementsKind::HOLE_NUMBER:
83            return ConstantIndex::ELEMENT_HOLE_NUMBER_HCLASS_INDEX;
84        case ElementsKind::HOLE_STRING:
85            return ConstantIndex::ELEMENT_HOLE_STRING_HCLASS_INDEX;
86        case ElementsKind::HOLE_OBJECT:
87            return ConstantIndex::ELEMENT_HOLE_OBJECT_HCLASS_INDEX;
88        case ElementsKind::HOLE_TAGGED:
89            return ConstantIndex::ELEMENT_HOLE_TAGGED_HCLASS_INDEX;
90        default:
91            LOG_ECMA(FATAL) << "Unknown elementsKind when getting constantIndx: " << static_cast<int32_t>(kind);
92    }
93}
94
95ElementsKind Elements::MergeElementsKind(ElementsKind curKind, ElementsKind newKind)
96{
97    auto result = ElementsKind(static_cast<uint8_t>(curKind) | static_cast<uint8_t>(newKind));
98    result = FixElementsKind(result);
99    return result;
100}
101
102ElementsKind Elements::FixElementsKind(ElementsKind oldKind)
103{
104    auto result = oldKind;
105    switch (result) {
106        case ElementsKind::NONE:
107        case ElementsKind::INT:
108        case ElementsKind::NUMBER:
109        case ElementsKind::STRING:
110        case ElementsKind::OBJECT:
111        case ElementsKind::HOLE:
112        case ElementsKind::HOLE_INT:
113        case ElementsKind::HOLE_NUMBER:
114        case ElementsKind::HOLE_STRING:
115        case ElementsKind::HOLE_OBJECT:
116            break;
117        default:
118            if (IsHole(result)) {
119                result = ElementsKind::HOLE_TAGGED;
120            } else {
121                result = ElementsKind::TAGGED;
122            }
123            break;
124    }
125    return result;
126}
127
128ElementsKind Elements::ToElementsKind(JSTaggedValue value, ElementsKind kind)
129{
130    ElementsKind valueKind = ElementsKind::NONE;
131    if (value.IsInt()) {
132        valueKind = ElementsKind::INT;
133    } else if (value.IsDouble()) {
134        valueKind = ElementsKind::NUMBER;
135    } else if (value.IsString()) {
136        valueKind = ElementsKind::STRING;
137    } else if (value.IsHeapObject()) {
138        valueKind = ElementsKind::OBJECT;
139    } else if (value.IsHole()) {
140        valueKind = ElementsKind::HOLE;
141    } else {
142        valueKind = ElementsKind::TAGGED;
143    }
144    return MergeElementsKind(valueKind, kind);
145}
146
147void Elements::HandleIntKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
148                                      const ElementsKind newKind, bool needCOW)
149{
150    if (IsStringOrNoneOrHole(newKind)) {
151        JSTaggedValue newElements = MigrateFromRawValueToHeapValue(thread, object, needCOW, true);
152        object->SetElements(thread, newElements);
153    } else if (newKind == ElementsKind::NUMBER || newKind == ElementsKind::HOLE_NUMBER) {
154        MigrateFromHoleIntToHoleNumber(thread, object);
155    }
156}
157
158bool Elements::IsNumberKind(const ElementsKind kind)
159{
160    return static_cast<uint32_t>(kind) >= static_cast<uint32_t>(ElementsKind::NUMBER) &&
161           static_cast<uint32_t>(kind) <= static_cast<uint32_t>(ElementsKind::HOLE_NUMBER);
162}
163
164bool Elements::IsStringOrNoneOrHole(const ElementsKind kind)
165{
166    return static_cast<uint32_t>(kind) >= static_cast<uint32_t>(ElementsKind::STRING) ||
167           kind == ElementsKind::NONE || kind == ElementsKind::HOLE;
168}
169
170void Elements::HandleNumberKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
171                                         const ElementsKind newKind, bool needCOW)
172{
173    if (IsStringOrNoneOrHole(newKind)) {
174        JSTaggedValue newElements = MigrateFromRawValueToHeapValue(thread, object, needCOW, false);
175        object->SetElements(thread, newElements);
176    } else if (newKind == ElementsKind::INT || newKind == ElementsKind::HOLE_INT) {
177        MigrateFromHoleNumberToHoleInt(thread, object);
178    }
179}
180
181void Elements::HandleOtherKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
182                                        const ElementsKind newKind, bool needCOW)
183{
184    if (newKind == ElementsKind::INT || newKind == ElementsKind::HOLE_INT) {
185        JSTaggedValue newElements = MigrateFromHeapValueToRawValue(thread, object, needCOW, true);
186        object->SetElements(thread, newElements);
187    } else if (IsNumberKind(newKind)) {
188        JSTaggedValue newElements = MigrateFromHeapValueToRawValue(thread, object, needCOW, false);
189        object->SetElements(thread, newElements);
190    }
191}
192
193void Elements::MigrateArrayWithKind(const JSThread *thread, const JSHandle<JSObject> &object,
194                                    const ElementsKind oldKind, const ElementsKind newKind)
195{
196    if (!thread->GetEcmaVM()->IsEnableElementsKind()) {
197        return;
198    }
199
200    if (oldKind == newKind ||
201        (oldKind == ElementsKind::INT && newKind == ElementsKind::HOLE_INT) ||
202        (oldKind == ElementsKind::NUMBER && newKind == ElementsKind::HOLE_NUMBER)) {
203        return;
204    }
205
206    bool needCOW = object->GetElements().IsCOWArray();
207
208    if (oldKind == ElementsKind::INT || oldKind == ElementsKind::HOLE_INT) {
209        HandleIntKindMigration(thread, object, newKind, needCOW);
210    } else if ((IsNumberKind(oldKind))) {
211        HandleNumberKindMigration(thread, object, newKind, needCOW);
212    } else {
213        HandleOtherKindMigration(thread, object, newKind, needCOW);
214    }
215}
216
217JSTaggedValue Elements::MigrateFromRawValueToHeapValue(const JSThread *thread, const JSHandle<JSObject> object,
218                                                       bool needCOW, bool isIntKind)
219{
220    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
221    JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements());
222    uint32_t length = elements->GetLength();
223    JSMutableHandle<TaggedArray> newElements(thread, JSTaggedValue::Undefined());
224    if (needCOW) {
225        newElements.Update(factory->NewCOWTaggedArray(length));
226    } else {
227        newElements.Update(factory->NewTaggedArray(length));
228    }
229    for (uint32_t i = 0; i < length; i++) {
230        JSTaggedType value = elements->Get(i).GetRawData();
231        if (value == base::SPECIAL_HOLE) {
232            newElements->Set(thread, i, JSTaggedValue::Hole());
233        } else if (isIntKind) {
234            int convertedValue = static_cast<int>(value);
235            newElements->Set(thread, i, JSTaggedValue(convertedValue));
236        } else {
237            double convertedValue = base::bit_cast<double>(value);
238            newElements->Set(thread, i, JSTaggedValue(convertedValue));
239        }
240    }
241    return newElements.GetTaggedValue();
242}
243
244JSTaggedValue Elements::MigrateFromHeapValueToRawValue(const JSThread *thread, const JSHandle<JSObject> object,
245                                                       bool needCOW, bool isIntKind)
246{
247    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
248    JSHandle<TaggedArray> elements = JSHandle<TaggedArray>(thread, object->GetElements());
249    uint32_t length = elements->GetLength();
250    JSMutableHandle<MutantTaggedArray> newElements(thread, JSTaggedValue::Undefined());
251    if (needCOW) {
252        newElements.Update(factory->NewCOWMutantTaggedArray(length));
253    } else {
254        newElements.Update(factory->NewMutantTaggedArray(length));
255    }
256    for (uint32_t i = 0; i < length; i++) {
257        JSTaggedValue value = elements->Get(i);
258        JSTaggedType convertedValue = 0;
259        // To distinguish Hole (0x5) in taggedvalue with Interger 5
260        if (value.IsHole()) {
261            convertedValue = base::SPECIAL_HOLE;
262        } else if (isIntKind) {
263            convertedValue = static_cast<JSTaggedType>(value.GetInt());
264        } else if (value.IsInt()) {
265            int intValue = value.GetInt();
266            convertedValue = base::bit_cast<JSTaggedType>(static_cast<double>(intValue));
267        } else {
268            convertedValue = base::bit_cast<JSTaggedType>(value.GetDouble());
269        }
270        newElements->Set<false>(thread, i, JSTaggedValue(convertedValue));
271    }
272    return newElements.GetTaggedValue();
273}
274
275void Elements::MigrateFromHoleIntToHoleNumber(const JSThread *thread, const JSHandle<JSObject> object)
276{
277    JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements());
278    uint32_t length = elements->GetLength();
279    for (uint32_t i = 0; i < length; i++) {
280        JSTaggedType value = elements->Get(i).GetRawData();
281        if (value == base::SPECIAL_HOLE) {
282            continue;
283        }
284        int intValue = static_cast<int>(elements->Get(i).GetRawData());
285        double convertedValue = static_cast<double>(intValue);
286        elements->Set<false>(thread, i, JSTaggedValue(base::bit_cast<JSTaggedType>(convertedValue)));
287    }
288}
289
290void Elements::MigrateFromHoleNumberToHoleInt(const JSThread *thread, const JSHandle<JSObject> object)
291{
292    JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements());
293    uint32_t length = elements->GetLength();
294    for (uint32_t i = 0; i < length; i++) {
295        JSTaggedType value = elements->Get(i).GetRawData();
296        if (value == base::SPECIAL_HOLE) {
297            continue;
298        }
299        double intValue = base::bit_cast<double>(elements->Get(i).GetRawData());
300        int64_t convertedValue = static_cast<int64_t>(intValue);
301        elements->Set<false>(thread, i, JSTaggedValue(base::bit_cast<JSTaggedType>(convertedValue)));
302    }
303}
304}  // namespace panda::ecmascript
305