Skip to content

Commit

Permalink
[NativeAOT] Reflection type refactoring (#93440)
Browse files Browse the repository at this point in the history
Design

- `RuntimeType` - sealed light-weight System.Type, similar to CoreCLR RuntimeType. It has just two fields `MethodTable*` and lazily initialized pointer to the full reflection. The light-weight method and properties are implemented using `MethodTable*`, the rest initializes the full reflection `RuntimeTypeInfo` lazily and calls it to do the work.

- `RuntimeTypeInfo` - internal, lazily created full reflection. It is similar to the `Cache` attached to RuntimeType in CoreCLR. The reflection-free mode is implemented by blocking creation of `RuntimeTypeInfo` and throwing instead.

Some reflection micro-benchmark may regress with this change on native AOT. It is expected due to the extra indirections, but it makes the reflection more pay-for-play and a bit closer to how it is implemented in CoreCLR.

Contributes to #91704
  • Loading branch information
jkotas authored Oct 31, 2023
1 parent 1082bd5 commit 41e4d65
Show file tree
Hide file tree
Showing 99 changed files with 1,527 additions and 2,201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="$(IlcInstructionSet) != ''" Include="--instruction-set:$(IlcInstructionSet)" />
<IlcArg Condition="$(IlcDisableReflection) == 'true'" Include="--reflectiondata:none" />
<IlcArg Condition="$(IlcDisableReflection) == 'true'" Include="--feature:System.Collections.Generic.DefaultComparers=false" />
<IlcArg Condition="$(IlcDisableReflection) == 'true'" Include="--feature:System.Reflection=false" />
<IlcArg Condition="$(IlcMaxVectorTBitWidth) != ''" Include="--max-vectort-bitwidth:$(IlcMaxVectorTBitWidth)" />
<IlcArg Condition="$(IlcSingleThreaded) == 'true'" Include="--parallelism:1" />
<IlcArg Condition="$(IlcSystemModule) != ''" Include="--systemmodule:$(IlcSystemModule)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,15 @@ internal bool IsNullable
}
}

internal bool IsDefType
{
get
{
EETypeKind kind = Kind;
return kind == EETypeKind.CanonicalEEType || kind == EETypeKind.GenericTypeDefEEType;
}
}

internal bool IsCanonical
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,14 @@ namespace System.Collections.Concurrent
// The key must be of a type that implements IEquatable<K>. The unifier calls IEquality<K>.Equals()
// and Object.GetHashCode() on the keys.
//
// The value must be a reference type that implements IKeyedItem<K>. The unifier invokes the
// IKeyedItem<K>.PrepareKey() method (outside the lock) on any value returned by the factory. This gives the value
// a chance to do any lazy evaluation of the keys while it's safe to do so.
// The value must be a reference type that implements IKeyedItem<K>.
//
// Deadlock risks:
// - Keys may be tested for equality and asked to compute their hashcode while the unifier
// holds its lock. Thus these operations must be written carefully to avoid deadlocks and
// reentrancy in to the table.
//
// - Values may get their IKeyedItem<K>.Key property called while the unifier holds its lock.
// Values that need to do lazy evaluation to compute their keys should do that in the PrepareKey()
// method which the unifier promises to call outside the lock prior to entering the value into the table.
//
// - The Factory method will never be called inside the unifier lock. If two threads race to
// enter a value for the same key, the Factory() may get invoked twice for the same key - one
Expand Down Expand Up @@ -150,10 +146,6 @@ public V GetOrAdd(K key)
return null;
}

// While still outside the lock, invoke the value's PrepareKey method to give the chance to do any lazy evaluation
// it needs to produce the key quickly and in a deadlock-free manner once we're inside the lock.
value.PrepareKey();

using (_lock.EnterScope())
{
V heyIWasHereFirst;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,9 @@ namespace System.Collections.Concurrent
//
internal interface IKeyedItem<K> where K : IEquatable<K>
{
//
// This method is the keyed item's chance to do any lazy evaluation needed to produce the key quickly.
// Concurrent unifiers are guaranteed to invoke this method at least once and wait for it
// to complete before invoking the Key property. The unifier lock is NOT held across the call.
//
// PrepareKey() must be idempodent and thread-safe. It may be invoked multiple times and concurrently.
//
void PrepareKey();

//
// Produce the key. This is a high-traffic property and is called while the hash table's lock is held. Thus, it should
// return a precomputed stored value and refrain from invoking other methods. If the keyed item wishes to
// do lazy evaluation of the key, it should do so in the PrepareKey() method.
// return a precomputed stored value and refrain from invoking other methods.
//
K Key { get; }
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -697,10 +697,6 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Reflection.Core.QScopeDefinition</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Reflection.Core.ReflectionDomainSetup</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Reflection.Extensions.NonPortable.CustomAttributeInheritanceRules</Target>
Expand All @@ -713,10 +709,6 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Runtime.Augments.DynamicDelegateAugments</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Runtime.Augments.ReflectionExecutionDomainCallbacks</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Runtime.Augments.RuntimeAugments</Target>
Expand Down Expand Up @@ -897,10 +889,6 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.PInvokeMarshal</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.InteropServices.UnsafeGCHandle</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.RuntimeImportAttribute</Target>
Expand All @@ -921,10 +909,6 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.RuntimeExceptionHelpers</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.RuntimeType</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Threading.Condition</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@
<type fullname="System.Reflection.Runtime.General.TypeUnifier" feature="System.Reflection.IsTypeConstructionEagerlyValidated" featurevalue="false">
<method signature="System.Boolean get_IsTypeConstructionEagerlyValidated()" body="stub" />
</type>

<type fullname="System.RuntimeType" feature="System.Reflection" featurevalue="false">
<method signature="System.Boolean get_IsReflectionDisabled()" body="stub" value="true" />
</type>

</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,6 @@ public virtual void TryGetILOffsetWithinMethod(IntPtr ip, out int ilOffset)
ilOffset = StackFrame.OFFSET_UNKNOWN;
}

/// <summary>
/// Makes reasonable effort to get the MethodBase reflection info. Returns null if it can't.
/// </summary>
public virtual void TryGetMethodBase(IntPtr methodStartAddress, out MethodBase method)
{
ReflectionExecutionDomainCallbacks reflectionCallbacks = RuntimeAugments.CallbacksIfAvailable;
method = null;
if (reflectionCallbacks != null)
{
method = reflectionCallbacks.GetMethodBaseFromStartAddressIfAvailable(methodStartAddress);
}
}

public static DeveloperExperience Default
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@ public static class ReflectionAugments
//
public static void Initialize(ReflectionCoreCallbacks reflectionCoreCallbacks)
{
Debug.Assert(s_reflectionCoreCallbacks == null);
s_reflectionCoreCallbacks = reflectionCoreCallbacks;
}

public static TypeCode GetRuntimeTypeCode(Type type)
internal static TypeCode GetRuntimeTypeCode(RuntimeType type)
{
Debug.Assert(type != null);

EETypePtr eeType;
if (!type.TryGetEEType(out eeType))
EETypePtr eeType = type.ToEETypePtrMayBeNull();
if (eeType.IsNull)
{
// Type exists in metadata only. Aside from the enums, there is no chance a type with a TypeCode would not have an MethodTable,
// so if it's not an enum, return the default.
if (!type.IsEnum || type.IsGenericParameter)
if (!type.IsActualEnum)
return TypeCode.Object;
Type underlyingType = Enum.GetUnderlyingType(type);
eeType = underlyingType.TypeHandle.ToEETypePtr();
Expand Down Expand Up @@ -89,11 +90,6 @@ public static TypeCode GetRuntimeTypeCode(Type type)
return TypeCode.Object;
}

public static Type MakeGenericSignatureType(Type genericTypeDefinition, Type[] genericTypeArguments)
{
return new SignatureConstructedGenericType(genericTypeDefinition, genericTypeArguments);
}

public static TypeLoadException CreateTypeLoadException(string message, string typeName)
{
return new TypeLoadException(message, typeName);
Expand All @@ -109,6 +105,14 @@ internal static ReflectionCoreCallbacks ReflectionCoreCallbacks
}
}

internal static bool IsInitialized
{
get
{
return s_reflectionCoreCallbacks != null;
}
}

private static ReflectionCoreCallbacks s_reflectionCoreCallbacks;
}

Expand Down Expand Up @@ -153,14 +157,20 @@ public abstract object ActivatorCreateInstance(

public abstract IntPtr GetFunctionPointer(RuntimeMethodHandle runtimeMethodHandle, RuntimeTypeHandle declaringTypeHandle);

public abstract void RunModuleConstructor(Module module);

public abstract void MakeTypedReference(object target, FieldInfo[] flds, out Type type, out int offset);

public abstract Assembly[] GetLoadedAssemblies();

public abstract EnumInfo GetEnumInfo(Type type, Func<Type, string[], object[], bool, EnumInfo> create);

public abstract DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type);

public abstract MethodInfo GetDelegateMethod(Delegate del);

public abstract MethodBase GetMethodBaseFromStartAddressIfAvailable(IntPtr methodStartAddress);

public abstract Assembly GetAssemblyForHandle(RuntimeTypeHandle typeHandle);

public abstract void RunClassConstructor(RuntimeTypeHandle typeHandle);
}
}
Loading

0 comments on commit 41e4d65

Please sign in to comment.