Skip to content

Commit 2c4b4c0

Browse files
Make textual stack trace metadata smaller (#89377)
When we need to generate a textual stack trace in NativeAOT, we rely on two things - reflection metadata for methods, or a supplementary stack trace metadata format. The reflection metadata case is simple - reflection keeps track of method addresses and the associated metadata like names and signatures. We ask for metadata handles (tokens, basically) and we get the handles back. We then walk the metadata and format textual strings. When reflection metadata is not available, we have to emit some other metadata in the compiler. We use the same metadata format and conceptually the closest thing were unresolvable `MemberReference`s, so we basically generated a pair of method pointer + MemberReference handle for each method body that doesn't have reflection metadata. It's a lot less metadata than for a resolvable definition, but is still quite a bit wasteful. This change avoids generating full `MemberReference` and instead inlines owning type/name/signature handle/token into the mapping table. We also do it in a way that avoids emitting owning type/name/signature if it's the same as the record before that.
1 parent 119667b commit 2c4b4c0

File tree

10 files changed

+286
-186
lines changed

10 files changed

+286
-186
lines changed

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs

+41-124
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,38 @@ private MethodNameFormatter(MetadataReader metadataReader, SigTypeContext typeCo
3535
_typeContext = typeContext;
3636
}
3737

38-
public static string FormatMethodName(MetadataReader metadataReader, Handle methodHandle)
38+
public static string FormatMethodName(MetadataReader metadataReader, Handle owningType, ConstantStringValueHandle name, MethodSignatureHandle signature, ConstantStringArrayHandle genericArguments)
3939
{
40-
MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, methodHandle));
41-
formatter.EmitMethodName(methodHandle);
40+
MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, owningType, genericArguments));
41+
formatter.EmitTypeName(owningType, namespaceQualified: true);
42+
formatter._outputBuilder.Append('.');
43+
formatter.EmitString(name);
44+
45+
if (!genericArguments.IsNull(metadataReader))
46+
{
47+
var args = metadataReader.GetConstantStringArray(genericArguments);
48+
bool first = true;
49+
foreach (Handle handle in args.Value)
50+
{
51+
if (first)
52+
{
53+
first = false;
54+
formatter._outputBuilder.Append('[');
55+
}
56+
else
57+
{
58+
formatter._outputBuilder.Append(',');
59+
}
60+
formatter.EmitString(handle.ToConstantStringValueHandle(metadataReader));
61+
}
62+
if (!first)
63+
{
64+
formatter._outputBuilder.Append(']');
65+
}
66+
}
67+
68+
formatter.EmitMethodParameters(metadataReader.GetMethodSignature(signature));
69+
4270
return formatter._outputBuilder.ToString();
4371
}
4472

@@ -75,99 +103,6 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit
75103
return formatter._outputBuilder.ToString();
76104
}
77105

78-
/// <summary>
79-
/// Emit a given method signature to a specified string builder.
80-
/// </summary>
81-
/// <param name="methodHandle">Method reference or instantiation token</param>
82-
private void EmitMethodName(Handle methodHandle)
83-
{
84-
switch (methodHandle.HandleType)
85-
{
86-
case HandleType.MemberReference:
87-
EmitMethodReferenceName(methodHandle.ToMemberReferenceHandle(_metadataReader));
88-
break;
89-
90-
case HandleType.MethodInstantiation:
91-
EmitMethodInstantiationName(methodHandle.ToMethodInstantiationHandle(_metadataReader));
92-
break;
93-
94-
case HandleType.QualifiedMethod:
95-
EmitMethodDefinitionName(methodHandle.ToQualifiedMethodHandle(_metadataReader));
96-
break;
97-
98-
default:
99-
Debug.Assert(false);
100-
_outputBuilder.Append("???");
101-
break;
102-
}
103-
}
104-
105-
/// <summary>
106-
/// Emit method reference to the output string builder.
107-
/// </summary>
108-
/// <param name="memberRefHandle">Member reference handle</param>
109-
private void EmitMethodReferenceName(MemberReferenceHandle memberRefHandle)
110-
{
111-
MemberReference methodRef = _metadataReader.GetMemberReference(memberRefHandle);
112-
MethodSignature methodSignature;
113-
EmitContainingTypeAndMethodName(methodRef, out methodSignature);
114-
EmitMethodParameters(methodSignature);
115-
}
116-
117-
/// <summary>
118-
/// Emit generic method instantiation to the output string builder.
119-
/// </summary>
120-
/// <param name="methodInstHandle">Method instantiation handle</param>
121-
private void EmitMethodInstantiationName(MethodInstantiationHandle methodInstHandle)
122-
{
123-
MethodInstantiation methodInst = _metadataReader.GetMethodInstantiation(methodInstHandle);
124-
125-
if (methodInst.Method.HandleType == HandleType.MemberReference)
126-
{
127-
MemberReferenceHandle methodRefHandle = methodInst.Method.ToMemberReferenceHandle(_metadataReader);
128-
MemberReference methodRef = methodRefHandle.GetMemberReference(_metadataReader);
129-
EmitContainingTypeAndMethodName(methodRef, out MethodSignature methodSignature);
130-
EmitGenericArguments(methodInst.GenericTypeArguments);
131-
EmitMethodParameters(methodSignature);
132-
}
133-
else
134-
{
135-
QualifiedMethodHandle qualifiedMethodHandle = methodInst.Method.ToQualifiedMethodHandle(_metadataReader);
136-
QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle);
137-
EmitContainingTypeAndMethodName(qualifiedMethod);
138-
EmitGenericArguments(methodInst.GenericTypeArguments);
139-
EmitMethodParameters(qualifiedMethod.Method);
140-
}
141-
}
142-
143-
private void EmitMethodDefinitionName(QualifiedMethodHandle qualifiedMethodHandle)
144-
{
145-
QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle);
146-
EmitContainingTypeAndMethodName(qualifiedMethod);
147-
EmitMethodParameters(qualifiedMethod.Method);
148-
}
149-
150-
/// <summary>
151-
/// Emit containing type and method name and extract the method signature from a method reference.
152-
/// </summary>
153-
/// <param name="methodRef">Method reference to format</param>
154-
/// <param name="methodSignature">Output method signature</param>
155-
private void EmitContainingTypeAndMethodName(MemberReference methodRef, out MethodSignature methodSignature)
156-
{
157-
methodSignature = _metadataReader.GetMethodSignature(methodRef.Signature.ToMethodSignatureHandle(_metadataReader));
158-
EmitTypeName(methodRef.Parent, namespaceQualified: true);
159-
_outputBuilder.Append('.');
160-
EmitString(methodRef.Name);
161-
}
162-
163-
private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod)
164-
{
165-
Method method = _metadataReader.GetMethod(qualifiedMethod.Method);
166-
EmitTypeName(qualifiedMethod.EnclosingType, namespaceQualified: true);
167-
_outputBuilder.Append('.');
168-
EmitString(method.Name);
169-
}
170-
171106
/// <summary>
172107
/// Emit parenthesized method argument type list.
173108
/// </summary>
@@ -308,6 +243,11 @@ private void EmitTypeName(Handle typeHandle, bool namespaceQualified)
308243
EmitFunctionPointerTypeName();
309244
break;
310245

246+
// This is not an actual type, but we don't always bother representing generic arguments on generic methods as types
247+
case HandleType.ConstantStringValue:
248+
EmitString(typeHandle.ToConstantStringValueHandle(_metadataReader));
249+
break;
250+
311251
default:
312252
Debug.Assert(false, $"Type handle {typeHandle.HandleType} was not handled");
313253
_outputBuilder.Append("???");
@@ -588,35 +528,12 @@ private static object GetTypeContext(MetadataReader metadataReader, Handle handl
588528
}
589529
}
590530

591-
public static SigTypeContext FromMethod(MetadataReader metadataReader, Handle methodHandle)
531+
public static SigTypeContext FromMethod(MetadataReader metadataReader, Handle enclosingTypeHandle, ConstantStringArrayHandle methodInst)
592532
{
593-
object typeContext;
594-
object methodContext;
595-
596-
switch (methodHandle.HandleType)
597-
{
598-
case HandleType.MemberReference:
599-
typeContext = GetTypeContext(metadataReader, methodHandle);
600-
methodContext = default(HandleCollection);
601-
break;
602-
603-
case HandleType.MethodInstantiation:
604-
MethodInstantiation methodInst = methodHandle.ToMethodInstantiationHandle(metadataReader).GetMethodInstantiation(metadataReader);
605-
typeContext = GetTypeContext(metadataReader, methodInst.Method);
606-
methodContext = methodInst.GenericTypeArguments;
607-
break;
608-
609-
case HandleType.QualifiedMethod:
610-
QualifiedMethod qualifiedMethod = methodHandle.ToQualifiedMethodHandle(metadataReader).GetQualifiedMethod(metadataReader);
611-
typeContext = GetTypeContext(metadataReader, qualifiedMethod.EnclosingType);
612-
methodContext = qualifiedMethod.Method.GetMethod(metadataReader).GenericParameters;
613-
break;
614-
default:
615-
Debug.Assert(false);
616-
return default(SigTypeContext);
617-
}
618-
619-
return new SigTypeContext(typeContext, methodContext);
533+
object methodContext = null;
534+
if (!methodInst.IsNull(metadataReader))
535+
methodContext = methodInst.GetConstantStringArray(metadataReader).Value;
536+
return new SigTypeContext(GetTypeContext(metadataReader, enclosingTypeHandle), methodContext);
620537
}
621538

622539
public static SigTypeContext FromMethod(MetadataReader metadataReader, TypeDefinitionHandle enclosingTypeHandle, MethodHandle methodHandle)

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs

+80-18
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
using System.Collections.Generic;
66

77
using Internal.Metadata.NativeFormat;
8+
using Internal.NativeFormat;
89
using Internal.Runtime;
910
using Internal.Runtime.Augments;
1011
using Internal.Runtime.TypeLoader;
1112
using Internal.TypeSystem;
1213

1314
using ReflectionExecution = Internal.Reflection.Execution.ReflectionExecution;
15+
using Debug = System.Diagnostics.Debug;
1416

1517
namespace Internal.StackTraceMetadata
1618
{
@@ -144,7 +146,7 @@ private sealed class PerModuleMethodNameResolver
144146
/// <summary>
145147
/// Dictionary mapping method RVA's to tokens within the metadata blob.
146148
/// </summary>
147-
private readonly Dictionary<int, int> _methodRvaToTokenMap;
149+
private readonly StackTraceData[] _stacktraceDatas;
148150

149151
/// <summary>
150152
/// Metadata reader for the stack trace metadata.
@@ -195,10 +197,10 @@ public unsafe PerModuleMethodNameResolver(IntPtr moduleAddress)
195197
{
196198
_metadataReader = new MetadataReader(new IntPtr(metadataBlob), (int)metadataBlobSize);
197199

198-
// RVA to token map consists of pairs of integers (method RVA - token)
199-
int rvaToTokenMapEntryCount = (int)(rvaToTokenMapBlobSize / (2 * sizeof(int)));
200-
_methodRvaToTokenMap = new Dictionary<int, int>(rvaToTokenMapEntryCount);
201-
PopulateRvaToTokenMap(handle, (int *)rvaToTokenMapBlob, rvaToTokenMapEntryCount);
200+
int entryCount = *(int*)rvaToTokenMapBlob;
201+
_stacktraceDatas = new StackTraceData[entryCount];
202+
203+
PopulateRvaToTokenMap(handle, rvaToTokenMapBlob + sizeof(int), rvaToTokenMapBlobSize - sizeof(int));
202204
}
203205
}
204206

@@ -207,39 +209,99 @@ public unsafe PerModuleMethodNameResolver(IntPtr moduleAddress)
207209
/// within a single binary module.
208210
/// </summary>
209211
/// <param name="handle">Module to use to construct the mapping</param>
210-
/// <param name="rvaToTokenMap">List of RVA - token pairs</param>
211-
/// <param name="entryCount">Number of the RVA - token pairs in the list</param>
212-
private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, int *rvaToTokenMap, int entryCount)
212+
/// <param name="pMap">List of RVA - token pairs</param>
213+
/// <param name="length">Length of the blob</param>
214+
private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, byte* pMap, uint length)
213215
{
214-
for (int entryIndex = 0; entryIndex < entryCount; entryIndex++)
216+
Handle currentOwningType = default;
217+
MethodSignatureHandle currentSignature = default;
218+
ConstantStringValueHandle currentName = default;
219+
ConstantStringArrayHandle currentMethodInst = default;
220+
221+
int current = 0;
222+
byte* pCurrent = pMap;
223+
while (pCurrent < pMap + length)
215224
{
216-
int* pRelPtr32 = &rvaToTokenMap[2 * entryIndex + 0];
217-
byte* pointer = (byte*)pRelPtr32 + *pRelPtr32;
218-
int methodRva = (int)(pointer - (byte*)handle.OsModuleBase);
219-
int token = rvaToTokenMap[2 * entryIndex + 1];
220-
_methodRvaToTokenMap[methodRva] = token;
225+
byte command = *pCurrent++;
226+
227+
if ((command & StackTraceDataCommand.UpdateOwningType) != 0)
228+
{
229+
currentOwningType = Handle.FromIntToken((int)NativePrimitiveDecoder.ReadUInt32(ref pCurrent));
230+
Debug.Assert(currentOwningType.HandleType is HandleType.TypeDefinition or HandleType.TypeReference or HandleType.TypeSpecification);
231+
}
232+
233+
if ((command & StackTraceDataCommand.UpdateName) != 0)
234+
{
235+
currentName = new Handle(HandleType.ConstantStringValue, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringValueHandle(_metadataReader);
236+
}
237+
238+
if ((command & StackTraceDataCommand.UpdateSignature) != 0)
239+
{
240+
currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader);
241+
currentMethodInst = default;
242+
}
243+
244+
if ((command & StackTraceDataCommand.UpdateGenericSignature) != 0)
245+
{
246+
currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader);
247+
currentMethodInst = new Handle(HandleType.ConstantStringArray, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringArrayHandle(_metadataReader);
248+
}
249+
250+
void* pMethod = ReadRelPtr32(pCurrent);
251+
pCurrent += sizeof(int);
252+
253+
Debug.Assert((nint)pMethod > handle.OsModuleBase);
254+
int methodRva = (int)((nint)pMethod - handle.OsModuleBase);
255+
256+
_stacktraceDatas[current++] = new StackTraceData
257+
{
258+
Rva = methodRva,
259+
OwningType = currentOwningType,
260+
Name = currentName,
261+
Signature = currentSignature,
262+
GenericArguments = currentMethodInst,
263+
};
264+
265+
static void* ReadRelPtr32(byte* address)
266+
=> address + *(int*)address;
221267
}
268+
269+
Debug.Assert(current == _stacktraceDatas.Length);
270+
271+
Array.Sort(_stacktraceDatas);
222272
}
223273

224274
/// <summary>
225275
/// Try to resolve method name based on its address using the stack trace metadata
226276
/// </summary>
227277
public string GetMethodNameFromRvaIfAvailable(int rva)
228278
{
229-
if (_methodRvaToTokenMap == null)
279+
if (_stacktraceDatas == null)
230280
{
231281
// No stack trace metadata for this module
232282
return null;
233283
}
234284

235-
int rawToken;
236-
if (!_methodRvaToTokenMap.TryGetValue(rva, out rawToken))
285+
int index = Array.BinarySearch(_stacktraceDatas, new StackTraceData() { Rva = rva });
286+
if (index < 0)
237287
{
238288
// Method RVA not found in the map
239289
return null;
240290
}
241291

242-
return MethodNameFormatter.FormatMethodName(_metadataReader, Handle.FromIntToken(rawToken));
292+
StackTraceData data = _stacktraceDatas[index];
293+
return MethodNameFormatter.FormatMethodName(_metadataReader, data.OwningType, data.Name, data.Signature, data.GenericArguments);
294+
}
295+
296+
private struct StackTraceData : IComparable<StackTraceData>
297+
{
298+
public int Rva { get; init; }
299+
public Handle OwningType { get; init; }
300+
public ConstantStringValueHandle Name { get; init; }
301+
public MethodSignatureHandle Signature { get; init; }
302+
public ConstantStringArrayHandle GenericArguments { get; init; }
303+
304+
public int CompareTo(StackTraceData other) => Rva.CompareTo(other.Rva);
243305
}
244306
}
245307
}

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj

+6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
<Compile Include="Internal\Runtime\CompilerHelpers\LibraryInitializer.cs" />
1919
<Compile Include="Internal\StackTraceMetadata\StackTraceMetadata.cs" />
2020
<Compile Include="Internal\StackTraceMetadata\MethodNameFormatter.cs" />
21+
<Compile Include="$(CompilerCommonPath)\Internal\NativeFormat\NativeFormatReader.Primitives.cs">
22+
<Link>Internal\NativeFormat\NativeFormatReader.Primitives.cs</Link>
23+
</Compile>
2124
<Compile Include="$(CompilerCommonPath)\Internal\Runtime\MetadataBlob.cs">
2225
<Link>Internal\Runtime\MetadataBlob.cs</Link>
2326
</Compile>
27+
<Compile Include="$(CompilerCommonPath)\Internal\Runtime\StackTraceData.cs">
28+
<Link>Internal\Runtime\StackTraceData.cs</Link>
29+
</Compile>
2430
</ItemGroup>
2531
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
4+
namespace Internal.Runtime
5+
{
6+
internal static class StackTraceDataCommand
7+
{
8+
public const byte UpdateOwningType = 0x01;
9+
public const byte UpdateName = 0x02;
10+
public const byte UpdateSignature = 0x04;
11+
public const byte UpdateGenericSignature = 0x08; // Just a shortcut - sig metadata has the info
12+
}
13+
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AnalysisBasedMetadataManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ protected override void ComputeMetadata(NodeFactory factory,
136136
out List<MetadataMapping<MetadataType>> typeMappings,
137137
out List<MetadataMapping<MethodDesc>> methodMappings,
138138
out List<MetadataMapping<FieldDesc>> fieldMappings,
139-
out List<MetadataMapping<MethodDesc>> stackTraceMapping)
139+
out List<StackTraceMapping> stackTraceMapping)
140140
{
141141
ComputeMetadata(new Policy(_blockingPolicy, this), factory,
142142
out metadataBlob,

0 commit comments

Comments
 (0)