]> git.saurik.com Git - apple/dyld.git/blob - dyld3/shared-cache/IMPCaches.hpp
dyld-832.7.3.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / IMPCaches.hpp
1 //
2 // IMPCaches.hpp
3 // dyld_shared_cache_builder
4 //
5 // Created by Thomas Deniau on 18/12/2019.
6 //
7
8 #ifndef IMPCaches_hpp
9 #define IMPCaches_hpp
10
11 #include <vector>
12 #include <unordered_map>
13 #include <unordered_set>
14 #include <set>
15 #include <map>
16 #include <string>
17 #include <random>
18
19 #define DEBUG_SLOTS 1
20
21 template <typename P> class objc_method_t;
22
23 namespace IMPCaches {
24 class Selector;
25 class Constraint;
26
27 class ClassData {
28 private:
29 #if DEBUG_SLOTS
30 std::vector<const Selector*> slots;
31 #else
32 std::vector<bool> slots;
33 #endif
34
35 // Scratchpad for constraintForMethod
36 std::vector<int> allowedValues;
37
38 void resetSlots();
39
40 public:
41 bool isMetaclass;
42
43 // True most of the time, but can also be false in 2 cases:
44 // * We have entries for classes for which we don't want to generate IMP caches
45 // when they are superclasses of "interesting" (= for which we want an IMP cache)
46 // classes, so that we can properly attach non-cross-image categories to them and
47 // reference the right IMP for child classes which are actually interesting
48 // * We can also have failed to generate a cache for this class, or for a class
49 // in its flattening superclass hierarchy
50 bool shouldGenerateImpCache;
51
52 // Class has duplicates or is a child of a class with duplicates.
53 bool isPartOfDuplicateSet = false;
54
55 // This is set when we drop a class because a class in its flattening superclasss
56 // hierarchy was dropped. In that case, we won't try to flip its shouldGenerateImpCache
57 // value back to true when restoring a snapshot. (We could keep track of all the
58 // dependencies but it would be very messy and the reward is only a few classes here and there).
59 bool droppedBecauseFlatteningSuperclassWasDropped = false;
60
61 uint8_t backtracks = 0;
62
63 // For debug purposes
64 const char* name;
65
66 struct Method {
67 const char* installName = nullptr;
68 const char* className = nullptr;
69 const char* categoryName = nullptr;
70 Selector* selector = nullptr;
71 bool wasInlined = false;
72 bool fromFlattening = false;
73 };
74
75 std::vector<Method> methods;
76
77 std::string description() const;
78
79 int neededBits;
80
81 void didFinishAddingMethods();
82
83 // Not const because this uses the slots as a scratchpad
84 Constraint constraintForMethod(const Selector* m);
85
86 bool operator<(const ClassData& r) const {
87 if (isMetaclass != r.isMetaclass) return isMetaclass;
88 return strcmp(name, r.name) < 0;
89 }
90
91 struct PlacementAttempt {
92 int numberOfBitsToSet;
93 int shift;
94 int neededBits;
95
96 PlacementAttempt(int _numberOfBitsToSet, int _shift, int _neededBits) : numberOfBitsToSet(_numberOfBitsToSet), shift(_shift), neededBits(_neededBits) {
97
98 }
99
100 std::string description() const;
101
102 friend bool operator<(const PlacementAttempt& l, const PlacementAttempt& r)
103 {
104 return std::tie(l.neededBits, l.numberOfBitsToSet, l.shift)
105 < std::tie(r.neededBits, r.numberOfBitsToSet, r.shift);
106 }
107
108 int mask() const {
109 return (1 << neededBits) - 1;
110 }
111
112 struct PreviousMethodAddress {
113 int address;
114 int fixedBitsMask;
115 };
116
117 struct PreviousState {
118 int neededBits;
119 int shift;
120 int mask;
121 std::unordered_map<Selector*, PreviousMethodAddress> methods;
122 };
123
124 struct Result {
125 bool success;
126 PreviousState previousState;
127 };
128 };
129
130 // Possibilities we go through in the greedy backtracking algorithm findShiftsAndMask()
131 // Each class has a set (attempts()) of shift-mask possibilities, ordered, and we go through them
132 // sequentially.
133 PlacementAttempt attemptForShift(int shift, int neededBits) const;
134 std::vector<PlacementAttempt> attempts() const;
135
136 int shift;
137
138 inline int modulo() const {
139 return 1 << neededBits;
140 }
141
142 inline int mask() const {
143 return modulo() - 1;
144 }
145
146 // Attempt to place the class with the shift/mask in the attempt argument.
147 typename PlacementAttempt::Result applyAttempt(const PlacementAttempt& attempt, std::minstd_rand & randomNumberGenerator);
148
149 // Reassign the addresses as they were before we produced resultToBacktrackFrom
150 void backtrack(typename PlacementAttempt::Result& resultToBacktrackFrom);
151
152 // Did we have to grow the size of the hash table to one more bit when attempting to place it?
153 bool hadToIncreaseSize() const;
154
155 // Not const because this stomps over the slots array for checking
156 bool checkConsistency();
157
158 // Size in bytes needed for this hash table in the shared cache.
159 size_t sizeInSharedCache() const;
160
161 // Used to store the location of a flattening root's superclass, so that
162 // we can compute its final vmAddr once we are in the ObjC optimizer.
163 struct ClassLocator {
164 const char *installName;
165 unsigned segmentIndex;
166 unsigned segmentOffset;
167 bool operator==(const ClassLocator & other);
168 };
169
170 std::string_view flatteningRootName;
171 std::set<std::string_view> flattenedSuperclasses;
172 std::optional<ClassLocator> flatteningRootSuperclass;
173
174 // For debug purposes
175 bool operator==(const ClassData &other);
176 };
177
178 /// A unique selector. Has a name, a list of classes it's used in, and an address (which is actually an offset from
179 /// the beginning of the selectors section). Due to how the placement algorithm work, it also has a current
180 /// partial address and the corresponding bitmask of fixed bits. The algorithm adds bits to the partial address
181 /// as it makes progress and updates the mask accordingly
182 class Selector {
183 public:
184 // For debug purposes
185 const char* name;
186
187 /// Classes the selector is used in
188 std::vector<IMPCaches::ClassData*> classes;
189
190 /// Which bits of address are already set
191 int fixedBitsMask;
192
193 /// Current 128-byte bucket index for this selector. Only the bits in fixedBitsMask are actually frozen.
194 int inProgressBucketIndex;
195
196 /// Full offset of the selector, including its low 7 bits (so, not the bucket's index ; inProgressBucketIndex (assuming all bits are setr by now) << 7 + some low bits)
197 int offset; // including low bits
198
199 /// Number of bits that you would need to freeze if you were to use this selector with this shift and mask.
200 int numberOfBitsToSet(int shift, int mask) const {
201 int fixedBits = (fixedBitsMask >> shift) & mask;
202 return __builtin_popcount(mask) - __builtin_popcount(fixedBits);
203 }
204
205 int numberOfSetBits() const {
206 return __builtin_popcount(fixedBitsMask);
207 }
208
209 unsigned int size() const {
210 return (unsigned int)strlen(name) + 1;
211 }
212
213 // For debug purposes
214 bool operator==(const Selector &other) const {
215 return (strcmp(name, other.name) == 0)
216 && (inProgressBucketIndex == other.inProgressBucketIndex)
217 && (fixedBitsMask == other.fixedBitsMask);
218 }
219
220 bool operator!=(const Selector &other) const {
221 return !(*this == other);
222 }
223
224 };
225
226 class AddressSpace;
227 std::ostream& operator<<(std::ostream& o, const AddressSpace& c);
228
229 struct Hole {
230 // [startAddress, endAddress[
231 int startAddress;
232 int endAddress;
233 int size() const {
234 return endAddress - startAddress;
235 }
236
237 // All our intervals are non-overlapping
238 bool operator<(const Hole& other) const {
239 auto a = std::make_tuple(size(), startAddress);
240 auto b = std::make_tuple(other.size(), other.startAddress);
241 return a < b;
242 }
243 };
244
245 /// Represents the holes left by the selector placement algorithm, to be filled later with other selectors we did not target.
246 class HoleMap {
247 public:
248
249 /// Returns the position at which we should place a string of size `size`.
250 int addStringOfSize(unsigned size);
251
252 /// Total size of all the holes
253 unsigned long totalHoleSize() const;
254
255 // Empty the hole map.
256 void clear();
257
258 HoleMap();
259
260 private:
261 friend class AddressSpace;
262 friend std::ostream& operator<< (std::ostream& o, const HoleMap& m);
263
264 int endAddress = 0;
265 std::set<IMPCaches::Hole> holes;
266 };
267
268 // A selector that is known to be at offset 0, to let objc easily compute
269 // the offset of a selector given the SEL.
270 constexpr std::string_view magicSelector = "\xf0\x9f\xa4\xaf";
271
272 /// This is used to place the selectors in 128-byte buckets.
273 /// The "indices" below are the indices of the 128-byte bucket. To get an actual selector offset from this,
274 /// we shift the index to the left by 7 bits, and assign low bits depending on the length of each selector (this happens
275 /// in @see computeLowBits()).
276 /// The goal of this class is to validate that selectors can actually be placed in the buckets without overflowing
277 /// the 128-byte total length limit (based on the length of each individual selector)
278 class AddressSpace {
279 public:
280 int sizeAtIndex(int idx) const;
281 int sizeAvailableAfterIndex(int idx) const;
282 bool canPlaceMethodAtIndex(const Selector* method, int idx) const;
283 void placeMethodAtIndex(Selector* method, int idx);
284
285 // If we decided to drop any classes, remove the selectors that were only present in them
286 void removeUninterestingSelectors();
287
288 // Once all selectors are placed in their 128-byte buckets,
289 // actually assign the low 7 bits for each, and make a map of the
290 // holes so that we can fill them with other selectors later.
291 void computeLowBits(HoleMap& selectorsHoleMap) const;
292
293 std::string description() const;
294
295 static const int maximumIndex = (1 << 17) - 1;
296 static constexpr int bagSizeShift = 7;
297
298 friend std::ostream& operator<< (std::ostream& o, const AddressSpace& c);
299 private:
300 inline int bagSizeAtIndex(int idx) const {
301 static constexpr int bagSize = 1 << bagSizeShift;
302 static constexpr int bag0Size = bagSize - (magicSelector.length() + 1);
303 return idx ? bagSize : bag0Size;
304 }
305 bool canPlaceWithoutFillingOverflowCellAtIndex(int idx) const;
306 std::unordered_map<int, std::vector<Selector*>> methodsByIndex;
307 std::unordered_map<int, int> sizes;
308 };
309
310 /// Represents a constraint on some of the bits of an address
311 /// It stores a set of allowed values for a given range of bits (shift and mask)
312 class Constraint {
313 public:
314 int mask;
315 int shift;
316 std::unordered_set<int> allowedValues;
317
318 Constraint intersecting(const Constraint& other) const;
319 friend std::ostream& operator << (std::ostream& o, const Constraint& c);
320
321 bool operator==(const Constraint& other) const {
322 return mask == other.mask &&
323 shift == other.shift &&
324 allowedValues == other.allowedValues;
325 }
326
327 struct Hasher {
328 size_t operator()(const IMPCaches::Constraint& c) const {
329 return c.shift << 24 | c.mask << 16 | c.allowedValues.size() << 8 | *c.allowedValues.begin();
330 }
331 };
332 };
333
334 /// Merges several Constraints together to generate a simplified constraint equivalent to the original set of constraints
335 class ConstraintSet {
336 std::unordered_set<Constraint, Constraint::Hasher> constraints;
337
338 public:
339 std::optional<Constraint> mergedConstraint;
340
341 bool add(const Constraint& c);
342 void clear();
343 };
344
345 class SelectorMap {
346 public:
347 using UnderlyingMap = std::map<std::string_view, std::unique_ptr<IMPCaches::Selector>>;
348 UnderlyingMap map;
349 SelectorMap();
350 };
351
352 // Implemented in OptimizerObjC
353 size_t sizeForImpCacheWithCount(int entries);
354
355 struct ClassKey {
356 std::string_view name;
357 bool metaclass;
358
359 size_t hash() const {
360 std::size_t seed = 0;
361 seed ^= std::hash<std::string_view>()(name) + 0x9e3779b9 + (seed<<6) + (seed>>2);
362 seed ^= std::hash<bool>()(metaclass) + 0x9e3779b9 + (seed<<6) + (seed>>2);
363 return seed;
364 }
365
366 bool operator==(const ClassKey &other) const {
367 return (name == other.name) && (metaclass == other.metaclass);
368 }
369 };
370
371 struct ClassKeyHasher {
372 size_t operator()(const ClassKey& k) const {
373 return k.hash();
374 }
375 };
376
377 }
378
379
380 #endif /* IMPCaches_hpp */