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 "berberis/runtime_primitives/translation_cache.h"
18
19 #include <atomic>
20 #include <map>
21 #include <mutex> // std::lock_guard, std::mutex
22
23 #include "berberis/base/checks.h"
24 #include "berberis/base/forever_alloc.h"
25 #include "berberis/guest_state/guest_addr.h"
26 #include "berberis/runtime_primitives/host_code.h"
27 #include "berberis/runtime_primitives/runtime_library.h"
28
29 namespace berberis {
30
GetInstance()31 TranslationCache* TranslationCache::GetInstance() {
32 static auto* g_translation_cache = NewForever<TranslationCache>();
33 return g_translation_cache;
34 }
35
AddAndLockForTranslation(GuestAddr pc,uint32_t counter_threshold)36 GuestCodeEntry* TranslationCache::AddAndLockForTranslation(GuestAddr pc,
37 uint32_t counter_threshold) {
38 // Make sure host code is updated under the mutex, so that it's in sync with
39 // the set of the translating regions (e.g as invalidation observes it).
40 std::lock_guard<std::mutex> lock(mutex_);
41
42 auto* host_code_ptr = GetHostCodePtrWritable(pc);
43 bool added;
44 auto* entry = AddUnsafe(pc,
45 host_code_ptr,
46 {kEntryNotTranslated, 0}, // TODO(b/232598137): set true host_size?
47 1, // Non-zero size simplifies invalidation.
48 GuestCodeEntry::Kind::kInterpreted,
49 &added);
50 CHECK(entry);
51
52 // Must not be translated yet.
53 if (entry->host_code->load() != kEntryNotTranslated) {
54 return nullptr;
55 }
56
57 // Check the threshold.
58 if (entry->invocation_counter < counter_threshold) {
59 ++entry->invocation_counter;
60 return nullptr;
61 }
62
63 LockForTranslationUnsafe(entry);
64 return entry;
65 }
66
LockForGearUpTranslation(GuestAddr pc)67 GuestCodeEntry* TranslationCache::LockForGearUpTranslation(GuestAddr pc) {
68 std::lock_guard<std::mutex> lock(mutex_);
69
70 auto* entry = LookupGuestCodeEntryUnsafe(pc);
71 if (!entry) {
72 // Entry could have been invalidated and erased.
73 return nullptr;
74 }
75
76 // This method should be called for lite-translated region, but we cannot
77 // guarantee they stay as such before we lock the mutex.
78 if (entry->kind != GuestCodeEntry::Kind::kLiteTranslated) {
79 return nullptr;
80 }
81
82 LockForTranslationUnsafe(entry);
83 return entry;
84 }
85
LockForTranslationUnsafe(GuestCodeEntry * entry)86 void TranslationCache::LockForTranslationUnsafe(GuestCodeEntry* entry) {
87 entry->host_code->store(kEntryTranslating);
88 entry->kind = GuestCodeEntry::Kind::kUnderProcessing;
89
90 bool inserted = translating_.insert(entry).second;
91 CHECK(inserted);
92 }
93
SetTranslatedAndUnlock(GuestAddr pc,GuestCodeEntry * entry,uint32_t guest_size,GuestCodeEntry::Kind kind,HostCodePiece code)94 void TranslationCache::SetTranslatedAndUnlock(GuestAddr pc,
95 GuestCodeEntry* entry,
96 uint32_t guest_size,
97 GuestCodeEntry::Kind kind,
98 HostCodePiece code) {
99 CHECK(kind != GuestCodeEntry::Kind::kUnderProcessing);
100 CHECK(kind != GuestCodeEntry::Kind::kGuestWrapped);
101 CHECK(kind != GuestCodeEntry::Kind::kHostWrapped);
102 // Make sure host code is updated under the mutex, so that it's in sync with
103 // the set of the translating regions (e.g as invalidation observes it).
104 std::lock_guard<std::mutex> lock(mutex_);
105
106 auto current = entry->host_code->load();
107
108 // Might have been invalidated while translating.
109 if (current == kEntryInvalidating) {
110 // ATTENTION: all transitions from kEntryInvalidating are protected by mutex!
111 entry->host_code->store(kEntryNotTranslated);
112 guest_entries_.erase(pc);
113 return;
114 }
115
116 // Must be translating
117 CHECK_EQ(current, kEntryTranslating);
118 CHECK(entry->kind == GuestCodeEntry::Kind::kUnderProcessing);
119
120 // ATTENTION: all transitions from kEntryTranslating are protected by mutex!
121 entry->host_code->store(code.code);
122
123 CHECK_GT(guest_size, 0);
124 entry->host_size = code.size;
125 entry->guest_size = guest_size;
126 entry->kind = kind;
127
128 size_t num_erased = translating_.erase(entry);
129 CHECK_EQ(num_erased, 1);
130
131 if (max_guest_size_ < guest_size) {
132 max_guest_size_ = guest_size;
133 }
134 }
135
AddAndLockForWrapping(GuestAddr pc)136 GuestCodeEntry* TranslationCache::AddAndLockForWrapping(GuestAddr pc) {
137 // This should be relatively rare, don't need a fast pass.
138 std::lock_guard<std::mutex> lock(mutex_);
139
140 // ATTENTION: kEntryWrapping is a locked state, can return the entry.
141 bool locked;
142 auto* entry = AddUnsafe(pc,
143 GetHostCodePtrWritable(pc),
144 {kEntryWrapping, 0}, // TODO(b/232598137): set true host_size?
145 1, // Non-zero size simplifies invalidation.
146 GuestCodeEntry::Kind::kUnderProcessing,
147 &locked);
148 return locked ? entry : nullptr;
149 }
150
SetWrappedAndUnlock(GuestAddr pc,GuestCodeEntry * entry,bool is_host_func,HostCodePiece code)151 void TranslationCache::SetWrappedAndUnlock(GuestAddr pc,
152 GuestCodeEntry* entry,
153 bool is_host_func,
154 HostCodePiece code) {
155 std::lock_guard<std::mutex> lock(mutex_);
156
157 auto current = entry->host_code->load();
158
159 // Might have been invalidated while wrapping.
160 if (current == kEntryInvalidating) {
161 // ATTENTION: all transitions from kEntryInvalidating are protected by mutex!
162 entry->host_code->store(kEntryNotTranslated);
163 guest_entries_.erase(pc);
164 return;
165 }
166
167 // Must be wrapping.
168 CHECK_EQ(current, kEntryWrapping);
169 CHECK(entry->kind == GuestCodeEntry::Kind::kUnderProcessing);
170
171 // ATTENTION: all transitions from kEntryWrapping are protected by mutex!
172 entry->host_code->store(code.code);
173
174 entry->host_size = code.size;
175 entry->kind =
176 is_host_func ? GuestCodeEntry::Kind::kHostWrapped : GuestCodeEntry::Kind::kGuestWrapped;
177 // entry->guest_size remains from 'wrapping'.
178 CHECK_EQ(entry->guest_size, 1);
179 }
180
IsHostFunctionWrapped(GuestAddr pc) const181 bool TranslationCache::IsHostFunctionWrapped(GuestAddr pc) const {
182 std::lock_guard<std::mutex> lock(mutex_);
183 if (auto* entry = LookupGuestCodeEntryUnsafe(pc)) {
184 return entry->kind == GuestCodeEntry::Kind::kHostWrapped;
185 }
186 return false;
187 }
188
AddUnsafe(GuestAddr pc,std::atomic<HostCodeAddr> * host_code_ptr,HostCodePiece host_code_piece,uint32_t guest_size,GuestCodeEntry::Kind kind,bool * added)189 GuestCodeEntry* TranslationCache::AddUnsafe(GuestAddr pc,
190 std::atomic<HostCodeAddr>* host_code_ptr,
191 HostCodePiece host_code_piece,
192 uint32_t guest_size,
193 GuestCodeEntry::Kind kind,
194 bool* added) {
195 auto [it, inserted] = guest_entries_.emplace(
196 std::pair{pc, GuestCodeEntry{host_code_ptr, host_code_piece.size, guest_size, kind, 0}});
197
198 if (inserted) {
199 host_code_ptr->store(host_code_piece.code);
200 }
201
202 *added = inserted;
203 return &it->second;
204 }
205
ProfilerLookupGuestCodeEntryByGuestPC(GuestAddr pc)206 GuestCodeEntry* TranslationCache::ProfilerLookupGuestCodeEntryByGuestPC(GuestAddr pc) {
207 std::lock_guard<std::mutex> lock(mutex_);
208 return LookupGuestCodeEntryUnsafe(pc);
209 }
210
GetInvocationCounter(GuestAddr pc) const211 uint32_t TranslationCache::GetInvocationCounter(GuestAddr pc) const {
212 std::lock_guard<std::mutex> lock(mutex_);
213 auto* entry = LookupGuestCodeEntryUnsafe(pc);
214 if (entry == nullptr) {
215 return 0;
216 }
217 return entry->invocation_counter;
218 }
219
LookupGuestCodeEntryUnsafe(GuestAddr pc)220 GuestCodeEntry* TranslationCache::LookupGuestCodeEntryUnsafe(GuestAddr pc) {
221 auto it = guest_entries_.find(pc);
222 if (it != std::end(guest_entries_)) {
223 return &it->second;
224 }
225
226 return nullptr;
227 }
228
LookupGuestCodeEntryUnsafe(GuestAddr pc) const229 const GuestCodeEntry* TranslationCache::LookupGuestCodeEntryUnsafe(GuestAddr pc) const {
230 return const_cast<TranslationCache*>(this)->LookupGuestCodeEntryUnsafe(pc);
231 }
232
SlowLookupGuestCodeEntryPCByHostPC(HostCode pc)233 GuestAddr TranslationCache::SlowLookupGuestCodeEntryPCByHostPC(HostCode pc) {
234 std::lock_guard<std::mutex> lock(mutex_);
235 const auto pc_addr = AsHostCodeAddr(pc);
236
237 for (auto& it : guest_entries_) {
238 auto* entry = &it.second;
239 auto host_code = entry->host_code->load();
240 if (host_code <= pc_addr && pc_addr < host_code + entry->host_size) {
241 return it.first;
242 }
243 }
244 return 0;
245 }
246
InvalidateEntriesBeingTranslatedUnsafe()247 void TranslationCache::InvalidateEntriesBeingTranslatedUnsafe() {
248 for (GuestCodeEntry* entry : translating_) {
249 CHECK(entry->kind == GuestCodeEntry::Kind::kUnderProcessing);
250 CHECK_EQ(entry->host_code->load(), kEntryTranslating);
251 entry->host_code->store(kEntryInvalidating);
252 entry->host_size = 0; // TODO(b/232598137): set true host_size?
253 // entry->guest_size and entry->kind remain from 'translating'.
254 // The entry will be erased on SetTranslatedAndUnlock.
255 }
256 translating_.clear();
257 }
258
InvalidateGuestRange(GuestAddr start,GuestAddr end)259 void TranslationCache::InvalidateGuestRange(GuestAddr start, GuestAddr end) {
260 std::lock_guard<std::mutex> lock(mutex_);
261
262 // Also invalidate all entries being translated, since they may possibly overlap with the
263 // start/end invalidation range. Technically, in the current implementation where we only
264 // translate regions that are a linear range of addresses, we would not need to invalidate the
265 // Translating entries that come after the end of the region being invalidated. But whether this
266 // would be beneficial is unclear and unlikely, and furthermore we may change the "linear" aspect
267 // later e.g. to follow static jumps.
268 InvalidateEntriesBeingTranslatedUnsafe();
269
270 std::map<GuestAddr, GuestCodeEntry>::iterator first;
271 if (start <= max_guest_size_) {
272 first = guest_entries_.begin();
273 } else {
274 first = guest_entries_.upper_bound(start - max_guest_size_);
275 }
276
277 while (first != guest_entries_.end()) {
278 auto curr = first++;
279 auto guest_pc = curr->first;
280 GuestCodeEntry* entry = &curr->second;
281
282 CHECK_GT(entry->guest_size, 0);
283 if (guest_pc + entry->guest_size <= start) {
284 continue;
285 }
286 if (guest_pc >= end) {
287 break;
288 }
289
290 HostCodeAddr current = entry->host_code->load();
291
292 if (current == kEntryInvalidating) {
293 // Translating but invalidated entry is handled in SetTranslatedAndUnlock.
294 } else if (current == kEntryWrapping) {
295 // Wrapping entry range is known in advance, so we don't have it in translating_.
296 entry->host_code->store(kEntryInvalidating);
297 // Wrapping but invalidated entry is handled in SetWrappedAndUnlock.
298 } else {
299 entry->host_code->store(kEntryNotTranslated);
300 guest_entries_.erase(curr);
301 }
302 }
303 }
304
305 } // namespace berberis
306