Skip to content

Commit

Permalink
Fast object instantiation, take two
Browse files Browse the repository at this point in the history
- Eliminates ObjectFactory<T> except when caller explicitly wants a Func<T>
- Uses C# 9.0 function pointers to avoid introducing new JIT intrinsics
  • Loading branch information
GrabYourPitchforks committed May 11, 2020
1 parent e18250b commit d0bde83
Show file tree
Hide file tree
Showing 17 changed files with 696 additions and 70 deletions.
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<UsingToolIbcOptimization>true</UsingToolIbcOptimization>
<UsingToolXliff>false</UsingToolXliff>
<!-- Upgrade compiler version to work around build failures: https://github.com/dotnet/runtime/issues/34417. -->
<MicrosoftNetCompilersToolsetVersion>3.7.0-2.20258.1</MicrosoftNetCompilersToolsetVersion>
<MicrosoftNetCompilersToolsetVersion>3.7.0-ci.final</MicrosoftNetCompilersToolsetVersion>
<!-- Blob storage container that has the "Latest" channel to publish to. -->
<ContainerName>dotnet</ContainerName>
<ChecksumContainerName>$(ContainerName)</ChecksumContainerName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
<_FullFrameworkReferenceAssemblyPaths>$(MSBuildThisFileDirectory)/Documentation</_FullFrameworkReferenceAssemblyPaths>
<SkipCommonResourcesIncludes>true</SkipCommonResourcesIncludes>
<DocumentationFile>$(OutputPath)$(MSBuildProjectName).xml</DocumentationFile>
<EnableAnalyzers>true</EnableAnalyzers>
<!-- Turning off analyzers for now because they fail when they see calli invocations -->
<EnableAnalyzers>false</EnableAnalyzers>
</PropertyGroup>

<!-- Platform specific properties -->
Expand Down Expand Up @@ -200,6 +201,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\MemberInfo.Internal.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\AssemblyExtensions.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodBase.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\ObjectFactory.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RtFieldInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeAssembly.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeConstructorInfo.cs" />
Expand All @@ -212,6 +214,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeModule.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeParameterInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimePropertyInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\UninitializedObjectFactory.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CrossLoaderAllocatorHashHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\DependentHandle.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using Internal.Runtime.CompilerServices;

namespace System.Reflection
{
// Creates initialized instances of reference types or of value types.
// For reference types, calls the parameterless ctor.
// For value types, calls the parameterless ctor if it exists; otherwise
// return a boxed default(T). Must not be used with Nullable<T>.
internal unsafe sealed class ObjectFactory : UninitializedObjectFactory
{
private readonly void* _pfnCtor;
private readonly bool _isNonPublicCtor;

// Creates a factory from an existing parameterless ctor
internal ObjectFactory(RuntimeMethodHandleInternal hCtor)
: base(RuntimeMethodHandle.GetDeclaringType(hCtor))
{
_pfnCtor = (void*)RuntimeMethodHandle.GetFunctionPointer(hCtor);
Debug.Assert(_pfnCtor != null);

_isNonPublicCtor = (RuntimeMethodHandle.GetAttributes(hCtor) & MethodAttributes.MemberAccessMask) != MethodAttributes.Public;
}

private ObjectFactory(RuntimeType type)
: base(type)
{
Debug.Assert(_pMT->IsValueType);
_isNonPublicCtor = false; // default(T) is always "public"
}

// Creates a factory for "box(default(T))" around a value type
internal static ObjectFactory CreateFactoryForValueTypeDefaultOfT(RuntimeType type)
{
return new ObjectFactory(type);
}

public bool IsNonPublicCtor => _isNonPublicCtor;

public object CreateInstance()
{
object newObj = CreateUninitializedInstance();

if (!_pMT->IsValueType)
{
// Common case: we're creating a reference type
((delegate*<object, void>)_pfnCtor)(newObj);
}
else
{
// Less common case: we're creating a boxed value type
// If an explicit parameterless ctor exists, call it now.
if (_pfnCtor != null)
{
((delegate*<ref byte, void>)_pfnCtor)(ref newObj.GetRawData());
}
}

return newObj;
}
}

// Similar to ObjectFactory, but does not box value types 'T'.
internal unsafe sealed class ObjectFactory<T> : UninitializedObjectFactory
{
private readonly void* _pfnCtor;

internal ObjectFactory()
: base((RuntimeType)typeof(T))
{
RuntimeType type = (RuntimeType)typeof(T);

// It's ok if there's no default constructor on a value type.
// We'll return default(T). For reference types, the constructor
// must be present. In all cases, if a constructor is present, it
// must be public.

RuntimeMethodHandleInternal hCtor = RuntimeMethodHandleInternal.EmptyHandle;
if (_pMT->HasDefaultConstructor)
{
hCtor = RuntimeTypeHandle.GetDefaultConstructor(type);
Debug.Assert(!hCtor.IsNullHandle());
if ((RuntimeMethodHandle.GetAttributes(hCtor) & MethodAttributes.MemberAccessMask) != MethodAttributes.Public)
{
// parameterless ctor exists but is not public
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, type));
}

_pfnCtor = (void*)RuntimeMethodHandle.GetFunctionPointer(hCtor);
}
else
{
if (!_pMT->IsValueType)
{
// parameterless ctor missing on reference type
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, type));
}
}
}

public T CreateInstance()
{
if (typeof(T).IsValueType)
{
T value = default!;
if (_pfnCtor != null)
{
((delegate*<ref T, void>)_pfnCtor)(ref value);
}
return value;
}
else
{
object value = CreateUninitializedInstance();
Debug.Assert(_pfnCtor != null);
((delegate*<object, void>)_pfnCtor)(value!);
return Unsafe.As<object, T>(ref value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System.Reflection
{
// Creates zero-initialized instances of types.
// For reference types, equivalent of allocating memory without running ctor.
// For value types, equivalent of boxing default(T).
// Must not be used with Nullable<T>.
internal unsafe class UninitializedObjectFactory
{
protected readonly MethodTable* _pMT;
private readonly delegate*<MethodTable*, object> _pfnAllocate;
private readonly RuntimeType _type;

internal UninitializedObjectFactory(RuntimeType type)
{
Debug.Assert(type != null);
Debug.Assert(RuntimeHelpers.IsFastInstantiable(type));

_type = type;
_pMT = RuntimeTypeHandle.GetMethodTable(type);
_pfnAllocate = RuntimeHelpers.GetNewobjHelper(type);

Debug.Assert(_pMT != null);
Debug.Assert(!_pMT->IsNullable);
Debug.Assert(_pfnAllocate != null);
}

public object CreateUninitializedInstance()
{
// If a GC kicks in between the time we load the newobj
// helper address and the time we calli it, we don't want
// the Type object to be eligible for collection. To avoid
// this, we KeepAlive(this) - and the referenced Type -
// until we have an instance of the object. From that point
// onward, the object itself will keep the Type alive.

object newObj = _pfnAllocate(_pMT);
GC.KeepAlive(this);
return newObj;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using Internal.Runtime.CompilerServices;

Expand Down Expand Up @@ -145,6 +146,41 @@ public static int OffsetToStringData
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object AllocateUninitializedClone(object obj);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern unsafe delegate*<MethodTable*, object> GetNewobjHelper(RuntimeType type);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool IsFastInstantiable(RuntimeType type);

public static unsafe Func<object> GetUninitializedObjectFactory(Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}

if (!(type.UnderlyingSystemType is RuntimeType rt))
{
throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
}

type = null!; // just to make sure we don't use Type for the rest of the method

if (rt.IsPointer || rt.IsByRef || rt.IsByRefLike || !RuntimeHelpers.IsFastInstantiable(rt))
{
throw new ArgumentException(
paramName: nameof(type),
message: SR.NotSupported_Type);
}

// Compat with GetUninitializedObject: if incoming type T is really
// Nullable<U>, then this factory should return a boxed default(U)
// instead of a boxed default(Nullable<U>).

rt = (RuntimeType?)Nullable.GetUnderlyingType(rt) ?? rt;
return (Func<object>)new UninitializedObjectFactory(rt).CreateUninitializedInstance;
}

/// <returns>true if given type is reference type or value type that contains references</returns>
[Intrinsic]
public static bool IsReferenceOrContainsReferences<T>()
Expand Down Expand Up @@ -332,10 +368,15 @@ internal unsafe struct MethodTable
[FieldOffset(InterfaceMapOffset)]
public MethodTable** InterfaceMap;

// WFLAGS_HIGH_ENUM
// WFLAGS_HIGH_ENUM & WFLAGS_LOW_ENUM
private const uint enum_flag_Category_Mask = 0x000F0000;
private const uint enum_flag_Category_Nullable = 0x00050000;
private const uint enum_flag_Category_ValueType = 0x00040000;
private const uint enum_flag_Category_ValueType_Mask = 0x000C0000;
private const uint enum_flag_ContainsPointers = 0x01000000;
private const uint enum_flag_HasComponentSize = 0x80000000;
private const uint enum_flag_HasTypeEquivalence = 0x00004000;
private const uint enum_flag_HasDefaultCtor = 0x00000200;
private const uint enum_flag_HasTypeEquivalence = 0x00004000; // TODO: shouldn't this be 0x02000000?
// Types that require non-trivial interface cast have this bit set in the category
private const uint enum_flag_NonTrivialInterfaceCast = 0x00080000 // enum_flag_Category_Array
| 0x40000000 // enum_flag_ComObject
Expand Down Expand Up @@ -391,6 +432,30 @@ public bool NonTrivialInterfaceCast
}
}

public bool HasDefaultConstructor
{
get
{
return (Flags & enum_flag_HasDefaultCtor) != 0;
}
}

public bool IsNullable
{
get
{
return (Flags & enum_flag_Category_Mask) == enum_flag_Category_Nullable;
}
}

public bool IsValueType
{
get
{
return (Flags & enum_flag_Category_ValueType_Mask) == enum_flag_Category_ValueType;
}
}

public bool HasTypeEquivalence
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ public ModuleHandle GetModuleHandle()
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern RuntimeMethodHandleInternal GetMethodAt(RuntimeType type, int slot);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static unsafe extern MethodTable* GetMethodTable(RuntimeType type);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern RuntimeMethodHandleInternal GetDefaultConstructor(RuntimeType type);

// This is managed wrapper for MethodTable::IntroducedMethodIterator
internal struct IntroducedMethodEnumerator
{
Expand Down
Loading

0 comments on commit d0bde83

Please sign in to comment.