Skip to content

Commit f3548ef

Browse files
Walk compressed IL -> Native map for EventTrace and stacktrace symbolication scenarios (#116031)
Add paths for reading the IL-Native map which doesn't create a `DebuggerMethodInfo` or `DebuggerJitInfo` Use it in the stackwalker code for EH, and for the event tracing `MethodToILMap` This process is somewhat slower than the path through the diagnostic infrastructure, but it has no expensive locks required. To mitigate the performance loss, the format of the compressed bounds information was changed to an encoding where all entries in the table are fixed size, but the actual bit widths of the various fields are optimally small. Overall this results in a size savings of about 50KB on System.Private.CoreLib.dll, as well as capturing back all of the performance costs of this change for relatively small functions. In combination with the NativeToIL map cache added a few days ago, this allows us to eliminate locks from the native offset to il offset path, which should substantially reduce the cost when heavily multithreaded applications are experiencing significant error rates. (And the cache added mitigates the remaining performance loss from using the compressed data instead of the uncompressed form.) In addition, by changing the event trace path to use the same approach, we will remove the `DebuggerJitInfo` generation path from most customers non-debugging scenarios. (It still remains in the `ICorProfiler::GetILToNativeMapping` apis, but usage of that api is relatively rare). There is validation which utilizes the debugger apis to validate that the behavior hasn't changed at all.
1 parent e873943 commit f3548ef

File tree

26 files changed

+1919
-427
lines changed

26 files changed

+1919
-427
lines changed

src/coreclr/debug/daccess/daccess.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5990,6 +5990,7 @@ ClrDataAccess::GetMethodVarInfo(MethodDesc* methodDesc,
59905990
BOOL success = DebugInfoManager::GetBoundariesAndVars(
59915991
request,
59925992
DebugInfoStoreNew, NULL, // allocator
5993+
BoundsType::Instrumented,
59935994
NULL, NULL,
59945995
&countNativeVarInfo, &nativeVars);
59955996

@@ -6052,6 +6053,7 @@ ClrDataAccess::GetMethodNativeMap(MethodDesc* methodDesc,
60526053
BOOL success = DebugInfoManager::GetBoundariesAndVars(
60536054
request,
60546055
DebugInfoStoreNew, NULL, // allocator
6056+
BoundsType::Instrumented,
60556057
&countMapCopy, &mapCopy,
60566058
NULL, NULL);
60576059

src/coreclr/debug/daccess/dacdbiimpl.cpp

Lines changed: 4 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ void DacDbiInterfaceImpl::GetNativeVarData(MethodDesc * pMethodDesc,
868868

869869
BOOL success = DebugInfoManager::GetBoundariesAndVars(request,
870870
InfoStoreNew, NULL, // allocator
871+
BoundsType::Instrumented,
871872
NULL, NULL,
872873
&entryCount, &nativeVars);
873874

@@ -879,76 +880,6 @@ void DacDbiInterfaceImpl::GetNativeVarData(MethodDesc * pMethodDesc,
879880
} // GetNativeVarData
880881

881882

882-
//-----------------------------------------------------------------------------
883-
// Given a instrumented IL map from the profiler that maps:
884-
// Original offset IL_A -> Instrumentend offset IL_B
885-
// And a native mapping from the JIT that maps:
886-
// Instrumented offset IL_B -> native offset Native_C
887-
// This function merges the two maps and stores the result back into the nativeMap.
888-
// The nativeMap now maps:
889-
// Original offset IL_A -> native offset Native_C
890-
// pEntryCount is the number of valid entries in nativeMap, and it may be adjusted downwards
891-
// as part of the composition.
892-
//-----------------------------------------------------------------------------
893-
void DacDbiInterfaceImpl::ComposeMapping(const InstrumentedILOffsetMapping * pProfilerILMap, ICorDebugInfo::OffsetMapping nativeMap[], ULONG32* pEntryCount)
894-
{
895-
// Translate the IL offset if the profiler has provided us with a mapping.
896-
// The ICD public API should always expose the original IL offsets, but GetBoundaries()
897-
// directly accesses the debug info, which stores the instrumented IL offsets.
898-
899-
ULONG32 entryCount = *pEntryCount;
900-
// The map pointer could be NULL or there could be no entries in the map, in either case no work to do
901-
if (pProfilerILMap && !pProfilerILMap->IsNull())
902-
{
903-
// If we did instrument, then we can't have any sequence points that
904-
// are "in-between" the old-->new map that the profiler gave us.
905-
// Ex, if map is:
906-
// (6 old -> 36 new)
907-
// (8 old -> 50 new)
908-
// And the jit gives us an entry for 44 new, that will map back to 6 old.
909-
// Since the map can only have one entry for 6 old, we remove 44 new.
910-
911-
// First Pass: invalidate all the duplicate entries by setting their IL offset to MAX_ILNUM
912-
ULONG32 cDuplicate = 0;
913-
ULONG32 prevILOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM);
914-
for (ULONG32 i = 0; i < entryCount; i++)
915-
{
916-
ULONG32 origILOffset = TranslateInstrumentedILOffsetToOriginal(nativeMap[i].ilOffset, pProfilerILMap);
917-
918-
if (origILOffset == prevILOffset)
919-
{
920-
// mark this sequence point as invalid; refer to the comment above
921-
nativeMap[i].ilOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM);
922-
cDuplicate += 1;
923-
}
924-
else
925-
{
926-
// overwrite the instrumented IL offset with the original IL offset
927-
nativeMap[i].ilOffset = origILOffset;
928-
prevILOffset = origILOffset;
929-
}
930-
}
931-
932-
// Second Pass: move all the valid entries up front
933-
ULONG32 realIndex = 0;
934-
for (ULONG32 curIndex = 0; curIndex < entryCount; curIndex++)
935-
{
936-
if (nativeMap[curIndex].ilOffset != (ULONG32)(ICorDebugInfo::MAX_ILNUM))
937-
{
938-
// This is a valid entry. Move it up front.
939-
nativeMap[realIndex] = nativeMap[curIndex];
940-
realIndex += 1;
941-
}
942-
}
943-
944-
// make sure we have done the bookkeeping correctly
945-
_ASSERTE((realIndex + cDuplicate) == entryCount);
946-
947-
// Final Pass: derecement entryCount
948-
entryCount -= cDuplicate;
949-
*pEntryCount = entryCount;
950-
}
951-
}
952883

953884

954885
//-----------------------------------------------------------------------------
@@ -983,38 +914,14 @@ void DacDbiInterfaceImpl::GetSequencePoints(MethodDesc * pMethodDesc,
983914

984915
ULONG32 entryCount;
985916
BOOL success = DebugInfoManager::GetBoundariesAndVars(request,
986-
InfoStoreNew, NULL, // allocator
917+
InfoStoreNew,
918+
NULL, // allocator
919+
BoundsType::Uninstrumented,
987920
&entryCount, &mapCopy,
988921
NULL, NULL);
989922
if (!success)
990923
ThrowHR(E_FAIL);
991924

992-
#ifdef FEATURE_REJIT
993-
CodeVersionManager * pCodeVersionManager = pMethodDesc->GetCodeVersionManager();
994-
ILCodeVersion ilVersion;
995-
NativeCodeVersion nativeCodeVersion = pCodeVersionManager->GetNativeCodeVersion(dac_cast<PTR_MethodDesc>(pMethodDesc), (PCODE)startAddr);
996-
if (!nativeCodeVersion.IsNull())
997-
{
998-
ilVersion = nativeCodeVersion.GetILCodeVersion();
999-
}
1000-
1001-
// if there is a rejit IL map for this function, apply that in preference to load-time mapping
1002-
if (!ilVersion.IsNull() && !ilVersion.IsDefaultVersion())
1003-
{
1004-
const InstrumentedILOffsetMapping * pRejitMapping = ilVersion.GetInstrumentedILMap();
1005-
ComposeMapping(pRejitMapping, mapCopy, &entryCount);
1006-
}
1007-
else
1008-
{
1009-
#endif
1010-
// if there is a profiler load-time mapping and not a rejit mapping, apply that instead
1011-
InstrumentedILOffsetMapping loadTimeMapping =
1012-
pMethodDesc->GetAssembly()->GetModule()->GetInstrumentedILOffsetMapping(pMethodDesc->GetMemberDef());
1013-
ComposeMapping(&loadTimeMapping, mapCopy, &entryCount);
1014-
#ifdef FEATURE_REJIT
1015-
}
1016-
#endif
1017-
1018925
pSeqPoints->InitSequencePoints(entryCount);
1019926

1020927
// mapCopy and pSeqPoints have elements of different types. Thus, we
@@ -1024,46 +931,6 @@ void DacDbiInterfaceImpl::GetSequencePoints(MethodDesc * pMethodDesc,
1024931

1025932
} // GetSequencePoints
1026933

1027-
// ----------------------------------------------------------------------------
1028-
// DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal
1029-
//
1030-
// Description:
1031-
// Helper function to convert an instrumented IL offset to the corresponding original IL offset.
1032-
//
1033-
// Arguments:
1034-
// * ilOffset - offset to be translated
1035-
// * pMapping - the profiler-provided mapping between original IL offsets and instrumented IL offsets
1036-
//
1037-
// Return Value:
1038-
// Return the translated offset.
1039-
//
1040-
1041-
ULONG DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal(ULONG ilOffset,
1042-
const InstrumentedILOffsetMapping * pMapping)
1043-
{
1044-
SIZE_T cMap = pMapping->GetCount();
1045-
ARRAY_PTR_COR_IL_MAP rgMap = pMapping->GetOffsets();
1046-
1047-
_ASSERTE((cMap == 0) == (rgMap == NULL));
1048-
1049-
// Early out if there is no mapping, or if we are dealing with a special IL offset such as
1050-
// prolog, epilog, etc.
1051-
if ((cMap == 0) || ((int)ilOffset < 0))
1052-
{
1053-
return ilOffset;
1054-
}
1055-
1056-
SIZE_T i = 0;
1057-
for (i = 1; i < cMap; i++)
1058-
{
1059-
if (ilOffset < rgMap[i].newOffset)
1060-
{
1061-
return rgMap[i - 1].oldOffset;
1062-
}
1063-
}
1064-
return rgMap[i - 1].oldOffset;
1065-
}
1066-
1067934
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1068935
// Function Data
1069936
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

src/coreclr/debug/daccess/dacdbiimpl.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,6 @@ class DacDbiInterfaceImpl :
183183
CORDB_ADDRESS startAddr,
184184
SequencePoints * pNativeMap);
185185

186-
// Helper to compose a IL->IL and IL->Native mapping
187-
void ComposeMapping(const InstrumentedILOffsetMapping * pProfilerILMap, ICorDebugInfo::OffsetMapping nativeMap[], ULONG32* pEntryCount);
188-
189-
// Helper function to convert an instrumented IL offset to the corresponding original IL offset.
190-
ULONG TranslateInstrumentedILOffsetToOriginal(ULONG ilOffset,
191-
const InstrumentedILOffsetMapping * pMapping);
192-
193186
public:
194187
//----------------------------------------------------------------------------------
195188
// class MapSortILMap: A template class that will sort an array of DebuggerILToNativeMap.

src/coreclr/debug/ee/debugger.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,7 +2508,10 @@ void Debugger::JITComplete(NativeCodeVersion nativeCodeVersion, TADDR newAddress
25082508
// Can be called on managed thread only
25092509
// This API Implements DebugInterface
25102510

2511+
#ifndef DEBUG // We need to do this in debug builds so that the ValidateILOffsets method can be called
2512+
// TODO: We may wish to remove this in the future.
25112513
if (CORDebuggerAttached())
2514+
#endif
25122515
{
25132516
// Populate the debugger's cache of DJIs. Normally we can do this lazily,
25142517
// the only reason we do it here is b/c the MethodDesc is not yet officially marked as "jitted",
@@ -2876,10 +2879,11 @@ HRESULT Debugger::GetILToNativeMapping(PCODE pNativeCodeStartAddress, ULONG32 cM
28762879
{
28772880
DebugInfoRequest diq;
28782881
diq.InitFromStartingAddr(fd, pNativeCodeStartAddress);
2882+
// TODO This is currently returning the instrumented boundaries, but the path for non-dynamic methods returns uninstrumented ones.
28792883

28802884
if (cMap == 0)
28812885
{
2882-
if (DebugInfoManager::GetBoundariesAndVars(diq, nullptr, nullptr, pcMap, nullptr, nullptr, nullptr))
2886+
if (DebugInfoManager::GetBoundariesAndVars(diq, nullptr, nullptr, BoundsType::Instrumented, pcMap, nullptr, nullptr, nullptr))
28832887
{
28842888
return S_OK;
28852889
}
@@ -2888,7 +2892,7 @@ HRESULT Debugger::GetILToNativeMapping(PCODE pNativeCodeStartAddress, ULONG32 cM
28882892
}
28892893

28902894
ICorDebugInfo::OffsetMapping* pMap = nullptr;
2891-
if (DebugInfoManager::GetBoundariesAndVars(diq, InteropSafeNoThrowNew, nullptr, pcMap, &pMap, nullptr, nullptr))
2895+
if (DebugInfoManager::GetBoundariesAndVars(diq, InteropSafeNoThrowNew, nullptr, BoundsType::Instrumented, pcMap, &pMap, nullptr, nullptr))
28922896
{
28932897
for (ULONG32 i = 0; i < cMap; ++i)
28942898
{
@@ -2992,7 +2996,7 @@ HRESULT Debugger::GetILToNativeMapping(PCODE pNativeCodeStartAddress, ULONG32 cM
29922996
// events for the same MethodDesc (each time it's EnC'd), with each event
29932997
// corresponding to the most recent EnC version at the time.
29942998
//
2995-
2999+
#ifdef DEBUG
29963000
HRESULT Debugger::GetILToNativeMappingIntoArrays(
29973001
MethodDesc * pMethodDesc,
29983002
PCODE pNativeCodeStartAddress,
@@ -3063,8 +3067,7 @@ HRESULT Debugger::GetILToNativeMappingIntoArrays(
30633067

30643068
return S_OK;
30653069
}
3066-
3067-
3070+
#endif // DEBUG
30683071

30693072

30703073
#endif // #ifndef DACCESS_COMPILE

src/coreclr/debug/ee/debugger.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,13 +2105,15 @@ class Debugger : public DebugInterface
21052105
HRESULT GetILToNativeMapping(PCODE pNativeCodeStartAddress, ULONG32 cMap, ULONG32 *pcMap,
21062106
COR_DEBUG_IL_TO_NATIVE_MAP map[]);
21072107

2108+
#ifdef DEBUG
21082109
HRESULT GetILToNativeMappingIntoArrays(
21092110
MethodDesc * pMethodDesc,
21102111
PCODE pNativeCodeStartAddress,
21112112
USHORT cMapMax,
21122113
USHORT * pcMap,
21132114
UINT ** prguiILOffset,
21142115
UINT ** prguiNativeOffset);
2116+
#endif // DEBUG
21152117

21162118
PRD_TYPE GetPatchedOpcode(CORDB_ADDRESS_TYPE *ip);
21172119
BOOL CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode);

src/coreclr/debug/ee/functioninfo.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,7 @@ void DebuggerJitInfo::LazyInitBounds()
920920
BOOL fSuccess = DebugInfoManager::GetBoundariesAndVars(
921921
request,
922922
InteropSafeNew, NULL, // allocator
923+
BoundsType::Instrumented, // TODO We currently don't use the uninstrumented bounds here but we should and remove the instrumented bounds logic from the SetBoundaries function.
923924
&cMap, &pMap,
924925
&cVars, &pVars);
925926

@@ -1045,6 +1046,12 @@ void DebuggerJitInfo::SetBoundaries(ULONG32 cMap, ICorDebugInfo::OffsetMapping *
10451046

10461047
DebuggerILToNativeMap *m = m_sequenceMap;
10471048

1049+
// TODO: Consider removing the handling for the InstrumentedILMap here.
1050+
// since we now have the ability to get an uninstrumented IL offset mapping
1051+
// directly from the VM. This work was not done when adding the instrumented
1052+
// IL mapping due to the work ocurring too close to the shipping deadline for .NET 10.
1053+
// If we do so, we need to change the input to this function to be the uninstrumented IL offset mapping
1054+
10481055
// For the instrumented-IL case, we need to remove all duplicate entries.
10491056
// So we keep a record of the last old IL offset. If the current old IL
10501057
// offset is the same as the last old IL offset, we remove it.

src/coreclr/inc/clrtypes.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,15 @@ inline UINT64 AlignDown(UINT64 value, UINT alignment)
370370
return (value&~(UINT64)(alignment-1));
371371
}
372372

373+
#ifdef __wasm__
374+
inline uintptr_t AlignDown(uintptr_t value, UINT alignment)
375+
{
376+
STATIC_CONTRACT_LEAF;
377+
STATIC_CONTRACT_SUPPORTS_DAC;
378+
return (value&~(uintptr_t)(alignment-1));
379+
}
380+
#endif
381+
373382
#ifdef __APPLE__
374383
inline SIZE_T AlignDown(SIZE_T value, UINT alignment)
375384
{

0 commit comments

Comments
 (0)