Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast createinstance dev 2 #10

Closed
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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
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 @@ -136,15 +137,21 @@ public static int OffsetToStringData
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern bool TryEnsureSufficientExecutionStack();

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object GetUninitializedObjectInternal(
// This API doesn't call any constructors, but the type needs to be seen as constructed.
// A type is seen as constructed if a constructor is kept.
// This obviously won't cover a type with no constructor. Reference types with no
// constructor are an academic problem. Valuetypes with no constructors are a problem,
// but IL Linker currently treats them as always implicitly boxed.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type);
private static unsafe object GetUninitializedObjectInternal(Type type)
{
RuntimeType rt = (RuntimeType)type;
Debug.Assert(rt != null);

// If somebody asks us to create a Nullable<T>, create a T instead.
delegate*<MethodTable*, object> newobjHelper = RuntimeTypeHandle.GetNewobjHelperFnPtr(rt, out MethodTable* pMT, unwrapNullable: true, allowCom: false);
Debug.Assert(newobjHelper != null);
Debug.Assert(pMT != null);

object retVal = newobjHelper(pMT);
GC.KeepAlive(rt); // don't allow the type to be collected before the object is instantiated

return retVal;
}

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object AllocateUninitializedClone(object obj);
Expand Down Expand Up @@ -342,10 +349,16 @@ 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_ComObject = 0x40000000;
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 @@ -402,6 +415,38 @@ public bool NonTrivialInterfaceCast
}
}

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

public bool IsComObject
{
get
{
return (Flags & enum_flag_ComObject) != 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 @@ -209,6 +209,32 @@ internal static bool HasElementType(RuntimeType type)
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object CreateInstanceForAnotherGenericParameter(RuntimeType type, RuntimeType genericParameter);

/// <summary>
/// Given a RuntimeType, returns both the address of the JIT's newobj helper for that type and the
/// MethodTable* corresponding to that type. If the type is <see cref="Nullable{T}"/> closed over
/// some T and <paramref name="unwrapNullable"/> is true, then returns the values for the 'T'.
/// </summary>
internal static delegate*<MethodTable*, object> GetNewobjHelperFnPtr(RuntimeType type, out MethodTable* pMT, bool unwrapNullable, bool allowCom)
{
Debug.Assert(type != null);

delegate*<MethodTable*, object> pNewobjHelperTemp = null;
MethodTable* pMTTemp = null;

GetNewobjHelperFnPtr(
new QCallTypeHandle(ref type),
&pNewobjHelperTemp,
&pMTTemp,
(unwrapNullable) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
(allowCom) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);

pMT = pMTTemp;
return pNewobjHelperTemp;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern void GetNewobjHelperFnPtr(QCallTypeHandle typeHandle, delegate*<MethodTable*, object>* ppNewobjHelper, MethodTable** ppMT, Interop.BOOL fUnwrapNullable, Interop.BOOL fAllowCom);

internal RuntimeType GetRuntimeType()
{
return m_type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@

namespace System
{
// this is a work around to get the concept of a calli. It's not as fast but it would be interesting to
// see how it compares to the current implementation.
// This delegate will disappear at some point in favor of calli

internal delegate void CtorDelegate(object instance);

// Keep this in sync with FormatFlags defined in typestring.h
internal enum TypeNameFormatFlags
{
Expand Down Expand Up @@ -3873,43 +3867,71 @@ private void CreateInstanceCheckThis()
}

// the cache entry
private sealed class ActivatorCache
private sealed unsafe class ActivatorCache
{
// the delegate containing the call to the ctor
internal readonly RuntimeMethodHandleInternal _hCtorMethodHandle;
internal MethodAttributes _ctorAttributes;
internal CtorDelegate? _ctor;

// Lazy initialization was performed
internal volatile bool _isFullyInitialized;

private static ConstructorInfo? s_delegateCtorInfo;
internal volatile delegate*<MethodTable*, object?> _pfnNewobj;
internal readonly delegate*<object?, void> _pfnCtor;
internal MethodTable* _pMT;
internal readonly bool _ctorIsPublic;

internal ActivatorCache(RuntimeMethodHandleInternal rmh)
{
_hCtorMethodHandle = rmh;
if (rmh.IsNullHandle())
{
// Value type with no explicit constructor, which means the "default" ctor
// is implicitly public and no-ops.

_ctorIsPublic = true;
_pfnCtor = &GC.KeepAlive; // no-op fn
}
else
{
// Reference type with a parameterless ctor.

_ctorIsPublic = (RuntimeMethodHandle.GetAttributes(rmh) & MethodAttributes.Public) != 0;
_pfnCtor = (delegate*<object?, void>)RuntimeMethodHandle.GetFunctionPointer(rmh);
}

Debug.Assert(_pfnCtor != null);
}

private void Initialize()
private void Initialize(RuntimeType type)
{
if (!_hCtorMethodHandle.IsNullHandle())
{
_ctorAttributes = RuntimeMethodHandle.GetAttributes(_hCtorMethodHandle);
Debug.Assert(type != null);

// The default ctor path is optimized for reference types only
ConstructorInfo delegateCtorInfo = s_delegateCtorInfo ??= typeof(CtorDelegate).GetConstructor(new Type[] { typeof(object), typeof(IntPtr) })!;
// If we reached this point, we already know that the construction information
// can be cached, so all of the runtime reflection checks succeeded. We just
// need to special-case Nullable<T> (to return null).
//
// No synchronization is needed in this method since we have marked the _pfnNewobj
// field as volatile, and if there's multi-threaded access all threads will agree
// on the values to write anyway.
//
// !! IMPORTANT !!
// Don't assign the function pointer return value of GetNewobjHelperFnPtr directly
// to the backing field, as setting the field marks initialization as complete.
// Be sure to perform any last-minute checks *before* setting the backing field.

// No synchronization needed here. In the worst case we create extra garbage
_ctor = (CtorDelegate)delegateCtorInfo.Invoke(new object?[] { null, RuntimeMethodHandle.GetFunctionPointer(_hCtorMethodHandle) });
delegate*<MethodTable*, object?> pfnNewobj = RuntimeTypeHandle.GetNewobjHelperFnPtr(type, out _pMT, unwrapNullable: false, allowCom: true);
if (_pMT->IsNullable)
{
pfnNewobj = &GetNull; // Activator.CreateInstance(typeof(Nullable<T>)) => null
}
_isFullyInitialized = true;

Debug.Assert(pfnNewobj != null);
_pfnNewobj = pfnNewobj; // setting this field marks the instance as fully initialized
}

public void EnsureInitialized()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureInitialized(RuntimeType type)
{
if (!_isFullyInitialized)
Initialize();
if (_pfnNewobj == null) // use this field's value as the indicator of whether initialization is finished
{
Initialize(type);
}
}

private static object? GetNull(MethodTable* pMT) => null;
}

/// <summary>
Expand All @@ -3929,7 +3951,6 @@ public void EnsureInitialized()

if (canBeCached && fillCache)
{
// cache the ctor
GenericCache = new ActivatorCache(runtimeCtor);
}

Expand All @@ -3941,38 +3962,33 @@ public void EnsureInitialized()
/// </summary>
[DebuggerStepThrough]
[DebuggerHidden]
internal object? CreateInstanceDefaultCtor(bool publicOnly, bool skipCheckThis, bool fillCache, bool wrapExceptions)
internal unsafe object? CreateInstanceDefaultCtor(bool publicOnly, bool skipCheckThis, bool fillCache, bool wrapExceptions)
{
// Call the cached
if (GenericCache is ActivatorCache cacheEntry)
// Call the cached factory if it exists
if (GenericCache is ActivatorCache cache)
{
cacheEntry.EnsureInitialized();
cache.EnsureInitialized(this);

Debug.Assert(cache._pfnNewobj != null);
Debug.Assert(cache._pfnCtor != null);

if (publicOnly)
if (!cache._ctorIsPublic && publicOnly)
{
if (cacheEntry._ctor != null &&
(cacheEntry._ctorAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public)
{
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this));
}
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this));
}

// Allocate empty object and call the default constructor if present.
object instance = RuntimeTypeHandle.Allocate(this);
Debug.Assert(cacheEntry._ctor != null || IsValueType);
if (cacheEntry._ctor != null)
try
{
try
{
cacheEntry._ctor(instance);
}
catch (Exception e) when (wrapExceptions)
{
throw new TargetInvocationException(e);
}
}
object? obj = cache._pfnNewobj(cache._pMT);
GC.KeepAlive(this); // can't allow the type to be collected before the object is created

return instance;
cache._pfnCtor(obj);
return obj;
}
catch (Exception e) when (wrapExceptions)
{
throw new TargetInvocationException(e);
}
}

if (!skipCheckThis)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ FCFuncStart(gCOMTypeHandleFuncs)
FCFuncElement("ContainsGenericVariables", RuntimeTypeHandle::ContainsGenericVariables)
FCFuncElement("SatisfiesConstraints", RuntimeTypeHandle::SatisfiesConstraints)
FCFuncElement("Allocate", RuntimeTypeHandle::Allocate) //for A.CI
QCFuncElement("GetNewobjHelperFnPtr", RuntimeTypeHandle::GetNewobjHelperFnPtr)
FCFuncElement("CompareCanonicalHandles", RuntimeTypeHandle::CompareCanonicalHandles)
FCIntrinsic("GetValueInternal", RuntimeTypeHandle::GetValueInternal, CORINFO_INTRINSIC_RTH_GetValueInternal)
FCFuncElement("IsEquivalentTo", RuntimeTypeHandle::IsEquivalentTo)
Expand Down Expand Up @@ -882,7 +883,6 @@ FCFuncStart(gRuntimeHelpers)
FCFuncElement("AllocateUninitializedClone", ObjectNative::AllocateUninitializedClone)
FCFuncElement("EnsureSufficientExecutionStack", ReflectionInvocation::EnsureSufficientExecutionStack)
FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack)
FCFuncElement("GetUninitializedObjectInternal", ReflectionSerialization::GetUninitializedObject)
QCFuncElement("AllocateTypeAssociatedMemoryInternal", RuntimeTypeHandle::AllocateTypeAssociatedMemory)
FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer)
FCFuncElement("FreeTailCallArgBuffer", TailCallHelp::FreeTailCallArgBuffer)
Expand Down
Loading