1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "gtest/gtest.h"
18
19 #include <chrono> // chrono_literals::operator""ms
20 #include <string>
21 #include <thread> // this_thread::sleep_for
22
23 #include "berberis/guest_state/guest_addr.h"
24 #include "berberis/runtime_primitives/host_code.h"
25 #include "berberis/runtime_primitives/runtime_library.h" // kEntry*
26 #include "berberis/runtime_primitives/translation_cache.h"
27
28 namespace berberis {
29
30 namespace {
31
32 using std::chrono_literals::operator""ms;
33
TEST(TranslationCacheTest,DefaultNotTranslated)34 TEST(TranslationCacheTest, DefaultNotTranslated) {
35 constexpr GuestAddr pc = 0x12345678;
36
37 TranslationCache tc;
38
39 EXPECT_EQ(tc.GetHostCodePtr(pc)->load(), kEntryNotTranslated);
40 EXPECT_EQ(tc.GetHostCodePtr(pc + 1024)->load(), kEntryNotTranslated);
41 EXPECT_EQ(tc.GetInvocationCounter(pc), 0U);
42 }
43
TEST(TranslationCacheTest,UpdateInvocationCounter)44 TEST(TranslationCacheTest, UpdateInvocationCounter) {
45 constexpr GuestAddr pc = 0x12345678;
46
47 TranslationCache tc;
48
49 // Create entry
50 GuestCodeEntry* entry = tc.AddAndLockForTranslation(pc, 0);
51 ASSERT_TRUE(entry);
52 EXPECT_EQ(entry->invocation_counter, 0U);
53 entry->invocation_counter = 42;
54 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kSpecialHandler, {kEntryNoExec, 0});
55
56 EXPECT_EQ(tc.GetInvocationCounter(pc), 42U);
57 }
58
TEST(TranslationCacheTest,AddAndLockForTranslation)59 TEST(TranslationCacheTest, AddAndLockForTranslation) {
60 constexpr GuestAddr pc = 0x12345678;
61
62 TranslationCache tc;
63
64 // Cannot lock if counter is below the threshold, but entry is created anyway.
65 ASSERT_FALSE(tc.AddAndLockForTranslation(pc, 1));
66 GuestCodeEntry* entry = tc.LookupGuestCodeEntryUnsafeForTesting(pc);
67 ASSERT_TRUE(entry);
68 EXPECT_EQ(tc.GetHostCodePtr(pc)->load(), kEntryNotTranslated);
69 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kInterpreted);
70 EXPECT_EQ(tc.GetInvocationCounter(pc), 1U);
71
72 // Lock when counter is equal or above the threshold.
73 entry = tc.AddAndLockForTranslation(pc, 1);
74 ASSERT_TRUE(entry);
75 EXPECT_EQ(tc.GetHostCodePtr(pc)->load(), kEntryTranslating);
76 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
77 EXPECT_EQ(tc.GetInvocationCounter(pc), 1U);
78
79 // Cannot lock locked.
80 ASSERT_FALSE(tc.AddAndLockForTranslation(pc, 0));
81
82 // Unlock.
83 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kSpecialHandler, {kEntryNoExec, 0});
84 EXPECT_EQ(tc.GetHostCodePtr(pc)->load(), kEntryNoExec);
85 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kSpecialHandler);
86
87 // Cannot lock translated.
88 ASSERT_FALSE(tc.AddAndLockForTranslation(pc, 0));
89 }
90
91 constexpr bool kWrappedHostFunc = true;
92
TEST(TranslationCacheTest,AddAndLockForWrapping)93 TEST(TranslationCacheTest, AddAndLockForWrapping) {
94 constexpr GuestAddr pc = 0x12345678;
95
96 TranslationCache tc;
97
98 // Add and lock nonexistent.
99 GuestCodeEntry* entry = tc.AddAndLockForWrapping(pc);
100 ASSERT_TRUE(entry);
101 ASSERT_EQ(kEntryWrapping, tc.GetHostCodePtr(pc)->load());
102 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
103
104 // Cannot lock locked.
105 ASSERT_FALSE(tc.AddAndLockForWrapping(pc));
106
107 // Unlock.
108 tc.SetWrappedAndUnlock(pc, entry, kWrappedHostFunc, {kEntryNoExec, 0});
109 ASSERT_EQ(kEntryNoExec, tc.GetHostCodePtr(pc)->load());
110 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kHostWrapped);
111
112 // Cannot lock wrapped.
113 ASSERT_FALSE(tc.AddAndLockForWrapping(pc));
114
115 // Cannot lock not translated but already interpreted.
116 ASSERT_FALSE(tc.AddAndLockForTranslation(pc + 64, 1));
117 entry = tc.LookupGuestCodeEntryUnsafeForTesting(pc + 64);
118 ASSERT_TRUE(entry);
119 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kInterpreted);
120 ASSERT_FALSE(tc.AddAndLockForWrapping(pc + 64));
121 }
122
123 HostCodeAddr kHostCodeStub = AsHostCodeAddr(AsHostCode(0xdeadbeef));
124
TestWrappingWorker(TranslationCache * tc,GuestAddr pc)125 void TestWrappingWorker(TranslationCache* tc, GuestAddr pc) {
126 while (true) {
127 GuestCodeEntry* entry = tc->AddAndLockForWrapping(pc);
128 if (entry) {
129 EXPECT_EQ(entry->host_code->load(), kEntryWrapping);
130 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
131 // Give other threads some time to run the loop. Typical test
132 // time is 1 ms, make sleep order of magnitude longer - 10ms.
133 std::this_thread::sleep_for(10ms);
134 tc->SetWrappedAndUnlock(pc, entry, kWrappedHostFunc, {kHostCodeStub, 0});
135 EXPECT_EQ(entry->host_code->load(), kHostCodeStub);
136 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kHostWrapped);
137 return;
138 }
139
140 auto host_code = tc->GetHostCodePtr(pc)->load();
141
142 // Warning: the order of comparisons here is
143 // important since code can change in between.
144 if (host_code == kEntryWrapping) {
145 continue;
146 }
147
148 EXPECT_EQ(host_code, kHostCodeStub);
149 break;
150 }
151
152 return;
153 }
154
TestTranslationWorker(TranslationCache * tc,GuestAddr pc)155 void TestTranslationWorker(TranslationCache* tc, GuestAddr pc) {
156 while (true) {
157 GuestCodeEntry* entry = tc->AddAndLockForTranslation(pc, 0);
158 if (entry) {
159 EXPECT_EQ(entry->host_code->load(), kEntryTranslating);
160 EXPECT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
161 // Give other threads some time to run the loop. Typical test
162 // time is 1 ms, make sleep order of magnitude longer - 10ms.
163 std::this_thread::sleep_for(10ms);
164 tc->SetTranslatedAndUnlock(
165 pc, entry, 1, GuestCodeEntry::Kind::kSpecialHandler, {kHostCodeStub, 0});
166 EXPECT_EQ(entry->host_code->load(), kHostCodeStub);
167 return;
168 }
169
170 auto host_code = tc->GetHostCodePtr(pc)->load();
171 if (host_code == kEntryTranslating) {
172 continue;
173 }
174 EXPECT_EQ(host_code, kHostCodeStub);
175 break;
176 }
177
178 return;
179 }
180
181 template <void(WorkerFunc)(TranslationCache*, GuestAddr)>
TranslationCacheTestRunThreads()182 void TranslationCacheTestRunThreads() {
183 TranslationCache tc;
184 constexpr uint32_t kNumThreads = 16;
185 std::array<std::thread, kNumThreads> threads;
186
187 // First test situation, when every thread has its own pc.
188 for (uint32_t i = 0; i < kNumThreads; i++) {
189 threads[i] = std::thread(WorkerFunc, &tc, i);
190 }
191
192 for (auto& thread : threads) {
193 thread.join();
194 }
195
196 // Now introduce heavy contention.
197 GuestAddr pc = 0x12345678;
198 for (auto& thread : threads) {
199 thread = std::thread(WorkerFunc, &tc, pc);
200 }
201
202 for (auto& thread : threads) {
203 thread.join();
204 }
205 }
206
TEST(TranslationCacheTest,InvalidateNotTranslated)207 TEST(TranslationCacheTest, InvalidateNotTranslated) {
208 constexpr GuestAddr pc = 0x12345678;
209
210 TranslationCache tc;
211
212 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
213
214 tc.InvalidateGuestRange(pc, pc + 1);
215
216 // Not translated stays not translated
217 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
218 ASSERT_FALSE(tc.LookupGuestCodeEntryUnsafeForTesting(pc));
219 }
220
TEST(TranslationCacheTest,InvalidateTranslated)221 TEST(TranslationCacheTest, InvalidateTranslated) {
222 constexpr GuestAddr pc = 0x12345678;
223 const auto host_code = AsHostCodeAddr(AsHostCode(0xdeadbeef));
224
225 TranslationCache tc;
226
227 GuestCodeEntry* entry = tc.AddAndLockForTranslation(pc, 0);
228 ASSERT_TRUE(entry);
229 ASSERT_EQ(kEntryTranslating, tc.GetHostCodePtr(pc)->load());
230
231 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kHeavyOptimized, {host_code, 4});
232 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc)->load());
233
234 tc.InvalidateGuestRange(pc, pc + 1);
235
236 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
237 ASSERT_FALSE(tc.LookupGuestCodeEntryUnsafeForTesting(pc));
238 }
239
TEST(TranslationCacheTest,InvalidateTranslating)240 TEST(TranslationCacheTest, InvalidateTranslating) {
241 constexpr GuestAddr pc = 0x12345678;
242 const auto host_code = AsHostCodeAddr(AsHostCode(0xdeadbeef));
243
244 TranslationCache tc;
245
246 GuestCodeEntry* entry = tc.AddAndLockForTranslation(pc, 0);
247 ASSERT_TRUE(entry);
248 ASSERT_EQ(kEntryTranslating, tc.GetHostCodePtr(pc)->load());
249
250 tc.InvalidateGuestRange(pc, pc + 1);
251 ASSERT_EQ(kEntryInvalidating, tc.GetHostCodePtr(pc)->load());
252 entry = tc.LookupGuestCodeEntryUnsafeForTesting(pc);
253 ASSERT_TRUE(entry);
254 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
255
256 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kSpecialHandler, {host_code, 4});
257 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
258 ASSERT_FALSE(tc.LookupGuestCodeEntryUnsafeForTesting(pc));
259 }
260
TEST(TranslationCacheTest,InvalidateTranslatingOutOfRange)261 TEST(TranslationCacheTest, InvalidateTranslatingOutOfRange) {
262 constexpr GuestAddr pc = 0x12345678;
263 const auto host_code = AsHostCodeAddr(AsHostCode(0xdeadbeef));
264
265 TranslationCache tc;
266
267 GuestCodeEntry* entry = tc.AddAndLockForTranslation(pc, 0);
268 ASSERT_TRUE(entry);
269 ASSERT_EQ(kEntryTranslating, tc.GetHostCodePtr(pc)->load());
270
271 // Invalidate range that does *not* contain translating address.
272 // The entry should still be invalidated, as translated region is only known after translation,
273 // and it might overlap with the invalidated range.
274 tc.InvalidateGuestRange(pc + 100, pc + 101);
275 ASSERT_EQ(kEntryInvalidating, tc.GetHostCodePtr(pc)->load());
276
277 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kSpecialHandler, {host_code, 4});
278 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
279 }
280
Translate(TranslationCache * tc,GuestAddr pc,uint32_t size,HostCodeAddr host_code)281 bool Translate(TranslationCache* tc, GuestAddr pc, uint32_t size, HostCodeAddr host_code) {
282 GuestCodeEntry* entry = tc->AddAndLockForTranslation(pc, 0);
283 if (!entry) {
284 return false;
285 }
286 tc->SetTranslatedAndUnlock(
287 pc, entry, size, GuestCodeEntry::Kind::kSpecialHandler, {host_code, 4});
288 return true;
289 }
290
TEST(TranslationCacheTest,LockForGearUpTranslation)291 TEST(TranslationCacheTest, LockForGearUpTranslation) {
292 constexpr GuestAddr pc = 0x12345678;
293 const auto host_code = AsHostCodeAddr(AsHostCode(0xdeadbeef));
294
295 TranslationCache tc;
296
297 // Cannot lock if not yet added.
298 ASSERT_FALSE(tc.LockForGearUpTranslation(pc));
299
300 ASSERT_TRUE(Translate(&tc, pc + 0, 1, host_code));
301 GuestCodeEntry* entry = tc.LookupGuestCodeEntryUnsafeForTesting(pc);
302 ASSERT_TRUE(entry);
303 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kSpecialHandler);
304
305 // Cannot lock if kind is not kLiteTranslated.
306 ASSERT_FALSE(tc.LockForGearUpTranslation(pc));
307
308 entry->kind = GuestCodeEntry::Kind::kLiteTranslated;
309
310 entry = tc.LockForGearUpTranslation(pc);
311 ASSERT_TRUE(entry);
312 ASSERT_EQ(kEntryTranslating, tc.GetHostCodePtr(pc)->load());
313 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kUnderProcessing);
314
315 // Unlock.
316 tc.SetTranslatedAndUnlock(pc, entry, 1, GuestCodeEntry::Kind::kHeavyOptimized, {kEntryNoExec, 0});
317 ASSERT_EQ(kEntryNoExec, tc.GetHostCodePtr(pc)->load());
318 ASSERT_EQ(entry->kind, GuestCodeEntry::Kind::kHeavyOptimized);
319
320 // Cannot lock translated.
321 ASSERT_FALSE(tc.AddAndLockForTranslation(pc, 0));
322 }
323
TEST(TranslationCacheTest,InvalidateRange)324 TEST(TranslationCacheTest, InvalidateRange) {
325 constexpr GuestAddr pc = 0x12345678;
326 const auto host_code = AsHostCodeAddr(AsHostCode(0xdeadbeef));
327
328 TranslationCache tc;
329
330 ASSERT_TRUE(Translate(&tc, pc + 0, 1, host_code));
331 ASSERT_TRUE(Translate(&tc, pc + 1, 1, host_code));
332 ASSERT_TRUE(Translate(&tc, pc + 2, 1, host_code));
333
334 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc + 0)->load());
335 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc + 1)->load());
336 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc + 2)->load());
337
338 tc.InvalidateGuestRange(pc + 1, pc + 2);
339
340 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc + 0)->load());
341 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc + 1)->load());
342 ASSERT_EQ(host_code, tc.GetHostCodePtr(pc + 2)->load());
343 }
344
Wrap(TranslationCache * tc,GuestAddr pc,HostCodeAddr host_code)345 bool Wrap(TranslationCache* tc, GuestAddr pc, HostCodeAddr host_code) {
346 GuestCodeEntry* entry = tc->AddAndLockForWrapping(pc);
347 if (!entry) {
348 return false;
349 }
350 tc->SetWrappedAndUnlock(pc, entry, kWrappedHostFunc, {host_code, 0});
351 return true;
352 }
353
TEST(TranslationCacheTest,InvalidateWrapped)354 TEST(TranslationCacheTest, InvalidateWrapped) {
355 constexpr GuestAddr pc = 0x12345678;
356
357 TranslationCache tc;
358
359 ASSERT_TRUE(Wrap(&tc, pc, kEntryNoExec));
360
361 tc.InvalidateGuestRange(pc, pc + 1);
362
363 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
364 }
365
TEST(TranslationCacheTest,InvalidateWrappingWrap)366 TEST(TranslationCacheTest, InvalidateWrappingWrap) {
367 constexpr GuestAddr pc = 0x12345678;
368
369 TranslationCache tc;
370
371 GuestCodeEntry* entry = tc.AddAndLockForWrapping(pc);
372 ASSERT_TRUE(entry);
373
374 tc.InvalidateGuestRange(pc, pc + 1);
375 ASSERT_EQ(kEntryInvalidating, tc.GetHostCodePtr(pc)->load());
376
377 tc.SetWrappedAndUnlock(pc, entry, kWrappedHostFunc, {kEntryNoExec, 0});
378 ASSERT_EQ(kEntryNotTranslated, tc.GetHostCodePtr(pc)->load());
379
380 ASSERT_TRUE(Wrap(&tc, pc, kEntryNoExec));
381 }
382
TEST(TranslationCacheTest,WrapInvalidateWrap)383 TEST(TranslationCacheTest, WrapInvalidateWrap) {
384 constexpr GuestAddr pc = 0x12345678;
385
386 TranslationCache tc;
387
388 ASSERT_TRUE(Wrap(&tc, pc, kEntryNoExec));
389
390 tc.InvalidateGuestRange(pc, pc + 1);
391
392 ASSERT_TRUE(Wrap(&tc, pc, kEntryNoExec));
393 }
394
TEST(TranslationCacheTest,WrapInvalidateTranslate)395 TEST(TranslationCacheTest, WrapInvalidateTranslate) {
396 constexpr GuestAddr pc = 0x12345678;
397
398 TranslationCache tc;
399
400 ASSERT_TRUE(Wrap(&tc, pc, kEntryNoExec));
401
402 tc.InvalidateGuestRange(pc, pc + 1);
403
404 ASSERT_TRUE(Translate(&tc, pc, 1, kEntryNoExec));
405 }
406
TEST(NdkTest,TranslationCacheWrappingStatesTest)407 TEST(NdkTest, TranslationCacheWrappingStatesTest) {
408 TranslationCacheTestRunThreads<TestWrappingWorker>();
409 }
410
TEST(NdkTest,TranslationCacheTranslationStatesTest)411 TEST(NdkTest, TranslationCacheTranslationStatesTest) {
412 TranslationCacheTestRunThreads<TestTranslationWorker>();
413 }
414
415 } // namespace
416
417 } // namespace berberis
418