Skip to content

Improved uninitialized object creation performance #98016

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

Merged
merged 2 commits into from
Feb 8, 2024
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
21 changes: 9 additions & 12 deletions src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,12 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionHandling.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionIDs.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\StackFrameIterator.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\AsmOffsets.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\InternalCalls.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')"/>
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionHandling.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\ExceptionIDs.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Runtime.Base\src\System\Runtime\StackFrameIterator.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\AsmOffsets.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ExceptionServices\InternalCalls.cs" Condition="('$(Platform)' != 'x86' or '$(TargetsWindows)' != 'true')" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComTypes\IEnumerable.cs" />
Expand Down Expand Up @@ -248,7 +248,8 @@
<Compile Include="$(BclSourcesRoot)\System\ValueType.cs" />
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
</Compile>
</Compile>
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureComWrappers)' == 'true'">
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ComWrappers.cs" />
Expand Down Expand Up @@ -328,11 +329,7 @@
<Compile Include="@(EventingSourceFile)" />
</ItemGroup>

<Target Name="GenerateEventingFiles"
Inputs="@(EventingGenerationScript);@(EventManifestFile)"
Outputs="@(EventingSourceFile)"
DependsOnTargets="FindPython"
BeforeTargets="BeforeCompile">
<Target Name="GenerateEventingFiles" Inputs="@(EventingGenerationScript);@(EventManifestFile)" Outputs="@(EventingSourceFile)" DependsOnTargets="FindPython" BeforeTargets="BeforeCompile">

<Error Condition="'$(PYTHON)' == ''" Text="Unable to locate Python. NativeRuntimeEventSource.CoreCLR.cs cannot be generated without Python installed on the machine. Either install Python in your path or point to it with the PYTHON environment variable." />
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,9 @@ public static object GetUninitializedObject(
throw new SerializationException(SR.Format(SR.Serialization_InvalidType, type));
}

object? obj = null;
GetUninitializedObject(new QCallTypeHandle(ref rt), ObjectHandleOnStack.Create(ref obj));
return obj!;
return rt.GetUninitializedObject();
}

[LibraryImport(QCall, EntryPoint = "ReflectionSerialization_GetUninitializedObject")]
private static partial void GetUninitializedObject(QCallTypeHandle type, ObjectHandleOnStack retObject);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object AllocateUninitializedClone(object obj);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ private sealed unsafe class ActivatorCache
private readonly delegate*<object?, void> _pfnCtor;
private readonly bool _ctorIsPublic;

private CreateUninitializedCache? _createUninitializedCache;

#if DEBUG
private readonly RuntimeType _originalRuntimeType;
#endif
Expand Down Expand Up @@ -110,21 +112,36 @@ static void CtorNoopStub(object? uninitializedObject) { }
// as the object itself will keep the type alive.

#if DEBUG
if (_originalRuntimeType != rt)
{
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
}
CheckOriginalRuntimeType(rt);
#endif

object? retVal = _pfnAllocator(_allocatorFirstArg);
GC.KeepAlive(rt);
return retVal;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal CreateUninitializedCache GetCreateUninitializedCache(RuntimeType rt)
{
#if DEBUG
CheckOriginalRuntimeType(rt);
#endif
return _createUninitializedCache ??= new CreateUninitializedCache(rt);
}

#if DEBUG
private void CheckOriginalRuntimeType(RuntimeType rt)
{
if (_originalRuntimeType != rt)
{
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
}
}
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3879,6 +3879,31 @@ private void CreateInstanceCheckThis()
}
}

/// <summary>
/// Helper to get instances of uninitialized objects.
/// </summary>
[DebuggerStepThrough]
[DebuggerHidden]
internal object GetUninitializedObject()
{
object? genericCache = GenericCache;

if (genericCache is not CreateUninitializedCache cache)
{
if (genericCache is ActivatorCache activatorCache)
{
cache = activatorCache.GetCreateUninitializedCache(this);
}
else
{
cache = new CreateUninitializedCache(this);
GenericCache = cache;
}
}

return cache.CreateUninitializedObject(this);
}

/// <summary>
/// Helper to invoke the default (parameterless) constructor.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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

namespace System
{
internal sealed partial class RuntimeType
{
/// <summary>
/// A cache which allows optimizing <see cref="RuntimeHelpers.GetUninitializedObject(Type)"/>.
/// </summary>
private sealed unsafe partial class CreateUninitializedCache
{
// The managed calli to the newobj allocator, plus its first argument (MethodTable*).
private readonly delegate*<void*, object> _pfnAllocator;
private readonly void* _allocatorFirstArg;

#if DEBUG
private readonly RuntimeType _originalRuntimeType;
#endif

internal CreateUninitializedCache(RuntimeType rt)
{
Debug.Assert(rt != null);

#if DEBUG
_originalRuntimeType = rt;
#endif

GetCreateUninitializedInfo(rt, out _pfnAllocator, out _allocatorFirstArg);
}

internal object CreateUninitializedObject(RuntimeType rt)
{
#if DEBUG
if (_originalRuntimeType != rt)
{
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
}
#endif
object retVal = _pfnAllocator(_allocatorFirstArg);
GC.KeepAlive(rt);
return retVal;
}

/// <summary>
/// Given a RuntimeType, returns information about how to create uninitialized instances
/// of it via calli semantics.
/// </summary>
private static void GetCreateUninitializedInfo(
RuntimeType rt,
out delegate*<void*, object> pfnAllocator,
out void* vAllocatorFirstArg)
{
Debug.Assert(rt != null);

delegate*<void*, object> pfnAllocatorTemp = default;
void* vAllocatorFirstArgTemp = default;

GetCreateUninitializedInfo(
new QCallTypeHandle(ref rt),
&pfnAllocatorTemp, &vAllocatorFirstArgTemp);

pfnAllocator = pfnAllocatorTemp;
vAllocatorFirstArg = vAllocatorFirstArgTemp;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ReflectionSerialization_GetCreateUninitializedObjectInfo")]
private static partial void GetCreateUninitializedInfo(
QCallTypeHandle type,
delegate*<void*, object>* ppfnAllocator,
void** pvAllocatorFirstArg);
}
}
}
2 changes: 1 addition & 1 deletion src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ static const Entry s_QCall[] =
DllImportEntry(ReflectionInvocation_RunModuleConstructor)
DllImportEntry(ReflectionInvocation_CompileMethod)
DllImportEntry(ReflectionInvocation_PrepareMethod)
DllImportEntry(ReflectionSerialization_GetUninitializedObject)
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
#if defined(FEATURE_COMWRAPPERS)
DllImportEntry(ComWrappers_GetIUnknownImpl)
DllImportEntry(ComWrappers_TryGetComInstance)
Expand Down
27 changes: 21 additions & 6 deletions src/coreclr/vm/reflectioninvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1862,9 +1862,19 @@ FCIMPLEND
//*************************************************************************************************
//*************************************************************************************************
//*************************************************************************************************
extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject)
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(
QCall::TypeHandle pType,
PCODE* ppfnAllocator,
void** pvAllocatorFirstArg)
{
QCALL_CONTRACT;
CONTRACTL{
QCALL_CHECK;
PRECONDITION(CheckPointer(ppfnAllocator));
PRECONDITION(CheckPointer(pvAllocatorFirstArg));
PRECONDITION(*ppfnAllocator == NULL);
PRECONDITION(*pvAllocatorFirstArg == NULL);
}
CONTRACTL_END;

BEGIN_QCALL;

Expand All @@ -1880,14 +1890,19 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::
COMPlusThrow(kNotSupportedException, W("NotSupported_ManagedActivation"));
#endif // FEATURE_COMINTEROP

// If it is a nullable, return the underlying type instead.
// If it is a nullable, return the allocator for the underlying type instead.
if (pMT->IsNullable())
pMT = pMT->GetInstantiation()[0].GetMethodTable();

bool fHasSideEffectsUnused;
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
*pvAllocatorFirstArg = pMT;

pMT->EnsureInstanceActive();

if (pMT->HasPreciseInitCctors())
{
GCX_COOP();
// Allocation will invoke any precise static cctors as needed.
retObject.Set(pMT->Allocate());
pMT->CheckRunClassInitAsIfConstructingThrowing();
}

END_QCALL;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/reflectioninvocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ extern "C" void QCALLTYPE ReflectionInvocation_RunModuleConstructor(QCall::Modul

extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc* pMD, TypeHandle *pInstantiation, UINT32 cInstantiation);

extern "C" void QCALLTYPE ReflectionSerialization_GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject);
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(QCall::TypeHandle pType, PCODE* ppfnAllocator, void** pvAllocatorFirstArg);

class ReflectionEnum {
public:
Expand Down