1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // |
5 | |
6 | // |
7 | // NgenHash is an abstract base class (actually a templated base class) designed to factor out the |
8 | // functionality common to hashes persisted into ngen images. |
9 | // |
10 | // SEMANTICS |
11 | // |
12 | // * Arbitrary entry payload via payload. |
13 | // * 32-bit hash code for entries. |
14 | // * Separate entry allocation and insertion (allowing reliable insertion if required). |
15 | // * Enumerate all entries or entries matching a particular hash. |
16 | // * No entry deletion. |
17 | // * Base logic to efficiently serialize hash contents at ngen time. Hot/cold splitting of entries is |
18 | // supported (along with the ability to tweak the Save and Fixup stages of each entry if needed). |
19 | // * Base logic to support DAC memory enumeration of the hash (including per-entry tweaks as needed). |
20 | // * Lock free lookup (the caller must follow the protocol laid out below under USER REQUIREMENTS). |
21 | // * Automatic hash expansion (with dialable scale factor). |
22 | // * Hash insertion is supported at runtime even when an ngen image is loaded with previously serialized hash |
23 | // entries. |
24 | // * Base logic to support formatting hashes in the nidump tool (only need to supply code for the unique |
25 | // aspects of your hash). |
26 | // |
27 | // BENEFITS |
28 | // |
29 | // * Removes next pointer from all persisted hash entries: |
30 | // o Reduces data footprint of each entry. |
31 | // o Increases density of entries. |
32 | // o Removes a base relocation entry. |
33 | // o Removes a runtime write to each entry (from the relocation above). |
34 | // * Serializes all hot/cold hash entries contigiuously: |
35 | // o Helps keeps hash entries in the same bucket in the same cache line. |
36 | // * Compresses persisted bucket list and removes the use of pointers: |
37 | // o Reduces working set hit of reading hash table (especially on 64-bit systems). |
38 | // o Allows bucket list to be saved in read-only memory and thus use shared rather than private pages. |
39 | // * Factors out common code: |
40 | // o Less chance of bugs, one place to make fixes. |
41 | // o Less code overall. |
42 | // |
43 | // SUB-CLASSING REQUIREMENTS |
44 | // |
45 | // To author a new NgenHash-based hashtable, the following steps are required: |
46 | // 1) In most cases (where each hash entry will have multiple fields) a structure defining the hash entry |
47 | // should be declared (see EEClassHashEntry in ClassHash.h for an example). This structure need not |
48 | // include a field for the hash code or pointer to the next entry in the hash bucket; these are taken care |
49 | // of automatically by the base class. If the entry must reference another entry in the hash (this should |
50 | // be rare) the NgenHashEntryRef<> template class should be used to abstract the reference (this class |
51 | // hides some of the transformation work that must take place when entries are re-ordered during ngen |
52 | // serialization). |
53 | // 2) Declare your new hash class deriving from NgenHash and providing the following template parameters: |
54 | // FINAL_CLASS : The class you're declaring (this is used by the base class to locate certain helper |
55 | // methods in your class used to tweak hash behavior). |
56 | // VALUE : The type of your hash entries (the class defined in the previous step). |
57 | // SCALE_FACTOR : A multipler on bucket count every time the hash table is grown (currently once the |
58 | // number of hash entries exceeds twice the number of buckets). A value of 2 would double |
59 | // the number of buckets on each grow operation for example. |
60 | // 3) Define a constructor that invokes the base class constructor with various setup parameters (see |
61 | // NgenHash constructor in this header). If your hash table is created via a static method rather than |
62 | // direct construction (common) then call your constructor using an in-place new inside the static method |
63 | // (see EEClassHashTable::Create in ClassHash.cpp for an example). |
64 | // 4) Define your basic hash functionality (creation, insertion, lookup, enumeration, ngen Save/Fixup and DAC |
65 | // memory enumeration) using the Base* methods provided by NgenHash. |
66 | // 5) Tweak the operation of BaseSave, BaseFixup and BaseEnumMemoryRegions by providing definitions of the |
67 | // following methods (note that all methods must be defined though they may be no-ops): |
68 | // |
69 | // bool ShouldSave(DataImage *pImage, VALUE *pEntry); |
70 | // Return true if the given entry should be persisted into the ngen image (otherwise it won't be |
71 | // saved with the rest). |
72 | // |
73 | // bool IsHotEntry(VALUE *pEntry, CorProfileData *pProfileData); |
74 | // Return true is the entry is considered hot given the profiling data. |
75 | // |
76 | // bool SaveEntry(DataImage *pImage, CorProfileData *pProfileData, VALUE *pOldEntry, VALUE *pNewEntry, EntryMappingTable *pMap); |
77 | // Gives your hash class a chance to save any additional data needed into the ngen image during |
78 | // the Save phase or otherwise make entry updates prior to saving. The saving process creates a |
79 | // new copy of each hash entry and this method is passed pointers both to the original entry and |
80 | // the new version along with a mapping class that can translate any old entry address in the |
81 | // table into the corresponding new address. If you have inter-entry pointer fields this is your |
82 | // chance to fix up those fields with the new location of their target entries. |
83 | // |
84 | // void FixupEntry(DataImage *pImage, VALUE *pEntry, void *pFixupBase, DWORD cbFixupOffset); |
85 | // Similar to SaveEntry but called during BaseFixup. This is your chance to register fixups for |
86 | // any pointer type fields in your entry. Due to the way hash entries are packed during ngen |
87 | // serialization individual hash entries are not saved as separate ngen zap nodes. So this method |
88 | // is passed a pointer to the enclosing zapped data structure (pFixupBase) and the offset of the |
89 | // entry from this base (cbFixupOffset). When calling pImage->FixupPointerField(...) for |
90 | // instance, pass pFixupBase as the first parameter and cbFixupOffset + offsetof(YourEntryClass, |
91 | // yourField) as the second parameter. |
92 | // |
93 | // void EnumMemoryRegionsForEntry(EEClassHashEntry_t *pEntry, CLRDataEnumMemoryFlags flags); |
94 | // Called during BaseEnumMemoryRegions for each entry in the hash. Use to enumerate any memory |
95 | // referenced by the entry (but not the entry itself). |
96 | // |
97 | // USER REQUIREMENTS |
98 | // |
99 | // Synchronization: It is permissable to read data from the hash without taking a lock as long as: |
100 | // 1) Any hash modifications are performed under a lock or otherwise serialized. |
101 | // 2) Any miss on a lookup is handled by taking a lock are retry-ing the lookup. |
102 | // |
103 | // OVERALL DESIGN |
104 | // |
105 | // The hash contains up to three groups of hash entries. These consist of two groups of entries persisted to |
106 | // disk at ngen time (split into hot and cold based on profile data) and live entries added at runtime (or |
107 | // during the ngen process itself, prior to the save operation). |
108 | // |
109 | // The persisted entries are tightly packed together and can eliminate some pointers and other metadata since |
110 | // we statically know about every entry at the time we format the hash entries (the save phase of ngen |
111 | // generation). |
112 | // |
113 | // Each persisted entry is assigned to a bucket based on its hash code and all entries that collide on a given |
114 | // bucket are placed contiguously in memory. The bucket list itself therefore consists of an array or pairs, |
115 | // each pair containing the count of entries in the bucket and the location of the first entry in the chain. |
116 | // Since all entries are allocated contiguously entry location can be specified by an index into the array of |
117 | // entries. |
118 | // |
119 | // Separate bucket lists and entry arrays are stored for hot and cold entries. |
120 | // |
121 | // The live entries (referred to here as volatile or warm entries) follow a more traditional hash |
122 | // implementation where entries are allocated individually from a loader heap and are chained together with a |
123 | // singly linked list if they collide. Here the bucket list is a simple array of pointers to the first entry |
124 | // in each chain (if any). |
125 | // |
126 | // Unlike the persisted entris the warm section of the table must cope with entry insertions and growing the |
127 | // bucket list when the table becomes too loaded (too many entries causing excessive bucket collisions). This |
128 | // happens when an entry insertion notes that there are twice as many entries as buckets. The bucket list is |
129 | // then reallocated (from a loader heap, consequently the old one is leaked) and resized based on a scale |
130 | // factor supplied by the hash sub-class. |
131 | // |
132 | // At runtime we lookup or enumerate entries by visiting all three sets of entries in the order Hot, Warm and |
133 | // Cold. This imposes a slight but constant time overhead. |
134 | // |
135 | |
136 | #ifndef __NGEN_HASH_INCLUDED |
137 | #define __NGEN_HASH_INCLUDED |
138 | |
139 | #ifdef FEATURE_PREJIT |
140 | #include "corcompile.h" |
141 | #endif |
142 | |
143 | // The type used to contain an entry hash value. This is not customizable on a per-hash class basis: all |
144 | // NgenHash derived hashes will share the same definition. Note that we only care about the data size, and the |
145 | // fact that it is an unsigned integer value (so we can take a modulus for bucket computation and use bitwise |
146 | // equality checks). The base class does not care about or participate in how these hash values are calculated. |
147 | typedef DWORD NgenHashValue; |
148 | |
149 | // The following code (and code in NgenHash.inl) has to replicate the base class template parameters (and in |
150 | // some cases the arguments) many many times. In the interests of brevity (and to make it a whole lot easier |
151 | // to modify these parameters in the future) we define macro shorthands for them here. Scan through the code |
152 | // to see how these are used. |
153 | #define NGEN_HASH_PARAMS typename FINAL_CLASS, typename VALUE, int SCALE_FACTOR |
154 | #define NGEN_HASH_ARGS FINAL_CLASS, VALUE, SCALE_FACTOR |
155 | |
156 | // Forward definition of NgenHashEntryRef (it takes the same template parameters as NgenHash and simplifies |
157 | // hash entries that need to refer to other hash entries). |
158 | template <NGEN_HASH_PARAMS> |
159 | class NgenHashEntryRef; |
160 | |
161 | // The base hash class itself. It's abstract and exposes its functionality via protected members (nothing is |
162 | // public). |
163 | template <NGEN_HASH_PARAMS> |
164 | class NgenHashTable |
165 | { |
166 | // NgenHashEntryRef needs access to the base table internal during Fixup in order to compute zap node |
167 | // bases. |
168 | friend class NgenHashEntryRef<NGEN_HASH_ARGS>; |
169 | |
170 | #ifdef DACCESS_COMPILE |
171 | // Nidump knows how to walk this data structure. |
172 | friend class NativeImageDumper; |
173 | #endif |
174 | |
175 | protected: |
176 | // This opaque structure provides enumeration context when walking the set of entries which share a common |
177 | // hash code. Initialized by BaseFindFirstEntryByHash and read/updated by BaseFindNextEntryByHash. |
178 | class LookupContext |
179 | { |
180 | friend class NgenHashTable<NGEN_HASH_ARGS>; |
181 | |
182 | TADDR m_pEntry; // The entry the caller is currently looking at (or NULL to begin |
183 | // with). This is a VolatileEntry* or PersistedEntry* (depending on |
184 | // m_eType below) and should always be a target address not a DAC |
185 | // PTR_. |
186 | DWORD m_eType; // The entry types we're currently walking (Hot, Warm, Cold in that order) |
187 | DWORD m_cRemainingEntries; // The remaining entries in the bucket chain (Hot or Cold entries only) |
188 | }; |
189 | |
190 | // This opaque structure provides enumeration context when walking all entries in the table. Initialized |
191 | // by BaseInitIterator and updated via the BaseIterator::Next. Note that this structure is somewhat |
192 | // similar to LookupContext above (though it requires a bit more state). It's possible we could factor |
193 | // these two iterators into some common base code but the actual implementations have enough differing |
194 | // requirements that the resultant code could be less readable (and slightly less performant). |
195 | class BaseIterator |
196 | { |
197 | public: |
198 | // Returns a pointer to the next entry in the hash table or NULL once all entries have been |
199 | // enumerated. Once NULL has been return the only legal operation is to re-initialize the iterator |
200 | // with BaseInitIterator. |
201 | DPTR(VALUE) Next(); |
202 | |
203 | private: |
204 | friend class NgenHashTable<NGEN_HASH_ARGS>; |
205 | |
206 | DPTR(NgenHashTable<NGEN_HASH_ARGS>) m_pTable; // Pointer back to the table being enumerated. |
207 | TADDR m_pEntry; // The entry the caller is currently looking at (or |
208 | // NULL to begin with). This is a VolatileEntry* or |
209 | // PersistedEntry* (depending on m_eType below) and |
210 | // should always be a target address not a DAC PTR_. |
211 | DWORD m_eType; // The entry types we're currently walking (Hot, Warm, |
212 | // Cold in that order). |
213 | union |
214 | { |
215 | DWORD m_dwBucket; // Index of bucket we're currently walking (Warm). |
216 | DWORD m_cRemainingEntries; // Number of entries remaining in hot/cold section |
217 | // (Hot, Cold). |
218 | }; |
219 | }; |
220 | |
221 | #ifndef DACCESS_COMPILE |
222 | // Base constructor. Call this from your derived constructor to provide the owning module, loader heap and |
223 | // initial number of buckets (which must be non-zero). Module must be provided if this hash is to be |
224 | // serialized into an ngen image. It is exposed to the derived hash class (many need it) but otherwise is |
225 | // only used to locate a loader heap for allocating bucket lists and entries unless an alternative heap is |
226 | // provided. Note that the heap provided is not serialized (so you'll allocate from that heap at |
227 | // ngen-time, but revert to allocating from the module's heap at runtime). If no Module pointer is |
228 | // supplied (non-ngen'd hash table) you must provide a direct heap pointer. |
229 | NgenHashTable(Module *pModule, LoaderHeap *pHeap, DWORD cInitialBuckets); |
230 | |
231 | // Allocate an uninitialized entry for the hash table (it's not inserted). The AllocMemTracker is optional |
232 | // and may be specified as NULL for untracked allocations. This is split from the hash insertion logic so |
233 | // that callers can pre-allocate entries and then perform insertions which cannot fault. |
234 | VALUE *BaseAllocateEntry(AllocMemTracker *pamTracker); |
235 | |
236 | // Insert an entry previously allocated via BaseAllocateEntry (you cannot allocated entries in any other |
237 | // manner) and associated with the given hash value. The entry should have been initialized prior to |
238 | // insertion. |
239 | void BaseInsertEntry(NgenHashValue iHash, VALUE *pEntry); |
240 | #endif // !DACCESS_COMPILE |
241 | |
242 | // Return the number of entries held in the table (does not include entries allocated but not inserted |
243 | // yet). |
244 | DWORD BaseGetElementCount(); |
245 | |
246 | // Initializes the iterator context passed by the caller to make it ready to walk every entry in the table |
247 | // in an arbitrary order. Call pIterator->Next() to retrieve the first entry. |
248 | void BaseInitIterator(BaseIterator *pIterator); |
249 | |
250 | // Find first entry matching a given hash value (returns NULL on no match). Call BaseFindNextEntryByHash |
251 | // to iterate the remaining matches (until it returns NULL). The LookupContext supplied by the caller is |
252 | // initialized by BaseFindFirstEntryByHash and read/updated by BaseFindNextEntryByHash to keep track of |
253 | // where we are. |
254 | DPTR(VALUE) BaseFindFirstEntryByHash(NgenHashValue iHash, LookupContext *pContext); |
255 | DPTR(VALUE) BaseFindNextEntryByHash(LookupContext *pContext); |
256 | |
257 | #if defined(FEATURE_PREJIT) && !defined(DACCESS_COMPILE) |
258 | // Call during ngen to save hash table data structures into the ngen image. Calls derived-class |
259 | // implementations of ShouldSave to determine which entries should be serialized, IsHotEntry to hot/cold |
260 | // split the entries and SaveEntry to allow per-entry extension of the saving process. |
261 | void BaseSave(DataImage *pImage, CorProfileData *pProfileData); |
262 | |
263 | // Call during ngen to register fixups for hash table data structure fields. Calls derived-class |
264 | // implementation of FixupEntry to allow per-entry extension of the fixup process. |
265 | void BaseFixup(DataImage *pImage); |
266 | |
267 | // Opaque structure used to store state will BaseSave is re-arranging hash entries and also passed to |
268 | // sub-classes' SaveEntry method to facilitate mapping old entry addresses to their new locations. |
269 | class EntryMappingTable |
270 | { |
271 | public: |
272 | ~EntryMappingTable(); |
273 | |
274 | // Given an old entry address (pre-BaseSave) return the address of the entry relocated ready for |
275 | // saving to disk. Note that this address is the (ngen) runtime address, not the disk image address |
276 | // you can further obtain by calling DataImage::GetImagePointer(). |
277 | VALUE *GetNewEntryAddress(VALUE *pOldEntry); |
278 | |
279 | private: |
280 | friend class NgenHashTable<NGEN_HASH_ARGS>; |
281 | |
282 | // Each Entry holds the mapping from one old-to-new hash entry. |
283 | struct Entry |
284 | { |
285 | VALUE *m_pOldEntry; // Pointer to the user part of the old entry |
286 | VALUE *m_pNewEntry; // Pointer to the user part of the new version |
287 | NgenHashValue m_iHashValue; // The hash code of the entry |
288 | DWORD m_dwNewBucket; // The new bucket index of the entry |
289 | DWORD m_dwChainOrdinal; // The 0-based position within the chain of that bucket |
290 | bool m_fHot; // If true this entry was identified as hot by the sub-class |
291 | }; |
292 | |
293 | Entry *m_pEntries; // Pointer to array of Entries |
294 | DWORD m_cEntries; // Count of valid entries in the above (may be smaller than |
295 | // allocated size) |
296 | }; |
297 | #endif // FEATURE_PREJIT && !DACCESS_COMPILE |
298 | |
299 | #ifdef DACCESS_COMPILE |
300 | // Call during DAC enumeration of memory regions to save in mini-dump to enumerate all hash table data |
301 | // structures. Calls derived-class implementation of EnumMemoryRegionsForEntry to allow additional |
302 | // per-entry memory to be reported. |
303 | void BaseEnumMemoryRegions(CLRDataEnumMemoryFlags flags); |
304 | #endif // DACCESS_COMPILE |
305 | |
306 | PTR_Module GetModule() |
307 | { |
308 | return ReadPointerMaybeNull(this, &NgenHashTable<NGEN_HASH_ARGS>::m_pModule); |
309 | } |
310 | |
311 | // Owning module set at hash creation time (possibly NULL if this hash instance is not to be ngen'd). |
312 | RelativePointer<PTR_Module> m_pModule; |
313 | |
314 | private: |
315 | // Internal implementation details. Nothing of interest to sub-classers for here on. |
316 | |
317 | #ifdef FEATURE_PREJIT |
318 | // This is the format of each hash entry that is persisted to disk (Hot or Cold entries). |
319 | struct PersistedEntry |
320 | { |
321 | VALUE m_sValue; // The sub-class supplied entry layout |
322 | NgenHashValue m_iHashValue; // The hash code associated with the entry (comes after m_sValue to |
323 | // minimize chance of pad bytes on a 64-bit system). |
324 | }; |
325 | typedef DPTR(PersistedEntry) PTR_PersistedEntry; |
326 | typedef ArrayDPTR(PersistedEntry) APTR_PersistedEntry; |
327 | |
328 | // This class encapsulates a bucket list identifying chains of related persisted entries. It's compressed |
329 | // rather than being a simple array, hence the encapsulation. |
330 | // A bucket list represents a non-zero sequence of buckets, each bucket identified by a zero-based index. |
331 | // Each bucket holds the index of the entry at the start of the bucket chain and a count of entries in |
332 | // that chain. (In persisted form hash entries are collated into hot and cold entries which are then |
333 | // allocated in contiguous blocks: this allows entries to be identified by an index into the entry block). |
334 | // Buckets with zero entries have an undefined start index (and a zero count obviously). |
335 | class PersistedBucketList |
336 | { |
337 | #ifdef DACCESS_COMPILE |
338 | friend class NativeImageDumper; |
339 | #endif |
340 | |
341 | public: |
342 | // Allocate and initialize a new list with the given count of buckets and configured to hold no more |
343 | // than the given number of entries or have a bucket chain longer than the specified maximum. These |
344 | // two maximums allow the implementation to choose an optimal data format for the bucket list at |
345 | // runtime and are enforced by asserts in the debug build. |
346 | static PersistedBucketList *CreateList(DWORD cBuckets, DWORD cEntries, DWORD cMaxEntriesInBucket); |
347 | |
348 | // For the given bucket set the index of the initial entry and the count of entries in the chain. If |
349 | // the count is zero the initial entry index is meaningless and ignored. |
350 | void SetBucket(DWORD dwIndex, DWORD dwFirstEntry, DWORD cEntries); |
351 | |
352 | // Get the size in bytes of this entire bucket list (need to pass in the bucket count since we save |
353 | // space by not storing it here, but we do validate this in debug mode). |
354 | size_t GetSize(DWORD cBuckets); |
355 | |
356 | // Get the initial entry index and entry count for the given bucket. Initial entry index value is |
357 | // undefined when count comes back as zero. |
358 | void GetBucket(DWORD dwIndex, DWORD *pdwFirstEntry, DWORD *pdwCount); |
359 | |
360 | // Simplified initial entry index when you don't need the count (don't call this for buckets with zero |
361 | // entries). |
362 | DWORD GetInitialEntry(DWORD dwIndex); |
363 | |
364 | private: |
365 | // Return the number of bits required to express a unique ID for the number of entities given. |
366 | static DWORD BitsRequired(DWORD cEntities); |
367 | |
368 | // Return the minimum size (in bytes) of each bucket list entry that can express all buckets given the |
369 | // max count of entries and entries in a single bucket chain. |
370 | static DWORD GetBucketSize(DWORD cEntries, DWORD cMaxEntriesInBucket); |
371 | |
372 | // Each bucket is represented by a variable sized bitfield (16, 32 or 64 bits) whose low-order bits |
373 | // contain the index of the first entry in the chain and higher-order (just above the initial entry |
374 | // bits) contain the count of entries in the chain. |
375 | |
376 | DWORD m_cbBucket; // The size in bytes of each bucket descriptor (2, 4 or 8) |
377 | DWORD m_dwInitialEntryMask; // The bitmask used to extract the initial entry index from a bucket |
378 | DWORD m_dwEntryCountShift; // The bit shift used to extract the entry count from a bucket |
379 | |
380 | #ifdef _DEBUG |
381 | // In debug mode we remember more initial state to catch common errors. |
382 | DWORD m_cBuckets; // Number of buckets in the list |
383 | DWORD m_cEntries; // Total number of entries mapped by the list |
384 | DWORD m_cMaxEntriesInBucket; // Largest bucket chain in the list |
385 | #endif |
386 | }; |
387 | typedef DPTR(PersistedBucketList) PTR_PersistedBucketList; |
388 | |
389 | // Pointers and counters for entries and buckets persisted to disk during ngen. Collected into a structure |
390 | // because this logic is replicated for Hot and Cold entries so we can factor some common code. |
391 | struct PersistedEntries |
392 | { |
393 | RelativePointer<APTR_PersistedEntry> m_pEntries; // Pointer to a contiguous block of PersistedEntry structures |
394 | // (NULL if zero entries) |
395 | RelativePointer<PTR_PersistedBucketList> m_pBuckets; // Pointer to abstracted bucket list mapping above entries |
396 | // into a hash (NULL if zero buckets, which is iff zero |
397 | // entries) |
398 | DWORD m_cEntries; // Count of entries in the above block |
399 | DWORD m_cBuckets; // Count of buckets in the above bucket list |
400 | }; |
401 | #endif // FEATURE_PREJIT |
402 | |
403 | // This is the format of a Warm entry, defined for our purposes to be a non-persisted entry (i.e. those |
404 | // created at runtime or during the creation of the ngen image itself). |
405 | struct VolatileEntry; |
406 | typedef DPTR(struct VolatileEntry) PTR_VolatileEntry; |
407 | struct VolatileEntry |
408 | { |
409 | VALUE m_sValue; // The derived-class format of an entry |
410 | PTR_VolatileEntry m_pNextEntry; // Pointer to the next entry in the bucket chain (or NULL) |
411 | NgenHashValue m_iHashValue; // The hash value associated with the entry |
412 | }; |
413 | |
414 | // Types of hash entry. |
415 | enum EntryType |
416 | { |
417 | Cold, // Persisted, profiling suggests this data is not read typically |
418 | Warm, // Volatile (in-memory) |
419 | Hot // Persisted, profiling suggests this data is probably read (or no profiling data was available) |
420 | }; |
421 | |
422 | #ifdef FEATURE_PREJIT |
423 | // Find the first persisted entry (hot or cold based on pEntries) that matches the given hash. Looks only |
424 | // in the persisted block given (i.e. searches only hot *or* cold entries). Returns NULL on failure. |
425 | // Otherwise returns pointer to the derived class portion of the entry and initializes the provided |
426 | // LookupContext to allow enumeration of any further matches. |
427 | DPTR(VALUE) FindPersistedEntryByHash(PersistedEntries *pEntries, NgenHashValue iHash, LookupContext *pContext); |
428 | #endif // FEATURE_PREJIT |
429 | |
430 | // Find the first volatile (warm) entry that matches the given hash. Looks only at warm entries. Returns |
431 | // NULL on failure. Otherwise returns pointer to the derived class portion of the entry and initializes |
432 | // the provided LookupContext to allow enumeration of any further matches. |
433 | DPTR(VALUE) FindVolatileEntryByHash(NgenHashValue iHash, LookupContext *pContext); |
434 | |
435 | #ifndef DACCESS_COMPILE |
436 | // Determine loader heap to be used for allocation of entries and bucket lists. |
437 | LoaderHeap *GetHeap(); |
438 | |
439 | // Increase the size of the bucket list in order to reduce the size of bucket chains. Does nothing on |
440 | // failure to allocate (since this impacts perf, not correctness). |
441 | void GrowTable(); |
442 | |
443 | // Returns the next prime larger (or equal to) than the number given. |
444 | DWORD NextLargestPrime(DWORD dwNumber); |
445 | #endif // !DACCESS_COMPILE |
446 | |
447 | DPTR(PTR_VolatileEntry) GetWarmBuckets() |
448 | { |
449 | SUPPORTS_DAC; |
450 | |
451 | return ReadPointer(this, &NgenHashTable<NGEN_HASH_ARGS>::m_pWarmBuckets); |
452 | } |
453 | |
454 | #ifdef FEATURE_PREJIT |
455 | APTR_PersistedEntry GetPersistedHotEntries() |
456 | { |
457 | SUPPORTS_DAC; |
458 | |
459 | return ReadPointerMaybeNull(this, |
460 | &NgenHashTable<NGEN_HASH_ARGS>::m_sHotEntries, |
461 | &decltype(NgenHashTable<NGEN_HASH_ARGS>::m_sHotEntries)::m_pEntries); |
462 | } |
463 | |
464 | PTR_PersistedBucketList GetPersistedHotBuckets() |
465 | { |
466 | SUPPORTS_DAC; |
467 | |
468 | return ReadPointerMaybeNull(this, |
469 | &NgenHashTable<NGEN_HASH_ARGS>::m_sHotEntries, |
470 | &decltype(NgenHashTable<NGEN_HASH_ARGS>::m_sHotEntries)::m_pBuckets); |
471 | } |
472 | |
473 | APTR_PersistedEntry GetPersistedColdEntries() |
474 | { |
475 | SUPPORTS_DAC; |
476 | |
477 | return ReadPointerMaybeNull(this, |
478 | &NgenHashTable<NGEN_HASH_ARGS>::m_sColdEntries, |
479 | &decltype(NgenHashTable<NGEN_HASH_ARGS>::m_sColdEntries)::m_pEntries); |
480 | } |
481 | |
482 | PTR_PersistedBucketList GetPersistedColdBuckets() |
483 | { |
484 | SUPPORTS_DAC; |
485 | |
486 | return ReadPointerMaybeNull(this, |
487 | &NgenHashTable<NGEN_HASH_ARGS>::m_sColdEntries, |
488 | &decltype(NgenHashTable<NGEN_HASH_ARGS>::m_sColdEntries)::m_pBuckets); |
489 | } |
490 | |
491 | #ifdef DACCESS_COMPILE |
492 | APTR_PersistedEntry GetPersistedEntries(DPTR(PersistedEntries) pEntries) |
493 | { |
494 | SUPPORTS_DAC; |
495 | |
496 | TADDR hotEntriesAddr = dac_cast<TADDR>(this) + offsetof(NgenHashTable<NGEN_HASH_ARGS>, m_sHotEntries); |
497 | TADDR coldEntriesAddr = dac_cast<TADDR>(this) + offsetof(NgenHashTable<NGEN_HASH_ARGS>, m_sColdEntries); |
498 | |
499 | if (hotEntriesAddr == dac_cast<TADDR>(pEntries)) |
500 | { |
501 | return GetPersistedHotEntries(); |
502 | } |
503 | else |
504 | { |
505 | _ASSERTE(hotEntriesAddr == dac_cast<TADDR>(pEntries)); |
506 | |
507 | return GetPersistedColdEntries(); |
508 | } |
509 | } |
510 | |
511 | PTR_PersistedBucketList GetPersistedBuckets(DPTR(PersistedEntries) pEntries) |
512 | { |
513 | SUPPORTS_DAC; |
514 | |
515 | TADDR hotEntriesAddr = dac_cast<TADDR>(this) + offsetof(NgenHashTable<NGEN_HASH_ARGS>, m_sHotEntries); |
516 | TADDR coldEntriesAddr = dac_cast<TADDR>(this) + offsetof(NgenHashTable<NGEN_HASH_ARGS>, m_sColdEntries); |
517 | |
518 | if (hotEntriesAddr == dac_cast<TADDR>(pEntries)) |
519 | { |
520 | return GetPersistedHotBuckets(); |
521 | } |
522 | else |
523 | { |
524 | _ASSERTE(hotEntriesAddr == dac_cast<TADDR>(pEntries)); |
525 | |
526 | return GetPersistedColdBuckets(); |
527 | } |
528 | } |
529 | #endif // DACCESS_COMPILE |
530 | #endif // FEATURE_PREJIT |
531 | |
532 | // Loader heap provided at construction time. May be NULL (in which case m_pModule must *not* be NULL). |
533 | LoaderHeap *m_pHeap; |
534 | |
535 | // Fields related to the runtime (volatile or warm) part of the hash. |
536 | RelativePointer<DPTR(PTR_VolatileEntry)> m_pWarmBuckets; // Pointer to a simple bucket list (array of VolatileEntry pointers) |
537 | DWORD m_cWarmBuckets; // Count of buckets in the above array (always non-zero) |
538 | DWORD m_cWarmEntries; // Count of elements in the warm section of the hash |
539 | |
540 | #ifdef FEATURE_PREJIT |
541 | PersistedEntries m_sHotEntries; // Hot persisted hash entries (if any) |
542 | PersistedEntries m_sColdEntries; // Cold persisted hash entries (if any) |
543 | |
544 | DWORD m_cInitialBuckets; // Initial number of warm buckets we started with. Only used |
545 | // to reset warm bucket count in ngen-persisted table. |
546 | #endif // FEATURE_PREJIT |
547 | }; |
548 | |
549 | // Abstraction around cross-hash entry references (e.g. EEClassHashTable, where entries for nested types point |
550 | // to entries for their enclosing types). Under the covers we use a relative pointer which avoids the need to |
551 | // allocate a base relocation fixup and the resulting write into the entry at load time. The abstraction hides |
552 | // some of the complexity needed to achieve this. |
553 | template <NGEN_HASH_PARAMS> |
554 | class NgenHashEntryRef |
555 | { |
556 | public: |
557 | // Get a pointer to the referenced entry. |
558 | DPTR(VALUE) Get(); |
559 | |
560 | #ifndef DACCESS_COMPILE |
561 | // Set the reference to point to the given entry. |
562 | void Set(VALUE *pEntry); |
563 | |
564 | #ifdef FEATURE_PREJIT |
565 | // Call this during the ngen Fixup phase to adjust the relative pointer to account for ngen image layout. |
566 | void Fixup(DataImage *pImage, NgenHashTable<NGEN_HASH_ARGS> *pTable); |
567 | #endif // FEATURE_PREJIT |
568 | |
569 | NgenHashEntryRef<NGEN_HASH_ARGS>& operator = (const NgenHashEntryRef<NGEN_HASH_ARGS> &src) |
570 | { |
571 | src.m_rpEntryRef.BitwiseCopyTo(m_rpEntryRef); |
572 | |
573 | return *this; |
574 | } |
575 | #endif // !DACCESS_COMPILE |
576 | |
577 | private: |
578 | RelativePointer<DPTR(VALUE)> m_rpEntryRef; // Entry ref encoded as a delta from this field's location. |
579 | }; |
580 | |
581 | #endif // __NGEN_HASH_INCLUDED |
582 | |