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