1/*
2 * Copyright (c) 2022 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/ecma_vm.h"
17#include "ecmascript/js_symbol.h"
18#include "ecmascript/object_factory.h"
19#include "ecmascript/property_attributes.h"
20#include "ecmascript/tagged_hash_table.h"
21#include "ecmascript/tests/test_helper.h"
22#include "ecmascript/transitions_dictionary.h"
23
24using namespace panda::ecmascript;
25
26namespace panda::test {
27class TransitionsDictionaryTest : public BaseTestWithScope<false> {
28};
29
30HWTEST_F_L0(TransitionsDictionaryTest, IsMatch)
31{
32    JSHandle<JSTaggedValue> key(thread, JSTaggedValue::True());
33    JSHandle<JSTaggedValue> otherKey(thread, JSTaggedValue::False());
34    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
35    JSHandle<JSTaggedValue> otherDetails(thread, JSTaggedValue::Null());
36    bool result = TransitionsDictionary::IsMatch(key.GetTaggedValue(), metaData.GetTaggedValue(),
37        otherKey.GetTaggedValue(), otherDetails.GetTaggedValue());
38    EXPECT_FALSE(result);
39
40    result = TransitionsDictionary::IsMatch(key.GetTaggedValue(), metaData.GetTaggedValue(),
41        key.GetTaggedValue(), metaData.GetTaggedValue());
42    EXPECT_TRUE(result);
43}
44
45HWTEST_F_L0(TransitionsDictionaryTest, Hash)
46{
47    auto vm = thread->GetEcmaVM();
48    auto factory = vm->GetFactory();
49
50    // test when key is string.
51    JSHandle<JSTaggedValue> key1(factory->NewFromStdString("k"));
52    JSHandle<JSTaggedValue> metaData1(thread, JSTaggedValue(1)); // test metaData : 1
53    int hash = TransitionsDictionary::Hash(key1.GetTaggedValue(), metaData1.GetTaggedValue());
54    // "k" : 107, hashSeed : 0, shift : 5, metaData : 1
55    EXPECT_EQ(hash, 108); // 108 : (0 << 5) - 0 + 107 + 1
56
57    JSHandle<JSTaggedValue> key2(factory->NewFromStdString("key"));
58    hash = TransitionsDictionary::Hash(key2.GetTaggedValue(), metaData1.GetTaggedValue());
59    EXPECT_EQ(hash, 106080);
60
61    // test when key is symbol.
62    JSHandle<JSTaggedValue> symbolName(factory->NewFromStdString("s"));
63    JSHandle<JSSymbol> privateNameSymbol = factory->NewPrivateNameSymbol(symbolName);
64    JSHandle<JSTaggedValue> symbolValue = JSHandle<JSTaggedValue>::Cast(privateNameSymbol);
65    JSHandle<JSTaggedValue> metaData2(thread, JSTaggedValue(2)); // test metaData : 2
66    hash = TransitionsDictionary::Hash(symbolValue.GetTaggedValue(), metaData2.GetTaggedValue());
67    EXPECT_EQ(hash, 117); // 117 : 115 + 2
68}
69
70HWTEST_F_L0(TransitionsDictionaryTest, GetKeyIndex)
71{
72    int entry = 10;
73    EXPECT_EQ(TransitionsDictionary::GetKeyIndex(entry), 33); // 33 : 3 + 10 * 3 + 0
74}
75
76HWTEST_F_L0(TransitionsDictionaryTest, GetValueIndex)
77{
78    int entry = 10;
79    EXPECT_EQ(TransitionsDictionary::GetValueIndex(entry), 34); // 34 : 3 + 10 * 3 + 1
80}
81
82HWTEST_F_L0(TransitionsDictionaryTest, GetEntryIndex)
83{
84    int entry = 10;
85    EXPECT_EQ(TransitionsDictionary::GetEntryIndex(entry), 33); // 33 : 3 + 10 * 3
86}
87
88HWTEST_F_L0(TransitionsDictionaryTest, Create)
89{
90    int numberOfElements = 8;
91    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
92    EXPECT_EQ(transDic->GetLength(), 27U); // 27 : 3 + 8 * 3
93    EXPECT_EQ(transDic->EntriesCount(), 0);
94    EXPECT_EQ(transDic->HoleEntriesCount(), 0);
95}
96
97HWTEST_F_L0(TransitionsDictionaryTest, Shrink)
98{
99    auto vm = thread->GetEcmaVM();
100    auto factory = vm->GetFactory();
101    int numberOfElements = 64;
102    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
103    EXPECT_EQ(transDic->GetLength(), 195U); // 195 : 3 + 64 * 3
104    EXPECT_EQ(transDic->EntriesCount(), 0);
105
106    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
107    int eleNum = 7;
108    for (int index = 0; index < eleNum; index++) {
109        std::string keyStr = "key" + std::to_string(index);
110        JSHandle<JSTaggedValue> key(factory->NewFromStdString(keyStr));
111        JSHandle<JSTaggedValue> value(factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT));
112        TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
113    }
114    JSHandle<TransitionsDictionary> transDicAfterShink = TransitionsDictionary::Shrink(thread, transDic);
115    EXPECT_EQ(transDicAfterShink->GetLength(), 51U); // (1 << (32 - Clz((7 + (7 >> 1)) - 1))) * 3 + 3
116    EXPECT_EQ(transDic->EntriesCount(), eleNum);
117}
118
119HWTEST_F_L0(TransitionsDictionaryTest, Get_Set_Attributes)
120{
121    int numberOfElements = 8;
122    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
123    uint32_t length = transDic->GetLength();
124    EXPECT_EQ(length, 27U); // 27 : 3 + 8 * 3
125
126    for (int index = 0; index < numberOfElements; index++) {
127        transDic->SetAttributes(thread, index, JSTaggedValue(index));
128        JSTaggedValue value = transDic->GetAttributes(index);
129        EXPECT_EQ(value, JSTaggedValue(index));
130    }
131}
132
133using TestCommonCB = std::function<void(JSThread *, int index,
134                                        JSHandle<JSTaggedValue> &, JSHandle<JSTaggedValue> &)>;
135void TestCommon(JSThread *thread, int numberOfElements, TestCommonCB cb)
136{
137    auto factory = thread->GetEcmaVM()->GetFactory();
138
139    for (int index = 0; index < numberOfElements; index++) {
140        std::string keyStr = "key" + std::to_string(index);
141        JSHandle<JSTaggedValue> key(factory->NewFromStdString(keyStr));
142        JSHandle<JSTaggedValue> value(factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT));
143        cb(thread, index, key, value);
144    }
145}
146
147HWTEST_F_L0(TransitionsDictionaryTest, SetEntry)
148{
149    int numberOfElements = 8;
150    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
151
152    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
153    TestCommon(thread, numberOfElements,
154                [&](JSThread *thread, int index, JSHandle<JSTaggedValue> &key, JSHandle<JSTaggedValue> &value) {
155                    transDic->SetEntry(thread, index, key.GetTaggedValue(), value.GetTaggedValue(),
156                                       metaData.GetTaggedValue());
157                    EXPECT_EQ(transDic->GetKey(index), key.GetTaggedValue());
158                });
159}
160
161HWTEST_F_L0(TransitionsDictionaryTest, FindEntry)
162{
163    int numberOfElements = 8;
164    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
165
166    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
167    TestCommon(thread, numberOfElements,
168               [&](JSThread *thread, int index, JSHandle<JSTaggedValue> &key, JSHandle<JSTaggedValue> &value) {
169                   transDic = TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
170                   int foundEntry = transDic->FindEntry(key.GetTaggedValue(), metaData.GetTaggedValue());
171                   EXPECT_EQ(index + 3, foundEntry);  // 3 : table header size
172               });
173}
174
175HWTEST_F_L0(TransitionsDictionaryTest, RemoveElement)
176{
177    int numberOfElements = 8;
178    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
179    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
180    TestCommon(thread, numberOfElements,
181               [&](JSThread *thread, int index, JSHandle<JSTaggedValue> &key, JSHandle<JSTaggedValue> &value) {
182                   transDic = TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
183               });
184    auto factory = thread->GetEcmaVM()->GetFactory();
185    JSHandle<JSTaggedValue> key7(factory->NewFromStdString("key7")); // test remove element by "key7"
186    int foundEntry = transDic->FindEntry(key7.GetTaggedValue(), metaData.GetTaggedValue());
187    EXPECT_EQ(foundEntry, 7 + 3);
188    EXPECT_EQ(transDic->EntriesCount(), 8);
189
190    transDic->RemoveElement(thread, foundEntry);
191    foundEntry = transDic->FindEntry(key7.GetTaggedValue(), metaData.GetTaggedValue());
192    EXPECT_EQ(foundEntry, -1); // -1 : not found entry
193    EXPECT_EQ(transDic->EntriesCount(), 7);
194}
195
196HWTEST_F_L0(TransitionsDictionaryTest, PutIfAbsent)
197{
198    auto vm = thread->GetEcmaVM();
199    auto factory = vm->GetFactory();
200    int numberOfElements = 8;
201    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
202    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
203    vm->SetEnableForceGC(false);
204    for (int index = 0; index < numberOfElements; index++) {
205        std::string keyStr = "key" + std::to_string(index);
206        JSHandle<JSTaggedValue> key(factory->NewFromStdString(keyStr));
207        JSHandle<JSTaggedValue> value(factory->NewEcmaHClass(JSObject::SIZE, JSType::JS_OBJECT));
208        transDic = TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
209        int foundEntry = transDic->FindEntry(key.GetTaggedValue(), metaData.GetTaggedValue());
210        EXPECT_EQ(foundEntry, index + 3);
211
212        JSTaggedValue foundValue = transDic->GetValue(foundEntry);
213        JSTaggedValue weakValue = value->CreateAndGetWeakRef();
214        EXPECT_EQ(foundValue, weakValue);
215    }
216    vm->SetEnableForceGC(true);
217}
218
219HWTEST_F_L0(TransitionsDictionaryTest, Remove)
220{
221    int numberOfElements = 64;
222    int eleNum = 7;
223    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
224    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
225    TestCommon(thread, eleNum,
226               [&](JSThread *thread, int index, JSHandle<JSTaggedValue> &key, JSHandle<JSTaggedValue> &value) {
227                   transDic = TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
228               });
229    auto factory = thread->GetEcmaVM()->GetFactory();
230    JSHandle<JSTaggedValue> key6(factory->NewFromStdString("key6")); // test remove element by "key6"
231    EXPECT_EQ(transDic->EntriesCount(), 7);
232    EXPECT_EQ(transDic->GetLength(), 195U); // 195 : 3 + 64 * 3
233
234    transDic = TransitionsDictionary::Remove(thread, transDic, key6, metaData.GetTaggedValue());
235    EXPECT_EQ(transDic->EntriesCount(), 6); // 6 : 7 - 1
236    EXPECT_EQ(transDic->GetLength(), 51U);  // (1 << (32 - Clz((6 + (6 >> 1)) - 1))) * 3 + 3
237}
238
239HWTEST_F_L0(TransitionsDictionaryTest, Rehash)
240{
241    int numberOfElements = 64;
242    int eleNum = 7;
243    JSHandle<TransitionsDictionary> transDic = TransitionsDictionary::Create(thread, numberOfElements);
244    JSHandle<JSTaggedValue> metaData(thread, JSTaggedValue::Undefined());
245    TestCommon(thread, eleNum,
246               [&](JSThread *thread, int index, JSHandle<JSTaggedValue> &key, JSHandle<JSTaggedValue> &value) {
247                   transDic = TransitionsDictionary::PutIfAbsent(thread, transDic, key, value, metaData);
248               });
249    EXPECT_EQ(transDic->HoleEntriesCount(), 0);
250
251    int lastEntry = 7 + 3;
252    transDic->RemoveElement(thread, lastEntry); // remove one element
253    EXPECT_EQ(transDic->HoleEntriesCount(), 1);
254
255    transDic->Rehash(thread, *transDic);
256    EXPECT_EQ(transDic->HoleEntriesCount(), 0);
257}
258}  // namespace panda::test