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 1 commit
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-28810-03</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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Debug.Assert(Internal.Runtime.EEType.SupportsWritableData);
Debug.Assert(!Internal.Runtime.EEType.SupportsWritableData);

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, good catch. I guess CppCodegen would quickly point that out.

return RuntimeTypeHandleToTypeCache.Table.GetOrAdd(eeType.RawValue);
}

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

// Desktop compat: Allows Type.GetTypeFromHandle(default(RuntimeTypeHandle)) to map to null.
if (runtimeTypeHandle.IsNull)
return null;
public static readonly RuntimeTypeHandleToTypeCache Table = new RuntimeTypeHandleToTypeCache();
}

ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;
// 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);

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)
// Desktop compat: Allows Type.GetTypeFromHandle(default(RuntimeTypeHandle)) to map to null.
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
if (runtimeTypeHandle.IsNull)
return null;

ReflectionExecutionDomainCallbacks callbacks = RuntimeAugments.Callbacks;

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