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// Code for tracking method inlinings in NGen and R2R images.
6// The only information stored is "who" got inlined "where", no offsets or inlining depth tracking.
7// (No good for debugger yet.)
8// This information is later exposed to profilers and can be useful for ReJIT.
9// Runtime inlining is not being tracked because profilers can deduce it via callbacks anyway.
10// =============================================================================================
11#include "common.h"
12#include "inlinetracking.h"
13#include "ceeload.h"
14
15#ifndef DACCESS_COMPILE
16
17bool MethodInModule::operator <(const MethodInModule& other) const
18{
19 STANDARD_VM_CONTRACT;
20 if (m_module == other.m_module)
21 {
22 return m_methodDef < other.m_methodDef;
23 }
24 else
25 {
26 // Since NGen images are supposed to be determenistic,
27 // we need stable sort order that isn't changing between different runs
28 // That's why we use names and GUIDs instead of just doing m_module < other.m_module
29
30 // First we try to compare simple names (should be fast enough)
31 LPCUTF8 simpleName = m_module ? m_module->GetSimpleName() : "";
32 LPCUTF8 otherSimpleName = other.m_module ? other.m_module->GetSimpleName() : "";
33 int nameCmpResult = strcmp(simpleName, otherSimpleName);
34
35 if (nameCmpResult == 0)
36 {
37 // Names are equal but module addresses aren't, it's suspicious
38 // falling back to module GUIDs
39 GUID thisGuid, otherGuid;
40 if (m_module == NULL)
41 {
42 memset(&thisGuid, 0, sizeof(GUID));
43 }
44 else
45 {
46 m_module->GetFile()->GetMVID(&thisGuid);
47 }
48
49 if (other.m_module == NULL)
50 {
51 memset(&otherGuid, 0, sizeof(GUID));
52 }
53 else
54 {
55 other.m_module->GetFile()->GetMVID(&otherGuid);
56 }
57
58 return memcmp(&thisGuid, &otherGuid, sizeof(GUID)) < 0;
59 }
60 else
61 {
62 return nameCmpResult < 0;
63 }
64 }
65}
66
67bool MethodInModule::operator ==(const MethodInModule& other) const
68{
69 LIMITED_METHOD_DAC_CONTRACT;
70 return m_methodDef == other.m_methodDef &&
71 m_module == other.m_module;
72}
73
74bool MethodInModule::operator !=(const MethodInModule& other) const
75{
76 LIMITED_METHOD_DAC_CONTRACT;
77 return m_methodDef != other.m_methodDef ||
78 m_module != other.m_module;
79}
80
81
82void InlineTrackingEntry::SortAndDeduplicate()
83{
84 STANDARD_VM_CONTRACT;
85
86 //Sort
87 MethodInModule *begin = &m_inliners[0];
88 MethodInModule *end = begin + m_inliners.GetCount();
89 util::sort(begin, end);
90
91 //Deduplicate
92 MethodInModule *left = begin;
93 MethodInModule *right = left + 1;
94 while (right < end)
95 {
96 auto rvalue = *right;
97 if (*left != rvalue)
98 {
99 left++;
100 if (left != right)
101 {
102 *left = rvalue;
103 }
104 }
105 right++;
106 }
107
108 //Shrink
109 int newCount = (int)(left - begin + 1);
110 m_inliners.SetCount(newCount);
111}
112
113InlineTrackingEntry::InlineTrackingEntry(const InlineTrackingEntry& other)
114 :m_inlinee(other.m_inlinee)
115{
116 STANDARD_VM_CONTRACT;
117 m_inliners.Set(other.m_inliners);
118}
119
120InlineTrackingEntry & InlineTrackingEntry::operator = (const InlineTrackingEntry &other)
121{
122 STANDARD_VM_CONTRACT;
123 m_inlinee = other.m_inlinee;
124 m_inliners.Set(other.m_inliners);
125 return *this;
126}
127
128void InlineTrackingEntry::Add(PTR_MethodDesc inliner)
129{
130 STANDARD_VM_CONTRACT;
131
132 MethodInModule method(inliner->GetModule(), inliner->GetMemberDef());
133
134 // Going through last 10 inliners to check if a given inliner has recently been registered.
135 // It allows to filter out most duplicates without having to scan through hundreds of inliners
136 // for methods like Object.ctor or Monitor.Enter.
137 // We are OK to keep occasional duplicates in m_inliners, we'll get rid of them
138 // in SortAndDeduplicate() anyway.
139 int count = static_cast<int>(m_inliners.GetCount());
140 int start = max(0, count - 10);
141 for (int i = count - 1; i >= start; i--)
142 {
143 if (m_inliners[i] == method)
144 return;
145 }
146
147 //look like we see this inliner for the first time, add it to the collection
148 m_inliners.Append(method);
149}
150
151InlineTrackingMap::InlineTrackingMap()
152 : m_mapCrst(CrstInlineTrackingMap)
153{
154 STANDARD_VM_CONTRACT;
155}
156
157void InlineTrackingMap::AddInlining(MethodDesc *inliner, MethodDesc *inlinee)
158{
159 STANDARD_VM_CONTRACT;
160 _ASSERTE(inliner != NULL);
161 _ASSERTE(inlinee != NULL);
162
163 MethodInModule inlineeMnM(inlinee->GetModule(), inlinee->GetMemberDef());
164
165 if (RidFromToken(inlineeMnM.m_methodDef) == 0 || RidFromToken(inliner->GetMemberDef()) == 0)
166 {
167 // Sometimes we do see methods that don't have valid tokens (stubs etc)
168 // we just ignore them.
169 return;
170 }
171
172 CrstHolder lock(&m_mapCrst);
173 InlineTrackingEntry *existingEntry = const_cast<InlineTrackingEntry *>(LookupPtr(inlineeMnM));
174 if (existingEntry)
175 {
176 // We saw this inlinee before, just add one more inliner
177 existingEntry->Add(inliner);
178 }
179 else
180 {
181 // We haven't seen this inlinee before, create a new record in the hashtable
182 // and add a first inliner to it.
183 InlineTrackingEntry newEntry;
184 newEntry.m_inlinee = inlineeMnM;
185 newEntry.Add(inliner);
186 Add(newEntry);
187 }
188}
189
190#endif //!DACCESS_COMPILE
191
192void ZapInlineeRecord::InitForNGen(RID rid, LPCUTF8 simpleName)
193{
194 LIMITED_METHOD_CONTRACT;
195 //XOR of up to first 24 bytes in module name
196 DWORD hash = 0;
197 for (int i = 0; simpleName[i] && i < 24; i++)
198 hash ^= (BYTE)simpleName[i];
199
200 // This key contains 24 bits of RID and 8 bits from module name.
201 // Since RID can't be longer than 24 bits, we can't have method RID collistions,
202 // that's why PersistentInlineTrackingMap::GetInliners only deals with module collisions.
203 m_key = (hash << 24) | rid;
204}
205
206
207COUNT_T PersistentInlineTrackingMapNGen::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData)
208{
209 CONTRACTL
210 {
211 THROWS;
212 GC_NOTRIGGER;
213 MODE_ANY;
214 }
215 CONTRACTL_END;
216
217 _ASSERTE(inlineeOwnerMod);
218 _ASSERTE(inliners);
219
220 if (incompleteData)
221 {
222 *incompleteData = FALSE;
223 }
224 if (m_inlineeIndex == NULL || m_inlinersBuffer == NULL)
225 {
226 //No inlines saved in this image.
227 return 0;
228 }
229
230 // Binary search to find all records matching (inlineeTkn/inlineeOwnerMod)
231 ZapInlineeRecord probeRecord;
232 probeRecord.InitForNGen(RidFromToken(inlineeTkn), inlineeOwnerMod->GetSimpleName());
233 ZapInlineeRecord *begin = m_inlineeIndex;
234 ZapInlineeRecord *end = m_inlineeIndex + m_inlineeIndexSize;
235 ZapInlineeRecord *foundRecord = util::lower_bound(begin, end, probeRecord);
236 DWORD result = 0;
237 DWORD outputIndex = 0;
238
239 // Go through all matching records
240 for (; foundRecord < end && *foundRecord == probeRecord; foundRecord++)
241 {
242 DWORD offset = foundRecord->m_offset;
243 NibbleReader stream(m_inlinersBuffer + offset, m_inlinersBufferSize - offset);
244
245 DWORD inlineeModuleZapIndex = stream.ReadEncodedU32();
246 Module *decodedInlineeModule = GetModuleByIndex(inlineeModuleZapIndex);
247
248 // Check if this is just token/method name hash collision
249 if (decodedInlineeModule == inlineeOwnerMod)
250 {
251 // We found the token and the module we were looking for!
252 DWORD inlinerModuleZapIndex = stream.ReadEncodedU32(); //read inliner module, it is same for all inliners
253 Module *inlinerModule = GetModuleByIndex(inlinerModuleZapIndex);
254
255 if (inlinerModule != NULL)
256 {
257 DWORD inlinersCount = stream.ReadEncodedU32();
258 _ASSERTE(inlinersCount > 0);
259
260 RID inlinerRid = 0;
261 // Reading inliner RIDs one by one, each RID is represented as an adjustment (diff) to the previous one.
262 // Adding inliners module and coping to the output buffer
263 for (DWORD i = 0; i < inlinersCount && outputIndex < inlinersSize; i++)
264 {
265 inlinerRid += stream.ReadEncodedU32();
266 mdMethodDef inlinerTkn = TokenFromRid(inlinerRid, mdtMethodDef);
267 inliners[outputIndex++] = MethodInModule(inlinerModule, inlinerTkn);
268 }
269 result += inlinersCount;
270 }
271 else
272 {
273 // We can't find module for this inlineeModuleZapIndex, it means it hasn't been loaded yet
274 // (maybe it never will be), we just report it to the profiler.
275 // Profiler might want to try later when more modules are loaded.
276 if (incompleteData)
277 {
278 *incompleteData = TRUE;
279 }
280 }
281 }
282 }
283
284 return result;
285}
286
287
288
289Module *PersistentInlineTrackingMapNGen::GetModuleByIndex(DWORD index)
290{
291 CONTRACTL
292 {
293 NOTHROW;
294 GC_NOTRIGGER;
295 MODE_ANY;
296 }
297 CONTRACTL_END;
298
299 // This "black magic spell" has in fact nothing to do with GenericInstantiationCompare per se, but just sets a thread flag
300 // that later activates more thorough search inside Module::GetAssemblyIfLoaded, which is indirectly called from GetModuleFromIndexIfLoaded.
301 // This is useful when ngen image was compiler against a different assembly version than the one loaded now.
302 ClrFlsThreadTypeSwitch genericInstantionCompareHolder(ThreadType_GenericInstantiationCompare);
303
304 return m_module->GetModuleFromIndexIfLoaded(index);
305}
306
307
308
309#ifndef DACCESS_COMPILE
310#ifdef FEATURE_NATIVE_IMAGE_GENERATION
311
312// This is a shared serialization routine used for both NGEN and R2R formats. If image != NULL the NGEN format is generated, otherwise the R2R format
313void SerializeInlineTrackingEntry(DataImage* image, SBuffer *inlinersBuffer, SArray<ZapInlineeRecord> *inlineeIndex, InlineTrackingEntry *entry)
314{
315 STANDARD_VM_CONTRACT;
316 // This call removes duplicates from inliners and makes sure they are sorted by module
317 entry->SortAndDeduplicate();
318 MethodInModule inlinee = entry->m_inlinee;
319 DWORD inlineeModuleZapIndex = 0;
320 if (image != NULL)
321 {
322 inlineeModuleZapIndex = image->GetModuleImportIndex(inlinee.m_module);
323 }
324 InlineSArray<MethodInModule, 3> &inliners = entry->m_inliners;
325 COUNT_T totalInlinersCount = inliners.GetCount();
326 _ASSERTE(totalInlinersCount > 0);
327
328 COUNT_T sameModuleCount;
329 // Going through all inliners and grouping them by their module, for each module we'll create
330 // an ZapInlineeRecord and encode inliners as bytes in inlinersBuffer.
331 for (COUNT_T thisModuleBegin = 0; thisModuleBegin < totalInlinersCount; thisModuleBegin += sameModuleCount)
332 {
333 Module *lastInlinerModule = inliners[thisModuleBegin].m_module;
334 DWORD lastInlinerModuleZapIndex = 0;
335 if (image != NULL)
336 {
337 lastInlinerModuleZapIndex = image->GetModuleImportIndex(lastInlinerModule);
338 }
339
340 // Counting how many inliners belong to this module
341 sameModuleCount = 1;
342 while (thisModuleBegin + sameModuleCount < totalInlinersCount &&
343 inliners[thisModuleBegin + sameModuleCount].m_module == lastInlinerModule)
344 {
345 sameModuleCount++;
346 }
347
348 // Saving module indexes and number of inliners
349 NibbleWriter inlinersStream;
350 if (image != NULL)
351 {
352 inlinersStream.WriteEncodedU32(inlineeModuleZapIndex);
353 inlinersStream.WriteEncodedU32(lastInlinerModuleZapIndex);
354 }
355 inlinersStream.WriteEncodedU32(sameModuleCount);
356
357 // Saving inliners RIDs, each new RID is represented as an adjustment (diff) to the previous one
358 RID prevMethodRid = 0;
359 for (COUNT_T i = thisModuleBegin; i < thisModuleBegin + sameModuleCount; i++)
360 {
361 RID methodRid = RidFromToken(inliners[i].m_methodDef);
362 _ASSERTE(methodRid >= prevMethodRid);
363 inlinersStream.WriteEncodedU32(methodRid - prevMethodRid);
364 prevMethodRid = methodRid;
365 }
366 inlinersStream.Flush();
367
368 // Copy output of NibbleWriter into a big buffer (inlinersBuffer) for inliners from the same module
369 // and create an InlineeRecord with correct offset
370 DWORD inlinersStreamSize;
371 const BYTE *inlinersStreamPtr = (const BYTE *)inlinersStream.GetBlob(&inlinersStreamSize);
372 ZapInlineeRecord record;
373 if (image != NULL)
374 {
375 record.InitForNGen(RidFromToken(inlinee.m_methodDef), inlinee.m_module->GetSimpleName());
376 }
377 else
378 {
379 record.InitForR2R(RidFromToken(inlinee.m_methodDef));
380 }
381 record.m_offset = inlinersBuffer->GetSize();
382 inlinersBuffer->Insert(inlinersBuffer->End(), SBuffer(SBuffer::Immutable, inlinersStreamPtr, inlinersStreamSize));
383 inlineeIndex->Append(record);
384 }
385}
386
387bool compare_entry(const InlineTrackingEntry* first, const InlineTrackingEntry* second)
388{
389 return first->m_inlinee < second->m_inlinee;
390}
391
392// This is a shared serialization routine used for both NGEN and R2R formats. If image != NULL the NGEN format is generated, otherwise the R2R format
393void SerializeTrackingMapBuffers(ZapHeap* heap, DataImage *image, SBuffer *inlinersBuffer, SArray<ZapInlineeRecord> *inlineeIndex, InlineTrackingMap* runtimeMap)
394{
395 STANDARD_VM_CONTRACT;
396 _ASSERTE(runtimeMap != NULL);
397
398 // Sort records from runtimeMap, because we need to make sure
399 // we save everything in deterministic order. Hashtable iteration is not deterministic.
400 COUNT_T runtimeMapCount = runtimeMap->GetCount();
401 InlineTrackingEntry **inlinees = new (heap) InlineTrackingEntry *[runtimeMapCount];
402 int index = 0;
403 for (auto iter = runtimeMap->Begin(), end = runtimeMap->End(); iter != end; ++iter)
404 {
405 inlinees[index++] = const_cast<InlineTrackingEntry *>(&*iter);
406 }
407 util::sort(inlinees, inlinees + runtimeMapCount, compare_entry);
408
409
410 // Iterate throught each inlinee record from the InlineTrackingMap
411 // and write corresponding records into inlineeIndex and inlinersBuffer
412 for (COUNT_T i = 0; i < runtimeMapCount; i++)
413 {
414 SerializeInlineTrackingEntry(image, inlinersBuffer, inlineeIndex, inlinees[i]);
415 }
416}
417
418
419
420void PersistentInlineTrackingMapNGen::Save(DataImage *image, InlineTrackingMap* runtimeMap)
421{
422 STANDARD_VM_CONTRACT;
423 _ASSERTE(image != NULL);
424 _ASSERTE(runtimeMap != NULL);
425
426 SArray<ZapInlineeRecord> inlineeIndex;
427 SBuffer inlinersBuffer;
428
429 SerializeTrackingMapBuffers(image->GetHeap(), image, &inlinersBuffer, &inlineeIndex, runtimeMap);
430
431 m_inlineeIndexSize = inlineeIndex.GetCount();
432 m_inlinersBufferSize = inlinersBuffer.GetSize();
433 _ASSERTE((m_inlineeIndexSize == 0) == (m_inlinersBufferSize == 0));
434
435 if (m_inlineeIndexSize != 0 && m_inlinersBufferSize != 0)
436 {
437 // Copy everything to the class fields, we didn't use the class fields for addition
438 // because we want to make sure we don't waste memory for buffer's amortized growth
439 m_inlineeIndex = new (image->GetHeap()) ZapInlineeRecord[m_inlineeIndexSize];
440 inlineeIndex.Copy(m_inlineeIndex, inlineeIndex.Begin(), m_inlineeIndexSize);
441
442 m_inlinersBuffer = new (image->GetHeap()) BYTE[m_inlinersBufferSize];
443 inlinersBuffer.Copy(m_inlinersBuffer, inlinersBuffer.Begin(), m_inlinersBufferSize);
444
445 //Sort m_inlineeIndex so we can later use binary search
446 util::sort(m_inlineeIndex, m_inlineeIndex + m_inlineeIndexSize);
447
448 //Making sure all this memory actually gets saved into NGEN image
449 image->StoreStructure(m_inlineeIndex, m_inlineeIndexSize * sizeof(m_inlineeIndex[0]), DataImage::ITEM_INLINING_DATA);
450 image->StoreStructure(m_inlinersBuffer, m_inlinersBufferSize, DataImage::ITEM_INLINING_DATA);
451 }
452
453 image->StoreStructure(this, sizeof(*this), DataImage::ITEM_INLINING_DATA);
454 LOG((LF_ZAP, LL_INFO100000,
455 "PersistentInlineTrackingMap saved. InlineeIndexSize: %d bytes, InlinersBufferSize: %d bytes\n",
456 m_inlineeIndexSize * sizeof(m_inlineeIndex[0]), m_inlinersBufferSize));
457}
458
459void PersistentInlineTrackingMapNGen::Fixup(DataImage *image)
460{
461 STANDARD_VM_CONTRACT;
462 image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_module));
463 image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_inlineeIndex));
464 image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_inlinersBuffer));
465}
466
467#endif //FEATURE_NATIVE_IMAGE_GENERATION
468#endif //!DACCESS_COMPILE
469
470#ifdef FEATURE_READYTORUN
471
472struct InliningHeader
473{
474 int SizeOfInlineeIndex;
475};
476
477#ifndef DACCESS_COMPILE
478#ifdef FEATURE_NATIVE_IMAGE_GENERATION
479
480
481
482void PersistentInlineTrackingMapR2R::Save(ZapHeap* pHeap, SBuffer* pSaveTarget, InlineTrackingMap* runtimeMap)
483{
484 STANDARD_VM_CONTRACT;
485 _ASSERTE(pSaveTarget != NULL);
486 _ASSERTE(runtimeMap != NULL);
487
488 SArray<ZapInlineeRecord> inlineeIndex;
489 SBuffer inlinersBuffer;
490
491 SerializeTrackingMapBuffers(pHeap, NULL, &inlinersBuffer, &inlineeIndex, runtimeMap);
492
493 InliningHeader header;
494 header.SizeOfInlineeIndex = inlineeIndex.GetCount() * sizeof(ZapInlineeRecord);
495
496 pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) &header, sizeof(header)));
497 DWORD unused = 0;
498 pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) inlineeIndex.GetElements(), header.SizeOfInlineeIndex));
499 pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) inlinersBuffer, inlinersBuffer.GetSize()));
500
501 LOG((LF_ZAP, LL_INFO100000,
502 "PersistentInlineTrackingMap saved. InlineeIndexSize: %d bytes, InlinersBufferSize: %d bytes\n",
503 header.SizeOfInlineeIndex, inlinersBuffer.GetSize()));
504}
505
506#endif //FEATURE_NATIVE_IMAGE_GENERATION
507
508BOOL PersistentInlineTrackingMapR2R::TryLoad(Module* pModule, const BYTE* pBuffer, DWORD cbBuffer,
509 AllocMemTracker *pamTracker, PersistentInlineTrackingMapR2R** ppLoadedMap)
510{
511 InliningHeader* pHeader = (InliningHeader*)pBuffer;
512 if (pHeader->SizeOfInlineeIndex > (int)(cbBuffer - sizeof(InliningHeader)))
513 {
514 //invalid serialized data, the index can't be larger the entire block
515 _ASSERTE(!"R2R image is invalid or there is a bug in the R2R parser");
516 return FALSE;
517 }
518
519 //NOTE: Error checking on the format is very limited at this point.
520 //We trust the image format is valid and this initial check is a cheap
521 //verification that may help catch simple bugs. It does not secure against
522 //a deliberately maliciously formed binary.
523
524 LoaderHeap *pHeap = pModule->GetLoaderAllocator()->GetHighFrequencyHeap();
525 void * pMemory = pamTracker->Track(pHeap->AllocMem((S_SIZE_T)sizeof(PersistentInlineTrackingMapR2R)));
526 PersistentInlineTrackingMapR2R* pMap = new (pMemory) PersistentInlineTrackingMapR2R();
527
528 pMap->m_module = pModule;
529 pMap->m_inlineeIndex = (PTR_ZapInlineeRecord)(pHeader + 1);
530 pMap->m_inlineeIndexSize = pHeader->SizeOfInlineeIndex / sizeof(ZapInlineeRecord);
531 pMap->m_inlinersBuffer = ((PTR_BYTE)(pHeader+1)) + pHeader->SizeOfInlineeIndex;
532 pMap->m_inlinersBufferSize = cbBuffer - sizeof(InliningHeader) - pMap->m_inlineeIndexSize;
533 *ppLoadedMap = pMap;
534 return TRUE;
535}
536
537#endif //!DACCESS_COMPILE
538
539COUNT_T PersistentInlineTrackingMapR2R::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData)
540{
541 CONTRACTL
542 {
543 THROWS;
544 GC_NOTRIGGER;
545 MODE_ANY;
546 }
547 CONTRACTL_END;
548
549 _ASSERTE(inlineeOwnerMod);
550 _ASSERTE(inliners);
551
552 if (incompleteData)
553 {
554 *incompleteData = FALSE;
555 }
556 if (m_inlineeIndex == NULL || m_inlinersBuffer == NULL)
557 {
558 //No inlines saved in this image.
559 return 0;
560 }
561 if(inlineeOwnerMod != m_module)
562 {
563 // no cross module inlining (yet?)
564 return 0;
565 }
566
567 // Binary search to find all records matching (inlineeTkn)
568 ZapInlineeRecord probeRecord;
569 probeRecord.InitForR2R(RidFromToken(inlineeTkn));
570 ZapInlineeRecord *begin = m_inlineeIndex;
571 ZapInlineeRecord *end = m_inlineeIndex + m_inlineeIndexSize;
572 ZapInlineeRecord *foundRecord = util::lower_bound(begin, end, probeRecord);
573 DWORD result = 0;
574 DWORD outputIndex = 0;
575
576 // Go through all matching records
577 for (; foundRecord < end && *foundRecord == probeRecord; foundRecord++)
578 {
579 DWORD offset = foundRecord->m_offset;
580 NibbleReader stream(m_inlinersBuffer + offset, m_inlinersBufferSize - offset);
581 Module *inlinerModule = m_module;
582
583 DWORD inlinersCount = stream.ReadEncodedU32();
584 _ASSERTE(inlinersCount > 0);
585
586 RID inlinerRid = 0;
587 // Reading inliner RIDs one by one, each RID is represented as an adjustment (diff) to the previous one.
588 // Adding inliners module and coping to the output buffer
589 for (DWORD i = 0; i < inlinersCount && outputIndex < inlinersSize; i++)
590 {
591 inlinerRid += stream.ReadEncodedU32();
592 mdMethodDef inlinerTkn = TokenFromRid(inlinerRid, mdtMethodDef);
593 inliners[outputIndex++] = MethodInModule(inlinerModule, inlinerTkn);
594 }
595 result += inlinersCount;
596 }
597
598 return result;
599}
600
601#endif //FEATURE_READYTORUN