Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Speed up typeof(Foo) and Object.GetType by 50+% #8095

Merged
merged 3 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dependencies.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RyuJITVersion Condition="'$(RyuJITVersion)' == ''">5.0.0-preview.4.20205.13</RyuJITVersion>
<ObjectWriterVersion Condition="'$(ObjectWriterVersion)' == ''">1.0.0-alpha-28204-03</ObjectWriterVersion>
<ObjectWriterVersion Condition="'$(ObjectWriterVersion)' == ''">1.0.0-alpha-28820-01</ObjectWriterVersion>
<RuntimeLibrariesVersion Condition="'$(RuntimeLibrariesVersion)' == ''">5.0.0-preview.2.20153.3</RuntimeLibrariesVersion>
<CoreFxUapVersion Condition="'$(CoreFxUapVersion)' == ''">4.7.0-preview6.19265.2</CoreFxUapVersion>
<MicrosoftNETCoreAppPackageVersion Condition="'$(MicrosoftNETCoreAppPackageVersion)' == ''">2.1.14</MicrosoftNETCoreAppPackageVersion>
Expand Down
7 changes: 7 additions & 0 deletions src/Common/src/Internal/Runtime/EEType.Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ internal enum EETypeField
{
ETF_InterfaceMap,
ETF_TypeManagerIndirection,
ETF_WritableData,
ETF_Finalizer,
ETF_OptionalFieldsPtr,
ETF_SealedVirtualSlots,
Expand Down Expand Up @@ -296,4 +297,10 @@ internal static class StringComponentSize
{
public const int Value = sizeof(char);
}

internal static class WritableData
{
public static int GetSize(int pointerSize) => pointerSize;
public static int GetAlignment(int pointerSize) => pointerSize;
}
}
60 changes: 58 additions & 2 deletions src/Common/src/Internal/Runtime/EEType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ internal static bool SupportsRelativePointers
}
}

/// <summary>
/// Gets a value indicating whether writable data is supported.
/// </summary>
internal static bool SupportsWritableData
{
get
{
// For now just key this off of SupportsRelativePointer to avoid this on both CppCodegen and WASM.
return SupportsRelativePointers;
}
}

private ushort _usComponentSize;
private ushort _usFlags;
private uint _uBaseSize;
Expand Down Expand Up @@ -1248,6 +1260,35 @@ internal IntPtr PointerToTypeManager
}
#endif

/// <summary>
/// Gets a pointer to a segment of writable memory associated with this EEType.
/// The purpose of the segment is controlled by the class library. The runtime doesn't
/// use this memory for any purpose.
/// </summary>
internal IntPtr WritableData
{
get
{
Debug.Assert(SupportsWritableData);

uint offset = GetFieldOffset(EETypeField.ETF_WritableData);

if (!IsDynamicType)
return GetField<RelativePointer>(offset).Value;
else
return GetField<Pointer>(offset).Value;
}
#if TYPE_LOADER_IMPLEMENTATION
set
{
Debug.Assert(IsDynamicType && SupportsWritableData);

uint cbOffset = GetFieldOffset(EETypeField.ETF_WritableData);
*(IntPtr*)((byte*)Unsafe.AsPointer(ref this) + cbOffset) = value;
}
#endif
}

internal unsafe EETypeRareFlags RareFlags
{
get
Expand Down Expand Up @@ -1319,6 +1360,16 @@ public uint GetFieldOffset(EETypeField eField)
}
cbOffset += relativeOrFullPointerOffset;

// Followed by writable data.
if (SupportsWritableData)
{
if (eField == EETypeField.ETF_WritableData)
{
return cbOffset;
}
cbOffset += relativeOrFullPointerOffset;
}

// Followed by the pointer to the finalizer method.
if (eField == EETypeField.ETF_Finalizer)
{
Expand Down Expand Up @@ -1421,8 +1472,12 @@ public uint GetFieldOffset(EETypeField eField)

public ref T GetField<T>(EETypeField eField)
{
fixed (EEType* pThis = &this)
return ref Unsafe.AddByteOffset(ref Unsafe.As<EEType, T>(ref *pThis), (IntPtr)GetFieldOffset(eField));
return ref Unsafe.As<byte, T>(ref *((byte*)Unsafe.AsPointer(ref this) + GetFieldOffset(eField)));
}

public ref T GetField<T>(uint offset)
{
return ref Unsafe.As<byte, T>(ref *((byte*)Unsafe.AsPointer(ref this) + offset));
}

#if TYPE_LOADER_IMPLEMENTATION
Expand All @@ -1441,6 +1496,7 @@ internal static UInt32 GetSizeofEEType(
(IntPtr.Size * cVirtuals) +
(sizeof(EEInterfaceInfo) * cInterfaces) +
sizeof(IntPtr) + // TypeManager
(SupportsWritableData ? sizeof(IntPtr) : 0) + // WritableData
(fHasFinalizer ? sizeof(UIntPtr) : 0) +
(fRequiresOptionalFields ? sizeof(IntPtr) : 0) +
(fHasSealedVirtuals ? sizeof(IntPtr) : 0) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ namespace ILCompiler.DependencyAnalysis
/// |
/// [Relative ptr] | Pointer to containing TypeManager indirection cell
/// |
/// [Relative ptr] | Pointer to writable data
/// |
/// [Relative ptr] | Pointer to finalizer method (optional)
/// |
/// [Relative ptr] | Pointer to optional fields (optional)
Expand Down Expand Up @@ -491,6 +493,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
}

OutputTypeManagerIndirection(factory, ref objData);
OutputWritableData(factory, ref objData);
OutputFinalizerMethod(factory, ref objData);
OutputOptionalFields(factory, ref objData);
OutputSealedVTable(factory, relocsOnly, ref objData);
Expand Down Expand Up @@ -825,6 +828,21 @@ protected void OutputTypeManagerIndirection(NodeFactory factory, ref ObjectDataB
objData.EmitPointerReloc(factory.TypeManagerIndirection);
}

protected void OutputWritableData(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (factory.Target.SupportsRelativePointers)
{
Utf8StringBuilder writableDataBlobName = new Utf8StringBuilder();
writableDataBlobName.Append("__writableData");
writableDataBlobName.Append(factory.NameMangler.GetMangledTypeName(_type));

BlobNode blob = factory.UninitializedWritableDataBlob(writableDataBlobName.ToUtf8String(),
WritableData.GetSize(factory.Target.PointerSize), WritableData.GetAlignment(factory.Target.PointerSize));

objData.EmitReloc(blob, RelocType.IMAGE_REL_BASED_RELPTR32);
}
}

protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (HasOptionalFields)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
dataBuilder.EmitShort(0); // No interface map
dataBuilder.EmitInt(_type.GetHashCode());
OutputTypeManagerIndirection(factory, ref dataBuilder);
OutputWritableData(factory, ref dataBuilder);
OutputOptionalFields(factory, ref dataBuilder);

return dataBuilder.ToObjectData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ private void CreateNodeCaches()
return new BlobNode(key.Name, ObjectNodeSection.ReadOnlyDataSection, key.Data, key.Alignment);
});

_uninitializedWritableDataBlobs = new NodeCache<UninitializedWritableDataBlobKey, BlobNode>(key =>
{
return new BlobNode(key.Name, ObjectNodeSection.BssSection, new byte[key.Size], key.Alignment);
});

_settableReadOnlyDataBlobs = new NodeCache<Utf8String, SettableReadOnlyDataBlob>(key =>
{
return new SettableReadOnlyDataBlob(key, ObjectNodeSection.ReadOnlyDataSection);
Expand Down Expand Up @@ -655,6 +660,13 @@ public ISymbolNode GCStaticEEType(GCPointerMap gcMap)
return _GCStaticEETypes.GetOrAdd(gcMap);
}

private NodeCache<UninitializedWritableDataBlobKey, BlobNode> _uninitializedWritableDataBlobs;

public BlobNode UninitializedWritableDataBlob(Utf8String name, int size, int alignment)
{
return _uninitializedWritableDataBlobs.GetOrAdd(new UninitializedWritableDataBlobKey(name, size, alignment));
}

private NodeCache<ReadOnlyDataBlobKey, BlobNode> _readOnlyDataBlobs;

public BlobNode ReadOnlyDataBlob(Utf8String name, byte[] blobData, int alignment)
Expand Down Expand Up @@ -1205,5 +1217,26 @@ public ReadOnlyDataBlobKey(Utf8String name, byte[] data, int alignment)
public override bool Equals(object obj) => obj is ReadOnlyDataBlobKey && Equals((ReadOnlyDataBlobKey)obj);
public override int GetHashCode() => Name.GetHashCode();
}

protected struct UninitializedWritableDataBlobKey : IEquatable<UninitializedWritableDataBlobKey>
{
public readonly Utf8String Name;
public readonly int Size;
public readonly int Alignment;

public UninitializedWritableDataBlobKey(Utf8String name, int size, int alignment)
{
Name = name;
Size = size;
Alignment = alignment;
}

// The assumption here is that the name of the blob is unique.
// We can't emit two blobs with the same name and different contents.
// The name is part of the symbolic name and we don't do any mangling on it.
public bool Equals(UninitializedWritableDataBlobKey other) => Name.Equals(other.Name);
public override bool Equals(object obj) => obj is UninitializedWritableDataBlobKey && Equals((UninitializedWritableDataBlobKey)obj);
public override int GetHashCode() => Name.GetHashCode();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ public bool IsStandardSection
{
get
{
return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection;
return this == DataSection || this == ReadOnlyDataSection || this == FoldableReadOnlyDataSection || this == TextSection || this == XDataSection || this == BssSection;
}
}

public static readonly ObjectNodeSection XDataSection = new ObjectNodeSection("xdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection DataSection = new ObjectNodeSection("data", SectionType.Writeable);
public static readonly ObjectNodeSection ReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata$F", SectionType.ReadOnly);
public static readonly ObjectNodeSection FoldableReadOnlyDataSection = new ObjectNodeSection("rdata", SectionType.ReadOnly);
public static readonly ObjectNodeSection TextSection = new ObjectNodeSection("text", SectionType.Executable);
public static readonly ObjectNodeSection TLSSection = new ObjectNodeSection("TLS", SectionType.Writeable);
public static readonly ObjectNodeSection BssSection = new ObjectNodeSection("bss", SectionType.Writeable);
public static readonly ObjectNodeSection ManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable);
public static readonly ObjectNodeSection FoldableManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable);
public static readonly ObjectNodeSection ManagedCodeUnixContentSection = new ObjectNodeSection("__managedcode", SectionType.Executable);
Expand Down
5 changes: 5 additions & 0 deletions src/Native/Runtime/inc/eetype.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ struct TypeManagerHandle;
class DynamicModule;
struct EETypeRef;

#if !defined(USE_PORTABLE_HELPERS)
#define SUPPORTS_WRITABLE_DATA 1
#endif

//-------------------------------------------------------------------------------------------------
// Array of these represents the interfaces implemented by a type

Expand Down Expand Up @@ -86,6 +90,7 @@ enum EETypeField
{
ETF_InterfaceMap,
ETF_TypeManagerIndirection,
ETF_WritableData,
ETF_Finalizer,
ETF_OptionalFieldsPtr,
ETF_SealedVirtualSlots,
Expand Down
9 changes: 9 additions & 0 deletions src/Native/Runtime/inc/eetype.inl
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ __forceinline UInt32 EEType::GetFieldOffset(EETypeField eField)
}
cbOffset += relativeOrFullPointerOffset;

#if SUPPORTS_WRITABLE_DATA
// Followed by writable data.
if (eField == ETF_WritableData)
{
return cbOffset;
}
cbOffset += relativeOrFullPointerOffset;
#endif

// Followed by the pointer to the finalizer method.
if (eField == ETF_Finalizer)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.Private.CoreLib/shared/System/Nullable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public static bool Equals<T>(Nullable<T> n1, Nullable<T> n2) where T : struct
{
if (nullableEEType.IsNullable)
{
return Internal.Reflection.Core.NonPortable.RuntimeTypeUnifier.GetRuntimeTypeForEEType(nullableEEType.NullableType);
return Type.GetTypeFromEETypePtr(nullableEEType.NullableType);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ internal static partial class RuntimeTypeUnifier
//
// Retrieves the unified Type object for given RuntimeTypeHandle (this is basically the Type.GetTypeFromHandle() api without the input validation.)
//
public static Type GetTypeForRuntimeTypeHandle(RuntimeTypeHandle runtimeTypeHandle)
=> RuntimeTypeHandleToTypeCache.Table.GetOrAdd(runtimeTypeHandle.RawValue);

internal static Type GetRuntimeTypeForEEType(EETypePtr eeType)
=> RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue);
{
// If writable data is supported, we shouldn't be using the hashtable - the runtime type
// is accessible through a couple indirections from the EETypePtr which is much faster.
Debug.Assert(!Internal.Runtime.EEType.SupportsWritableData);
return RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue);
}

//
// TypeTable mapping raw RuntimeTypeHandles (normalized or otherwise) to Types.
Expand All @@ -65,51 +67,54 @@ private RuntimeTypeHandleToTypeCache() { }
protected sealed override Type Factory(IntPtr rawRuntimeTypeHandleKey)
{
EETypePtr eeType = new EETypePtr(rawRuntimeTypeHandleKey);
RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType);
return GetRuntimeTypeBypassCache(eeType);
}

public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache();
}

// Desktop compat: Allows Type.GetTypeFromHandle(default(RuntimeTypeHandle)) to map to null.
if (runtimeTypeHandle.IsNull)
return null;
// This bypasses the CoreLib's unifier, but there's another unifier deeper within the reflection stack:
// this code is safe to call without locking. See comment above.
public static Type GetRuntimeTypeBypassCache(EETypePtr eeType)
{
RuntimeTypeHandle runtimeTypeHandle = new RuntimeTypeHandle(eeType);

ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;
ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;

if (eeType.IsDefType)
{
if (eeType.IsGenericTypeDefinition)
{
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true);
}
else if (eeType.IsGeneric)
{
return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle);
}
else
{
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false);
}
}
else if (eeType.IsArray)
{
if (!eeType.IsSzArray)
return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank);
else
return callbacks.GetArrayTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsPointer)
if (eeType.IsDefType)
{
if (eeType.IsGenericTypeDefinition)
{
return callbacks.GetPointerTypeForHandle(runtimeTypeHandle);
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: true);
}
else if (eeType.IsByRef)
else if (eeType.IsGeneric)
{
return callbacks.GetByRefTypeForHandle(runtimeTypeHandle);
return callbacks.GetConstructedGenericTypeForHandle(runtimeTypeHandle);
}
else
{
throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle);
return callbacks.GetNamedTypeForHandle(runtimeTypeHandle, isGenericTypeDefinition: false);
}
}

public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache();
else if (eeType.IsArray)
{
if (!eeType.IsSzArray)
return callbacks.GetMdArrayTypeForHandle(runtimeTypeHandle, eeType.ArrayRank);
else
return callbacks.GetArrayTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsPointer)
{
return callbacks.GetPointerTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsByRef)
{
return callbacks.GetByRefTypeForHandle(runtimeTypeHandle);
}
else
{
throw new ArgumentException(SR.Arg_InvalidRuntimeTypeHandle);
}
}
}
}
Loading