diff options
-rw-r--r-- | src/inc/corcompile.h | 6 | ||||
-rw-r--r-- | src/inc/corprof.idl | 9 | ||||
-rw-r--r-- | src/inc/readytorun.h | 10 | ||||
-rw-r--r-- | src/mscorlib/src/System/Runtime/Versioning/NonVersionableAttribute.cs | 6 | ||||
-rw-r--r-- | src/vm/ceeload.cpp | 38 | ||||
-rw-r--r-- | src/vm/ceeload.h | 10 | ||||
-rw-r--r-- | src/vm/compile.cpp | 7 | ||||
-rw-r--r-- | src/vm/compile.h | 2 | ||||
-rw-r--r-- | src/vm/dataimage.cpp | 3 | ||||
-rw-r--r-- | src/vm/inlinetracking.cpp | 390 | ||||
-rw-r--r-- | src/vm/inlinetracking.h | 221 | ||||
-rw-r--r-- | src/vm/jitinterface.cpp | 21 | ||||
-rw-r--r-- | src/vm/prestub.cpp | 1 | ||||
-rw-r--r-- | src/vm/proftoeeinterfaceimpl.cpp | 7 | ||||
-rw-r--r-- | src/vm/readytoruninfo.cpp | 26 | ||||
-rw-r--r-- | src/vm/readytoruninfo.h | 11 | ||||
-rw-r--r-- | src/zap/zapimage.cpp | 1 | ||||
-rw-r--r-- | src/zap/zapimage.h | 1 | ||||
-rw-r--r-- | src/zap/zapinfo.cpp | 9 | ||||
-rw-r--r-- | src/zap/zapreadytorun.cpp | 9 |
20 files changed, 612 insertions, 176 deletions
diff --git a/src/inc/corcompile.h b/src/inc/corcompile.h index e2d3c05d82..f99e27eec0 100644 --- a/src/inc/corcompile.h +++ b/src/inc/corcompile.h @@ -1304,6 +1304,12 @@ class ICorCompilePreloader CORINFO_METHOD_HANDLE method, CORINFO_METHOD_HANDLE duplicateMethod) = 0; + // Returns a compressed encoding of the inline tracking map + // for this compilation + virtual void GetSerializedInlineTrackingMap( + IN OUT SBuffer * pSerializedInlineTrackingMap + ) = 0; + // // Release frees the preloader // diff --git a/src/inc/corprof.idl b/src/inc/corprof.idl index 9af1cd97a8..3378431317 100644 --- a/src/inc/corprof.idl +++ b/src/inc/corprof.idl @@ -3740,15 +3740,20 @@ interface ICorProfilerInfo6 : ICorProfilerInfo5 { /* * Returns an enumerator for all methods that - * - belong to a given NGen module (inlinersModuleId) and + * - belong to a given NGen or R2R module (inlinersModuleId) and * - inlined a body of a given method (inlineeModuleId / inlineeMethodId). * * If incompleteData is set to TRUE after function is called, it means that the methods enumerator * doesn't contain all methods inlining a given method. * It can happen when one or more direct or indirect dependencies of inliners module haven't been loaded yet. - * If profiler needs accurate data it should retry later when more modules are loaded (preferable on each module load). + * If profiler needs accurate data it should retry later when more modules are loaded (preferably on each module load). * * It can be used to lift limitation on inlining for ReJIT. + * + * NOTE: If the inlinee method is decorated with the System.Runtime.Versioning.NonVersionable attribute then + * then some inliners may not ever be reported. If you need to get a full accounting you can avoid the issue + * by disabling the use of all native images. + * */ HRESULT EnumNgenModuleMethodsInliningThisMethod( [in] ModuleID inlinersModuleId, diff --git a/src/inc/readytorun.h b/src/inc/readytorun.h index ebc557b6f1..9084b92814 100644 --- a/src/inc/readytorun.h +++ b/src/inc/readytorun.h @@ -16,7 +16,8 @@ #define READYTORUN_SIGNATURE 0x00525452 // 'RTR' #define READYTORUN_MAJOR_VERSION 0x0002 -#define READYTORUN_MINOR_VERSION 0x0000 +#define READYTORUN_MINOR_VERSION 0x0001 +// R2R Version 2.1 adds the READYTORUN_SECTION_INLINING_INFO section struct READYTORUN_HEADER { @@ -57,6 +58,13 @@ enum ReadyToRunSectionType // 107 used by an older format of READYTORUN_SECTION_AVAILABLE_TYPES READYTORUN_SECTION_AVAILABLE_TYPES = 108, READYTORUN_SECTION_INSTANCE_METHOD_ENTRYPOINTS = 109, + READYTORUN_SECTION_INLINING_INFO = 110 // Added in V2.1 + + // If you add a new section consider whether it is a breaking or non-breaking change. + // Usually it is non-breaking, but if it is preferable to have older runtimes fail + // to load the image vs. ignoring the new section it could be marked breaking. + // Increment the READYTORUN_MINOR_VERSION (non-breaking) or READYTORUN_MAJOR_VERSION + // (breaking) as appropriate. }; // diff --git a/src/mscorlib/src/System/Runtime/Versioning/NonVersionableAttribute.cs b/src/mscorlib/src/System/Runtime/Versioning/NonVersionableAttribute.cs index 0a9845d9c2..9fafce4caa 100644 --- a/src/mscorlib/src/System/Runtime/Versioning/NonVersionableAttribute.cs +++ b/src/mscorlib/src/System/Runtime/Versioning/NonVersionableAttribute.cs @@ -13,6 +13,12 @@ ** is never changed in ReadyToRun native images. Any changes to such members or types would be ** breaking changes for ReadyToRun. ** +** Applying this type also has the side effect that the inlining tables in R2R images will not +** report that inlining of NonVersionable attributed methods occured. These inlining tables are used +** by profilers to figure out the set of methods that need to be rejited when one method is instrumented, +** so in effect NonVersionable methods are also non-instrumentable. Generally this is OK for +** extremely trivial low level methods where NonVersionable gets used, but if there is any plan to +** significantly extend its usage or allow 3rd parties to use it please discuss with the diagnostics team. ===========================================================*/ using System; using System.Diagnostics; diff --git a/src/vm/ceeload.cpp b/src/vm/ceeload.cpp index 4a03927ee4..aa071bcea3 100644 --- a/src/vm/ceeload.cpp +++ b/src/vm/ceeload.cpp @@ -183,10 +183,32 @@ ARRAY_PTR_COR_IL_MAP InstrumentedILOffsetMapping::GetOffsets() const return m_rgMap; } -PTR_PersistentInlineTrackingMap Module::GetNgenInlineTrackingMap() +BOOL Module::HasInlineTrackingMap() { LIMITED_METHOD_DAC_CONTRACT; - return m_persistentInlineTrackingMap; +#ifdef FEATURE_READYTORUN + if (IsReadyToRun() && GetReadyToRunInfo()->GetInlineTrackingMap() != NULL) + { + return TRUE; + } +#endif + return (m_pPersistentInlineTrackingMapNGen != NULL); +} + +COUNT_T Module::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData) +{ + WRAPPER_NO_CONTRACT; +#ifdef FEATURE_READYTORUN + if(IsReadyToRun() && GetReadyToRunInfo()->GetInlineTrackingMap() != NULL) + { + return GetReadyToRunInfo()->GetInlineTrackingMap()->GetInliners(inlineeOwnerMod, inlineeTkn, inlinersSize, inliners, incompleteData); + } +#endif + if(m_pPersistentInlineTrackingMapNGen != NULL) + { + return m_pPersistentInlineTrackingMapNGen->GetInliners(inlineeOwnerMod, inlineeTkn, inlinersSize, inliners, incompleteData); + } + return 0; } @@ -10064,8 +10086,8 @@ void Module::Save(DataImage *image) InlineTrackingMap *inlineTrackingMap = image->GetInlineTrackingMap(); if (inlineTrackingMap) { - m_persistentInlineTrackingMap = new (image->GetHeap()) PersistentInlineTrackingMap(this); - m_persistentInlineTrackingMap->Save(image, inlineTrackingMap); + m_pPersistentInlineTrackingMapNGen = new (image->GetHeap()) PersistentInlineTrackingMapNGen(this); + m_pPersistentInlineTrackingMapNGen->Save(image, inlineTrackingMap); } if (m_pNgenStats && g_CorCompileVerboseLevel >= CORCOMPILE_STATS) @@ -11064,14 +11086,14 @@ void Module::Fixup(DataImage *image) } // Fix up inlining data - if(m_persistentInlineTrackingMap) + if(m_pPersistentInlineTrackingMapNGen) { - image->FixupPointerField(this, offsetof(Module, m_persistentInlineTrackingMap)); - m_persistentInlineTrackingMap->Fixup(image); + image->FixupPointerField(this, offsetof(Module, m_pPersistentInlineTrackingMapNGen)); + m_pPersistentInlineTrackingMapNGen->Fixup(image); } else { - image->ZeroPointerField(this, offsetof(Module, m_persistentInlineTrackingMap)); + image->ZeroPointerField(this, offsetof(Module, m_pPersistentInlineTrackingMapNGen)); } SetIsModuleSaved(); diff --git a/src/vm/ceeload.h b/src/vm/ceeload.h index 42023b3ec1..de92900b2e 100644 --- a/src/vm/ceeload.h +++ b/src/vm/ceeload.h @@ -81,7 +81,8 @@ class TypeHandleList; class ProfileEmitter; class ReJitManager; class TrackingMap; -class PersistentInlineTrackingMap; +struct MethodInModule; +class PersistentInlineTrackingMapNGen; // Hash table parameter of available classes (name -> module/class) hash #define AVAILABLE_CLASSES_HASH_BUCKETS 1024 @@ -104,7 +105,7 @@ class PersistentInlineTrackingMap; #define NATIVE_SYMBOL_READER_DLL W("diasymreader.dll") #endif -typedef DPTR(PersistentInlineTrackingMap) PTR_PersistentInlineTrackingMap; +typedef DPTR(PersistentInlineTrackingMapNGen) PTR_PersistentInlineTrackingMapNGen; extern VerboseLevel g_CorCompileVerboseLevel; #endif // FEATURE_PREJIT @@ -2667,7 +2668,8 @@ public: void NotifyProfilerLoadFinished(HRESULT hr); #endif // PROFILING_SUPPORTED - PTR_PersistentInlineTrackingMap GetNgenInlineTrackingMap(); + BOOL HasInlineTrackingMap(); + COUNT_T GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData); public: void NotifyEtwLoadFinished(HRESULT hr); @@ -3436,7 +3438,7 @@ private: DebuggerSpecificData m_debuggerSpecificData; // This is a compressed read only copy of m_inlineTrackingMap, which is being saved to NGEN image. - PTR_PersistentInlineTrackingMap m_persistentInlineTrackingMap; + PTR_PersistentInlineTrackingMapNGen m_pPersistentInlineTrackingMapNGen; LPCSTR *m_AssemblyRefByNameTable; // array that maps mdAssemblyRef tokens into their simple name diff --git a/src/vm/compile.cpp b/src/vm/compile.cpp index 8aca2ad6df..09925cd219 100644 --- a/src/vm/compile.cpp +++ b/src/vm/compile.cpp @@ -68,6 +68,7 @@ #include "argdestination.h" #include "versionresilienthashcode.h" +#include "inlinetracking.h" #ifdef CROSSGEN_COMPILE CompilationDomain * theDomain; @@ -6841,6 +6842,12 @@ ULONG CEEPreloader::Release() return 0; } +void CEEPreloader::GetSerializedInlineTrackingMap(SBuffer* pBuffer) +{ + InlineTrackingMap * pInlineTrackingMap = m_image->GetInlineTrackingMap(); + PersistentInlineTrackingMapR2R::Save(m_image->GetHeap(), pBuffer, pInlineTrackingMap); +} + void CEEPreloader::Error(mdToken token, Exception * pException) { STANDARD_VM_CONTRACT; diff --git a/src/vm/compile.h b/src/vm/compile.h index 7aeef31b6c..8fdd383dfe 100644 --- a/src/vm/compile.h +++ b/src/vm/compile.h @@ -654,6 +654,8 @@ public: ULONG Release(); + void GetSerializedInlineTrackingMap(SBuffer* pBuffer); + void Error(mdToken token, Exception * pException); }; diff --git a/src/vm/dataimage.cpp b/src/vm/dataimage.cpp index f6b8337b7c..fc584d7b39 100644 --- a/src/vm/dataimage.cpp +++ b/src/vm/dataimage.cpp @@ -126,8 +126,7 @@ DataImage::DataImage(Module *module, CEEPreloader *preloader) m_pZapImage->m_pDataImage = this; m_pInternedStructures = new InternedStructureHashTable(); - - m_inlineTrackingMap = NULL; + m_inlineTrackingMap = new InlineTrackingMap(); } DataImage::~DataImage() diff --git a/src/vm/inlinetracking.cpp b/src/vm/inlinetracking.cpp index 02e2a7cea6..c4bfe0734c 100644 --- a/src/vm/inlinetracking.cpp +++ b/src/vm/inlinetracking.cpp @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // ============================================================================================= -// Code for tracking method inlinings in NGen images. +// Code for tracking method inlinings in NGen and R2R images. // The only information stored is "who" got inlined "where", no offsets or inlining depth tracking. // (No good for debugger yet.) // This information is later exposed to profilers and can be useful for ReJIT. @@ -12,6 +12,8 @@ #include "inlinetracking.h" #include "ceeload.h" +#ifndef DACCESS_COMPILE + bool MethodInModule::operator <(const MethodInModule& other) const { STANDARD_VM_CONTRACT; @@ -123,9 +125,86 @@ InlineTrackingEntry & InlineTrackingEntry::operator = (const InlineTrackingEntry return *this; } +void InlineTrackingEntry::Add(PTR_MethodDesc inliner) +{ + STANDARD_VM_CONTRACT; -#ifndef DACCESS_COMPILE -COUNT_T PersistentInlineTrackingMap::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData) + MethodInModule method(inliner->GetModule(), inliner->GetMemberDef()); + + // Going through last 10 inliners to check if a given inliner has recently been registered. + // It allows to filter out most duplicates without having to scan through hundreds of inliners + // for methods like Object.ctor or Monitor.Enter. + // We are OK to keep occasional duplicates in m_inliners, we'll get rid of them + // in SortAndDeduplicate() anyway. + int count = static_cast<int>(m_inliners.GetCount()); + int start = max(0, count - 10); + for (int i = count - 1; i >= start; i--) + { + if (m_inliners[i] == method) + return; + } + + //look like we see this inliner for the first time, add it to the collection + m_inliners.Append(method); +} + +InlineTrackingMap::InlineTrackingMap() + : m_mapCrst(CrstInlineTrackingMap) +{ + STANDARD_VM_CONTRACT; +} + +void InlineTrackingMap::AddInlining(MethodDesc *inliner, MethodDesc *inlinee) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(inliner != NULL); + _ASSERTE(inlinee != NULL); + + MethodInModule inlineeMnM(inlinee->GetModule(), inlinee->GetMemberDef()); + + if (RidFromToken(inlineeMnM.m_methodDef) == 0 || RidFromToken(inliner->GetMemberDef()) == 0) + { + // Sometimes we do see methods that don't have valid tokens (stubs etc) + // we just ignore them. + return; + } + + CrstHolder lock(&m_mapCrst); + InlineTrackingEntry *existingEntry = const_cast<InlineTrackingEntry *>(LookupPtr(inlineeMnM)); + if (existingEntry) + { + // We saw this inlinee before, just add one more inliner + existingEntry->Add(inliner); + } + else + { + // We haven't seen this inlinee before, create a new record in the hashtable + // and add a first inliner to it. + InlineTrackingEntry newEntry; + newEntry.m_inlinee = inlineeMnM; + newEntry.Add(inliner); + Add(newEntry); + } +} + +#endif //!DACCESS_COMPILE + +void ZapInlineeRecord::InitForNGen(RID rid, LPCUTF8 simpleName) +{ + LIMITED_METHOD_CONTRACT; + //XOR of up to first 24 bytes in module name + DWORD hash = 0; + for (int i = 0; simpleName[i] && i < 24; i++) + hash ^= (BYTE)simpleName[i]; + + // This key contains 24 bits of RID and 8 bits from module name. + // Since RID can't be longer than 24 bits, we can't have method RID collistions, + // that's why PersistentInlineTrackingMap::GetInliners only deals with module collisions. + m_key = (hash << 24) | rid; +} + + +COUNT_T PersistentInlineTrackingMapNGen::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData) { CONTRACTL { @@ -149,10 +228,11 @@ COUNT_T PersistentInlineTrackingMap::GetInliners(PTR_Module inlineeOwnerMod, mdM } // Binary search to find all records matching (inlineeTkn/inlineeOwnerMod) - InlineeRecord probeRecord(RidFromToken(inlineeTkn), inlineeOwnerMod->GetSimpleName()); - InlineeRecord *begin = m_inlineeIndex; - InlineeRecord *end = m_inlineeIndex + m_inlineeIndexSize; - InlineeRecord *foundRecord = util::lower_bound(begin, end, probeRecord); + ZapInlineeRecord probeRecord; + probeRecord.InitForNGen(RidFromToken(inlineeTkn), inlineeOwnerMod->GetSimpleName()); + ZapInlineeRecord *begin = m_inlineeIndex; + ZapInlineeRecord *end = m_inlineeIndex + m_inlineeIndexSize; + ZapInlineeRecord *foundRecord = util::lower_bound(begin, end, probeRecord); DWORD result = 0; DWORD outputIndex = 0; @@ -204,7 +284,9 @@ COUNT_T PersistentInlineTrackingMap::GetInliners(PTR_Module inlineeOwnerMod, mdM return result; } -Module *PersistentInlineTrackingMap::GetModuleByIndex(DWORD index) + + +Module *PersistentInlineTrackingMapNGen::GetModuleByIndex(DWORD index) { CONTRACTL { @@ -222,106 +304,42 @@ Module *PersistentInlineTrackingMap::GetModuleByIndex(DWORD index) return m_module->GetModuleFromIndexIfLoaded(index); } -PersistentInlineTrackingMap::InlineeRecord::InlineeRecord(RID rid, LPCUTF8 simpleName) -{ - LIMITED_METHOD_CONTRACT; - //XOR of up to first 24 bytes in module name - DWORD hash = 0; - for (int i = 0; simpleName[i] && i < 24; i++) - hash ^= (BYTE)simpleName[i]; - - // This key contains 24 bits of RID and 8 bits from module name. - // Since RID can't be longer than 24 bits, we can't have method RID collistions, - // that's why PersistentInlineTrackingMap::GetInliners only deals with module collisions. - m_key = (hash << 24) | rid; -} -InlineTrackingMap::InlineTrackingMap() - : m_mapCrst(CrstInlineTrackingMap) -{ - STANDARD_VM_CONTRACT; -} - -void InlineTrackingMap::AddInlining(MethodDesc *inliner, MethodDesc *inlinee) -{ - STANDARD_VM_CONTRACT; - _ASSERTE(inliner != NULL); - _ASSERTE(inlinee != NULL); - - MethodInModule inlineeMnM(inlinee->GetModule(), inlinee->GetMemberDef()); - - if (RidFromToken(inlineeMnM.m_methodDef) == 0 || RidFromToken(inliner->GetMemberDef()) == 0) - { - // Sometimes we do see methods that don't have valid tokens (stubs etc) - // we just ignore them. - return; - } - - CrstHolder lock(&m_mapCrst); - InlineTrackingEntry *existingEntry = const_cast<InlineTrackingEntry *>(LookupPtr(inlineeMnM)); - if (existingEntry) - { - // We saw this inlinee before, just add one more inliner - existingEntry->Add(inliner); - } - else - { - // We haven't seen this inlinee before, create a new record in the hashtable - // and add a first inliner to it. - InlineTrackingEntry newEntry; - newEntry.m_inlinee = inlineeMnM; - newEntry.Add(inliner); - Add(newEntry); - } -} - -void InlineTrackingEntry::Add(PTR_MethodDesc inliner) -{ - STANDARD_VM_CONTRACT; - - MethodInModule method(inliner->GetModule(), inliner->GetMemberDef()); - - // Going through last 10 inliners to check if a given inliner has recently been registered. - // It allows to filter out most duplicates without having to scan through hundreds of inliners - // for methods like Object.ctor or Monitor.Enter. - // We are OK to keep occasional duplicates in m_inliners, we'll get rid of them - // in SortAndDeduplicate() anyway. - int count = static_cast<int>(m_inliners.GetCount()); - int start = max(0, count - 10); - for (int i = count - 1; i >= start; i--) - { - if (m_inliners[i] == method) - return; - } - - //look like we see this inliner for the first time, add it to the collection - m_inliners.Append(method); -} +#ifndef DACCESS_COMPILE #ifdef FEATURE_NATIVE_IMAGE_GENERATION -void PersistentInlineTrackingMap::ProcessInlineTrackingEntry(DataImage *image, SBuffer *inlinersBuffer, SArray<InlineeRecord> *inlineeIndex, InlineTrackingEntry *entry) +// 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 +void SerializeInlineTrackingEntry(DataImage* image, SBuffer *inlinersBuffer, SArray<ZapInlineeRecord> *inlineeIndex, InlineTrackingEntry *entry) { STANDARD_VM_CONTRACT; // This call removes duplicates from inliners and makes sure they are sorted by module entry->SortAndDeduplicate(); MethodInModule inlinee = entry->m_inlinee; - DWORD inlineeModuleZapIndex = image->GetModuleImportIndex(inlinee.m_module); + DWORD inlineeModuleZapIndex = 0; + if (image != NULL) + { + inlineeModuleZapIndex = image->GetModuleImportIndex(inlinee.m_module); + } InlineSArray<MethodInModule, 3> &inliners = entry->m_inliners; - COUNT_T tatalInlinersCount = inliners.GetCount(); - _ASSERTE(tatalInlinersCount > 0); + COUNT_T totalInlinersCount = inliners.GetCount(); + _ASSERTE(totalInlinersCount > 0); COUNT_T sameModuleCount; // Going through all inliners and grouping them by their module, for each module we'll create - // InlineeRecord and encode inliners as bytes in inlinersBuffer. - for (COUNT_T thisModuleBegin = 0; thisModuleBegin < tatalInlinersCount; thisModuleBegin += sameModuleCount) + // an ZapInlineeRecord and encode inliners as bytes in inlinersBuffer. + for (COUNT_T thisModuleBegin = 0; thisModuleBegin < totalInlinersCount; thisModuleBegin += sameModuleCount) { Module *lastInlinerModule = inliners[thisModuleBegin].m_module; - DWORD lastInlinerModuleZapIndex = image->GetModuleImportIndex(lastInlinerModule); - + DWORD lastInlinerModuleZapIndex = 0; + if (image != NULL) + { + lastInlinerModuleZapIndex = image->GetModuleImportIndex(lastInlinerModule); + } + // Counting how many inliners belong to this module sameModuleCount = 1; - while (thisModuleBegin + sameModuleCount < tatalInlinersCount && + while (thisModuleBegin + sameModuleCount < totalInlinersCount && inliners[thisModuleBegin + sameModuleCount].m_module == lastInlinerModule) { sameModuleCount++; @@ -329,8 +347,11 @@ void PersistentInlineTrackingMap::ProcessInlineTrackingEntry(DataImage *image, S // Saving module indexes and number of inliners NibbleWriter inlinersStream; - inlinersStream.WriteEncodedU32(inlineeModuleZapIndex); - inlinersStream.WriteEncodedU32(lastInlinerModuleZapIndex); + if (image != NULL) + { + inlinersStream.WriteEncodedU32(inlineeModuleZapIndex); + inlinersStream.WriteEncodedU32(lastInlinerModuleZapIndex); + } inlinersStream.WriteEncodedU32(sameModuleCount); // Saving inliners RIDs, each new RID is represented as an adjustment (diff) to the previous one @@ -343,15 +364,22 @@ void PersistentInlineTrackingMap::ProcessInlineTrackingEntry(DataImage *image, S prevMethodRid = methodRid; } inlinersStream.Flush(); - + // Copy output of NibbleWriter into a big buffer (inlinersBuffer) for inliners from the same module // and create an InlineeRecord with correct offset - InlineeRecord record(RidFromToken(inlinee.m_methodDef), inlinee.m_module->GetSimpleName()); DWORD inlinersStreamSize; const BYTE *inlinersStreamPtr = (const BYTE *)inlinersStream.GetBlob(&inlinersStreamSize); + ZapInlineeRecord record; + if (image != NULL) + { + record.InitForNGen(RidFromToken(inlinee.m_methodDef), inlinee.m_module->GetSimpleName()); + } + else + { + record.InitForR2R(RidFromToken(inlinee.m_methodDef)); + } record.m_offset = inlinersBuffer->GetSize(); inlinersBuffer->Insert(inlinersBuffer->End(), SBuffer(SBuffer::Immutable, inlinersStreamPtr, inlinersStreamSize)); - inlineeIndex->Append(record); } } @@ -361,20 +389,16 @@ bool compare_entry(const InlineTrackingEntry* first, const InlineTrackingEntry* return first->m_inlinee < second->m_inlinee; } -void PersistentInlineTrackingMap::Save(DataImage *image, InlineTrackingMap* runtimeMap) +// 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 +void SerializeTrackingMapBuffers(ZapHeap* heap, DataImage *image, SBuffer *inlinersBuffer, SArray<ZapInlineeRecord> *inlineeIndex, InlineTrackingMap* runtimeMap) { STANDARD_VM_CONTRACT; - _ASSERTE(image != NULL); _ASSERTE(runtimeMap != NULL); - SArray<InlineeRecord> inlineeIndex; - SBuffer inlinersBuffer; - // Sort records from runtimeMap, because we need to make sure // we save everything in deterministic order. Hashtable iteration is not deterministic. COUNT_T runtimeMapCount = runtimeMap->GetCount(); - InlineTrackingEntry **inlinees = new InlineTrackingEntry *[runtimeMapCount]; - NewArrayHolder<InlineTrackingEntry *>inlineesHolder(inlinees); + InlineTrackingEntry **inlinees = new (heap) InlineTrackingEntry *[runtimeMapCount]; int index = 0; for (auto iter = runtimeMap->Begin(), end = runtimeMap->End(); iter != end; ++iter) { @@ -387,8 +411,22 @@ void PersistentInlineTrackingMap::Save(DataImage *image, InlineTrackingMap* runt // and write corresponding records into inlineeIndex and inlinersBuffer for (COUNT_T i = 0; i < runtimeMapCount; i++) { - ProcessInlineTrackingEntry(image, &inlinersBuffer, &inlineeIndex, inlinees[i]); + SerializeInlineTrackingEntry(image, inlinersBuffer, inlineeIndex, inlinees[i]); } +} + + + +void PersistentInlineTrackingMapNGen::Save(DataImage *image, InlineTrackingMap* runtimeMap) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(image != NULL); + _ASSERTE(runtimeMap != NULL); + + SArray<ZapInlineeRecord> inlineeIndex; + SBuffer inlinersBuffer; + + SerializeTrackingMapBuffers(image->GetHeap(), image, &inlinersBuffer, &inlineeIndex, runtimeMap); m_inlineeIndexSize = inlineeIndex.GetCount(); m_inlinersBufferSize = inlinersBuffer.GetSize(); @@ -398,7 +436,7 @@ void PersistentInlineTrackingMap::Save(DataImage *image, InlineTrackingMap* runt { // Copy everything to the class fields, we didn't use the class fields for addition // because we want to make sure we don't waste memory for buffer's amortized growth - m_inlineeIndex = new (image->GetHeap()) InlineeRecord[m_inlineeIndexSize]; + m_inlineeIndex = new (image->GetHeap()) ZapInlineeRecord[m_inlineeIndexSize]; inlineeIndex.Copy(m_inlineeIndex, inlineeIndex.Begin(), m_inlineeIndexSize); m_inlinersBuffer = new (image->GetHeap()) BYTE[m_inlinersBufferSize]; @@ -418,12 +456,146 @@ void PersistentInlineTrackingMap::Save(DataImage *image, InlineTrackingMap* runt m_inlineeIndexSize * sizeof(m_inlineeIndex[0]), m_inlinersBufferSize)); } -void PersistentInlineTrackingMap::Fixup(DataImage *image) +void PersistentInlineTrackingMapNGen::Fixup(DataImage *image) +{ + STANDARD_VM_CONTRACT; + image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_module)); + image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_inlineeIndex)); + image->FixupPointerField(this, offsetof(PersistentInlineTrackingMapNGen, m_inlinersBuffer)); +} + +#endif //FEATURE_NATIVE_IMAGE_GENERATION +#endif //!DACCESS_COMPILE + +#ifdef FEATURE_READYTORUN + +struct InliningHeader +{ + int SizeOfInlineeIndex; +}; + +#ifndef DACCESS_COMPILE +#ifdef FEATURE_NATIVE_IMAGE_GENERATION + + + +void PersistentInlineTrackingMapR2R::Save(ZapHeap* pHeap, SBuffer* pSaveTarget, InlineTrackingMap* runtimeMap) { STANDARD_VM_CONTRACT; - image->FixupPointerField(this, offsetof(PersistentInlineTrackingMap, m_module)); - image->FixupPointerField(this, offsetof(PersistentInlineTrackingMap, m_inlineeIndex)); - image->FixupPointerField(this, offsetof(PersistentInlineTrackingMap, m_inlinersBuffer)); + _ASSERTE(pSaveTarget != NULL); + _ASSERTE(runtimeMap != NULL); + + SArray<ZapInlineeRecord> inlineeIndex; + SBuffer inlinersBuffer; + + SerializeTrackingMapBuffers(pHeap, NULL, &inlinersBuffer, &inlineeIndex, runtimeMap); + + InliningHeader header; + header.SizeOfInlineeIndex = inlineeIndex.GetCount() * sizeof(ZapInlineeRecord); + + pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) &header, sizeof(header))); + DWORD unused = 0; + pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) inlineeIndex.GetElements(), header.SizeOfInlineeIndex)); + pSaveTarget->Insert(pSaveTarget->End(), SBuffer(SBuffer::Immutable, (const BYTE*) inlinersBuffer, inlinersBuffer.GetSize())); + + LOG((LF_ZAP, LL_INFO100000, + "PersistentInlineTrackingMap saved. InlineeIndexSize: %d bytes, InlinersBufferSize: %d bytes\n", + header.SizeOfInlineeIndex, inlinersBuffer.GetSize())); } + #endif //FEATURE_NATIVE_IMAGE_GENERATION + +BOOL PersistentInlineTrackingMapR2R::TryLoad(Module* pModule, const BYTE* pBuffer, DWORD cbBuffer, + AllocMemTracker *pamTracker, PersistentInlineTrackingMapR2R** ppLoadedMap) +{ + InliningHeader* pHeader = (InliningHeader*)pBuffer; + if (pHeader->SizeOfInlineeIndex > (int)(cbBuffer - sizeof(InliningHeader))) + { + //invalid serialized data, the index can't be larger the entire block + _ASSERTE(!"R2R image is invalid or there is a bug in the R2R parser"); + return FALSE; + } + + //NOTE: Error checking on the format is very limited at this point. + //We trust the image format is valid and this initial check is a cheap + //verification that may help catch simple bugs. It does not secure against + //a deliberately maliciously formed binary. + + LoaderHeap *pHeap = pModule->GetLoaderAllocator()->GetHighFrequencyHeap(); + void * pMemory = pamTracker->Track(pHeap->AllocMem((S_SIZE_T)sizeof(PersistentInlineTrackingMapR2R))); + PersistentInlineTrackingMapR2R* pMap = new (pMemory) PersistentInlineTrackingMapR2R(); + + pMap->m_module = pModule; + pMap->m_inlineeIndex = (PTR_ZapInlineeRecord)(pHeader + 1); + pMap->m_inlineeIndexSize = pHeader->SizeOfInlineeIndex / sizeof(ZapInlineeRecord); + pMap->m_inlinersBuffer = ((PTR_BYTE)(pHeader+1)) + pHeader->SizeOfInlineeIndex; + pMap->m_inlinersBufferSize = cbBuffer - sizeof(InliningHeader) - pMap->m_inlineeIndexSize; + *ppLoadedMap = pMap; + return TRUE; +} + #endif //!DACCESS_COMPILE + +COUNT_T PersistentInlineTrackingMapR2R::GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + _ASSERTE(inlineeOwnerMod); + _ASSERTE(inliners); + + if (incompleteData) + { + *incompleteData = FALSE; + } + if (m_inlineeIndex == NULL || m_inlinersBuffer == NULL) + { + //No inlines saved in this image. + return 0; + } + if(inlineeOwnerMod != m_module) + { + // no cross module inlining (yet?) + return 0; + } + + // Binary search to find all records matching (inlineeTkn) + ZapInlineeRecord probeRecord; + probeRecord.InitForR2R(RidFromToken(inlineeTkn)); + ZapInlineeRecord *begin = m_inlineeIndex; + ZapInlineeRecord *end = m_inlineeIndex + m_inlineeIndexSize; + ZapInlineeRecord *foundRecord = util::lower_bound(begin, end, probeRecord); + DWORD result = 0; + DWORD outputIndex = 0; + + // Go through all matching records + for (; foundRecord < end && *foundRecord == probeRecord; foundRecord++) + { + DWORD offset = foundRecord->m_offset; + NibbleReader stream(m_inlinersBuffer + offset, m_inlinersBufferSize - offset); + Module *inlinerModule = m_module; + + DWORD inlinersCount = stream.ReadEncodedU32(); + _ASSERTE(inlinersCount > 0); + + RID inlinerRid = 0; + // Reading inliner RIDs one by one, each RID is represented as an adjustment (diff) to the previous one. + // Adding inliners module and coping to the output buffer + for (DWORD i = 0; i < inlinersCount && outputIndex < inlinersSize; i++) + { + inlinerRid += stream.ReadEncodedU32(); + mdMethodDef inlinerTkn = TokenFromRid(inlinerRid, mdtMethodDef); + inliners[outputIndex++] = MethodInModule(inlinerModule, inlinerTkn); + } + result += inlinersCount; + } + + return result; +} + +#endif //FEATURE_READYTORUN
\ No newline at end of file diff --git a/src/vm/inlinetracking.h b/src/vm/inlinetracking.h index cf05027785..2cfb35bd50 100644 --- a/src/vm/inlinetracking.h +++ b/src/vm/inlinetracking.h @@ -2,12 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // ============================================================================================= -// Definitions for tracking method inlinings in NGen images. +// Definitions for tracking method inlinings in NGen and R2R images. // The only information stored is "who" got inlined "where", no offsets or inlining depth tracking. // (No good for debugger yet.) // This information is later exposed to profilers and can be useful for ReJIT. // Runtime inlining is not being tracked because profilers can deduce it via callbacks anyway. +// +// This file is made of two major component groups: +// a) InlineTrackingMap - This is a compilation time datastructure that holds an uncompressed +// version of the inline tracking information. It is appended to as methods are compiled. +// MethodInModule, InlineTrackingEntry, InlineTrackingMapTraits are all support infratsructure +// in this group. +// +// b) PersistentInlineTrackingMap[R2R/NGen] - These are the types that understand the image persistence +// formats. At the end of image compilation one of them consumes all the data from an +// InlineTrackingMap to encode it. At runtime an instance will be constructed to read back +// the encoded data on demand. PersistantInlineTrackingMapR2R and PersistantInlineTrackingMapNGen +// would nominally use a common base type or interface, but due to ngen binary serialization vtables +// were avoided. See farther below for the different format descriptions. // ============================================================================================= + #ifndef INLINETRACKING_H_ #define INLINETRACKING_H_ #include "corhdr.h" @@ -16,6 +30,10 @@ #include "crsttypes.h" #include "daccess.h" + + +// ---------------------------------- Compile time support ---------------------------------------------- + class MethodDesc; typedef DPTR(class MethodDesc) PTR_MethodDesc; @@ -128,6 +146,17 @@ public: typedef DPTR(InlineTrackingMap) PTR_InlineTrackingMap; + + + +// ------------------------------------ Persistance support ---------------------------------------------------------- + + + + + +// NGEN format +// // This is a persistent map that is stored inside each NGen-ed module image and is used to track // inlines in the NGEN-ed code inside this module. // At runtime this map is used by profiler to track methods that inline a given method, @@ -135,25 +164,25 @@ typedef DPTR(InlineTrackingMap) PTR_InlineTrackingMap; // It doesn't require any load time unpacking and serves requests directly from NGEN image. // // It is composed of two arrays: -// m_inlineeIndex - sorted (by InlineeRecord.key i.e. by module then token) array of InlineeRecords, given an inlinee module name hash (8 bits) +// m_inlineeIndex - sorted (by ZapInlineeRecord.key i.e. by module then token) array of ZapInlineeRecords, given an inlinee module name hash (8 bits) // and a method token (24 bits) we use binary search to find if this method has ever been inlined in NGen-ed code of this image. // Each record has m_offset, which is an offset inside m_inlinersBuffer, it has more data on where the method got inlined. // -// It is totally possible to have more than one InlineeRecords with the same key, not only due hash collision, but also due to +// It is totally possible to have more than one ZapInlineeRecords with the same key, not only due hash collision, but also due to // the fact that we create one record for each (inlinee module / inliner module) pair. // For example: we have MyModule!MyType that uses mscorlib!List<T>. Let's say List<T>.ctor got inlined into // MyType.GetAllThinds() and into List<MyType>.FindAll. In this case we'll have two InlineeRecords for mscorlib!List<T>.ctor // one for MyModule and another one for mscorlib. -// PersistentInlineTrackingMap.GetInliners() always reads all InlineeRecords as long as they have the same key, few of them filtered out as hash collisions -// others provide legitimate inlining information for methods from different modules. +// PersistentInlineTrackingMap.GetInliners() always reads all ZapInlineeRecords as long as they have the same key, few of them filtered out +// as hash collisions others provide legitimate inlining information for methods from different modules. // -// m_inlinersBuffer - byte array compressed by NibbleWriter. At any valid offset taken from InlineeRecord from m_inlineeIndex, there is a compressed chunk +// m_inlinersBuffer - byte array compressed by NibbleWriter. At any valid offset taken from ZapInlineeRecord from m_inlineeIndex, there is a compressed chunk // of this format: // [InlineeModuleZapIndex][InlinerModuleZapIndex] [N - # of following inliners] [#1 inliner method RID] ... [#N inliner method RID] // [InlineeModuleZapIndex] is used to verify that we actually found a desired inlinee module (not just a name hash collision). // [InlinerModuleZapIndex] is an index of a module that owns following method tokens (inliners) // [1..N inliner RID] are the sorted diff compressed method RIDs from the module specified by InlinerModuleZapIndex, -// those methods directly or indirectly inlined code from inlinee method specified by InlineeRecord. +// those methods directly or indirectly inlined code from inlinee method specified by ZapInlineeRecord. // Since all the RIDs are sorted we'are actually able to save some space by using diffs instead of values, because NibbleWriter // is good at saving small numbers. // For example for RIDs: 5, 6, 19, 25, 30, we'll write: 5, 1 (=6-5), 13 (=19-6), 6 (=25-19), 5 (=30-25) @@ -170,39 +199,111 @@ typedef DPTR(InlineTrackingMap) PTR_InlineTrackingMap; // | - - - | InlineeModuleZapIndex | InlinerModuleZapIndex | SavedInlinersCount (N) | rid1 | rid2 | ...... | ridN | - - - | // +-----------------+-----------------------+------------------------+------------------------+------+------+--------+------+-------------+ // -class PersistentInlineTrackingMap + + + + + + + + + +// R2R encoding variation for the map +// +// It has several differences from the NGEN encoding. NGEN refers to methods outside the current assembly via module index + foreign module's token +// but R2R can't take those fragile dependencies. Instead we refer to all methods via MethodDef tokens in the current assembly's metadata. This +// is sufficient for everything we need to track now but in the future we may need to upgrade to a more expressive encoding. Currently NonVersionable +// attributed methods may be inlined but will not be tracked. This shows up as a known limitation in the profiler APIs that expose this data. +// +// The format changes from NGEN: +// a) The InlineIndex uses a MethodDef RID token as the key. +// b) InlineeModuleZapIndex is omitted because the module is always the current one being compiled. +// c) InlinerModuleZapIndex is similarly omitted. +// d) (a), (b) and (c) together imply there is at most one entry in the inlineeIndex for any given key +// e) A trivial header is now explicitly described +// +// +// The resulting serialized format is a sequence of blobs: +// 1) Header (4 byte aligned) +// short MajorVersion - currently set to 1, increment on breaking change +// short MinorVersion - currently set to 0, increment on non-breaking format addition +// int SizeOfInlineIndex - size in bytes of the inline index +// +// 2) InlineIndex - Immediately following header. This is a sorted (by ZapInlineeRecord.key) array of ZapInlineeRecords, given a method token (32 bits) +// we use binary search to find if this method has ever been inlined in R2R code of this image. Each record has m_offset, which is +// an offset inside InlinersBuffer, it has more data on where the method got inlined. There is at most one ZapInlineeRecord with the +// same key. +// +// 3) InlinersBuffer - Located immediately following the InlineIndex (Header RVA + sizeof(Header) + header.SizeOfInlineIndex) +// This is a byte array compressed by NibbleWriter. At any valid offset taken from ZapInlineeRecord from InlineeIndex, there is a +// compressed chunk of this format: +// [N - # of following inliners] [#1 inliner method RID] ... [#N inliner method RID] +// [1..N inliner RID] are the sorted diff compressed method RIDs interpreted as MethodDefs in this assembly's metadata, +// Those methods directly or indirectly inlined code from inlinee method specified by ZapInlineeRecord. +// Since all the RIDs are sorted we'are actually able to save some space by using diffs instead of values, because NibbleWriter +// is good at saving small numbers. +// For example for RIDs: 5, 6, 19, 25, 30, we'll write: 5, 1 (=6-5), 13 (=19-6), 6 (=25-19), 5 (=30-25) +// +// InlineeIndex +// +-----+-----+---------------------------------------+-----+-----+ +// | - | - | m_key {MethodDefToken); m_offset | - | - | +// +-----+-----+---------------------------------|-----+-----+-----+ +// | +// +--------------------------+ +// | +// InlinersBuffer \-/ +// +-----------------+------------------------+------+------+--------+------+-------------+ +// | - - - | SavedInlinersCount (N) | rid1 | rid2 | ...... | ridN | - - - | +// +-----------------+------------------------+------+------+--------+------+-------------+ +// + + + +//A common key format for R2R and NGEN. If the formats +//diverge further this might become irrelevant +struct ZapInlineeRecord { -private: - struct InlineeRecord + DWORD m_key; + DWORD m_offset; + + ZapInlineeRecord() + : m_key(0) + { + LIMITED_METHOD_CONTRACT; + } + + void InitForR2R(RID rid) + { + LIMITED_METHOD_CONTRACT; + m_key = rid; + } + + void InitForNGen(RID rid, LPCUTF8 simpleName); + + bool operator <(const ZapInlineeRecord& other) const { - DWORD m_key; - DWORD m_offset; - - InlineeRecord() - : m_key(0) - { - LIMITED_METHOD_CONTRACT; - } - - InlineeRecord(RID rid, LPCUTF8 simpleName); - - bool operator <(const InlineeRecord& other) const - { - LIMITED_METHOD_DAC_CONTRACT; - return m_key < other.m_key; - } - - bool operator ==(const InlineeRecord& other) const - { - LIMITED_METHOD_DAC_CONTRACT; - return m_key == other.m_key; - } - }; - typedef DPTR(InlineeRecord) PTR_InlineeRecord; + LIMITED_METHOD_DAC_CONTRACT; + return m_key < other.m_key; + } + + bool operator ==(const ZapInlineeRecord& other) const + { + LIMITED_METHOD_DAC_CONTRACT; + return m_key == other.m_key; + } +}; +typedef DPTR(ZapInlineeRecord) PTR_ZapInlineeRecord; + + +// This type knows how to serialize and deserialize the inline tracking map format within an NGEN image. See +// above for a description of the format. +class PersistentInlineTrackingMapNGen +{ +private: PTR_Module m_module; - PTR_InlineeRecord m_inlineeIndex; + PTR_ZapInlineeRecord m_inlineeIndex; DWORD m_inlineeIndexSize; PTR_BYTE m_inlinersBuffer; @@ -210,23 +311,63 @@ private: public: - PersistentInlineTrackingMap(Module *module) + PersistentInlineTrackingMapNGen(Module *module) : m_module(dac_cast<PTR_Module>(module)) { LIMITED_METHOD_CONTRACT; _ASSERTE(module != NULL); } + // runtime deserialization + COUNT_T GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData); + + // compile-time serialization +#ifndef DACCESS_COMPILE void Save(DataImage *image, InlineTrackingMap* runtimeMap); void Fixup(DataImage *image); - COUNT_T GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData); - private: - void ProcessInlineTrackingEntry(DataImage *image, SBuffer *inlinersBuffer, SArray<InlineeRecord> *inlineeIndex, InlineTrackingEntry *entry); +#endif + Module *GetModuleByIndex(DWORD index); + }; -typedef DPTR(PersistentInlineTrackingMap) PTR_PersistentInlineTrackingMap; +typedef DPTR(PersistentInlineTrackingMapNGen) PTR_PersistentInlineTrackingMapNGen; + + +// This type knows how to serialize and deserialize the inline tracking map format within an R2R image. See +// above for a description of the format. +#ifdef FEATURE_READYTORUN +class PersistentInlineTrackingMapR2R +{ +private: + PTR_Module m_module; + + PTR_ZapInlineeRecord m_inlineeIndex; + DWORD m_inlineeIndexSize; + + PTR_BYTE m_inlinersBuffer; + DWORD m_inlinersBufferSize; + +public: + + // runtime deserialization +#ifndef DACCESS_COMPILE + static BOOL TryLoad(Module* pModule, const BYTE* pBuffer, DWORD cbBuffer, AllocMemTracker *pamTracker, PersistentInlineTrackingMapR2R** ppLoadedMap); +#endif + COUNT_T GetInliners(PTR_Module inlineeOwnerMod, mdMethodDef inlineeTkn, COUNT_T inlinersSize, MethodInModule inliners[], BOOL *incompleteData); + + + // compile time serialization +#ifndef DACCESS_COMPILE + static void Save(ZapHeap* pHeap, SBuffer *saveTarget, InlineTrackingMap* runtimeMap); +#endif + +}; + +typedef DPTR(PersistentInlineTrackingMapR2R) PTR_PersistentInlineTrackingMapR2R; +#endif //FEATURE_READYTORUN + #endif //INLINETRACKING_H_ diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index 64471249fc..b05b891951 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -725,11 +725,31 @@ BOOL CEEInfo::shouldEnforceCallvirtRestriction( // If the need arises (i.e. performance issues) we will define sets of assemblies (e.g. all app assemblies) // The main point is that all this logic is concentrated in one place. +// NOTICE: If you change this logic to allow multi-assembly version bubbles you +// need to consider the impact on diagnostic tools. Currently there is an inlining +// table which tracks inliner/inlinee relationships in R2R images but it is not +// yet capable of encoding cross-assembly inlines. The scenario where this +// may show are instrumenting profilers that want to instrument a given method A +// using the ReJit APIs. If method A happens to inlined within method B in another +// assembly then the profiler needs to know that so it can rejit B too. +// The recommended approach is to upgrade the inlining table (vm\inlinetracking.h\.cpp) +// now that presumably R2R images have some way to refer to methods in other +// assemblies in their version bubble. Chat with the diagnostics team if you need more +// details. +// +// There already is a case where cross-assembly inlining occurs in an +// unreported fashion for methods marked NonVersionable. There is a specific +// exemption called out for this on ICorProfilerInfo6::EnumNgenModuleMethodsInliningThisMethod +// and the impact of the cut was vetted with partners. It would not be appropriate +// to increase that unreported set without additional review. + + bool IsInSameVersionBubble(Assembly * current, Assembly * target) { LIMITED_METHOD_CONTRACT; // trivial case: current and target are identical + // DO NOT change this without reading the notice above if (current == target) return true; @@ -740,6 +760,7 @@ bool IsInSameVersionBubble(Assembly * current, Assembly * target) static bool IsInSameVersionBubble(MethodDesc* pCurMD, MethodDesc *pTargetMD) { LIMITED_METHOD_CONTRACT; + // DO NOT change this without reading the notice above if (IsInSameVersionBubble(pCurMD->GetModule()->GetAssembly(), pTargetMD->GetModule()->GetAssembly())) { diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index 723e6b68f2..8a2396d88f 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -1619,6 +1619,7 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) else #endif // FEATURE_INTERPRETER { + ReJitPublishMethodHolder publishWorker(this, pCode); SetStableEntryPointInterlocked(pCode); } } diff --git a/src/vm/proftoeeinterfaceimpl.cpp b/src/vm/proftoeeinterfaceimpl.cpp index 18702d7b60..b7a0eb75e6 100644 --- a/src/vm/proftoeeinterfaceimpl.cpp +++ b/src/vm/proftoeeinterfaceimpl.cpp @@ -9397,8 +9397,7 @@ HRESULT ProfToEEInterfaceImpl::EnumNgenModuleMethodsInliningThisMethod( return CORPROF_E_DATAINCOMPLETE; } - PersistentInlineTrackingMap *inliningMap = inlinersModule->GetNgenInlineTrackingMap(); - if (inliningMap == NULL) + if (!inlinersModule->HasInlineTrackingMap()) { return CORPROF_E_DATAINCOMPLETE; } @@ -9411,14 +9410,14 @@ HRESULT ProfToEEInterfaceImpl::EnumNgenModuleMethodsInliningThisMethod( EX_TRY { // Trying to use static buffer - COUNT_T methodsAvailable = inliningMap->GetInliners(inlineeOwnerModule, inlineeMethodId, staticBufferSize, staticBuffer, incompleteData); + COUNT_T methodsAvailable = inlinersModule->GetInliners(inlineeOwnerModule, inlineeMethodId, staticBufferSize, staticBuffer, incompleteData); // If static buffer is not enough, allocate an array. if (methodsAvailable > staticBufferSize) { DWORD dynamicBufferSize = methodsAvailable; dynamicBuffer = methodsBuffer = new MethodInModule[dynamicBufferSize]; - methodsAvailable = inliningMap->GetInliners(inlineeOwnerModule, inlineeMethodId, dynamicBufferSize, dynamicBuffer, incompleteData); + methodsAvailable = inlinersModule->GetInliners(inlineeOwnerModule, inlineeMethodId, dynamicBufferSize, dynamicBuffer, incompleteData); if (methodsAvailable > dynamicBufferSize) { _ASSERTE(!"Ngen image inlining info changed, this shouldn't be possible."); diff --git a/src/vm/readytoruninfo.cpp b/src/vm/readytoruninfo.cpp index b1be026a6c..6a47aa8f4c 100644 --- a/src/vm/readytoruninfo.cpp +++ b/src/vm/readytoruninfo.cpp @@ -533,11 +533,11 @@ PTR_ReadyToRunInfo ReadyToRunInfo::Initialize(Module * pModule, AllocMemTracker DoLog("Ready to Run initialized successfully"); - return new (pMemory) ReadyToRunInfo(pModule, pLayout, pHeader); + return new (pMemory) ReadyToRunInfo(pModule, pLayout, pHeader, pamTracker); } -ReadyToRunInfo::ReadyToRunInfo(Module * pModule, PEImageLayout * pLayout, READYTORUN_HEADER * pHeader) - : m_pModule(pModule), m_pLayout(pLayout), m_pHeader(pHeader), m_Crst(CrstLeafLock) +ReadyToRunInfo::ReadyToRunInfo(Module * pModule, PEImageLayout * pLayout, READYTORUN_HEADER * pHeader, AllocMemTracker *pamTracker) + : m_pModule(pModule), m_pLayout(pLayout), m_pHeader(pHeader), m_Crst(CrstLeafLock), m_pPersistentInlineTrackingMap(NULL) { STANDARD_VM_CONTRACT; @@ -589,6 +589,18 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, PEImageLayout * pLayout, READYT LockOwner lock = {&m_Crst, IsOwnerOfCrst}; m_entryPointToMethodDescMap.Init(TRUE, &lock); } + + //In format version >= 2.1 there is an optional inlining table + if (IsImageVersionAtLeast(2, 1)) + { + IMAGE_DATA_DIRECTORY * pInlineTrackingInfoDir = FindSection(READYTORUN_SECTION_INLINING_INFO); + if (pInlineTrackingInfoDir != NULL) + { + const BYTE* pInlineTrackingMapData = (const BYTE*)GetImage()->GetDirectoryData(pInlineTrackingInfoDir); + PersistentInlineTrackingMapR2R::TryLoad(pModule, pInlineTrackingMapData, pInlineTrackingInfoDir->Size, + pamTracker, &m_pPersistentInlineTrackingMap); + } + } } static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, Module * pModule) @@ -854,4 +866,12 @@ DWORD ReadyToRunInfo::GetFieldBaseOffset(MethodTable * pMT) return (DWORD)sizeof(Object) + dwCumulativeInstanceFieldPos - dwOffsetBias; } +BOOL ReadyToRunInfo::IsImageVersionAtLeast(int majorVersion, int minorVersion) +{ + LIMITED_METHOD_CONTRACT; + return (m_pHeader->MajorVersion == majorVersion && m_pHeader->MinorVersion >= minorVersion) || + (m_pHeader->MajorVersion > majorVersion); + +} + #endif // DACCESS_COMPILE diff --git a/src/vm/readytoruninfo.h b/src/vm/readytoruninfo.h index 28efe01e8d..2266e9c119 100644 --- a/src/vm/readytoruninfo.h +++ b/src/vm/readytoruninfo.h @@ -13,6 +13,7 @@ #define _READYTORUNINFO_H_ #include "nativeformatreader.h" +#include "inlinetracking.h" typedef DPTR(struct READYTORUN_SECTION) PTR_READYTORUN_SECTION; @@ -40,7 +41,9 @@ class ReadyToRunInfo Crst m_Crst; PtrHashMap m_entryPointToMethodDescMap; - ReadyToRunInfo(Module * pModule, PEImageLayout * pLayout, READYTORUN_HEADER * pHeader); + PTR_PersistentInlineTrackingMapR2R m_pPersistentInlineTrackingMap; + + ReadyToRunInfo(Module * pModule, PEImageLayout * pLayout, READYTORUN_HEADER * pHeader, AllocMemTracker *pamTracker); public: static BOOL IsReadyToRunEnabled(); @@ -118,10 +121,16 @@ public: static DWORD GetFieldBaseOffset(MethodTable * pMT); + PTR_PersistentInlineTrackingMapR2R GetInlineTrackingMap() + { + return m_pPersistentInlineTrackingMap; + } + private: BOOL GetTypeNameFromToken(IMDInternalImport * pImport, mdToken mdType, LPCUTF8 * ppszName, LPCUTF8 * ppszNameSpace); BOOL GetEnclosingToken(IMDInternalImport * pImport, mdToken mdType, mdToken * pEnclosingToken); BOOL CompareTypeNameOfTokens(mdToken mdToken1, IMDInternalImport * pImport1, mdToken mdToken2, IMDInternalImport * pImport2); + BOOL IsImageVersionAtLeast(int majorVersion, int minorVersion); }; class DynamicHelpers diff --git a/src/zap/zapimage.cpp b/src/zap/zapimage.cpp index 24f081a6f9..499a35b855 100644 --- a/src/zap/zapimage.cpp +++ b/src/zap/zapimage.cpp @@ -1742,6 +1742,7 @@ void ZapImage::Compile() OutputEntrypointsTableForReadyToRun(); OutputDebugInfoForReadyToRun(); OutputTypesTableForReadyToRun(m_pMDImport); + OutputInliningTableForReadyToRun(); } else #endif diff --git a/src/zap/zapimage.h b/src/zap/zapimage.h index 02985f5d12..65347ffcf1 100644 --- a/src/zap/zapimage.h +++ b/src/zap/zapimage.h @@ -557,6 +557,7 @@ private: void OutputEntrypointsTableForReadyToRun(); void OutputDebugInfoForReadyToRun(); void OutputTypesTableForReadyToRun(IMDInternalImport * pMDImport); + void OutputInliningTableForReadyToRun(); void CopyDebugDirEntry(); void CopyWin32VersionResource(); diff --git a/src/zap/zapinfo.cpp b/src/zap/zapinfo.cpp index 4b2f39a264..2095524609 100644 --- a/src/zap/zapinfo.cpp +++ b/src/zap/zapinfo.cpp @@ -3622,8 +3622,13 @@ void ZapInfo::reportInliningDecision (CORINFO_METHOD_HANDLE inlinerHnd, CorInfoInline inlineResult, const char * reason) { - - + if (!dontInline(inlineResult) && inlineeHnd != NULL) + { + // We deliberately report m_currentMethodHandle (not inlinerHnd) as inliner, because + // if m_currentMethodHandle != inlinerHnd, it simply means that inlinerHnd is intermediate link + // in inlining into m_currentMethodHandle, and we have no interest to track those intermediate links now. + m_pImage->m_pPreloader->ReportInlining(m_currentMethodHandle, inlineeHnd); + } return m_pEEJitInfo->reportInliningDecision(inlinerHnd, inlineeHnd, inlineResult, reason); } diff --git a/src/zap/zapreadytorun.cpp b/src/zap/zapreadytorun.cpp index 30ad296f95..8ed3f55159 100644 --- a/src/zap/zapreadytorun.cpp +++ b/src/zap/zapreadytorun.cpp @@ -380,6 +380,15 @@ void ZapImage::OutputDebugInfoForReadyToRun() GetReadyToRunHeader()->RegisterSection(READYTORUN_SECTION_DEBUG_INFO, pBlob); } +void ZapImage::OutputInliningTableForReadyToRun() +{ + SBuffer serializedInlineTrackingBuffer; + m_pPreloader->GetSerializedInlineTrackingMap(&serializedInlineTrackingBuffer); + ZapNode * pBlob = ZapBlob::NewAlignedBlob(this, (PVOID)(const BYTE*) serializedInlineTrackingBuffer, serializedInlineTrackingBuffer.GetSize(), 4); + m_pDebugSection->Place(pBlob); + GetReadyToRunHeader()->RegisterSection(READYTORUN_SECTION_INLINING_INFO, pBlob); +} + void ZapImage::OutputTypesTableForReadyToRun(IMDInternalImport * pMDImport) { NativeWriter writer; |