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