Skip to content

Commit

Permalink
Generate fewer ExactMethodInstantiationsHashtable entries (dotnet#8…
Browse files Browse the repository at this point in the history
…0090)

This hashtable contains information about unshared generic methods. We really only need it for generic virtual methods. There was some code in the reflection stack that was reading it to support stack traces and `Delegate.GetMethodInfo` but:

1. For stack traces, we have this information in the stack trace metadata table.
2. For `Delegate.GetMethodInfo` we have a rule that all targets of delegates should become reflectable in the compiler. We should have all this data in the reflection invoke table that we also parse for this purpose.
  • Loading branch information
MichalStrehovsky authored Jan 3, 2023
1 parent ee6de92 commit 2619d1c
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,7 @@ public bool TryGetOffsetsRange(IntPtr functionPointer, out int firstParserOffset
// ldftn reverse lookup hash. Must be cleared and reset if the module list changes. (All sets to
// this variable must happen under a lock)
private volatile KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets>[] _ldftnReverseLookup_InvokeMap;
private volatile KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets>[] _ldftnReverseLookup_ExactInstantiations;
private Func<NativeFormatModuleInfo, FunctionPointersToOffsets> _computeLdFtnLookupInvokeMapInvokeMap = ComputeLdftnReverseLookup_InvokeMap;
private Func<NativeFormatModuleInfo, FunctionPointersToOffsets> _computeLdFtnLookupExactInstantiations = ComputeLdftnReverseLookup_ExactInstantiations;

/// <summary>
/// Initialize a lookup array of module to function pointer/parser offset pair arrays. Do so in a manner that will allow
Expand Down Expand Up @@ -687,13 +685,6 @@ private KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets>[] GetLdF
#pragma warning restore 0420
}

private KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets>[] GetLdFtnReverseLookups_ExactInstantiations()
{
#pragma warning disable 0420 // GetLdFtnReverseLookups_Helper treats its first parameter as volatile by using explicit Volatile operations
return GetLdFtnReverseLookups_Helper(ref _ldftnReverseLookup_ExactInstantiations, _computeLdFtnLookupExactInstantiations);
#pragma warning restore 0420
}

internal unsafe void GetFunctionPointerAndInstantiationArgumentForOriginalLdFtnResult(IntPtr originalLdFtnResult, out IntPtr canonOriginalLdFtnResult, out IntPtr instantiationArgument)
{
if (FunctionPointerOps.IsGenericMethodPointer(originalLdFtnResult))
Expand All @@ -719,26 +710,6 @@ internal bool TryGetMethodForOriginalLdFtnResult(IntPtr originalLdFtnResult, ref
if (TryGetMethodForOriginalLdFtnResult_GenericMethodWithInstantiationArgument(instantiationArgument, ref declaringTypeHandle, out methodHandle, out genericMethodTypeArgumentHandles))
return true;
}
else
{
// Search ExactInstantiationsMap
foreach (KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets> perModuleLookup in GetLdFtnReverseLookups_ExactInstantiations())
{
int startIndex;
int endIndex;

if (perModuleLookup.Value.TryGetOffsetsRange(canonOriginalLdFtnResult, out startIndex, out endIndex))
{
for (int curIndex = startIndex; curIndex <= endIndex; curIndex++)
{
uint parserOffset = perModuleLookup.Value.Data[curIndex].Offset;
if (TryGetMethodForOriginalLdFtnResult_ExactInstantiation_Inner(perModuleLookup.Key, forStartAddress: false, canonOriginalLdFtnResult, parserOffset,
ref declaringTypeHandle, out methodHandle, out genericMethodTypeArgumentHandles))
return true;
}
}
}
}

// Search InvokeMap
foreach (KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets> perModuleLookup in GetLdFtnReverseLookups_InvokeMap())
Expand All @@ -764,27 +735,6 @@ internal bool TryGetMethodForOriginalLdFtnResult(IntPtr originalLdFtnResult, ref

internal bool TryGetMethodForStartAddress(IntPtr methodStartAddress, ref RuntimeTypeHandle declaringTypeHandle, out QMethodDefinition methodHandle)
{
// Search ExactInstantiationsMap
foreach (KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets> perModuleLookup in GetLdFtnReverseLookups_ExactInstantiations())
{
int startIndex;
int endIndex;

if (perModuleLookup.Value.TryGetOffsetsRange(methodStartAddress, out startIndex, out endIndex))
{
for (int curIndex = startIndex; curIndex <= endIndex; curIndex++)
{
uint parserOffset = perModuleLookup.Value.Data[curIndex].Offset;
if (TryGetMethodForOriginalLdFtnResult_ExactInstantiation_Inner(perModuleLookup.Key, forStartAddress: true, methodStartAddress, parserOffset, ref declaringTypeHandle, out methodHandle, out _))
{
if (RuntimeAugments.IsGenericType(declaringTypeHandle))
declaringTypeHandle = RuntimeAugments.GetGenericDefinition(declaringTypeHandle);
return true;
}
}
}
}

// Search InvokeMap
foreach (KeyValuePair<NativeFormatModuleInfo, FunctionPointersToOffsets> perModuleLookup in GetLdFtnReverseLookups_InvokeMap())
{
Expand Down Expand Up @@ -979,111 +929,6 @@ private unsafe bool TryGetMethodForOriginalLdFtnResult_InvokeMap_Inner(NativeFor
return true;
}

private static FunctionPointersToOffsets ComputeLdftnReverseLookup_ExactInstantiations(NativeFormatModuleInfo mappingTableModule)
{
FunctionPointersToOffsets functionPointerToOffsetInInvokeMap = new FunctionPointersToOffsets();

NativeReader methodTemplateMapReader;
if (!TryGetNativeReaderForBlob(mappingTableModule, ReflectionMapBlob.ExactMethodInstantiationsHashtable, out methodTemplateMapReader))
{
return functionPointerToOffsetInInvokeMap;
}

ExternalReferencesTable externalReferences = default(ExternalReferencesTable);
externalReferences.InitializeNativeReferences(mappingTableModule);

NativeParser methodTemplateMapParser = new NativeParser(methodTemplateMapReader, 0);
NativeHashtable invokeHashtable = new NativeHashtable(methodTemplateMapParser);

LowLevelList<FunctionPointerOffsetPair> functionPointers = new LowLevelList<FunctionPointerOffsetPair>();

var lookup = invokeHashtable.EnumerateAllEntries();
NativeParser entryParser;
while (!(entryParser = lookup.GetNext()).IsNull)
{
uint parserOffset = entryParser.Offset;

// Declaring Handle
entryParser.SkipInteger();

// NameAndSig
entryParser.SkipInteger();

// generic method arity
int parsedArity = (int)entryParser.GetSequenceCount();

for (int i = 0; i < parsedArity; i++)
{
entryParser.SkipInteger();
}

IntPtr functionPointer = externalReferences.GetIntPtrFromIndex(entryParser.GetUnsigned());
functionPointers.Add(new FunctionPointerOffsetPair(functionPointer, parserOffset));
}

functionPointerToOffsetInInvokeMap.Data = functionPointers.ToArray();
Array.Sort(functionPointerToOffsetInInvokeMap.Data);

return functionPointerToOffsetInInvokeMap;
}

private static unsafe bool TryGetMethodForOriginalLdFtnResult_ExactInstantiation_Inner(NativeFormatModuleInfo mappingTableModule, bool forStartAddress, IntPtr canonOriginalLdFtnResult, uint parserOffset, ref RuntimeTypeHandle declaringTypeHandle, out QMethodDefinition methodHandle, out RuntimeTypeHandle[] genericMethodTypeArgumentHandles)
{
methodHandle = default(QMethodDefinition);
genericMethodTypeArgumentHandles = null;

NativeReader invokeMapReader;
if (!TryGetNativeReaderForBlob(mappingTableModule, ReflectionMapBlob.ExactMethodInstantiationsHashtable, out invokeMapReader))
{
// This should have succeeded otherwise, how did we get a parser offset as an input parameter?
Debug.Assert(false);
return false;
}

ExternalReferencesTable externalReferences = default(ExternalReferencesTable);
externalReferences.InitializeNativeReferences(mappingTableModule);

NativeParser entryParser = new NativeParser(invokeMapReader, parserOffset);

RuntimeTypeHandle entryTypeHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());

// Hash table names / sigs are indirected through to the native layout info
MethodNameAndSignature nameAndSignature;
if (!TypeLoaderEnvironment.Instance.TryGetMethodNameAndSignatureFromNativeLayoutOffset(mappingTableModule.Handle, entryParser.GetUnsigned(), out nameAndSignature))
return false;

int parsedArity = (int)entryParser.GetSequenceCount();

if (forStartAddress)
{
for (int i = 0; i < parsedArity; i++)
{
entryParser.SkipInteger();
}
}
else
{
genericMethodTypeArgumentHandles = new RuntimeTypeHandle[parsedArity];

for (int i = 0; i < parsedArity; i++)
{
genericMethodTypeArgumentHandles[i] = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
}
}

IntPtr functionPointer = externalReferences.GetIntPtrFromIndex(entryParser.GetUnsigned());
if (functionPointer != canonOriginalLdFtnResult)
return false;

if (TypeLoaderEnvironment.Instance.TryGetMetadataForTypeMethodNameAndSignature(entryTypeHandle, nameAndSignature, out methodHandle))
{
declaringTypeHandle = entryTypeHandle;
return true;
}

return false;
}

private static unsafe bool TryGetMethodForOriginalLdFtnResult_GenericMethodWithInstantiationArgument(IntPtr instantiationArgument, ref RuntimeTypeHandle declaringTypeHandle, out QMethodDefinition methodHandle, out RuntimeTypeHandle[] genericMethodTypeArgumentHandles)
{
MethodNameAndSignature nameAndSig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,6 @@ private static bool IsMethodEligibleForTracking(NodeFactory factory, MethodDesc
if (method.IsVirtual)
return true;

// The hashtable is also used for reflection
if (!factory.MetadataManager.IsReflectionBlocked(method))
return true;

// The rest of the entries are potentially only useful for the universal
// canonical type loader.
return factory.TypeSystemContext.SupportsUniversalCanon;
Expand Down
53 changes: 53 additions & 0 deletions src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private static int Main()
#if !REFLECTION_FROM_USAGE
TestNotReflectedIsNotReflectable.Run();
TestGenericInstantiationsAreEquallyReflectable.Run();
TestStackTraces.Run();
#endif
TestAttributeInheritance2.Run();
TestInvokeMethodMetadata.Run();
Expand Down Expand Up @@ -1600,6 +1601,58 @@ public static void Run()
throw new Exception();
}
}

class TestStackTraces
{
class RefType { }
struct ValType { }

class C1<T>
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static string M1(T c1m1param) => Environment.StackTrace;
[MethodImpl(MethodImplOptions.NoInlining)]
public static string M2(T c1m2param) => Environment.StackTrace;
[MethodImpl(MethodImplOptions.NoInlining)]
public static string M3<U>(T c1m3param1, U c1m3param2) => Environment.StackTrace;
[MethodImpl(MethodImplOptions.NoInlining)]
public static string M4<U>(T c1m4param1, U c1m4param2) => Environment.StackTrace;
}

public static void Run()
{
// These methods are visible to reflection
typeof(C1<RefType>).GetMethod(nameof(C1<RefType>.M1));
typeof(C1<RefType>).GetMethod(nameof(C1<RefType>.M3));
typeof(C1<ValType>).GetMethod(nameof(C1<ValType>.M1));
typeof(C1<ValType>).GetMethod(nameof(C1<ValType>.M3));

Check("C1", "M1", "c1m1param", true, C1<RefType>.M1(default));
Check("C1", "M1", "c1m1param", true, C1<ValType>.M1(default));
Check("C1", "M2", "c1m2param", false, C1<RefType>.M2(default));
Check("C1", "M2", "c1m2param", false, C1<ValType>.M2(default));
Check("C1", "M3", "c1m3param", true, C1<RefType>.M3<RefType>(default, default));
Check("C1", "M3", "c1m3param", true, C1<RefType>.M3<ValType>(default, default));
Check("C1", "M3", "c1m3param", true, C1<ValType>.M3<RefType>(default, default));
Check("C1", "M3", "c1m3param", true, C1<ValType>.M3<ValType>(default, default));
Check("C1", "M4", "c1m4param", false, C1<RefType>.M4<RefType>(default, default));
Check("C1", "M4", "c1m4param", false, C1<RefType>.M4<ValType>(default, default));
Check("C1", "M4", "c1m4param", false, C1<ValType>.M4<RefType>(default, default));
Check("C1", "M4", "c1m4param", false, C1<ValType>.M4<ValType>(default, default));

static void Check(string type, string method, string param, bool hasParam, string s)
{
if (!s.Contains(type))
throw new Exception($"'{s}' doesn't contain '{type}'");
if (!s.Contains(method))
throw new Exception($"'{s}' doesn't contain '{method}'");
if (hasParam && !s.Contains(param))
throw new Exception($"'{s}' doesn't contain '{param}'");
if (!hasParam && s.Contains(param))
throw new Exception($"'{s}' contains '{param}'");
}
}
}
#endif

class TypeConstructionTest
Expand Down

0 comments on commit 2619d1c

Please sign in to comment.