1 /**
2 * Copyright (c) 2021-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 "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 panda::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
AllocUtf8String(std::vector<uint8_t> data)56 static coretypes::String *AllocUtf8String(std::vector<uint8_t> data)
57 {
58 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
59 return coretypes::String::CreateFromMUtf8(data.data(), utf::MUtf8ToUtf16Size(data.data()), ctx,
60 Runtime::GetCurrent()->GetPandaVM());
61 }
62
SetUp()63 void SetUp() override
64 {
65 table_ = new StringTable();
66 thread_ = panda::MTManagedThread::GetCurrent();
67 thread_->ManagedCodeBegin();
68 }
69
TearDown()70 void TearDown() override
71 {
72 thread_->ManagedCodeEnd();
73 delete table_;
74 table_ = nullptr;
75 }
76
GetTable()77 StringTable *GetTable()
78 {
79 return table_;
80 }
81
PreCheck()82 void PreCheck()
83 {
84 std::unique_lock<std::mutex> lk(pre_lock_);
85 counter_pre_++;
86 if (counter_pre_ == TEST_THREADS) {
87 pre_cv_.notify_all();
88 counter_pre_ = 0;
89 } else {
90 pre_cv_.wait(lk);
91 }
92 }
93
CheckSameString(coretypes::String * string)94 void CheckSameString(coretypes::String *string)
95 {
96 // Loop until lock is taken
97 while (lock_.test_and_set(std::memory_order_seq_cst)) {
98 }
99 if (string_ != nullptr) {
100 ASSERT_EQ(string_, string);
101 } else {
102 string_ = string;
103 }
104 lock_.clear(std::memory_order_seq_cst);
105 }
106
PostFree()107 void PostFree()
108 {
109 std::unique_lock<std::mutex> lk(post_lock_);
110 counter_post_++;
111 if (counter_post_ == TEST_THREADS) {
112 // There should be just one element in table
113 ASSERT_EQ(table_->Size(), 1);
114 string_ = nullptr;
115
116 {
117 os::memory::WriteLockHolder holder(table_->table_.table_lock_);
118 table_->table_.table_.clear();
119 }
120 {
121 os::memory::WriteLockHolder holder(table_->internal_table_.table_lock_);
122 table_->internal_table_.table_.clear();
123 }
124
125 post_cv_.notify_all();
126 counter_post_ = 0;
127 } else {
128 post_cv_.wait(lk);
129 }
130 }
131
132 std::mutex mutex_;
133
134 protected:
135 panda::MTManagedThread *thread_ {nullptr};
136
137 std::mutex pre_lock_;
138 std::condition_variable pre_cv_;
139 int counter_pre_ = 0;
140 std::mutex post_lock_;
141 std::condition_variable post_cv_;
142 int counter_post_ = 0;
143 StringTable *table_ {nullptr};
144
145 std::atomic_flag lock_ {0};
146 coretypes::String *string_ {nullptr};
147 };
148
TestThreadEntry(MultithreadedInternStringTableTest * test)149 void TestThreadEntry(MultithreadedInternStringTableTest *test)
150 {
151 auto *this_thread =
152 panda::MTManagedThread::Create(panda::Runtime::GetCurrent(), panda::Runtime::GetCurrent()->GetPandaVM());
153 this_thread->ManagedCodeBegin();
154 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
155 std::vector<uint8_t> data {0xc2, 0xa7, 0x34, 0x00};
156 auto *table = test->GetTable();
157 for (uint32_t i = 0; i < TEST_ITERS; i++) {
158 test->PreCheck();
159 auto *interned_str = table->GetOrInternString(data.data(), 2, ctx);
160 test->CheckSameString(interned_str);
161 test->PostFree();
162 }
163 this_thread->ManagedCodeEnd();
164 this_thread->Destroy();
165 }
166
TestConcurrentInsertion(const std::array<std::array<uint8_t,4>,TEST_ARRAY_SIZE> & strings,uint32_t & array_item,MultithreadedInternStringTableTest * test)167 void TestConcurrentInsertion(const std::array<std::array<uint8_t, 4>, TEST_ARRAY_SIZE> &strings, uint32_t &array_item,
168 MultithreadedInternStringTableTest *test)
169 {
170 auto *this_thread =
171 panda::MTManagedThread::Create(panda::Runtime::GetCurrent(), panda::Runtime::GetCurrent()->GetPandaVM());
172 this_thread->ManagedCodeBegin();
173 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
174 auto *table = test->GetTable();
175
176 uint32_t current_array_item = 0;
177 while (true) {
178 {
179 std::lock_guard<std::mutex> lock_guard(test->mutex_);
180 if (array_item >= TEST_ARRAY_SIZE) {
181 break;
182 }
183 current_array_item = array_item++;
184 }
185 table->GetOrInternString(strings[current_array_item].data(), 2, ctx);
186 }
187
188 this_thread->ManagedCodeEnd();
189 this_thread->Destroy();
190 }
191
TEST_F(MultithreadedInternStringTableTest,ConcurrentInsertion)192 TEST_F(MultithreadedInternStringTableTest, ConcurrentInsertion)
193 {
194 std::array<std::thread, TEST_THREADS> threads;
195 std::array<std::array<uint8_t, 4>, TEST_ARRAY_SIZE> strings;
196 std::random_device random_device;
197 std::mt19937 engine {random_device()};
198 std::uniform_int_distribution<uint8_t> dist(0, 255);
199 uint32_t array_item = 0;
200
201 for (uint32_t i = 0; i < TEST_ARRAY_SIZE; i++) {
202 strings[i] = {0xc2, dist(engine), dist(engine), 0x00};
203 }
204
205 for (uint32_t i = 0; i < TEST_THREADS; i++) {
206 threads[i] = std::thread(TestConcurrentInsertion, std::ref(strings), std::ref(array_item), this);
207 }
208
209 for (uint32_t i = 0; i < TEST_THREADS; i++) {
210 threads[i].join();
211 }
212 }
213
TEST_F(MultithreadedInternStringTableTest,CheckInternReturnsSameString)214 TEST_F(MultithreadedInternStringTableTest, CheckInternReturnsSameString)
215 {
216 std::array<std::thread, TEST_THREADS> threads;
217 for (uint32_t i = 0; i < TEST_THREADS; i++) {
218 threads[i] = std::thread(TestThreadEntry, this);
219 }
220 for (uint32_t i = 0; i < TEST_THREADS; i++) {
221 threads[i].join();
222 }
223 }
224 } // namespace panda::mem::test
225