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

[draft] Improve startup and warmup performance for invoke by reducing emit usage #109901

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0e4b6eb
Replace OpCodes.NewObj with GetUninitializedObject()
steveharter Oct 7, 2024
062552f
Initial switch from Call to Calli
steveharter Oct 23, 2024
47ea274
Merge branch 'main' of https://github.com/steveharter/runtime into Re…
steveharter Oct 23, 2024
76bd6f9
Add back CallVirt; add caching and well-known signatures
steveharter Nov 10, 2024
fbf8049
Only cache dynamic methods, not invoker
steveharter Nov 12, 2024
a570df7
Merge branch 'main' of https://github.com/steveharter/runtime into Re…
steveharter Nov 12, 2024
febdd32
Share cache and more code across 3 invokers
steveharter Nov 14, 2024
34d9669
Add more well-known signatures
steveharter Nov 14, 2024
e57a58c
Initial mono; keep interpreted path
steveharter Nov 16, 2024
517e5d5
Initial mono; keep interpreted path #2
steveharter Nov 17, 2024
c7b5336
Address build and some test issues
steveharter Nov 18, 2024
eb37b87
Address a test issue with generics
steveharter Nov 18, 2024
b0f8a58
Misc cleanup; disable well-known method impl for now
steveharter Nov 22, 2024
c16d691
Add back well-known types
steveharter Nov 24, 2024
146c7d2
Fix compile issue due to auto-added incorrect using
steveharter Nov 25, 2024
77532ed
Fix a couple test cases
steveharter Nov 25, 2024
a0a4d46
Fix a mono build issue
steveharter Nov 25, 2024
2f00fff
Address a test issue
steveharter Nov 26, 2024
b04f1bd
Fix build issue due to change in main
steveharter Nov 27, 2024
1513336
Merge branch 'main' of https://github.com/steveharter/runtime into Re…
steveharter Nov 27, 2024
7e86911
Fix null ref exception; remove unnecessary fields
steveharter Nov 27, 2024
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 @@ -179,6 +179,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodBase.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodBaseInvoker.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodInvoker.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodInvokerCommon.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\ModifiedType.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RtFieldInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeAssembly.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
// 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using static System.Reflection.InvokerEmitUtil;
using static System.Reflection.MethodBase;
using static System.RuntimeType;

namespace System.Reflection
{
public partial class ConstructorInvoker
{
private readonly Signature? _signature;
private readonly CreateUninitializedCache? _allocator;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private object CreateUninitializedObject() => _allocator!.CreateUninitializedObject(_declaringType);

internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments)
private bool ShouldAllocate => _allocator is not null;

private unsafe Delegate CreateInvokeDelegateForInterpreted()
{
_signature = constructor.Signature;
_invokeFunc_RefArgs = InterpretedInvoke;
Debug.Assert(MethodInvokerCommon.UseInterpretedPath);
Debug.Assert(_strategy == InvokerStrategy.Ref4 || _strategy == InvokerStrategy.RefMany);

return (InvokeFunc_RefArgs)InterpretedInvoke;
}

private unsafe object? InterpretedInvoke(object? obj, IntPtr* args)
private unsafe object? InterpretedInvoke(object? obj, IntPtr _, IntPtr* args)
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null);
return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _method.Signature, isConstructor: obj is null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private MethodBaseInvoker Invoker
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _invoker ??= new MethodBaseInvoker(this, Signature);
return _invoker ??= new MethodBaseInvoker(this, Signature.Arguments, ReturnType);
}
}

Expand Down Expand Up @@ -132,13 +132,17 @@ Signature LazyCreateSignature()
int argCount = (parameters != null) ? parameters.Length : 0;
if (Signature.Arguments.Length != argCount)
throw new TargetParameterCountException(SR.Arg_ParmCnt);
object? retValue = argCount switch

object? retValue = Invoker.Strategy switch
{
0 => Invoker.InvokeWithNoArgs(obj, invokeAttr),
1 => Invoker.InvokeWithOneArg(obj, invokeAttr, binder, parameters!, culture),
2 or 3 or 4 => Invoker.InvokeWithFewArgs(obj, invokeAttr, binder, parameters!, culture),
_ => Invoker.InvokeWithManyArgs(obj, invokeAttr, binder, parameters!, culture),
MethodBase.InvokerStrategy.Obj0 => Invoker.InvokeWithNoArgs(obj, invokeAttr),
MethodBase.InvokerStrategy.Obj1 => Invoker.InvokeWith1Arg(obj, invokeAttr, binder, parameters!, culture),
MethodBase.InvokerStrategy.Obj4 => Invoker.InvokeWith4Args(obj, invokeAttr, binder, parameters!, culture),
MethodBase.InvokerStrategy.ObjSpan => Invoker.InvokeWithSpanArgs(obj, invokeAttr, binder, parameters!, culture),
MethodBase.InvokerStrategy.Ref4 => Invoker.InvokeWith4RefArgs(obj, invokeAttr, binder, parameters, culture),
_ => Invoker.InvokeWithManyRefArgs(obj, invokeAttr, binder, parameters!, culture)
};

GC.KeepAlive(this);
return retValue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
// 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using static System.Reflection.InvokerEmitUtil;
using static System.Reflection.MethodBase;
using static System.RuntimeType;

namespace System.Reflection
{
internal partial class MethodBaseInvoker
{
private readonly Signature? _signature;
private readonly CreateUninitializedCache? _allocator;

internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments)
{
_signature = method.Signature;
_invocationFlags = method.ComputeAndUpdateInvocationFlags();
_invokeFunc_RefArgs = InterpretedInvoke_Method;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private object CreateUninitializedObject() => _allocator!.CreateUninitializedObject(_declaringType!);

internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments)
{
_signature = constructor.Signature;
_invocationFlags = constructor.ComputeAndUpdateInvocationFlags();
_invokeFunc_RefArgs = InterpretedInvoke_Constructor;
}
private bool ShouldAllocate => _allocator is not null;

internal unsafe MethodBaseInvoker(DynamicMethod method, Signature signature) : this(method, signature.Arguments)
private unsafe Delegate CreateInvokeDelegateForInterpreted()
{
_signature = signature;
_invokeFunc_RefArgs = InterpretedInvoke_Method;
Debug.Assert(MethodInvokerCommon.UseInterpretedPath);
Debug.Assert(_strategy == InvokerStrategy.Ref4 || _strategy == InvokerStrategy.RefMany);

if (_method is RuntimeMethodInfo)
{
return (InvokeFunc_RefArgs)InterpretedInvoke_Method;
}

if (_method is RuntimeConstructorInfo)
{
return (InvokeFunc_RefArgs)InterpretedInvoke_Constructor;
}

Debug.Assert(_method is DynamicMethod);
return (InvokeFunc_RefArgs)InterpretedInvoke_DynamicMethod;
}

private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null);
private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeMethodInfo)_method).Signature, isConstructor: false);

private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeConstructorInfo)_method).Signature, isConstructor: obj is null);

private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false);
private unsafe object? InterpretedInvoke_DynamicMethod(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((DynamicMethod)_method).Signature, isConstructor: false);
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
// 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.Reflection.Emit;
using static System.Reflection.InvokerEmitUtil;
using static System.Reflection.MethodBase;

namespace System.Reflection
{
public partial class MethodInvoker
{
private readonly Signature? _signature;

private unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments)
private unsafe Delegate CreateInvokeDelegateForInterpreted()
{
_signature = method.Signature;
_invokeFunc_RefArgs = InterpretedInvoke_Method;
_invocationFlags = method.ComputeAndUpdateInvocationFlags();
}
Debug.Assert(MethodInvokerCommon.UseInterpretedPath);
Debug.Assert(_strategy == InvokerStrategy.Ref4 || _strategy == InvokerStrategy.RefMany);

private unsafe MethodInvoker(DynamicMethod method) : this(method, method.Signature.Arguments)
{
_signature = method.Signature;
_invokeFunc_RefArgs = InterpretedInvoke_Method;
// No _invocationFlags for DynamicMethod.
}
if (_method is RuntimeMethodInfo)
{
return (InvokeFunc_RefArgs)InterpretedInvoke_Method;
}

private unsafe MethodInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments)
{
_signature = constructor.Signature;
_invokeFunc_RefArgs = InterpretedInvoke_Constructor;
_invocationFlags = constructor.ComputeAndUpdateInvocationFlags();
if (_method is RuntimeConstructorInfo)
{
return (InvokeFunc_RefArgs)InterpretedInvoke_Constructor;
}

Debug.Assert(_method is DynamicMethod);
return (InvokeFunc_RefArgs)InterpretedInvoke_DynamicMethod;
}

private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false);
private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeConstructorInfo)_method).Signature, isConstructor: obj is null);

private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeMethodInfo)_method).Signature, isConstructor: false);

private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null);
private unsafe object? InterpretedInvoke_DynamicMethod(object? obj, IntPtr _, IntPtr* args) =>
RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((DynamicMethod)_method).Signature, isConstructor: false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace System.Reflection
{
internal static partial class MethodInvokerCommon
{
// For CoreClr, we may be able to remove the interpreted path; it is not used in the CoreCLR implementation
// unless the feature switch is enabled. Unlike Mono, there are no interpreted-only platforms.
internal static bool UseInterpretedPath => LocalAppContextSwitches.ForceInterpretedInvoke || !RuntimeFeature.IsDynamicCodeSupported;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ internal sealed partial class RuntimeConstructorInfo : ConstructorInfo, IRuntime
private readonly BindingFlags m_bindingFlags;
private Signature? m_signature;
private MethodBaseInvoker? m_invoker;
private InvocationFlags m_invocationFlags;

internal InvocationFlags InvocationFlags
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
InvocationFlags flags = Invoker._invocationFlags;
InvocationFlags flags = m_invocationFlags;
if (flags == InvocationFlags.Unknown)
{
m_invocationFlags = flags = ComputeInvocationFlags();
}

Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized);
return flags;
}
Expand All @@ -46,8 +52,7 @@ private MethodBaseInvoker Invoker
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
m_invoker ??= new MethodBaseInvoker(this);
return m_invoker;
return m_invoker ??= new MethodBaseInvoker(this, ArgumentTypes, GetReturnType());
}
}
#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ internal sealed partial class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo
private readonly RuntimeType m_declaringType;
private readonly object? m_keepalive;
private MethodBaseInvoker? m_invoker;
private InvocationFlags m_invocationFlags;

internal InvocationFlags InvocationFlags
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
InvocationFlags flags = Invoker._invocationFlags;
InvocationFlags flags = m_invocationFlags;
if (flags == InvocationFlags.Unknown)
{
m_invocationFlags = flags = ComputeInvocationFlags();
}

Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized);
return flags;
}
Expand All @@ -45,8 +51,7 @@ private MethodBaseInvoker Invoker
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
m_invoker ??= new MethodBaseInvoker(this);
return m_invoker;
return m_invoker ??= new MethodBaseInvoker(this, ArgumentTypes, ReturnType);
}
}
#endregion
Expand Down Expand Up @@ -306,7 +311,7 @@ internal void InvokePropertySetter(object? obj, BindingFlags invokeAttr, Binder?
throw new TargetParameterCountException(SR.Arg_ParmCnt);
}

Invoker.InvokePropertySetter(obj, invokeAttr, binder, parameter, culture);
Invoker.InvokeWith1Arg(obj, invokeAttr, binder, parameter, culture);
}

#endregion
Expand Down
Loading
Loading