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