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 #include "gtest/gtest.h"
17 #include "runtime/include/coretypes/string.h"
18 #include "runtime/include/runtime.h"
19 #include "runtime/include/thread.h"
20 #include "runtime/include/gc_task.h"
21 #include "runtime/handle_base-inl.h"
22
23 #include <array>
24 #include <atomic>
25 #include <thread>
26 #include <mutex>
27 #include <condition_variable>
28 #include <random>
29 #include <climits>
30 #include <functional>
31
32 namespace ark::mem::test {
33
34 static constexpr uint32_t TEST_THREADS = 8;
35 static constexpr uint32_t TEST_ITERS = 1000;
36 static constexpr uint32_t TEST_ARRAY_SIZE = TEST_THREADS * 1000;
37
38 class MultithreadedInternStringTableTest : public testing::Test {
39 public:
MultithreadedInternStringTableTest()40 MultithreadedInternStringTableTest()
41 {
42 RuntimeOptions options;
43 options.SetShouldLoadBootPandaFiles(false);
44 options.SetShouldInitializeIntrinsics(false);
45
46 options.SetGcType("epsilon");
47 options.SetCompilerEnableJit(false);
48 Runtime::Create(options);
49 }
50
~MultithreadedInternStringTableTest()51 ~MultithreadedInternStringTableTest() override
52 {
53 Runtime::Destroy();
54 }
55
56 NO_COPY_SEMANTIC(MultithreadedInternStringTableTest);
57 NO_MOVE_SEMANTIC(MultithreadedInternStringTableTest);
58
AllocUtf8String(std::vector<uint8_t> data)59 static coretypes::String *AllocUtf8String(std::vector<uint8_t> data)
60 {
61 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
62 return coretypes::String::CreateFromMUtf8(data.data(), utf::MUtf8ToUtf16Size(data.data()), ctx,
63 Runtime::GetCurrent()->GetPandaVM());
64 }
65
SetUp()66 void SetUp() override
67 {
68 table_ = new StringTable();
69 thread_ = ark::MTManagedThread::GetCurrent();
70 thread_->ManagedCodeBegin();
71 }
72
TearDown()73 void TearDown() override
74 {
75 thread_->ManagedCodeEnd();
76 delete table_;
77 }
78
GetTable()79 StringTable *GetTable()
80 {
81 return table_;
82 }
83
PreCheck()84 void PreCheck()
85 {
86 std::unique_lock<std::mutex> lk(preLock_);
87 counterPre_++;
88 if (counterPre_ == TEST_THREADS) {
89 preCv_.notify_all();
90 counterPre_ = 0;
91 } else {
92 preCv_.wait(lk);
93 }
94 }
95
CheckSameString(coretypes::String * string)96 void CheckSameString(coretypes::String *string)
97 {
98 // Loop until lock is taken
99 while (lock_.test_and_set(std::memory_order_seq_cst)) {
100 }
101 if (string_ != nullptr) {
102 ASSERT_EQ(string_, string);
103 } else {
104 string_ = string;
105 }
106 lock_.clear(std::memory_order_seq_cst);
107 }
108
PostFree()109 void PostFree()
110 {
111 std::unique_lock<std::mutex> lk(postLock_);
112 counterPost_++;
113 if (counterPost_ == TEST_THREADS) {
114 // There should be just one element in table
115 ASSERT_EQ(table_->Size(), 1);
116 string_ = nullptr;
117
118 {
119 os::memory::WriteLockHolder holder(table_->table_.tableLock_);
120 table_->table_.table_.clear();
121 }
122 {
123 os::memory::WriteLockHolder holder(table_->internalTable_.tableLock_);
124 table_->internalTable_.table_.clear();
125 }
126
127 postCv_.notify_all();
128 counterPost_ = 0;
129 } else {
130 postCv_.wait(lk);
131 }
132 }
133
134 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
135 std::mutex mutex;
136
137 private:
138 ark::MTManagedThread *thread_ {};
139
140 std::mutex preLock_;
141 std::condition_variable preCv_;
142 int counterPre_ = 0;
143 std::mutex postLock_;
144 std::condition_variable postCv_;
145 int counterPost_ = 0;
146 StringTable *table_ {};
147
148 std::atomic_flag lock_ {false};
149 coretypes::String *string_ {nullptr};
150 };
151
TestThreadEntry(MultithreadedInternStringTableTest * test)152 void TestThreadEntry(MultithreadedInternStringTableTest *test)
153 {
154 auto *thisThread =
155 ark::MTManagedThread::Create(ark::Runtime::GetCurrent(), ark::Runtime::GetCurrent()->GetPandaVM());
156 thisThread->ManagedCodeBegin();
157 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
158 // NOLINTNEXTLINE(readability-magic-numbers)
159 std::vector<uint8_t> data {0xc2, 0xa7, 0x34, 0x00};
160 auto *table = test->GetTable();
161 for (uint32_t i = 0; i < TEST_ITERS; i++) {
162 test->PreCheck();
163 auto *internedStr = table->GetOrInternString(data.data(), 2, ctx);
164 test->CheckSameString(internedStr);
165 test->PostFree();
166 }
167 thisThread->ManagedCodeEnd();
168 thisThread->Destroy();
169 }
170
TestConcurrentInsertion(const std::array<std::array<uint8_t,4U>,TEST_ARRAY_SIZE> & strings,uint32_t & arrayItem,MultithreadedInternStringTableTest * test)171 void TestConcurrentInsertion(const std::array<std::array<uint8_t, 4U>, TEST_ARRAY_SIZE> &strings, uint32_t &arrayItem,
172 MultithreadedInternStringTableTest *test)
173 {
174 auto *thisThread =
175 ark::MTManagedThread::Create(ark::Runtime::GetCurrent(), ark::Runtime::GetCurrent()->GetPandaVM());
176 thisThread->ManagedCodeBegin();
177 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
178 auto *table = test->GetTable();
179
180 uint32_t currentArrayItem = 0;
181 while (true) {
182 {
183 std::lock_guard<std::mutex> lockGuard(test->mutex);
184 if (arrayItem >= TEST_ARRAY_SIZE) {
185 break;
186 }
187 currentArrayItem = arrayItem++;
188 }
189 table->GetOrInternString(strings[currentArrayItem].data(), 2U, ctx);
190 }
191
192 thisThread->ManagedCodeEnd();
193 thisThread->Destroy();
194 }
195
TEST_F(MultithreadedInternStringTableTest,ConcurrentInsertion)196 TEST_F(MultithreadedInternStringTableTest, ConcurrentInsertion)
197 {
198 std::array<std::thread, TEST_THREADS> threads;
199 std::array<std::array<uint8_t, 4U>, TEST_ARRAY_SIZE> strings {};
200 std::random_device randomDevice;
201 std::mt19937 engine {randomDevice()};
202 // NOLINTNEXTLINE(readability-magic-numbers)
203 std::uniform_int_distribution<uint8_t> dist(1, 127U);
204 uint32_t arrayItem = 0;
205
206 for (uint32_t i = 0; i < TEST_ARRAY_SIZE; i++) {
207 uint8_t second = utf::UTF8_2B_SECOND | static_cast<uint8_t>(dist(engine) >> 1U);
208 // NOLINTNEXTLINE(readability-magic-numbers)
209 strings[i] = {0xc2, second, dist(engine), 0x00};
210 }
211
212 for (uint32_t i = 0; i < TEST_THREADS; i++) {
213 threads[i] = std::thread(TestConcurrentInsertion, std::ref(strings), std::ref(arrayItem), this);
214 }
215
216 for (uint32_t i = 0; i < TEST_THREADS; i++) {
217 threads[i].join();
218 }
219 }
220
TEST_F(MultithreadedInternStringTableTest,CheckInternReturnsSameString)221 TEST_F(MultithreadedInternStringTableTest, CheckInternReturnsSameString)
222 {
223 std::array<std::thread, TEST_THREADS> threads;
224 for (uint32_t i = 0; i < TEST_THREADS; i++) {
225 threads[i] = std::thread(TestThreadEntry, this);
226 }
227 for (uint32_t i = 0; i < TEST_THREADS; i++) {
228 threads[i].join();
229 }
230 }
231 } // namespace ark::mem::test
232