From d9b18777698afc0da493c4cc2c050657c34d5b3b Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 6 May 2025 14:55:12 -0500 Subject: [PATCH 1/6] Improve startup perf by avoiding JIT when invoking well-known signatures --- .../System.Private.CoreLib.csproj | 1 + .../Reflection/ConstructorInvoker.CoreCLR.cs | 28 +- .../Reflection/Emit/DynamicMethod.CoreCLR.cs | 16 +- .../System/Reflection/InstanceCalliHelper.cs | 53 +- .../Reflection/MethodBaseInvoker.CoreCLR.cs | 52 +- .../Reflection/MethodInvoker.CoreCLR.cs | 46 +- .../Reflection/MethodInvokerCommon.CoreCLR.cs | 338 +++++++++++ .../RuntimeConstructorInfo.CoreCLR.cs | 11 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 11 +- .../IL/Stubs/InstanceCalliHelperIntrinsics.cs | 2 +- src/coreclr/vm/appdomain.cpp | 4 +- src/coreclr/vm/corelib.h | 4 +- .../System.Private.CoreLib.Shared.projitems | 5 +- .../System/Reflection/ConstructorInvoker.cs | 457 +++++++++------ .../src/System/Reflection/InvokerEmitUtil.cs | 351 ++++++----- .../src/System/Reflection/MethodBase.cs | 51 +- .../MethodBaseInvoker.Constructor.cs | 81 --- .../System/Reflection/MethodBaseInvoker.cs | 553 ++++++++++++------ .../src/System/Reflection/MethodInvoker.cs | 453 +++++++------- .../System/Reflection/MethodInvokerCommon.cs | 173 +++--- .../Reflection/RuntimeConstructorInfo.cs | 32 +- .../System/Reflection/RuntimeMethodInfo.cs | 15 +- .../System.Private.CoreLib.csproj | 1 + .../Reflection/ConstructorInvoker.Mono.cs | 22 +- .../Reflection/MethodBaseInvoker.Mono.cs | 43 +- .../System/Reflection/MethodInvoker.Mono.cs | 37 +- .../Reflection/MethodInvokerCommon.Mono.cs | 22 + .../Reflection/RuntimeMethodInfo.Mono.cs | 22 +- 28 files changed, 1797 insertions(+), 1087 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs create mode 100644 src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.Mono.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a4ddab439f6c07..9574dae04aec42 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -185,6 +185,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs index f58e24742dc500..3766f4af5f4cfe 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs @@ -1,21 +1,37 @@ // 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 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 { - _signature = constructor.Signature; - _invokeFunc_RefArgs = InterpretedInvoke; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _allocator is not null; + } + private unsafe Delegate CreateInvokeDelegateForInterpreted() + { + 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(IntPtr _, object? obj, 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); } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs index abdb8be14b27a3..61dce1500552c6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs @@ -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); } } @@ -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; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs index f79445292e7cbc..be0b0c90e8e40b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Runtime.CompilerServices; namespace System.Reflection @@ -15,152 +14,188 @@ internal static unsafe class InstanceCalliHelper // Zero parameter methods such as property getters: [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static bool Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static byte Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static char Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static DateTime Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static DateTimeOffset Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static decimal Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static double Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static float Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static Guid Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static short Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static int Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static long Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static nint Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static nuint Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static object? Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static sbyte Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static ushort Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static uint Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static ulong Call(delegate* fn, object o) => fn(o); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o) => fn(o); // One parameter methods with no return such as property setters: [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, bool arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, byte arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, char arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, DateTime arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, DateTimeOffset arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, decimal arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, double arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, float arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, Guid arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, short arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, int arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, long arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, nint arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, nuint arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, sbyte arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, ushort arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, uint arg1) => fn(o, arg1); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, ulong arg1) => fn(o, arg1); // Other methods: [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1, object? arg2) => fn(o, arg1, arg2); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3) => fn(o, arg1, arg2, arg3); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4) => fn(o, arg1, arg2, arg3, arg4); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5) => fn(o, arg1, arg2, arg3, arg4, arg5); [Intrinsic] + [MethodImpl(MethodImplOptions.NoInlining)] internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6) => fn(o, arg1, arg2, arg3, arg4, arg5, arg6); - - [Intrinsic] - internal static void Call(delegate*?, void> fn, object o, IEnumerable? arg1) - => fn(o, arg1); - - [Intrinsic] - internal static void Call(delegate*?, IEnumerable?, void> fn, object o, IEnumerable? arg1, IEnumerable? arg2) - => fn(o, arg1, arg2); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs index 7bb6439468d192..b01e486bcfcb9e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs @@ -1,38 +1,54 @@ // 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) + private bool ShouldAllocate { - _signature = constructor.Signature; - _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _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(IntPtr _, object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeMethodInfo)_method).Signature, isConstructor: false); + + private unsafe object? InterpretedInvoke_Constructor(IntPtr _, object? obj, 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(IntPtr _, object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((DynamicMethod)_method).Signature, isConstructor: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs index 644364a77266e2..dfa6bc8471b8c8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs @@ -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_Method(IntPtr _, object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeMethodInfo)_method).Signature, isConstructor: false); + + private unsafe object? InterpretedInvoke_Constructor(IntPtr _, object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((RuntimeConstructorInfo)_method).Signature, isConstructor: obj is null); - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + private unsafe object? InterpretedInvoke_DynamicMethod(IntPtr _, object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, ((DynamicMethod)_method).Signature, isConstructor: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs new file mode 100644 index 00000000000000..db7eb94efaae1d --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs @@ -0,0 +1,338 @@ +// 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; + +namespace System.Reflection +{ + internal static unsafe partial class MethodInvokerCommon + { + // Zero parameter methods such as property getters: + private static InvokeFunc_Obj0Args InvokeFuncObj0_bool => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_byte => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_char => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_DateTime => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_DateTimeOffset => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_decimal => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_double => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_float => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_Guid => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_short => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_int => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_long => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_nint => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_nuint => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_object => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_sbyte => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_ushort => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_uint => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_ulong => field ??= new InvokeFunc_Obj0Args((fn, o) => InstanceCalliHelper.Call((delegate*)fn, o!)); + private static InvokeFunc_Obj0Args InvokeFuncObj0_void => field ??= new InvokeFunc_Obj0Args((fn, o) => { InstanceCalliHelper.Call((delegate*)fn, o!); return null; }); + + // Zero parameter methods but for Enums; these require a return transform and are not cached. + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_byte_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_short_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_int_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_long_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_sbyte_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_ushort_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_uint_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + private static InvokeFunc_Obj0Args CreateInvokeFuncObj0_ulong_enum(Type enumType) => new InvokeFunc_Obj0Args((fn, o) => Enum.ToObject(enumType, InstanceCalliHelper.Call((delegate*)fn, o!))); + + // One parameter methods such as property setters: + private static InvokeFunc_Obj1Arg InvokeFuncObj1_bool => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (bool)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_byte => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (byte)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_char => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (char)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_DateTime => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (DateTime)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_DateTimeOffset => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (DateTimeOffset)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_decimal => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (decimal)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_double => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (double)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_float => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (float)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_Guid => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (Guid)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_short => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (short)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_int => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (int)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_long => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (long)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_nint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (nint)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_nuint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (nuint)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_object => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (object?)arg1); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_sbyte => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (sbyte)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_ushort => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (ushort)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_uint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (uint)arg1!); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_ulong => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (ulong)arg1!); return null; }); + + // Two or more parameter methods (object-based): + private static InvokeFunc_Obj4Args InvokeFunc_Obj4Args_2 => field ?? new InvokeFunc_Obj4Args((fn, o, arg1, arg2, _, _) => { InstanceCalliHelper.Call((delegate*)fn, o!, arg1, arg2); return null; }); + private static InvokeFunc_Obj4Args InvokeFunc_Obj4Args_3 => field ?? new InvokeFunc_Obj4Args((fn, o, arg1, arg2, arg3, _) => { InstanceCalliHelper.Call((delegate*)fn, o!, arg1, arg2, arg3); return null; }); + private static InvokeFunc_Obj4Args InvokeFunc_Obj4Args_4 => field ?? new InvokeFunc_Obj4Args((fn, o, arg1, arg2, arg3, arg4) => { InstanceCalliHelper.Call((delegate*)fn, o!, arg1, arg2, arg3, arg4); return null; }); + private static InvokeFunc_ObjSpanArgs InvokeFunc_ObjSpanArgs_5 => field ?? new InvokeFunc_ObjSpanArgs((fn, o, args) => { InstanceCalliHelper.Call((delegate*)fn, o!, args[0], args[1], args[2], args[3], args[4]); return null; }); + private static InvokeFunc_ObjSpanArgs InvokeFunc_ObjSpanArgs_6 => field ?? new InvokeFunc_ObjSpanArgs((fn, o, args) => { InstanceCalliHelper.Call((delegate*)fn, o!, args[0], args[1], args[2], args[3], args[4], args[5]); return null; }); + + // For CoreClr, this will eventually return 'false' as we plan on removing the interpreted path since it is + // only used for startup perf and that is now addressed by using calli. + internal static bool UseInterpretedPath => LocalAppContextSwitches.ForceInterpretedInvoke || !RuntimeFeature.IsDynamicCodeSupported; + + /// + /// Returns a delegate that can be used to invoke a method without having to JIT. + /// + private static unsafe bool TryGetCalliFunc(MethodBase method, RuntimeType[] parameterTypes, RuntimeType returnType, InvokerStrategy strategy, out Delegate? invokeFunc) + { + if (strategy == InvokerStrategy.Ref4 || strategy == InvokerStrategy.RefMany) + { + invokeFunc = null; + return false; + } + + Debug.Assert( + strategy == InvokerStrategy.Obj0 || + strategy == InvokerStrategy.Obj1 || + strategy == InvokerStrategy.Obj4 || + strategy == InvokerStrategy.ObjSpan); + + + if (method.DeclaringType is not Type declaringType || + // Instance methods on a value type are not supported. + declaringType.IsValueType || + // Currently we don't need to support statics for startup perf. + method.IsStatic || + !SupportsCalli(method)) + { + invokeFunc = null; + return false; + } + + if (strategy == InvokerStrategy.Obj0) + { + invokeFunc = GetWellKnownSignatureFor0Args(returnType); + if (invokeFunc is not null) + { + return true; + } + } + + if (strategy == InvokerStrategy.Obj1 && returnType == typeof(void)) + { + invokeFunc = GetWellKnownSignatureFor1Arg(parameterTypes[0]); + if (invokeFunc is not null) + { + return true; + } + } + + // We only support void return types and reference type parameters from here primarily to support common constructor patterns. + if (returnType != typeof(void) || !AreAllParametersReferenceTypes(parameterTypes, returnType)) + { + + invokeFunc = null; + return false; + } + + switch (parameterTypes.Length) + { + case 2: + invokeFunc = InvokeFunc_Obj4Args_2; + break; + case 3: + invokeFunc = InvokeFunc_Obj4Args_3; + break; + case 4: + invokeFunc = InvokeFunc_Obj4Args_4; + break; + case 5: + invokeFunc = InvokeFunc_ObjSpanArgs_5; + break; + case 6: + invokeFunc = InvokeFunc_ObjSpanArgs_6; + break; + default: + invokeFunc = null; + return false; + } + + return true; + + static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, RuntimeType returnType) + { + for (int i = 0; i < parameterTypes.Length; i++) + { + RuntimeType type = NormalizeType(parameterTypes[i]); + if (type != typeof(object)) + { + return false; + } + } + + return returnType == typeof(object); + } + } + + /// + /// Returns a delegate that can be used to invoke a method with no arguments which are typically property getters. + /// + public static unsafe Delegate? GetWellKnownSignatureFor0Args(RuntimeType returnType) + { + //In the checks below, the more common types are first to improve perf. + + // Enums require a return transform to convert from the underlying type to the enum type. + if (returnType.IsEnum) + { + Type underlyingType = (RuntimeType)returnType.GetEnumUnderlyingType()!; + if (underlyingType == typeof(int)) return CreateInvokeFuncObj0_int_enum(returnType); + if (underlyingType == typeof(byte)) return CreateInvokeFuncObj0_byte_enum(returnType); + if (underlyingType == typeof(short)) return CreateInvokeFuncObj0_short_enum(returnType); + if (underlyingType == typeof(long)) return CreateInvokeFuncObj0_long_enum(returnType); + if (underlyingType == typeof(uint)) return CreateInvokeFuncObj0_uint_enum(returnType); + if (underlyingType == typeof(sbyte)) return CreateInvokeFuncObj0_sbyte_enum(returnType); + if (underlyingType == typeof(ushort)) return CreateInvokeFuncObj0_ushort_enum(returnType); + Debug.Assert(underlyingType == typeof(ulong)); + return CreateInvokeFuncObj0_ulong_enum(underlyingType); + } + + returnType = NormalizeType(returnType); + + if (returnType.Assembly != typeof(object).Assembly) + { + // We can only hard-code types in this assembly. + return null; + } + + if (returnType == typeof(object)) return InvokeFuncObj0_object; + if (returnType == typeof(void)) return InvokeFuncObj0_void; + if (returnType == typeof(int)) return InvokeFuncObj0_int; + if (returnType == typeof(bool)) return InvokeFuncObj0_bool; + if (returnType == typeof(char)) return InvokeFuncObj0_char; + if (returnType == typeof(byte)) return InvokeFuncObj0_byte; + if (returnType == typeof(short)) return InvokeFuncObj0_short; + if (returnType == typeof(long)) return InvokeFuncObj0_long; + if (returnType == typeof(decimal)) return InvokeFuncObj0_decimal; + if (returnType == typeof(double)) return InvokeFuncObj0_double; + if (returnType == typeof(float)) return InvokeFuncObj0_float; + if (returnType == typeof(DateTime)) return InvokeFuncObj0_DateTime; + if (returnType == typeof(DateTimeOffset)) return InvokeFuncObj0_DateTimeOffset; + if (returnType == typeof(Guid)) return InvokeFuncObj0_Guid; + if (returnType == typeof(nint)) return InvokeFuncObj0_nint; + if (returnType == typeof(nuint)) return InvokeFuncObj0_nuint; + if (returnType == typeof(uint)) return InvokeFuncObj0_uint; + if (returnType == typeof(sbyte)) return InvokeFuncObj0_sbyte; + if (returnType == typeof(ushort)) return InvokeFuncObj0_ushort; + if (returnType == typeof(ulong)) return InvokeFuncObj0_ulong; + + return null; + } + + /// + /// Returns a delegate that can be used to invoke a method with a single argument and no return which are typically property setters. + /// + public static unsafe Delegate? GetWellKnownSignatureFor1Arg(RuntimeType argType) + { + // Enums require a return transform to convert to the underlying type. + if (argType.IsEnum) + { + argType = (RuntimeType)argType.GetEnumUnderlyingType(); + } + else + { + argType = NormalizeType(argType); + + if (argType.Assembly != typeof(object).Assembly) + { + // We can only hard-code types in this assembly. + return null; + } + } + + if (argType == typeof(object)) return InvokeFuncObj1_object; + if (argType == typeof(int)) return InvokeFuncObj1_int; + if (argType == typeof(bool)) return InvokeFuncObj1_bool; + if (argType == typeof(char)) return InvokeFuncObj1_char; + if (argType == typeof(byte)) return InvokeFuncObj1_byte; + if (argType == typeof(short)) return InvokeFuncObj1_short; + if (argType == typeof(long)) return InvokeFuncObj1_long; + if (argType == typeof(decimal)) return InvokeFuncObj1_decimal; + if (argType == typeof(double)) return InvokeFuncObj1_double; + if (argType == typeof(float)) return InvokeFuncObj1_float; + if (argType == typeof(DateTime)) return InvokeFuncObj1_DateTime; + if (argType == typeof(DateTimeOffset)) return InvokeFuncObj1_DateTimeOffset; + if (argType == typeof(Guid)) return InvokeFuncObj1_Guid; + if (argType == typeof(nint)) return InvokeFuncObj1_nint; + if (argType == typeof(nuint)) return InvokeFuncObj1_nuint; + if (argType == typeof(sbyte)) return InvokeFuncObj1_sbyte; + if (argType == typeof(ushort)) return InvokeFuncObj1_ushort; + if (argType == typeof(uint)) return InvokeFuncObj1_uint; + if (argType == typeof(ulong)) return InvokeFuncObj1_ulong; + + return null; + } + + private static RuntimeType NormalizeType(RuntimeType type) + { + if (type.IsClass || type.IsInterface) + { + type = (RuntimeType)typeof(object); + } + + return type; + } + + private static bool SupportsCalli(MethodBase method) + { + if (method is DynamicMethod) + { + return false; + } + + RuntimeType declaringType = (RuntimeType)method.DeclaringType!; + + // Generic types require newobj\call\callvirt. + if (declaringType.IsGenericType || method.IsGenericMethod) + { + return false; + } + + // Arrays have element types that are not supported by calli plus the constructor is special. + if (declaringType.IsArray) + { + return false; + } + + if (method is RuntimeConstructorInfo) + { + // Strings require initialization through newobj. + if (ReferenceEquals(declaringType, typeof(string))) + { + return false; + } + } + else + { + // Check if polymorphic. + // For value types, calli is not supported for boxed Object-based virtual methods such as ToString(). + if (method.IsVirtual && (declaringType.IsValueType || (!declaringType.IsSealed && !method.IsFinal))) + { + return false; + } + } + + return true; + } + + private static bool SupportsParameterTypes(RuntimeType[] parameterTypes, RuntimeType returnType) + { + for (int i = 0; i < parameterTypes.Length; i++) + { + // We already checked the strategy that would tell us if the method has ref parameters. + Debug.Assert(!parameterTypes[i].IsByRef); + + if (parameterTypes[i].IsPointer) + { + return false; + } + } + + return !returnType.IsPointer && !returnType.IsByRef; + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs index c16d10e97b38d5..1ef2c045bb60f6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs @@ -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; } @@ -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 diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index a4bd430b620082..9e8a362aeb3645 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -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; } @@ -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 diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs index 7f188461de7391..099291061d1305 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs @@ -14,7 +14,7 @@ public static MethodIL EmitIL(MethodDesc method) { MethodIL methodIL = EcmaMethodIL.Create((EcmaMethod)method); - if (method.Name.StartsWith("Invoke", StringComparison.Ordinal)) + if (method.Name.Equals("Call", StringComparison.Ordinal)) { methodIL = new ExplicitThisCall(methodIL); } diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 8c17e76a787cf5..67e711dc3bd5c9 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1285,7 +1285,9 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__DYNAMICMETHOD, CLASS__DELEGATE, CLASS__MULTICAST_DELEGATE, - CLASS__METHODBASEINVOKER, + CLASS__METHOD_BASE_INVOKER, + CLASS__METHOD_INVOKER_COMMON, + CLASS__INSTANCE_CALLI_HELPER, CLASS__INITHELPERS, CLASS__STATICSHELPERS, }; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index fe86ea43697e35..e4ecb84d042585 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -515,8 +515,8 @@ DEFINE_CLASS(VECTORT, Numerics, Vector`1) DEFINE_CLASS(MEMBER, Reflection, MemberInfo) -DEFINE_CLASS(METHODBASEINVOKER, Reflection, MethodBaseInvoker) - +DEFINE_CLASS(METHOD_BASE_INVOKER, Reflection, MethodBaseInvoker) +DEFINE_CLASS(METHOD_INVOKER_COMMON, Reflection, MethodInvokerCommon) DEFINE_CLASS(INSTANCE_CALLI_HELPER, Reflection, InstanceCalliHelper) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d3f62e4c34b7d9..e5f96a1979820a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -733,7 +733,6 @@ - @@ -2763,7 +2762,7 @@ - + @@ -2838,4 +2837,4 @@ - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index ab5f4132c23a0e..3ffa7a26b868b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using static System.Reflection.InvokerEmitUtil; using static System.Reflection.MethodBase; -using static System.Reflection.MethodInvokerCommon; namespace System.Reflection { @@ -24,16 +23,13 @@ namespace System.Reflection /// public sealed partial class ConstructorInvoker { - private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; - private InvokeFunc_Obj4Args? _invokeFunc_Obj4Args; - private InvokeFunc_RefArgs? _invokeFunc_RefArgs; - private InvokerStrategy _strategy; - private readonly int _argCount; - private readonly RuntimeType[] _argTypes; - private readonly InvocationFlags _invocationFlags; + private readonly RuntimeType _declaringType; + private readonly IntPtr _functionPointer; + private readonly Delegate _invokeFunc; private readonly InvokerArgFlags[] _invokerArgFlags; private readonly RuntimeConstructorInfo _method; - private readonly bool _needsByRefStrategy; + private readonly RuntimeType[] _parameterTypes; + private readonly InvokerStrategy _strategy; /// /// Creates a new instance of ConstructorInvoker. @@ -48,7 +44,7 @@ public sealed partial class ConstructorInvoker /// public static ConstructorInvoker Create(ConstructorInfo constructor) { - ArgumentNullException.ThrowIfNull(constructor); + ArgumentNullException.ThrowIfNull(constructor, nameof(constructor)); if (constructor is not RuntimeConstructorInfo runtimeConstructor) { @@ -58,14 +54,43 @@ public static ConstructorInvoker Create(ConstructorInfo constructor) return new ConstructorInvoker(runtimeConstructor); } - private ConstructorInvoker(RuntimeConstructorInfo constructor, RuntimeType[] argumentTypes) + private ConstructorInvoker(RuntimeConstructorInfo constructor) { _method = constructor; - _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); - _argTypes = argumentTypes; - _argCount = _argTypes.Length; - Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + if ((constructor.InvocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) + { + _declaringType = null!; + _invokeFunc = null!; + _invokerArgFlags = null!; + _parameterTypes = null!; + return; + } + + _declaringType = (RuntimeType)constructor.DeclaringType!; + _parameterTypes = constructor.ArgumentTypes; + + MethodInvokerCommon.Initialize( + backwardsCompat: false, + constructor, + _parameterTypes, + returnType: (RuntimeType)constructor.DeclaringType!, + callCtorAsMethod: false, + out _functionPointer, + out _invokeFunc!, + out _strategy, + out _invokerArgFlags); + + _invokeFunc ??= CreateInvokeDelegateForInterpreted(); + + if (_functionPointer != IntPtr.Zero) + { +#if MONO + _shouldAllocate = true; +#else + _allocator = _declaringType.GetOrCreateCacheEntry(); +#endif + } } /// @@ -85,16 +110,33 @@ private ConstructorInvoker(RuntimeConstructorInfo constructor, RuntimeType[] arg /// public object Invoke() { - if (_argCount != 0) + if (_invokeFunc is null) + { + _method.ThrowNoInvokeException(); + } + + if (_parameterTypes.Length != 0) { MethodBaseInvoker.ThrowTargetParameterCountException(); } - return InvokeImpl(null, null, null, null); + if (ShouldAllocate) + { + object returnValue = CreateUninitializedObject(); + ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, returnValue); + return returnValue; + } + + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(Span.Empty); + } + + return ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, obj: null)!; } /// - /// Invokes the constructor using the specified parameters. + /// Invokes the constructor using the specified arguments. /// /// /// The first argument for the invoked method. @@ -103,12 +145,31 @@ public object Invoke() /// public object Invoke(object? arg1) { - if (_argCount != 1) + if (_invokeFunc is null) + { + _method.ThrowNoInvokeException(); + } + + if (_parameterTypes.Length != 1) { MethodBaseInvoker.ThrowTargetParameterCountException(); } - return InvokeImpl(arg1, null, null, null); + CheckArgument(ref arg1, 0); + + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(arg1); + } + + if (ShouldAllocate) + { + object returnValue = CreateUninitializedObject(); + ((InvokeFunc_Obj1Arg)_invokeFunc)(_functionPointer, returnValue, arg1); + return returnValue; + } + + return ((InvokeFunc_Obj1Arg)_invokeFunc)(_functionPointer, obj: null, arg1)!; } /// @@ -116,11 +177,21 @@ public object Invoke(object? arg1) /// The second argument for the invoked method. public object Invoke(object? arg1, object? arg2) { - if (_argCount != 2) + if (_invokeFunc is null) + { + _method.ThrowNoInvokeException(); + } + + if (_parameterTypes.Length != 2) { MethodBaseInvoker.ThrowTargetParameterCountException(); } + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(arg1, arg2); + } + return InvokeImpl(arg1, arg2, null, null); } @@ -130,11 +201,21 @@ public object Invoke(object? arg1, object? arg2) /// The third argument for the invoked method. public object Invoke(object? arg1, object? arg2, object? arg3) { - if (_argCount != 3) + if (_invokeFunc is null) + { + _method.ThrowNoInvokeException(); + } + + if (_parameterTypes.Length != 3) { MethodBaseInvoker.ThrowTargetParameterCountException(); } + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(arg1, arg2, arg3); + } + return InvokeImpl(arg1, arg2, arg3, null); } @@ -145,22 +226,27 @@ public object Invoke(object? arg1, object? arg2, object? arg3) /// The fourth argument for the invoked method. public object Invoke(object? arg1, object? arg2, object? arg3, object? arg4) { - if (_argCount != 4) + if (_invokeFunc is null) + { + _method.ThrowNoInvokeException(); + } + + if (_parameterTypes.Length != 4) { MethodBaseInvoker.ThrowTargetParameterCountException(); } + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(arg1, arg2, arg3, arg4); + } + return InvokeImpl(arg1, arg2, arg3, arg4); } private object InvokeImpl(object? arg1, object? arg2, object? arg3, object? arg4) { - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) - { - _method.ThrowNoInvokeException(); - } - - switch (_argCount) + switch (_parameterTypes.Length) { case 4: CheckArgument(ref arg4, 3); @@ -176,22 +262,14 @@ private object InvokeImpl(object? arg1, object? arg2, object? arg3, object? arg4 break; } - // Check fast path first. - if (_invokeFunc_Obj4Args is not null) - { - return _invokeFunc_Obj4Args(obj: null, arg1, arg2, arg3, arg4)!; - } - - if ((_strategy & InvokerStrategy.StrategyDetermined_Obj4Args) == 0) + if (ShouldAllocate) { - DetermineStrategy_Obj4Args(ref _strategy, ref _invokeFunc_Obj4Args, _method, _needsByRefStrategy, backwardsCompat: false); - if (_invokeFunc_Obj4Args is not null) - { - return _invokeFunc_Obj4Args(obj: null, arg1, arg2, arg3, arg4)!; - } + object returnValue = CreateUninitializedObject(); + ((InvokeFunc_Obj4Args)_invokeFunc)(_functionPointer, returnValue, arg1, arg2, arg3, arg4); + return returnValue; } - return InvokeDirectByRef(arg1, arg2, arg3, arg4); + return ((InvokeFunc_Obj4Args)_invokeFunc)(_functionPointer, obj: null, arg1, arg2, arg3, arg4)!; } /// @@ -201,192 +279,225 @@ private object InvokeImpl(object? arg1, object? arg2, object? arg3, object? arg4 /// public object Invoke(Span arguments) { - int argLen = arguments.Length; - if (argLen != _argCount) + if (_invokeFunc is null) { - MethodBaseInvoker.ThrowTargetParameterCountException(); + _method.ThrowNoInvokeException(); } - if (!_needsByRefStrategy) + if (arguments.Length != _parameterTypes.Length) { - // Switch to fast path if possible. - switch (_argCount) - { - case 0: - return InvokeImpl(null, null, null, null); - case 1: - return InvokeImpl(arguments[0], null, null, null); - case 2: - return InvokeImpl(arguments[0], arguments[1], null, null); - case 3: - return InvokeImpl(arguments[0], arguments[1], arguments[2], null); - case 4: - return InvokeImpl(arguments[0], arguments[1], arguments[2], arguments[3]); - default: - break; - } + MethodBaseInvoker.ThrowTargetParameterCountException(); } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + switch (_strategy) { - _method.ThrowNoInvokeException(); - } + case InvokerStrategy.Obj0: + if (ShouldAllocate) + { + object returnValue = CreateUninitializedObject(); + ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, returnValue); + return returnValue; + } + return ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, obj: null)!; + case InvokerStrategy.Obj1: + object? arg1 = arguments[0]; + CheckArgument(ref arg1, 0); - if (argLen > MaxStackAllocArgCount) - { - return InvokeWithManyArgs(arguments); + if (ShouldAllocate) + { + object returnValue = CreateUninitializedObject(); + ((InvokeFunc_Obj1Arg)_invokeFunc)(_functionPointer, returnValue, arg1); + return returnValue; + } + return ((InvokeFunc_Obj1Arg)_invokeFunc)(_functionPointer, obj: null, arg1)!; + case InvokerStrategy.Obj4: + switch (_parameterTypes.Length) + { + case 2: + return InvokeImpl(arguments[0], arguments[1], null, null); + case 3: + return InvokeImpl(arguments[0], arguments[1], arguments[2], null); + default: + Debug.Assert(_parameterTypes.Length == 4); + return InvokeImpl(arguments[0], arguments[1], arguments[2], arguments[3]); + } + case InvokerStrategy.ObjSpan: + return InvokeWithSpanArgs(arguments); + case InvokerStrategy.Ref4: + return InvokeWithRefArgs4(arguments); + default: + Debug.Assert(_strategy == InvokerStrategy.RefMany); + return InvokeWithRefArgsMany(arguments); } - - return InvokeWithFewArgs(arguments); } - internal object InvokeWithFewArgs(Span arguments) + // Version with no copy-back. + private unsafe object InvokeWithSpanArgs(Span arguments) { - Debug.Assert(_argCount <= MaxStackAllocArgCount); + int argCount = _parameterTypes.Length; + IntPtr* pArgStorage = stackalloc IntPtr[argCount]; + NativeMemory.Clear(pArgStorage, (nuint)argCount * (nuint)sizeof(IntPtr)); + Span copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), argCount); + GCFrameRegistration regArgStorage = new((void**)pArgStorage, (uint)argCount, areByRefs: false); + + object returnValue; + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - StackAllocatedArgumentsWithCopyBack stackArgStorage = default; - Span copyOfArgs = ((Span)stackArgStorage._args).Slice(0, _argCount); - scoped Span shouldCopyBack = ((Span)stackArgStorage._shouldCopyBack).Slice(0, _argCount); + for (int i = 0; i < argCount; i++) + { + object? arg = arguments[i]; + CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + } - for (int i = 0; i < _argCount; i++) - { - object? arg = arguments[i]; - shouldCopyBack[i] = CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - } + if (ShouldAllocate) + { + returnValue = CreateUninitializedObject(); + ((InvokeFunc_ObjSpanArgs)_invokeFunc)(_functionPointer, returnValue, copyOfArgs); + } + else + { + returnValue = ((InvokeFunc_ObjSpanArgs)_invokeFunc)(_functionPointer, obj: null, copyOfArgs)!; + } - // Check fast path first. - if (_invokeFunc_ObjSpanArgs is not null) - { - return _invokeFunc_ObjSpanArgs(obj: null, copyOfArgs)!; // No need to call CopyBack here since there are no ref values. } - - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + finally { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); - if (_invokeFunc_ObjSpanArgs is not null) - { - return _invokeFunc_ObjSpanArgs(obj: null, copyOfArgs)!; - } + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } - object ret = InvokeDirectByRefWithFewArgs(copyOfArgs); - CopyBack(arguments, copyOfArgs, shouldCopyBack); - return ret; + return returnValue; } - internal object InvokeDirectByRef(object? arg1 = null, object? arg2 = null, object? arg3 = null, object? arg4 = null) + // Version with no copy-back + private unsafe object InvokeWithRefArgs4(object? arg1, object? arg2 = null, object? arg3 = null, object? arg4 = null) { + int argCount = _parameterTypes.Length; StackAllocatedArguments stackStorage = new(arg1, arg2, arg3, arg4); - return InvokeDirectByRefWithFewArgs(((Span)stackStorage._args).Slice(0, _argCount)); - } - - internal unsafe object InvokeDirectByRefWithFewArgs(Span copyOfArgs) - { - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); - } - + Span arguments = ((Span)(stackStorage._args)).Slice(0, argCount); StackAllocatedByRefs byrefs = default; IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; - for (int i = 0; i < _argCount; i++) + for (int i = 0; i < argCount; i++) { + CheckArgument(ref arguments[i], i); + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : - ByReference.Create(ref copyOfArgs[i]); + ByReference.Create(ref arguments[i]!.GetRawData()) : +#pragma warning disable CS9080 + ByReference.Create(ref arguments[i]); +#pragma warning restore CS9080 } - return _invokeFunc_RefArgs!(obj: null, pByRefFixedStorage)!; + object returnValue; + if (ShouldAllocate) + { + returnValue = CreateUninitializedObject(); + ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, returnValue, pByRefFixedStorage); + } + else + { + returnValue = ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj: null, pByRefFixedStorage)!; + } + + return returnValue; } - internal unsafe object InvokeWithManyArgs(Span arguments) + private unsafe object InvokeWithRefArgs4(Span arguments) { - Span copyOfArgs; - GCFrameRegistration regArgStorage; - object ret; + Debug.Assert(_parameterTypes.Length <= MaxStackAllocArgCount); - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); - } + int argCount = _parameterTypes.Length; + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = ((Span)stackArgStorage._args).Slice(0, argCount); + Span shouldCopyBack = ((Span)stackArgStorage._shouldCopyBack).Slice(0, argCount); + StackAllocatedByRefs byrefs = default; + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; - if (_invokeFunc_ObjSpanArgs is not null) + for (int i = 0; i < _parameterTypes.Length; i++) { - IntPtr* pArgStorage = stackalloc IntPtr[_argCount]; - NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr)); - copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); - regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); - - try - { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; - for (int i = 0; i < _argCount; i++) - { - object? arg = arguments[i]; - CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - } + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : +#pragma warning disable CS9080 + ByReference.Create(ref copyOfArgs[i]); +#pragma warning restore CS9080 + } - ret = _invokeFunc_ObjSpanArgs(obj: null, copyOfArgs)!; - // No need to call CopyBack here since there are no ref values. - } - finally - { - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); - } + object returnValue; + if (ShouldAllocate) + { + returnValue = CreateUninitializedObject(); + ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, returnValue, pByRefFixedStorage); } else { - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); - } + returnValue = ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj: null, pByRefFixedStorage)!; + } - IntPtr* pStorage = stackalloc IntPtr[2 * _argCount]; - NativeMemory.Clear(pStorage, (nuint)(2 * _argCount) * (nuint)sizeof(IntPtr)); - copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return returnValue; + } - IntPtr* pByRefStorage = pStorage + _argCount; - scoped Span shouldCopyBack = stackalloc bool[_argCount]; + private unsafe object InvokeWithRefArgsMany(Span arguments) + { + int argCount = _parameterTypes.Length; + IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); + Span copyOfArgs = new(ref Unsafe.AsRef(pStorage), argCount); - regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); - GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); + Span shouldCopyBack = stackalloc bool[argCount]; - try - { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + IntPtr* pByRefStorage = pStorage + argCount; + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); - for (int i = 0; i < _argCount; i++) - { - object? arg = arguments[i]; - shouldCopyBack[i] = CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : - ByReference.Create(ref Unsafe.AsRef(pStorage + i)); - } + object returnValue; + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); - ret = _invokeFunc_RefArgs!(obj: null, pByRefStorage)!; - CopyBack(arguments, copyOfArgs, shouldCopyBack); + for (int i = 0; i < argCount; i++) + { + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); } - finally + + if (ShouldAllocate) { - GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + returnValue = CreateUninitializedObject(); + ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, returnValue, pByRefStorage); } + else + { + returnValue = ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj: null, pByRefStorage)!; + } + + CopyBack(arguments, copyOfArgs, shouldCopyBack); + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } - return ret; + return returnValue; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - // Copy modified values out. This is only done with ByRef parameters. - internal void CopyBack(Span dest, ReadOnlySpan copyOfParameters, ReadOnlySpan shouldCopyBack) + // Copy modified values out. This is only done with ByRef arguments. + private void CopyBack(Span dest, ReadOnlySpan copyOfParameters, ReadOnlySpan shouldCopyBack) { for (int i = 0; i < dest.Length; i++) { @@ -409,7 +520,7 @@ internal void CopyBack(Span dest, ReadOnlySpan copyOfParameter [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CheckArgument(ref object? arg, int i) { - RuntimeType sigType = _argTypes[i]; + RuntimeType sigType = _parameterTypes[i]; // Convert the type if necessary. // Note that Type.Missing is not supported. diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs index 05c6ee32132359..fe1e365f08a5d2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -7,45 +7,52 @@ namespace System.Reflection { - internal static class InvokerEmitUtil + internal static unsafe class InvokerEmitUtil { // If changed, update native stack walking code that also uses this prefix to ignore reflection frames. private const string InvokeStubPrefix = "InvokeStub_"; - internal unsafe delegate object? InvokeFunc_RefArgs(object? obj, IntPtr* refArguments); - internal delegate object? InvokeFunc_ObjSpanArgs(object? obj, Span arguments); - internal delegate object? InvokeFunc_Obj4Args(object? obj, object? arg1, object? arg2, object? arg3, object? arg4); + internal delegate object? InvokeFunc_Obj0Args(IntPtr functionPointer, object? obj); + internal delegate object? InvokeFunc_Obj1Arg(IntPtr functionPointer, object? obj, object? arg1); + internal delegate object? InvokeFunc_Obj4Args(IntPtr functionPointer, object? obj, object? arg1, object? arg2, object? arg3, object? arg4); + internal delegate object? InvokeFunc_ObjSpanArgs(IntPtr functionPointer, object? obj, Span arguments); + internal delegate object? InvokeFunc_RefArgs(IntPtr functionPointer, object? obj, IntPtr* refArguments); - public static InvokeFunc_Obj4Args CreateInvokeDelegate_Obj4Args(MethodBase method, bool backwardsCompat) + public static InvokeFunc_Obj0Args CreateInvokeDelegateForObj0Args(MethodBase method, bool callCtorAsMethod, bool backwardsCompat) { - Debug.Assert(!method.ContainsGenericParameters); + DynamicMethod dm = CreateDynamicMethod(method, [typeof(IntPtr), typeof(object)]); + ILGenerator il = dm.GetILGenerator(); - bool emitNew = method is RuntimeConstructorInfo; - bool hasThis = !emitNew && !method.IsStatic; + EmitLdargForInstance(il, method, callCtorAsMethod); + EmitCall(il, method, callCtorAsMethod, backwardsCompat); + EmitReturnHandling(il, GetReturnType(method, callCtorAsMethod)); + return (InvokeFunc_Obj0Args)dm.CreateDelegate(typeof(InvokeFunc_Obj0Args), target: null); + } - Type[] delegateParameters = [typeof(object), typeof(object), typeof(object), typeof(object), typeof(object)]; + public static InvokeFunc_Obj1Arg CreateInvokeDelegateForObj1Arg(MethodBase method, bool callCtorAsMethod, bool backwardsCompat) + { + DynamicMethod dm = CreateDynamicMethod(method, [typeof(IntPtr), typeof(object), typeof(object)]); + ILGenerator il = dm.GetILGenerator(); - string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; - var dm = new DynamicMethod( - InvokeStubPrefix + declaringTypeName + method.Name, - returnType: typeof(object), - delegateParameters, - typeof(object).Module, // Use system module to identify our DynamicMethods. - skipVisibility: true); + EmitLdargForInstance(il, method, callCtorAsMethod); + + ReadOnlySpan parameters = method.GetParametersAsSpan(); + Debug.Assert(parameters.Length == 1); + il.Emit(OpCodes.Ldarg_2); + UnboxSpecialType(il, (RuntimeType)parameters[0].ParameterType); + EmitCall(il, method, callCtorAsMethod, backwardsCompat); + EmitReturnHandling(il, GetReturnType(method, callCtorAsMethod)); + return (InvokeFunc_Obj1Arg)dm.CreateDelegate(typeof(InvokeFunc_Obj1Arg), target: null); + } + + public static InvokeFunc_Obj4Args CreateInvokeDelegateForObj4Args(MethodBase method, bool callCtorAsMethod, bool backwardsCompat) + { + DynamicMethod dm = CreateDynamicMethod(method, [typeof(IntPtr), typeof(object), typeof(object), typeof(object), typeof(object), typeof(object)]); ILGenerator il = dm.GetILGenerator(); - // Handle instance methods. - if (hasThis) - { - il.Emit(OpCodes.Ldarg_0); - if (method.DeclaringType!.IsValueType) - { - il.Emit(OpCodes.Unbox, method.DeclaringType); - } - } + EmitLdargForInstance(il, method, callCtorAsMethod); - // Push the arguments. ReadOnlySpan parameters = method.GetParametersAsSpan(); for (int i = 0; i < parameters.Length; i++) { @@ -54,125 +61,58 @@ public static InvokeFunc_Obj4Args CreateInvokeDelegate_Obj4Args(MethodBase metho switch (i) { case 0: - il.Emit(OpCodes.Ldarg_1); - break; - case 1: il.Emit(OpCodes.Ldarg_2); break; - case 2: + case 1: il.Emit(OpCodes.Ldarg_3); break; default: - il.Emit(OpCodes.Ldarg_S, i + 1); + il.Emit(OpCodes.Ldarg_S, i + 2); break; } - if (parameterType.IsPointer || parameterType.IsFunctionPointer) - { - Unbox(il, typeof(IntPtr)); - } - else if (parameterType.IsValueType) - { - Unbox(il, parameterType); - } + UnboxSpecialType(il, parameterType); } - EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); - - // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + EmitCall(il, method, callCtorAsMethod, backwardsCompat); + EmitReturnHandling(il, GetReturnType(method, callCtorAsMethod)); return (InvokeFunc_Obj4Args)dm.CreateDelegate(typeof(InvokeFunc_Obj4Args), target: null); } - public static InvokeFunc_ObjSpanArgs CreateInvokeDelegate_ObjSpanArgs(MethodBase method, bool backwardsCompat) + public static InvokeFunc_ObjSpanArgs CreateInvokeDelegateForObjSpanArgs(MethodBase method, bool callCtorAsMethod, bool backwardsCompat) { - Debug.Assert(!method.ContainsGenericParameters); - - bool emitNew = method is RuntimeConstructorInfo; - bool hasThis = !emitNew && !method.IsStatic; - - // The first parameter is unused but supports treating the DynamicMethod as an instance method which is slightly faster than a static. - Type[] delegateParameters = [typeof(object), typeof(Span)]; - - string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; - var dm = new DynamicMethod( - InvokeStubPrefix + declaringTypeName + method.Name, - returnType: typeof(object), - delegateParameters, - typeof(object).Module, // Use system module to identify our DynamicMethods. - skipVisibility: true); - + DynamicMethod dm = CreateDynamicMethod(method, [typeof(IntPtr), typeof(object), typeof(Span)]); ILGenerator il = dm.GetILGenerator(); - // Handle instance methods. - if (hasThis) - { - il.Emit(OpCodes.Ldarg_0); - if (method.DeclaringType!.IsValueType) - { - il.Emit(OpCodes.Unbox, method.DeclaringType); - } - } + EmitLdargForInstance(il, method, callCtorAsMethod); - // Push the arguments. - ReadOnlySpan parameters = method.GetParametersAsSpan(); - for (int i = 0; i < parameters.Length; i++) + ReadOnlySpan parameterTypes = method.GetParametersAsSpan(); + for (int i = 0; i < parameterTypes.Length; i++) { - RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType; + RuntimeType parameterType = (RuntimeType)parameterTypes[i].ParameterType; - il.Emit(OpCodes.Ldarga_S, 1); + il.Emit(OpCodes.Ldarga_S, 2); il.Emit(OpCodes.Ldc_I4, i); il.Emit(OpCodes.Call, Methods.Span_get_Item()); il.Emit(OpCodes.Ldind_Ref); - if (parameterType.IsPointer || parameterType.IsFunctionPointer) - { - Unbox(il, typeof(IntPtr)); - } - else if (parameterType.IsValueType) - { - Unbox(il, parameterType); - } + UnboxSpecialType(il, parameterType); } - EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); - - // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + EmitCall(il, method, callCtorAsMethod, backwardsCompat); + EmitReturnHandling(il, GetReturnType(method, callCtorAsMethod)); return (InvokeFunc_ObjSpanArgs)dm.CreateDelegate(typeof(InvokeFunc_ObjSpanArgs), target: null); } - public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, bool backwardsCompat) + public static InvokeFunc_RefArgs CreateInvokeDelegateForRefArgs(MethodBase method, bool callCtorAsMethod, bool backwardsCompat) { - Debug.Assert(!method.ContainsGenericParameters); - - bool emitNew = method is RuntimeConstructorInfo; - bool hasThis = !(emitNew || method.IsStatic); - - // The first parameter is unused but supports treating the DynamicMethod as an instance method which is slightly faster than a static. - Type[] delegateParameters = [typeof(object), typeof(object), typeof(IntPtr*)]; - - string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; - var dm = new DynamicMethod( - InvokeStubPrefix + declaringTypeName + method.Name, - returnType: typeof(object), - delegateParameters, - typeof(object).Module, // Use system module to identify our DynamicMethods. - skipVisibility: true); - + DynamicMethod dm = CreateDynamicMethod(method, [typeof(IntPtr), typeof(object), typeof(IntPtr*)]); ILGenerator il = dm.GetILGenerator(); - // Handle instance methods. - if (hasThis) - { - il.Emit(OpCodes.Ldarg_1); - if (method.DeclaringType!.IsValueType) - { - il.Emit(OpCodes.Unbox, method.DeclaringType); - } - } + EmitLdargForInstance(il, method, callCtorAsMethod); - // Push the arguments. - ReadOnlySpan parameters = method.GetParametersAsSpan(); - for (int i = 0; i < parameters.Length; i++) + ReadOnlySpan parameterTypes = method.GetParametersAsSpan(); + for (int i = 0; i < parameterTypes.Length; i++) { il.Emit(OpCodes.Ldarg_2); if (i != 0) @@ -183,16 +123,15 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, il.Emit(OpCodes.Ldfld, Methods.ByReferenceOfByte_Value()); - RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType; + RuntimeType parameterType = (RuntimeType)parameterTypes[i].ParameterType; if (!parameterType.IsByRef) { il.Emit(OpCodes.Ldobj, parameterType.IsPointer || parameterType.IsFunctionPointer ? typeof(IntPtr) : parameterType); } } - EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); - - // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + EmitCall(il, method, callCtorAsMethod, backwardsCompat); + EmitReturnHandling(il, GetReturnType(method, callCtorAsMethod)); return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); } @@ -205,10 +144,59 @@ private static void Unbox(ILGenerator il, Type parameterType) il.Emit(OpCodes.Ldobj, parameterType); } - private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, bool emitNew, bool backwardsCompat) + private static void UnboxSpecialType(ILGenerator il, Type parameterType) + { + if (parameterType.IsPointer || parameterType.IsFunctionPointer) + { + Unbox(il, typeof(IntPtr)); + } + else if (parameterType.IsValueType) + { + Unbox(il, parameterType); + } + } + + private static DynamicMethod CreateDynamicMethod(MethodBase method, Type[] delegateParameters) + { + string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; + string stubName = InvokeStubPrefix + declaringTypeName + method.Name; + + return new DynamicMethod( + stubName, + returnType: typeof(object), + delegateParameters, + method?.DeclaringType is Type declaringType ? declaringType.Module : typeof(object).Module, + skipVisibility: true); // Supports creating the delegate immediately when calling CreateDelegate(). + } + + private static void EmitLdargForInstance(ILGenerator il, MethodBase method, bool callCtorAsMethod) + { + if (method is RuntimeConstructorInfo) + { + if (callCtorAsMethod) + { + EmitLdArg1(); + } + } + else if (!method.IsStatic) + { + EmitLdArg1(); + } + + void EmitLdArg1() + { + il.Emit(OpCodes.Ldarg_1); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + } + } + + private static void EmitCall(ILGenerator il, MethodBase method, bool callCtorAsMethod, bool backwardsCompat) { // For CallStack reasons, don't inline target method. - // Mono interpreter does not support\need this. + // EmitCalli above and Mono interpreter do not need this. if (backwardsCompat && RuntimeFeature.IsDynamicCodeCompiled) { #if MONO @@ -219,10 +207,17 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, #endif } - // Invoke the method. - if (emitNew) + if (method is RuntimeConstructorInfo rci) { - il.Emit(OpCodes.Newobj, (ConstructorInfo)method); + if (callCtorAsMethod) + { + il.Emit(OpCodes.Call, rci); + il.Emit(OpCodes.Ldnull); + } + else + { + il.Emit(OpCodes.Newobj, rci); + } } else if (method.IsStatic || method.DeclaringType!.IsValueType) { @@ -232,79 +227,75 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, { il.Emit(OpCodes.Callvirt, (MethodInfo)method); } + } - // Handle the return. - if (emitNew) + private static Type GetReturnType(MethodBase method, bool callCtorAsMethod) + { + if (method is RuntimeConstructorInfo rci) { - Type returnType = method.DeclaringType!; - if (returnType.IsValueType) - { - il.Emit(OpCodes.Box, returnType); - } + // When calling a constructor as a method we return null. + return callCtorAsMethod ? typeof(object) : rci.DeclaringType!; } - else + + if (method is DynamicMethod dm) { - RuntimeType returnType; - if (method is RuntimeMethodInfo rmi) - { - returnType = (RuntimeType)rmi.ReturnType; - } - else - { - Debug.Assert(method is DynamicMethod); - returnType = (RuntimeType)((DynamicMethod)method).ReturnType; - } + return dm.ReturnType; + } - if (returnType == typeof(void)) - { - il.Emit(OpCodes.Ldnull); - } - else if (returnType.IsValueType) + return ((RuntimeMethodInfo)method).ReturnType; + } + + private static void EmitReturnHandling(ILGenerator il, Type returnType) + { + if (returnType == typeof(void)) + { + il.Emit(OpCodes.Ldnull); + } + else if (returnType.IsValueType) + { + il.Emit(OpCodes.Box, returnType); + } + else if (returnType.IsPointer) + { + il.Emit(OpCodes.Ldtoken, returnType); + il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle()); + il.Emit(OpCodes.Call, Methods.Pointer_Box()); + } + else if (returnType.IsFunctionPointer) + { + il.Emit(OpCodes.Box, typeof(IntPtr)); + } + else if (returnType.IsByRef) + { + // Check for null ref return. + RuntimeType elementType = (RuntimeType)returnType.GetElementType()!; + Label retValueOk = il.DefineLabel(); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Brtrue_S, retValueOk); + il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_NullReference_InvokeNullRefReturned()); + il.MarkLabel(retValueOk); + + // Handle per-type differences. + if (elementType.IsValueType) { - il.Emit(OpCodes.Box, returnType); + il.Emit(OpCodes.Ldobj, elementType); + il.Emit(OpCodes.Box, elementType); } - else if (returnType.IsPointer) + else if (elementType.IsPointer) { - il.Emit(OpCodes.Ldtoken, returnType); + il.Emit(OpCodes.Ldind_Ref); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Ldtoken, elementType); il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle()); il.Emit(OpCodes.Call, Methods.Pointer_Box()); } - else if (returnType.IsFunctionPointer) + else if (elementType.IsFunctionPointer) { il.Emit(OpCodes.Box, typeof(IntPtr)); } - else if (returnType.IsByRef) + else { - // Check for null ref return. - RuntimeType elementType = (RuntimeType)returnType.GetElementType()!; - Label retValueOk = il.DefineLabel(); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Brtrue_S, retValueOk); - il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_NullReference_InvokeNullRefReturned()); - il.MarkLabel(retValueOk); - - // Handle per-type differences. - if (elementType.IsValueType) - { - il.Emit(OpCodes.Ldobj, elementType); - il.Emit(OpCodes.Box, elementType); - } - else if (elementType.IsPointer) - { - il.Emit(OpCodes.Ldind_Ref); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Ldtoken, elementType); - il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle()); - il.Emit(OpCodes.Call, Methods.Pointer_Box()); - } - else if (elementType.IsFunctionPointer) - { - il.Emit(OpCodes.Box, typeof(IntPtr)); - } - else - { - il.Emit(OpCodes.Ldobj, elementType); - } + il.Emit(OpCodes.Ldobj, elementType); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 72be9d4897d582..e9b6160e43807c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -143,7 +143,7 @@ internal virtual Type[] GetParameterTypes() { if (paramInfo.DefaultValue == DBNull.Value) { - throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); + ThrowHelperArgumentExceptionVariableMissing(); } object? arg = paramInfo.DefaultValue; @@ -165,17 +165,43 @@ internal virtual Type[] GetParameterTypes() return arg; } - [Flags] - internal enum InvokerStrategy : int - { - HasBeenInvoked_ObjSpanArgs = 0x1, - StrategyDetermined_ObjSpanArgs = 0x2, + [DoesNotReturn] + internal static void ThrowHelperArgumentExceptionVariableMissing() => + throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); - HasBeenInvoked_Obj4Args = 0x4, - StrategyDetermined_Obj4Args = 0x8, - - HasBeenInvoked_RefArgs = 0x10, - StrategyDetermined_RefArgs = 0x20, + internal enum InvokerStrategy + { + Uninitialized = 0, + + /// + /// Optimized for no arguments. + /// + Obj0 = 1, + + /// + /// Optimized for 1 argument. + /// + Obj1 = 2, + + /// + /// Optimized for 4 arguments or less. + /// + Obj4 = 3, + + /// + /// Optimized for 5 arguments or more. + /// + ObjSpan = 4, + + /// + /// Slower approach for the interpreted path case or to support copy back for 4 arguments or less. + /// + Ref4 = 5, + + /// + /// Slower approach for the interpreted path case or to support copy back for 5+ arguments. + /// + RefMany = 6, } [Flags] @@ -183,7 +209,8 @@ internal enum InvokerArgFlags : int { IsValueType = 0x1, IsValueType_ByRef_Or_Pointer = 0x2, - IsNullableOfT = 0x4, + IsByRefForValueType = 0x4, + IsNullableOfT = 0x8, } [InlineArray(MaxStackAllocArgCount)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs deleted file mode 100644 index 61ce60ff5bd4ec..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static System.Reflection.MethodBase; - -namespace System.Reflection -{ - internal sealed partial class MethodBaseInvoker - { - // The rarely used scenario of calling the constructor on an existing instance. - internal unsafe object? InvokeConstructorWithoutAlloc( - object? obj, - BindingFlags invokeAttr, - Binder? binder, - object?[] parameters, - CultureInfo? culture) - { - bool wrapInTargetInvocationException = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - object? ret; - int argCount = _argCount; - - scoped Span shouldCopyBack = stackalloc bool[argCount]; - IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; - NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); - Span copyOfArgs = new(ref Unsafe.AsRef(pStorage), argCount); - GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); - IntPtr* pByRefStorage = pStorage + argCount; - GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); - - try - { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); - - CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); - - for (int i = 0; i < argCount; i++) - { - *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : - ByReference.Create(ref Unsafe.AsRef(pStorage + i)); - } - - try - { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. - ret = InterpretedInvoke_Constructor(obj, pByRefStorage); - } - catch (Exception e) when (wrapInTargetInvocationException) - { - throw new TargetInvocationException(e); - } - - CopyBack(parameters, copyOfArgs, shouldCopyBack); - return ret; - } - finally - { - GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); - } - } - - internal unsafe object? InvokeConstructorWithoutAlloc(object? obj, bool wrapInTargetInvocationException) - { - try - { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. - return InterpretedInvoke_Constructor(obj, null); - } - catch (Exception e) when (wrapInTargetInvocationException) - { - throw new TargetInvocationException(e); - } - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs index ed2b4cd220a3dc..192fcc36d9feac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs @@ -13,48 +13,131 @@ namespace System.Reflection { - internal sealed partial class MethodBaseInvoker + /// + /// Provides the implementation of the Invoke() methods on MethodInfo, ConstructorInfo and DynamicMethod. + /// + /// + /// This class is known by the runtime in order to ignore reflection frames during stack walks. + /// + [StackTraceHidden] + [DebuggerStepThrough] + internal sealed unsafe partial class MethodBaseInvoker { internal const int MaxStackAllocArgCount = 4; - private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; - private InvokeFunc_RefArgs? _invokeFunc_RefArgs; - private InvokerStrategy _strategy; - internal readonly InvocationFlags _invocationFlags; + private readonly RuntimeType? _declaringType; + private readonly IntPtr _functionPointer; + private readonly Delegate _invokeFunc; + private volatile MethodBaseInvoker? _invokerForCallingCtorAsMethod; private readonly InvokerArgFlags[] _invokerArgFlags; - private readonly RuntimeType[] _argTypes; private readonly MethodBase _method; - private readonly int _argCount; - private readonly bool _needsByRefStrategy; + private readonly RuntimeType[] _parameterTypes; + private readonly InvokerStrategy _strategy; - private MethodBaseInvoker(MethodBase method, RuntimeType[] argumentTypes) + public MethodBaseInvoker(MethodBase method, RuntimeType[] parameterTypes, Type returnType, bool callCtorAsMethod = false) { _method = method; - _argTypes = argumentTypes; - _argCount = _argTypes.Length; + _declaringType = (RuntimeType?)_method.DeclaringType; + _parameterTypes = parameterTypes; + + Initialize( + backwardsCompat: true, + method, + parameterTypes, + (RuntimeType)returnType, + callCtorAsMethod, + out _functionPointer, + out _invokeFunc!, + out _strategy, + out _invokerArgFlags); + + _invokeFunc ??= CreateInvokeDelegateForInterpreted(); + + if (_functionPointer != IntPtr.Zero && method is RuntimeConstructorInfo) + { +#if MONO + _shouldAllocate = true; +#else + _allocator = _declaringType!.GetOrCreateCacheEntry(); +#endif + } + } + + // A clone constructor for calling a constructor as a method where the incoming obj parameter is specified. + private MethodBaseInvoker(MethodBaseInvoker other) + { + Debug.Assert(other._method is RuntimeConstructorInfo); + + _declaringType = other._declaringType; + _functionPointer = other._functionPointer; + _invokeFunc = other._invokeFunc; + _invokerArgFlags = other._invokerArgFlags; + _method = other._method; + _parameterTypes = other._parameterTypes; + _strategy = other._strategy; - Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + if (_functionPointer != IntPtr.Zero) + { +#if MONO + _shouldAllocate = false; +#else + _allocator = null; +#endif + } + else if (UseInterpretedPath) + { + // The same interpreted func can be used; the incoming 'obj' parameter checked for null to determine if an alloc is needed. + _invokeFunc = other._invokeFunc; + } + else + { + _invokeFunc = CreateIlInvokeFunc(backwardsCompat: true, _method, callCtorAsMethod: true, _strategy); + } } + public MethodBaseInvoker GetInvokerForCallingCtorAsMethod() + { + Debug.Assert(_method is RuntimeConstructorInfo); + + MethodBaseInvoker? invoker = _invokerForCallingCtorAsMethod; + if (invoker is null) + { + _invokerForCallingCtorAsMethod = invoker = new MethodBaseInvoker(this); + } + + return invoker; + } + + internal InvokerStrategy Strategy => _strategy; + [DoesNotReturn] internal static void ThrowTargetParameterCountException() { throw new TargetParameterCountException(SR.Arg_ParmCnt); } - - internal unsafe object? InvokeWithNoArgs(object? obj, BindingFlags invokeAttr) + internal object? InvokeWithNoArgs(object? obj, BindingFlags invokeAttr) { - Debug.Assert(_argCount == 0); - - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); - } + Debug.Assert(_parameterTypes.Length == 0); try { - return _invokeFunc_RefArgs!(obj, refArguments: null); + if (ShouldAllocate) + { + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, obj); + return obj; + } + +#if MONO + // Mono may call this method when invoking a constructor with no arguments. + if (_strategy == InvokerStrategy.Ref4) + { + return ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, refArguments: null); + } +#endif + return ((InvokeFunc_Obj0Args)_invokeFunc)(_functionPointer, obj); } catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { @@ -62,264 +145,311 @@ internal static void ThrowTargetParameterCountException() } } - internal object? InvokeWithOneArg( + internal object? InvokePropertySetter( object? obj, BindingFlags invokeAttr, Binder? binder, - object?[] parameters, + object? arg, CultureInfo? culture) { - Debug.Assert(_argCount == 1); - - object? arg = parameters[0]; - var parametersSpan = new ReadOnlySpan(in arg); - - object? copyOfArg = null; - Span copyOfArgs = new(ref copyOfArg); + Debug.Assert(_parameterTypes.Length == 1); - bool copyBack = false; - Span shouldCopyBack = new(ref copyBack); + bool _ = false; + CheckArgument(ref arg, ref _, 0, binder, culture, invokeAttr); - object? ret; - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); - } - - CheckArguments(parametersSpan, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); - - if (_invokeFunc_ObjSpanArgs is not null) + try { - try + if (_strategy == InvokerStrategy.Obj1) { - ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); - } - catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - throw new TargetInvocationException(e); + return ((InvokeFunc_Obj1Arg)_invokeFunc)!(_functionPointer, obj, arg); } + + // The interpreted path needs to use the byref strategies. + return InvokePropertySetter(obj, arg); } - else + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { - ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + throw new TargetInvocationException(e); } + } + + private object? InvokePropertySetter(object? obj, object? arg) + { + Debug.Assert(UseInterpretedPath); + Debug.Assert(_strategy == InvokerStrategy.Ref4); + + StackAllocatedByRefs byrefs = default; + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; + + *(ByReference*)(pByRefFixedStorage) = (_invokerArgFlags[0] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref arg!.GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(ref arg)); - CopyBack(parameters, copyOfArgs, shouldCopyBack); - return ret; + return ((InvokeFunc_RefArgs) _invokeFunc) (_functionPointer, obj, pByRefFixedStorage); } - internal object? InvokeWithFewArgs( + internal object? InvokeWith1Arg( object? obj, BindingFlags invokeAttr, Binder? binder, - object?[] parameters, + object?[] arguments, CultureInfo? culture) { - Debug.Assert(_argCount <= MaxStackAllocArgCount); - - StackAllocatedArgumentsWithCopyBack stackArgStorage = default; - Span copyOfArgs = ((Span)stackArgStorage._args).Slice(0, _argCount); - Span shouldCopyBack = ((Span)stackArgStorage._shouldCopyBack).Slice(0, _argCount); + Debug.Assert(_parameterTypes.Length == 1); - object? ret; - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); - } - - CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + bool copyBack = false; + object? arg1 = arguments[0]; + CheckArgument(ref arg1, ref copyBack, 0, binder, culture, invokeAttr); - if (_invokeFunc_ObjSpanArgs is not null) + try { - try + if (ShouldAllocate) { - ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_Obj1Arg)_invokeFunc)!(_functionPointer, obj, arg1); } - catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + else { - throw new TargetInvocationException(e); + obj = ((InvokeFunc_Obj1Arg)_invokeFunc)!(_functionPointer, obj, arg1); } } - else + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { - ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + throw new TargetInvocationException(e); + } + if (copyBack) + { + CopyBack(arguments, new Span(ref arg1), new Span(ref copyBack)); } - CopyBack(parameters, copyOfArgs, shouldCopyBack); - return ret; + return obj; } - internal unsafe object? InvokeDirectByRefWithFewArgs(object? obj, Span copyOfArgs, BindingFlags invokeAttr) + internal object? InvokeWith4Args( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] arguments, + CultureInfo? culture) { - Debug.Assert(_argCount <= MaxStackAllocArgCount); - - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); - } + Debug.Assert(_parameterTypes.Length <= MaxStackAllocArgCount); - StackAllocatedByRefs byrefs = default; - IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = (Span)stackArgStorage._args; + Span shouldCopyBack = (Span)stackArgStorage._shouldCopyBack; - for (int i = 0; i < _argCount; i++) + for (int i = 0; i < arguments.Length; i++) { - *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : - ByReference.Create(ref copyOfArgs[i]); + copyOfArgs[i] = arguments[i]; + CheckArgument(ref copyOfArgs[i], ref shouldCopyBack[i], i, binder, culture, invokeAttr); } try { - return _invokeFunc_RefArgs!(obj, pByRefFixedStorage); + if (ShouldAllocate) + { + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_Obj4Args)_invokeFunc)(_functionPointer, obj, copyOfArgs[0], copyOfArgs[1], copyOfArgs[2], copyOfArgs[3]); + } + else + { + obj = ((InvokeFunc_Obj4Args)_invokeFunc)(_functionPointer, obj, copyOfArgs[0], copyOfArgs[1], copyOfArgs[2], copyOfArgs[3]); + } } catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { throw new TargetInvocationException(e); } + + CopyBack(arguments, copyOfArgs, shouldCopyBack); + + return obj; } - internal unsafe object? InvokeWithManyArgs( + internal object? InvokeWithSpanArgs( object? obj, BindingFlags invokeAttr, Binder? binder, - object?[] parameters, + object?[] arguments, CultureInfo? culture) { - Debug.Assert(_argCount > MaxStackAllocArgCount); + Debug.Assert(_parameterTypes.Length > MaxStackAllocArgCount); - Span copyOfArgs; - object? ret; - GCFrameRegistration regArgStorage; - Span shouldCopyBack; + int argCount = _parameterTypes.Length; + IntPtr* pArgStorage = stackalloc IntPtr[argCount * 2]; + NativeMemory.Clear(pArgStorage, (nuint)argCount * (nuint)sizeof(IntPtr) * 2); + Span copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), argCount); + GCFrameRegistration regArgStorage = new((void**)pArgStorage, (uint)argCount, areByRefs: false); + Span shouldCopyBack = new Span(pArgStorage + argCount, argCount); + object? returnValue; - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); - } - - if (_invokeFunc_ObjSpanArgs is not null) + try { - IntPtr* pArgStorage = stackalloc IntPtr[_argCount * 2]; - NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr) * 2); - copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); - regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); - shouldCopyBack = new Span(pArgStorage + _argCount, _argCount); + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + CheckArguments(arguments, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); try { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - - CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); - - try + if (ShouldAllocate) { - ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_ObjSpanArgs)_invokeFunc)(_functionPointer, obj, copyOfArgs); + returnValue = obj; } - catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + else { - throw new TargetInvocationException(e); + returnValue = ((InvokeFunc_ObjSpanArgs)_invokeFunc)(_functionPointer, obj, copyOfArgs); } - - CopyBack(parameters, copyOfArgs, shouldCopyBack); } - finally + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + throw new TargetInvocationException(e); } + + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return returnValue; } - else + finally { - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); - } + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + } + } - IntPtr* pStorage = stackalloc IntPtr[3 * _argCount]; - NativeMemory.Clear(pStorage, (nuint)(3 * _argCount) * (nuint)sizeof(IntPtr)); - copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); - regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); - IntPtr* pByRefStorage = pStorage + _argCount; - GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); - shouldCopyBack = new Span(pStorage + _argCount * 2, _argCount); + internal object? InvokeWith4RefArgs( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? arguments, + CultureInfo? culture) + { + Debug.Assert(_parameterTypes.Length <= MaxStackAllocArgCount); + if (_strategy == InvokerStrategy.Ref4) + { + // This method may be called from the interpreted path for a property getter. + Debug.Assert(UseInterpretedPath); + Debug.Assert(_parameterTypes.Length == 0); try { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + return ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, refArguments: null); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + } - CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = (Span)stackArgStorage._args; + Span shouldCopyBack = (Span)stackArgStorage._shouldCopyBack; - for (int i = 0; i < _argCount; i++) - { - *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : - ByReference.Create(ref Unsafe.AsRef(pStorage + i)); - } + StackAllocatedByRefs byrefs = default; + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; - try - { - ret = _invokeFunc_RefArgs!(obj, pByRefStorage); - } - catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - throw new TargetInvocationException(e); - } + CheckArguments(arguments, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); - CopyBack(parameters, copyOfArgs, shouldCopyBack); + for (int i = 0; i < _parameterTypes.Length; i++) + { + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(ref copyOfArgs[i])); + } + + object? returnValue; + try + { + if (ShouldAllocate) + { + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, pByRefFixedStorage); + returnValue = obj; } - finally + else { - GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + returnValue = ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, pByRefFixedStorage); } } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } - return ret; + CopyBack(arguments!, copyOfArgs, shouldCopyBack); + return returnValue; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void InvokePropertySetter( + internal object? InvokeWithManyRefArgs( object? obj, BindingFlags invokeAttr, Binder? binder, - object? parameter, + object?[] arguments, CultureInfo? culture) { - Debug.Assert(_argCount == 1); + Debug.Assert(_parameterTypes.Length > MaxStackAllocArgCount); - object? copyOfArg = null; - Span copyOfArgs = new(ref copyOfArg, 1); + object? returnValue; + int argCount = _parameterTypes.Length; - bool copyBack = false; - Span shouldCopyBack = new(ref copyBack, 1); // Not used for setters + IntPtr* pStorage = stackalloc IntPtr[3 * argCount]; + NativeMemory.Clear(pStorage, (nuint)(3 * argCount) * (nuint)sizeof(IntPtr)); - CheckArguments(new ReadOnlySpan(in parameter), copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + Span copyOfArgs = new(ref Unsafe.AsRef(pStorage), argCount); - if (_invokeFunc_ObjSpanArgs is not null) // Fast path check + IntPtr* pByRefStorage = pStorage + argCount; + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); + Span shouldCopyBack = new Span(pStorage + argCount * 2, argCount); + + try { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + + CheckArguments(arguments, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + for (int i = 0; i < argCount; i++) + { + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); + } + try { - _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + if (ShouldAllocate) + { + Debug.Assert(obj is null); + obj = CreateUninitializedObject(); + ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, pByRefStorage); + returnValue = obj; + } + else + { + returnValue = ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, pByRefStorage); + } } catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { throw new TargetInvocationException(e); } + + CopyBack(arguments, copyOfArgs, shouldCopyBack); } - else + finally { - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - // Initialize for next time. - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); - } - - InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } + + return returnValue; } - // Copy modified values out. This is done with ByRef, Type.Missing and parameters changed by the Binder. + // Copy modified values out. This is done with ByRef, Type.Missing and arguments changed by the Binder. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void CopyBack(object?[] dest, Span copyOfParameters, Span shouldCopyBack) { @@ -341,20 +471,62 @@ internal void CopyBack(object?[] dest, Span copyOfParameters, Span parameters, + ReadOnlySpan arguments, Span copyOfParameters, Span shouldCopyBack, Binder? binder, CultureInfo? culture, - BindingFlags invokeAttr - ) + BindingFlags invokeAttr) { - for (int i = 0; i < parameters.Length; i++) + for (int i = 0; i < arguments.Length; i++) { - object? arg = parameters[i]; - RuntimeType sigType = _argTypes[i]; + object? arg = arguments[i]; + RuntimeType sigType = _parameterTypes[i]; // Convert a Type.Missing to the default value. if (ReferenceEquals(arg, Type.Missing)) @@ -364,6 +536,7 @@ BindingFlags invokeAttr } // Convert the type if necessary. + // Check fast path to ignore non-byref types for normalized arguments. if (arg is null) { if ((_invokerArgFlags[i] & InvokerArgFlags.IsValueType_ByRef_Or_Pointer) != 0) @@ -371,12 +544,11 @@ BindingFlags invokeAttr shouldCopyBack[i] = sigType.CheckValue(ref arg, binder, culture, invokeAttr); } } - else if (!ReferenceEquals(arg.GetType(), sigType)) + // Check fast path to ignore when arg type matches signature type. + else if (!ReferenceEquals(sigType, arg.GetType())) { - // Determine if we can use the fast path for byref types. - if (TryByRefFastPath(sigType, ref arg)) + if (((_invokerArgFlags[i] & InvokerArgFlags.IsByRefForValueType) != 0) && HandleByRefForValueType(sigType, ref arg)) { - // Fast path when the value's type matches the signature type of a byref parameter. shouldCopyBack[i] = true; } else @@ -389,18 +561,15 @@ BindingFlags invokeAttr } } - private static bool TryByRefFastPath(RuntimeType type, ref object arg) + private static bool HandleByRefForValueType(RuntimeType type, ref object arg) { - if (RuntimeType.TryGetByRefElementType(type, out RuntimeType? sigElementType) && - ReferenceEquals(sigElementType, arg.GetType())) + RuntimeType elementType = RuntimeTypeHandle.GetElementType(type)!; + Debug.Assert(RuntimeTypeHandle.IsByRef(type) && elementType.IsValueType); + if (ReferenceEquals(elementType, arg.GetType())) { - if (sigElementType.IsValueType) - { - Debug.Assert(!sigElementType.IsNullableOfT, "A true boxed Nullable should never be here."); - // Make a copy to prevent the boxed instance from being directly modified by the method. - arg = RuntimeType.AllocateValueType(sigElementType, arg); - } - + Debug.Assert(!elementType.IsNullableOfT, "A true boxed Nullable should never be here."); + // Make a copy to prevent the boxed instance from being directly modified by the method. + arg = RuntimeType.AllocateValueType(elementType, arg); return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index 09eddfa31f5112..71a5ec48c43da0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; using System.Runtime; using System.Runtime.CompilerServices; @@ -25,17 +26,12 @@ namespace System.Reflection /// public sealed partial class MethodInvoker { - private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; - private InvokeFunc_Obj4Args? _invokeFunc_Obj4Args; - private InvokeFunc_RefArgs? _invokeFunc_RefArgs; - private InvokerStrategy _strategy; - private readonly int _argCount; - private readonly RuntimeType[] _argTypes; - private readonly InvocationFlags _invocationFlags; + private readonly IntPtr _functionPointer; + private readonly Delegate? _invokeFunc; private readonly InvokerArgFlags[] _invokerArgFlags; + private readonly RuntimeType[] _parameterTypes; private readonly MethodBase _method; - private readonly bool _needsByRefStrategy; - private readonly bool _isStatic; + private readonly InvokerStrategy _strategy; /// /// Creates a new instance of MethodInvoker. @@ -50,41 +46,52 @@ public sealed partial class MethodInvoker /// public static MethodInvoker Create(MethodBase method) { - ArgumentNullException.ThrowIfNull(method); + ArgumentNullException.ThrowIfNull(method, nameof(method)); if (method is RuntimeMethodInfo rmi) { - return new MethodInvoker(rmi); + return new MethodInvoker(rmi, rmi.ArgumentTypes, (RuntimeType)rmi.ReturnType, rmi.InvocationFlags); } - if (method is DynamicMethod dm) + if (method is RuntimeConstructorInfo rci) { - return new MethodInvoker(dm); + return new MethodInvoker(rci, rci.ArgumentTypes, returnType: (RuntimeType)typeof(void), rci.InvocationFlags); } - if (method is RuntimeConstructorInfo rci) + if (method is DynamicMethod dm) { - // This is useful for calling a constructor on an already-initialized object - // such as created from RuntimeHelpers.GetUninitializedObject(Type). - MethodInvoker invoker = new MethodInvoker(rci); - - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. - invoker._strategy = GetStrategyForUsingInterpreted(); - - return invoker; + return new MethodInvoker(dm, dm.ArgumentTypes, (RuntimeType)dm.ReturnType, InvocationFlags.Unknown); } throw new ArgumentException(SR.Argument_MustBeRuntimeMethod, nameof(method)); } - private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) + private MethodInvoker(MethodBase method, RuntimeType[] parameterTypes, RuntimeType returnType, InvocationFlags invocationFlags) { _method = method; - _argTypes = argumentTypes; - _argCount = _argTypes.Length; - _isStatic = _method.IsStatic; - Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + if ((invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) + { + _invokeFunc = null!; + _invokerArgFlags = null!; + _parameterTypes = null!; + return; + } + + _parameterTypes = parameterTypes; + + Initialize( + backwardsCompat: false, + method, + _parameterTypes, + returnType, + callCtorAsMethod: method is RuntimeConstructorInfo, + out _functionPointer, + out _invokeFunc!, + out _strategy, + out _invokerArgFlags); + + _invokeFunc ??= CreateInvokeDelegateForInterpreted(); } /// @@ -115,12 +122,27 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// public object? Invoke(object? obj) { - if (_argCount != 0) + if (_invokeFunc is null) + { + ThrowNoInvokeException(); + } + + if (!_method.IsStatic) + { + ValidateInvokeTarget(obj, _method); + } + + if (_parameterTypes.Length != 0) { MethodBaseInvoker.ThrowTargetParameterCountException(); } - return InvokeImpl(obj, null, null, null, null); + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(obj, Span.Empty); + } + + return ((InvokeFunc_Obj0Args)_invokeFunc!)(_functionPointer, obj); } /// @@ -131,12 +153,28 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// public object? Invoke(object? obj, object? arg1) { - if (_argCount != 1) + if (_invokeFunc is null) + { + ThrowNoInvokeException(); + } + + if (!_method.IsStatic) + { + ValidateInvokeTarget(obj, _method); + } + + if (_parameterTypes.Length != 1) { MethodBaseInvoker.ThrowTargetParameterCountException(); } - return InvokeImpl(obj, arg1, null, null, null); + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(obj, new Span(ref arg1)); + } + + CheckArgument(ref arg1, 0); + return ((InvokeFunc_Obj1Arg)_invokeFunc!)(_functionPointer, obj, arg1); } /// @@ -145,11 +183,26 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// The second argument for the invoked method. public object? Invoke(object? obj, object? arg1, object? arg2) { - if (_argCount != 2) + if (_invokeFunc is null) + { + ThrowNoInvokeException(); + } + + if (!_method.IsStatic) + { + ValidateInvokeTarget(obj, _method); + } + + if (_parameterTypes.Length != 2) { MethodBaseInvoker.ThrowTargetParameterCountException(); } + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(obj, arg1, arg2); + } + return InvokeImpl(obj, arg1, arg2, null, null); } @@ -160,11 +213,26 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// The third argument for the invoked method. public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3) { - if (_argCount != 3) + if (_invokeFunc is null) + { + ThrowNoInvokeException(); + } + + if (!_method.IsStatic) + { + ValidateInvokeTarget(obj, _method); + } + + if (_parameterTypes.Length != 3) { MethodBaseInvoker.ThrowTargetParameterCountException(); } + if (_strategy == InvokerStrategy.Ref4) + { + return InvokeWithRefArgs4(obj, arg1, arg2, arg3); + } + return InvokeImpl(obj, arg1, arg2, arg3, null); } @@ -176,27 +244,32 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// The fourth argument for the invoked method. public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) { - if (_argCount != 4) + if (_invokeFunc is null) { - MethodBaseInvoker.ThrowTargetParameterCountException(); + ThrowNoInvokeException(); } - return InvokeImpl(obj, arg1, arg2, arg3, arg4); - } + if (!_method.IsStatic) + { + ValidateInvokeTarget(obj, _method); + } - private object? InvokeImpl(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) - { - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) + if (_parameterTypes.Length != 4) { - ThrowForBadInvocationFlags(); + MethodBaseInvoker.ThrowTargetParameterCountException(); } - if (!_isStatic) + if (_strategy == InvokerStrategy.Ref4) { - ValidateInvokeTarget(obj, _method); + return InvokeWithRefArgs4(obj, arg1, arg2, arg3, arg4); } - switch (_argCount) + return InvokeImpl(obj, arg1, arg2, arg3, arg4); + } + + private object? InvokeImpl(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) + { + switch (_parameterTypes.Length) { case 4: CheckArgument(ref arg4, 3); @@ -212,22 +285,7 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) break; } - // Check fast path first. - if (_invokeFunc_Obj4Args is not null) - { - return _invokeFunc_Obj4Args(obj, arg1, arg2, arg3, arg4); - } - - if ((_strategy & InvokerStrategy.StrategyDetermined_Obj4Args) == 0) - { - DetermineStrategy_Obj4Args(ref _strategy, ref _invokeFunc_Obj4Args, _method, _needsByRefStrategy, backwardsCompat: false); - if (_invokeFunc_Obj4Args is not null) - { - return _invokeFunc_Obj4Args(obj, arg1, arg2, arg3, arg4); - } - } - - return InvokeDirectByRef(obj, arg1, arg2, arg3, arg4); + return ((InvokeFunc_Obj4Args)_invokeFunc!)(_functionPointer, obj, arg1, arg2, arg3, arg4); } /// @@ -238,208 +296,173 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) /// public object? Invoke(object? obj, Span arguments) { - int argLen = arguments.Length; - if (argLen != _argCount) - { - MethodBaseInvoker.ThrowTargetParameterCountException(); - } - - if (!_needsByRefStrategy) + if (_invokeFunc is null) { - // Switch to fast path if possible. - switch (_argCount) - { - case 0: - return InvokeImpl(obj, null, null, null, null); - case 1: - return InvokeImpl(obj, arguments[0], null, null, null); - case 2: - return InvokeImpl(obj, arguments[0], arguments[1], null, null); - case 3: - return InvokeImpl(obj, arguments[0], arguments[1], arguments[2], null); - case 4: - return InvokeImpl(obj, arguments[0], arguments[1], arguments[2], arguments[3]); - default: - break; - } + ThrowNoInvokeException(); } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) - { - ThrowForBadInvocationFlags(); - } - - if (!_isStatic) + if (!_method.IsStatic) { ValidateInvokeTarget(obj, _method); } - if (argLen > MaxStackAllocArgCount) + if (arguments.Length != _parameterTypes.Length) { - return InvokeWithManyArgs(obj, arguments); + MethodBaseInvoker.ThrowTargetParameterCountException(); } - return InvokeWithFewArgs(obj, arguments); - } - - private void ThrowForBadInvocationFlags() - { - if (_method is RuntimeMethodInfo rmi) + switch (_strategy) { - rmi.ThrowNoInvokeException(); + case InvokerStrategy.Obj0: + return ((InvokeFunc_Obj0Args)_invokeFunc!)(_functionPointer, obj); + case InvokerStrategy.Obj1: + object? arg1 = arguments[0]; + CheckArgument(ref arg1, 0); + return ((InvokeFunc_Obj1Arg)_invokeFunc!)(_functionPointer, obj, arg1); + case InvokerStrategy.Obj4: + switch (_parameterTypes.Length) + { + case 2: + return InvokeImpl(obj, arguments[0], arguments[1], null, null); + case 3: + return InvokeImpl(obj, arguments[0], arguments[1], arguments[2], null); + default: + Debug.Assert(_parameterTypes.Length == 4); + return InvokeImpl(obj, arguments[0], arguments[1], arguments[2], arguments[3]); + } + case InvokerStrategy.ObjSpan: + return InvokeWithSpanArgs(obj, arguments); + case InvokerStrategy.Ref4: + return InvokeWithRefArgs4(obj, arguments); + default: + Debug.Assert(_strategy == InvokerStrategy.RefMany); + return InvokeWithRefArgsMany(obj, arguments); } - - Debug.Assert(_method is RuntimeConstructorInfo); - ((RuntimeConstructorInfo)_method).ThrowNoInvokeException(); } - internal object? InvokeWithFewArgs(object? obj, Span arguments) + // Version with no copy-back + private unsafe object? InvokeWithSpanArgs(object? obj, Span arguments) { - Debug.Assert(_argCount <= MaxStackAllocArgCount); - - StackAllocatedArgumentsWithCopyBack stackArgStorage = default; - Span copyOfArgs = ((Span)stackArgStorage._args).Slice(0, _argCount); - scoped Span shouldCopyBack = ((Span)stackArgStorage._shouldCopyBack).Slice(0, _argCount); + int argCount = _parameterTypes.Length; + IntPtr* pArgStorage = stackalloc IntPtr[argCount]; + NativeMemory.Clear(pArgStorage, (nuint)argCount * (nuint)sizeof(IntPtr)); + Span copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), argCount); + GCFrameRegistration regArgStorage = new((void**)pArgStorage, (uint)argCount, areByRefs: false); - for (int i = 0; i < _argCount; i++) + try { - object? arg = arguments[i]; - shouldCopyBack[i] = CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - } + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - // Check fast path first. - if (_invokeFunc_ObjSpanArgs is not null) - { - return _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + for (int i = 0; i < argCount; i++) + { + object? arg = arguments[i]; + CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + } + + return ((InvokeFunc_ObjSpanArgs)_invokeFunc!)(_functionPointer, obj, copyOfArgs); // No need to call CopyBack here since there are no ref values. } - - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + finally { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); - if (_invokeFunc_ObjSpanArgs is not null) - { - return _invokeFunc_ObjSpanArgs(obj, copyOfArgs); - } + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } - - object? ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs); - CopyBack(arguments, copyOfArgs, shouldCopyBack); - return ret; } - internal object? InvokeDirectByRef(object? obj, object? arg1 = null, object? arg2 = null, object? arg3 = null, object? arg4 = null) + // Version with no copy-back + private unsafe object? InvokeWithRefArgs4(object? obj, object? arg1, object? arg2, object? arg3 = null, object? arg4 = null) { + int argCount = _parameterTypes.Length; StackAllocatedArguments stackStorage = new(arg1, arg2, arg3, arg4); - return InvokeDirectByRefWithFewArgs(obj, ((Span)stackStorage._args).Slice(0, _argCount)); - } + Span arguments = ((Span)(stackStorage._args)).Slice(0, argCount); + StackAllocatedByRefs byrefs = default; + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; - internal unsafe object? InvokeDirectByRefWithFewArgs(object? obj, Span copyOfArgs) - { - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + for (int i = 0; i < argCount; i++) { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); + CheckArgument(ref arguments[i], i); + + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref arguments[i]!.GetRawData()) : +#pragma warning disable CS9080 + ByReference.Create(ref arguments[i]); +#pragma warning restore CS9080 } + return ((InvokeFunc_RefArgs)_invokeFunc!)(_functionPointer, obj, pByRefFixedStorage); + } + + private unsafe object? InvokeWithRefArgs4(object? obj, Span arguments) + { + Debug.Assert(_parameterTypes.Length <= MaxStackAllocArgCount); + + int argCount = _parameterTypes.Length; + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = ((Span)stackArgStorage._args).Slice(0, argCount); + Span shouldCopyBack = ((Span)stackArgStorage._shouldCopyBack).Slice(0, argCount); StackAllocatedByRefs byrefs = default; IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; - for (int i = 0; i < _argCount; i++) + for (int i = 0; i < _parameterTypes.Length; i++) { + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : +#pragma warning disable CS9080 ByReference.Create(ref copyOfArgs[i]); +#pragma warning restore CS9080 } - return _invokeFunc_RefArgs!(obj, pByRefFixedStorage); + object? returnValue = ((InvokeFunc_RefArgs)_invokeFunc!)(_functionPointer, obj, pByRefFixedStorage); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return returnValue; } - internal unsafe object? InvokeWithManyArgs(object? obj, Span arguments) + private unsafe object? InvokeWithRefArgsMany(object? obj, Span arguments) { - Span copyOfArgs; - GCFrameRegistration regArgStorage; - object? ret; + int argCount = _parameterTypes.Length; + IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); + Span copyOfArgs = new(ref Unsafe.AsRef(pStorage), argCount); - if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) - { - DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); - } + Span shouldCopyBack = stackalloc bool[argCount]; - if (_invokeFunc_ObjSpanArgs is not null) - { - IntPtr* pArgStorage = stackalloc IntPtr[_argCount]; - NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr)); - copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); - regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); - - try - { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + IntPtr* pByRefStorage = pStorage + argCount; + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); - for (int i = 0; i < _argCount; i++) - { - object? arg = arguments[i]; - CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - } + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); - ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); - // No need to call CopyBack here since there are no ref values. - } - finally + for (int i = 0; i < argCount; i++) { - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); } + + object? returnValue = ((InvokeFunc_RefArgs)_invokeFunc!)(_functionPointer, obj, pByRefStorage); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return returnValue; } - else + finally { - if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) - { - DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); - } - - IntPtr* pStorage = stackalloc IntPtr[2 * _argCount]; - NativeMemory.Clear(pStorage, (nuint)(2 * _argCount) * (nuint)sizeof(IntPtr)); - copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); - - IntPtr* pByRefStorage = pStorage + _argCount; - scoped Span shouldCopyBack = stackalloc bool[_argCount]; - - regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); - GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); - - try - { - GCFrameRegistration.RegisterForGCReporting(®ArgStorage); - GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); - - for (int i = 0; i < _argCount; i++) - { - object? arg = arguments[i]; - shouldCopyBack[i] = CheckArgument(ref arg, i); - copyOfArgs[i] = arg; - *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? - ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : - ByReference.Create(ref Unsafe.AsRef(pStorage + i)); - } - - ret = _invokeFunc_RefArgs!(obj, pByRefStorage); - CopyBack(arguments, copyOfArgs, shouldCopyBack); - } - finally - { - GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); - GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); - } + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } - - return ret; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - // Copy modified values out. This is only done with ByRef parameters. - internal void CopyBack(Span dest, Span copyOfParameters, Span shouldCopyBack) + // Copy modified values out. This is only done with ByRef arguments. + private void CopyBack(Span dest, Span copyOfParameters, Span shouldCopyBack) { for (int i = 0; i < dest.Length; i++) { @@ -462,7 +485,7 @@ internal void CopyBack(Span dest, Span copyOfParameters, Span< [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CheckArgument(ref object? arg, int i) { - RuntimeType sigType = _argTypes[i]; + RuntimeType sigType = _parameterTypes[i]; // Convert the type if necessary. // Note that Type.Missing is not supported. @@ -480,5 +503,17 @@ private bool CheckArgument(ref object? arg, int i) return false; } + + [DoesNotReturn] + private void ThrowNoInvokeException() + { + if (_method is RuntimeMethodInfo rmi) + { + rmi.ThrowNoInvokeException(); + } + + Debug.Assert(_method is RuntimeConstructorInfo); + ((RuntimeConstructorInfo)_method).ThrowNoInvokeException(); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs index 3e8bdb5778970b..0221861a2d4fb1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -2,51 +2,72 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.CompilerServices; using static System.Reflection.InvokerEmitUtil; using static System.Reflection.MethodBase; namespace System.Reflection { - internal static class MethodInvokerCommon + /// + /// Shared functionality for MethodBaseInvoker, MethodInvoker and ConstructorInvoker. + /// + /// + /// This class is known by the runtime in order to ignore reflection frames during stack walks. + /// + internal static partial class MethodInvokerCommon { internal static void Initialize( - RuntimeType[] argumentTypes, + bool backwardsCompat, + MethodBase method, + RuntimeType[] parameterTypes, + RuntimeType returnType, + bool callCtorAsMethod, + out IntPtr functionPointer, + out Delegate? invokeFunc, out InvokerStrategy strategy, - out InvokerArgFlags[] invokerFlags, - out bool needsByRefStrategy) + out InvokerArgFlags[] invokerArgFlags) { - if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) + invokerArgFlags = GetInvokerArgFlags(parameterTypes, out bool needsByRefStrategy); + strategy = GetInvokerStrategy(parameterTypes.Length, needsByRefStrategy); + + if (TryGetCalliFunc(method, parameterTypes, returnType, strategy, out invokeFunc)) { - // Always use the native interpreted invoke. - // Useful for testing, to avoid startup overhead of emit, or for calling a ctor on already initialized object. - strategy = GetStrategyForUsingInterpreted(); + functionPointer = method.MethodHandle.GetFunctionPointer(); + Debug.Assert(functionPointer != IntPtr.Zero); } - else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) + else if (UseInterpretedPath) { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. - strategy = GetStrategyForUsingEmit(); + invokeFunc = null; // The caller will create the invokeFunc. + functionPointer = IntPtr.Zero; } else { - strategy = default; + invokeFunc = CreateIlInvokeFunc(backwardsCompat, method, callCtorAsMethod, strategy); + functionPointer = IntPtr.Zero; } + } - int argCount = argumentTypes.Length; - invokerFlags = new InvokerArgFlags[argCount]; + private static InvokerArgFlags[] GetInvokerArgFlags(RuntimeType[] parameterTypes, out bool needsByRefStrategy) + { needsByRefStrategy = false; + int argCount = parameterTypes.Length; + InvokerArgFlags[] invokerFlags = new InvokerArgFlags[argCount]; for (int i = 0; i < argCount; i++) { - RuntimeType type = argumentTypes[i]; + RuntimeType type = (RuntimeType)parameterTypes[i]; if (RuntimeTypeHandle.IsByRef(type)) { type = (RuntimeType)type.GetElementType()!; invokerFlags[i] |= InvokerArgFlags.IsValueType_ByRef_Or_Pointer; needsByRefStrategy = true; - if (type.IsNullableOfT) + if (type.IsValueType) { - invokerFlags[i] |= InvokerArgFlags.IsNullableOfT; + invokerFlags[i] |= InvokerArgFlags.IsByRefForValueType; + + if (type.IsNullableOfT) + { + invokerFlags[i] |= InvokerArgFlags.IsNullableOfT; + } } } @@ -68,22 +89,42 @@ internal static void Initialize( } } } + + return invokerFlags; } - internal static InvokerStrategy GetStrategyForUsingInterpreted() + private static InvokerStrategy GetInvokerStrategy(int argCount, bool needsByRefStrategy) { - // This causes the default strategy, which is interpreted, to always be used. - return InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs; + if (needsByRefStrategy || UseInterpretedPath) + { + return argCount <= 4 ? InvokerStrategy.Ref4 : InvokerStrategy.RefMany; + } + + return argCount switch + { + 0 => InvokerStrategy.Obj0, + 1 => InvokerStrategy.Obj1, + 2 or 3 or 4 => InvokerStrategy.Obj4, + _ => InvokerStrategy.ObjSpan + }; } - private static InvokerStrategy GetStrategyForUsingEmit() + internal static Delegate CreateIlInvokeFunc(bool backwardsCompat, MethodBase method, bool callCtorAsMethod, InvokerStrategy strategy) { - // This causes the emit strategy, if supported, to be used on the first call as well as subsequent calls. - return InvokerStrategy.HasBeenInvoked_Obj4Args | InvokerStrategy.HasBeenInvoked_ObjSpanArgs | InvokerStrategy.HasBeenInvoked_RefArgs; + Debug.Assert(!UseInterpretedPath); + + return strategy switch + { + InvokerStrategy.Obj0 => CreateInvokeDelegateForObj0Args(method, callCtorAsMethod, backwardsCompat), + InvokerStrategy.Obj1 => CreateInvokeDelegateForObj1Arg(method, callCtorAsMethod, backwardsCompat), + InvokerStrategy.Obj4 => CreateInvokeDelegateForObj4Args(method, callCtorAsMethod, backwardsCompat), + InvokerStrategy.ObjSpan => CreateInvokeDelegateForObjSpanArgs(method, callCtorAsMethod, backwardsCompat), + _ => CreateInvokeDelegateForRefArgs(method, callCtorAsMethod, backwardsCompat) + }; } /// - /// Confirm member invocation has an instance and is of the correct type + /// Confirm member invocation has an instance and is of the correct type. /// internal static void ValidateInvokeTarget(object? target, MethodBase method) { @@ -99,87 +140,5 @@ internal static void ValidateInvokeTarget(object? target, MethodBase method) throw new TargetException(SR.Format(SR.RFLCT_Targ_ITargMismatch_WithType, method.DeclaringType, target.GetType())); } } - - internal static void DetermineStrategy_ObjSpanArgs( - ref InvokerStrategy strategy, - ref InvokeFunc_ObjSpanArgs? - invokeFunc_ObjSpanArgs, - MethodBase method, - bool needsByRefStrategy, - bool backwardsCompat) - { - if (needsByRefStrategy) - { - // If ByRefs are used, we can't use this strategy. - strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; - } - else if (((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0) && !Debugger.IsAttached) - { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. - strategy |= InvokerStrategy.HasBeenInvoked_ObjSpanArgs; - } - else - { - if (RuntimeFeature.IsDynamicCodeSupported) - { - invokeFunc_ObjSpanArgs = CreateInvokeDelegate_ObjSpanArgs(method, backwardsCompat); - } - - strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; - } - } - - internal static void DetermineStrategy_Obj4Args( - ref InvokerStrategy strategy, - ref InvokeFunc_Obj4Args? invokeFunc_Obj4Args, - MethodBase method, - bool needsByRefStrategy, - bool backwardsCompat) - { - if (needsByRefStrategy) - { - // If ByRefs are used, we can't use this strategy. - strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; - } - else if (((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0) && !Debugger.IsAttached) - { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. - strategy |= InvokerStrategy.HasBeenInvoked_Obj4Args; - } - else - { - if (RuntimeFeature.IsDynamicCodeSupported) - { - invokeFunc_Obj4Args = CreateInvokeDelegate_Obj4Args(method, backwardsCompat); - } - - strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; - } - } - - internal static void DetermineStrategy_RefArgs( - ref InvokerStrategy strategy, - ref InvokeFunc_RefArgs? invokeFunc_RefArgs, - MethodBase method, - bool backwardsCompat) - { - if (((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0) && !Debugger.IsAttached) - { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. - strategy |= InvokerStrategy.HasBeenInvoked_RefArgs; - } - else - { - if (RuntimeFeature.IsDynamicCodeSupported) - { - invokeFunc_RefArgs = CreateInvokeDelegate_RefArgs(method, backwardsCompat); - } - - strategy |= InvokerStrategy.StrategyDetermined_RefArgs; - } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index 81d6461679cb82..39b621f8b25f8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -10,8 +10,7 @@ namespace System.Reflection { internal sealed partial class RuntimeConstructorInfo : ConstructorInfo { - [MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path - internal InvocationFlags ComputeAndUpdateInvocationFlags() + internal InvocationFlags ComputeInvocationFlags() { InvocationFlags invocationFlags = InvocationFlags.IsConstructor; // this is a given @@ -104,7 +103,9 @@ internal void ThrowNoInvokeException() CultureInfo? culture) { if ((InvocationFlags & InvocationFlags.NoInvoke) != 0) + { ThrowNoInvokeException(); + } if (!IsStatic) { @@ -126,9 +127,18 @@ internal void ThrowNoInvokeException() return null; } - return argCount == 0 ? - Invoker.InvokeConstructorWithoutAlloc(obj!, (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) : - Invoker.InvokeConstructorWithoutAlloc(obj!, invokeAttr, binder, parameters!, culture); + MethodBaseInvoker invoker = Invoker.GetInvokerForCallingCtorAsMethod(); + _ = invoker.Strategy switch + { + 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) + }; + + return null; } [DebuggerStepThrough] @@ -150,12 +160,14 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] MethodBaseInvoker.ThrowTargetParameterCountException(); } - return argCount switch + return Invoker.Strategy switch { - 0 => Invoker.InvokeWithNoArgs(obj: null, invokeAttr)!, - 1 => Invoker.InvokeWithOneArg(obj: null, invokeAttr, binder, parameters!, culture)!, - 2 or 3 or 4 => Invoker.InvokeWithFewArgs(obj: null, invokeAttr, binder, parameters!, culture)!, - _ => Invoker.InvokeWithManyArgs(obj: null, invokeAttr, binder, parameters!, culture)!, + MethodBase.InvokerStrategy.Obj0 => Invoker.InvokeWithNoArgs(obj: null, invokeAttr)!, + MethodBase.InvokerStrategy.Obj1 => Invoker.InvokeWith1Arg(obj: null, invokeAttr, binder, parameters!, culture)!, + MethodBase.InvokerStrategy.Obj4 => Invoker.InvokeWith4Args(obj: null, invokeAttr, binder, parameters!, culture)!, + MethodBase.InvokerStrategy.ObjSpan => Invoker.InvokeWithSpanArgs(obj: null, invokeAttr, binder, parameters!, culture)!, + MethodBase.InvokerStrategy.Ref4 => Invoker.InvokeWith4RefArgs(obj: null, invokeAttr, binder, parameters, culture)!, + _ => Invoker.InvokeWithManyRefArgs(obj : null, invokeAttr, binder, parameters!, culture)! }; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index 2ad0258621f5a6..0c48a8ff09134b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -10,8 +10,7 @@ namespace System.Reflection { internal sealed partial class RuntimeMethodInfo : MethodInfo { - [MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path - internal InvocationFlags ComputeAndUpdateInvocationFlags() + internal InvocationFlags ComputeInvocationFlags() { InvocationFlags invocationFlags = InvocationFlags.Unknown; @@ -121,12 +120,14 @@ internal void ThrowNoInvokeException() MethodBaseInvoker.ThrowTargetParameterCountException(); } - return argCount switch + return 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) }; } } diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 84945b1e106e65..666f1c16ac9348 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -194,6 +194,7 @@ + diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs index e829600332e34d..5894fcd87c3395 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs @@ -1,16 +1,32 @@ // 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.Diagnostics.CodeAnalysis; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; + namespace System.Reflection { public partial class ConstructorInvoker { - internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.ArgumentTypes) + private bool _shouldAllocate; + private bool ShouldAllocate => _shouldAllocate; + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern", Justification = "Internal reflection implementation")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private object CreateUninitializedObject() => RuntimeHelpers.GetUninitializedObject(_declaringType); + + private unsafe Delegate CreateInvokeDelegateForInterpreted() { - _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(IntPtr _, object? obj, IntPtr* args) { object? o = _method.InternalInvoke(obj, args, out Exception? exc); diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs index e4c4b533900042..9b11aee48e1dcb 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs @@ -1,40 +1,55 @@ // 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.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; + namespace System.Reflection { internal partial class MethodBaseInvoker { - internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.ArgumentTypes) - { - _invocationFlags = method.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Method; - } + private bool _shouldAllocate; + private bool ShouldAllocate => _shouldAllocate; - internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.ArgumentTypes) + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077:UnrecognizedReflectionPattern", Justification = "Internal reflection implementation")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private object CreateUninitializedObject() => RuntimeHelpers.GetUninitializedObject(_declaringType!); + + private unsafe Delegate CreateInvokeDelegateForInterpreted() { - _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + Debug.Assert(MethodInvokerCommon.UseInterpretedPath); + Debug.Assert(_strategy == InvokerStrategy.Ref4 || _strategy == InvokerStrategy.RefMany); + + if (_method is RuntimeMethodInfo) + { + return (InvokeFunc_RefArgs)InterpretedInvoke_Method; + } + + Debug.Assert(_method is RuntimeConstructorInfo); + return (InvokeFunc_RefArgs)InterpretedInvoke_Constructor; } - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr *args) + private unsafe object? InterpretedInvoke_Method(IntPtr _, object? obj, IntPtr* args) { - object? o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); + object? ret = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; - return o; + return ret; } - internal unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) + internal unsafe object? InterpretedInvoke_Constructor(IntPtr _, object? obj, IntPtr* args) { - object? o = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); + object? ret = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; - return obj == null ? o : null; + return obj == null ? ret : null; } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs index 1b20c8bdf76fd6..53dc59f139cc1f 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs @@ -1,48 +1,47 @@ // 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 unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.ArgumentTypes) + private unsafe Delegate CreateInvokeDelegateForInterpreted() { - _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.GetRuntimeMethodInfo(), method.ArgumentTypes) - { - _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.ArgumentTypes) - { - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; - _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + Debug.Assert(_method is RuntimeConstructorInfo); + return (InvokeFunc_RefArgs)InterpretedInvoke_Constructor; } - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr *args) + private unsafe object? InterpretedInvoke_Method(IntPtr _, object? obj, IntPtr *args) { - object? o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); + object? ret = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; - return o; + return ret; } - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr *args) + private unsafe object? InterpretedInvoke_Constructor(IntPtr _, object? obj, IntPtr *args) { - object? o = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); + object? ret = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; - return obj == null ? o : null; + return obj == null ? ret : null; } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.Mono.cs new file mode 100644 index 00000000000000..e6a43253baea21 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.Mono.cs @@ -0,0 +1,22 @@ +// 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; +using static System.Reflection.MethodBase; + +namespace System.Reflection +{ + internal static partial class MethodInvokerCommon + { + internal static bool UseInterpretedPath => LocalAppContextSwitches.ForceInterpretedInvoke || !RuntimeFeature.IsDynamicCodeSupported; + +#pragma warning disable IDE0060 // Unused parameters - the Clr partial class implementation may use the parameter name. + // When Mono supports well-known funcs, remove this stub and then move the CoreClr implementation to the shared code. + private static unsafe bool TryGetCalliFunc(MethodBase method, RuntimeType[] parameterTypes, RuntimeType returnType, InvokerStrategy strategy, out Delegate? invokeFunc) + { + invokeFunc = null; + return false; + } +#pragma warning restore IDE0060 + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 4eb998b149412c..43fdbf135f5749 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs @@ -151,13 +151,19 @@ internal sealed unsafe partial class RuntimeMethodInfo : MethodInfo private string? toString; private RuntimeType[]? parameterTypes; private MethodBaseInvoker? invoker; + private InvocationFlags invocationFlags; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = Invoker._invocationFlags; + InvocationFlags flags = invocationFlags; + if (flags == InvocationFlags.Unknown) + { + invocationFlags = flags = ComputeInvocationFlags(); + } + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } @@ -168,8 +174,7 @@ private MethodBaseInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new MethodBaseInvoker(this); - return invoker; + return invoker ??= new MethodBaseInvoker(this, ArgumentTypes, ReturnType); } } @@ -717,13 +722,19 @@ internal sealed unsafe partial class RuntimeConstructorInfo : ConstructorInfo private string? toString; private RuntimeType[]? parameterTypes; private MethodBaseInvoker? invoker; + private InvocationFlags invocationFlags; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = Invoker._invocationFlags; + InvocationFlags flags = invocationFlags; + if (flags == InvocationFlags.Unknown) + { + invocationFlags = flags = ComputeInvocationFlags(); + } + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } @@ -734,8 +745,7 @@ internal MethodBaseInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new MethodBaseInvoker(this); - return invoker; + return invoker ??= new MethodBaseInvoker(this, ArgumentTypes, returnType: typeof(void)); } } From 152f1c966922888f0a83865ac540df19ded83312 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 7 May 2025 10:32:19 -0500 Subject: [PATCH 2/6] Fix issue with byref return; support rare enums --- .../Reflection/MethodInvokerCommon.CoreCLR.cs | 96 ++++++++----------- .../System/Reflection/MethodBaseInvoker.cs | 9 +- .../System/Reflection/MethodInvokerCommon.cs | 3 - 3 files changed, 40 insertions(+), 68 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs index db7eb94efaae1d..7c4ce0c0ef2c36 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.CoreCLR.cs @@ -58,7 +58,7 @@ internal static unsafe partial class MethodInvokerCommon private static InvokeFunc_Obj1Arg InvokeFuncObj1_long => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (long)arg1!); return null; }); private static InvokeFunc_Obj1Arg InvokeFuncObj1_nint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (nint)arg1!); return null; }); private static InvokeFunc_Obj1Arg InvokeFuncObj1_nuint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (nuint)arg1!); return null; }); - private static InvokeFunc_Obj1Arg InvokeFuncObj1_object => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (object?)arg1); return null; }); + private static InvokeFunc_Obj1Arg InvokeFuncObj1_object => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, arg1); return null; }); private static InvokeFunc_Obj1Arg InvokeFuncObj1_sbyte => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (sbyte)arg1!); return null; }); private static InvokeFunc_Obj1Arg InvokeFuncObj1_ushort => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (ushort)arg1!); return null; }); private static InvokeFunc_Obj1Arg InvokeFuncObj1_uint => field ??= new InvokeFunc_Obj1Arg((fn, o, arg1) => { InstanceCalliHelper.Call((delegate*)fn, o!, (uint)arg1!); return null; }); @@ -112,47 +112,42 @@ private static unsafe bool TryGetCalliFunc(MethodBase method, RuntimeType[] para return true; } } - - if (strategy == InvokerStrategy.Obj1 && returnType == typeof(void)) + else if (strategy == InvokerStrategy.Obj1) { - invokeFunc = GetWellKnownSignatureFor1Arg(parameterTypes[0]); - if (invokeFunc is not null) + if (returnType == typeof(void)) { - return true; + invokeFunc = GetWellKnownSignatureFor1Arg(parameterTypes[0]); + if (invokeFunc is not null) + { + return true; + } } } - - // We only support void return types and reference type parameters from here primarily to support common constructor patterns. - if (returnType != typeof(void) || !AreAllParametersReferenceTypes(parameterTypes, returnType)) + // At this point, we only support void return types and reference type parameters. + else if (returnType == typeof(void) && AreAllParametersReferenceTypes(parameterTypes, returnType)) { - - invokeFunc = null; - return false; + switch (parameterTypes.Length) + { + case 2: + invokeFunc = InvokeFunc_Obj4Args_2; + return true; + case 3: + invokeFunc = InvokeFunc_Obj4Args_3; + return true; + case 4: + invokeFunc = InvokeFunc_Obj4Args_4; + return true; + case 5: + invokeFunc = InvokeFunc_ObjSpanArgs_5; + return true; + case 6: + invokeFunc = InvokeFunc_ObjSpanArgs_6; + return true; + } } - switch (parameterTypes.Length) - { - case 2: - invokeFunc = InvokeFunc_Obj4Args_2; - break; - case 3: - invokeFunc = InvokeFunc_Obj4Args_3; - break; - case 4: - invokeFunc = InvokeFunc_Obj4Args_4; - break; - case 5: - invokeFunc = InvokeFunc_ObjSpanArgs_5; - break; - case 6: - invokeFunc = InvokeFunc_ObjSpanArgs_6; - break; - default: - invokeFunc = null; - return false; - } - - return true; + invokeFunc = null; + return false; static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, RuntimeType returnType) { @@ -165,7 +160,7 @@ static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, Runtime } } - return returnType == typeof(object); + return NormalizeType(returnType) == typeof(object); } } @@ -174,11 +169,10 @@ static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, Runtime /// public static unsafe Delegate? GetWellKnownSignatureFor0Args(RuntimeType returnType) { - //In the checks below, the more common types are first to improve perf. - // Enums require a return transform to convert from the underlying type to the enum type. if (returnType.IsEnum) { + // The more common types are first. Type underlyingType = (RuntimeType)returnType.GetEnumUnderlyingType()!; if (underlyingType == typeof(int)) return CreateInvokeFuncObj0_int_enum(returnType); if (underlyingType == typeof(byte)) return CreateInvokeFuncObj0_byte_enum(returnType); @@ -187,18 +181,19 @@ static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, Runtime if (underlyingType == typeof(uint)) return CreateInvokeFuncObj0_uint_enum(returnType); if (underlyingType == typeof(sbyte)) return CreateInvokeFuncObj0_sbyte_enum(returnType); if (underlyingType == typeof(ushort)) return CreateInvokeFuncObj0_ushort_enum(returnType); - Debug.Assert(underlyingType == typeof(ulong)); - return CreateInvokeFuncObj0_ulong_enum(underlyingType); + if (underlyingType == typeof(ulong)) return CreateInvokeFuncObj0_ulong_enum(returnType); + + return null; // We may have a rare enum type. } returnType = NormalizeType(returnType); - if (returnType.Assembly != typeof(object).Assembly) { // We can only hard-code types in this assembly. return null; } + // The more common types are first. if (returnType == typeof(object)) return InvokeFuncObj0_object; if (returnType == typeof(void)) return InvokeFuncObj0_void; if (returnType == typeof(int)) return InvokeFuncObj0_int; @@ -244,6 +239,7 @@ static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, Runtime } } + // The more common types are first. if (argType == typeof(object)) return InvokeFuncObj1_object; if (argType == typeof(int)) return InvokeFuncObj1_int; if (argType == typeof(bool)) return InvokeFuncObj1_bool; @@ -269,7 +265,7 @@ static bool AreAllParametersReferenceTypes(RuntimeType[] parameterTypes, Runtime private static RuntimeType NormalizeType(RuntimeType type) { - if (type.IsClass || type.IsInterface) + if ((type.IsClass && !type.IsByRef && !type.IsPointer) || type.IsInterface) { type = (RuntimeType)typeof(object); } @@ -318,21 +314,5 @@ private static bool SupportsCalli(MethodBase method) return true; } - - private static bool SupportsParameterTypes(RuntimeType[] parameterTypes, RuntimeType returnType) - { - for (int i = 0; i < parameterTypes.Length; i++) - { - // We already checked the strategy that would tell us if the method has ref parameters. - Debug.Assert(!parameterTypes[i].IsByRef); - - if (parameterTypes[i].IsPointer) - { - return false; - } - } - - return !returnType.IsPointer && !returnType.IsByRef; - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs index 192fcc36d9feac..e77e089e58b206 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs @@ -16,11 +16,6 @@ namespace System.Reflection /// /// Provides the implementation of the Invoke() methods on MethodInfo, ConstructorInfo and DynamicMethod. /// - /// - /// This class is known by the runtime in order to ignore reflection frames during stack walks. - /// - [StackTraceHidden] - [DebuggerStepThrough] internal sealed unsafe partial class MethodBaseInvoker { internal const int MaxStackAllocArgCount = 4; @@ -328,11 +323,11 @@ internal static void ThrowTargetParameterCountException() { Debug.Assert(_parameterTypes.Length <= MaxStackAllocArgCount); - if (_strategy == InvokerStrategy.Ref4) + if (_parameterTypes.Length == 0) { // This method may be called from the interpreted path for a property getter. Debug.Assert(UseInterpretedPath); - Debug.Assert(_parameterTypes.Length == 0); + Debug.Assert(_strategy == InvokerStrategy.Ref4); try { return ((InvokeFunc_RefArgs)_invokeFunc)(_functionPointer, obj, refArguments: null); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs index 0221861a2d4fb1..5ddad6e8ace9fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -10,9 +10,6 @@ namespace System.Reflection /// /// Shared functionality for MethodBaseInvoker, MethodInvoker and ConstructorInvoker. /// - /// - /// This class is known by the runtime in order to ignore reflection frames during stack walks. - /// internal static partial class MethodInvokerCommon { internal static void Initialize( From ca220903f448abe6b82ac9851e68f782194cf4a4 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 7 May 2025 16:03:46 -0500 Subject: [PATCH 3/6] Update unloadable assembly test that was flawed since reflection used to use interpreted mode on first call to each method --- .../tests/DI.Tests/ActivatorUtilitiesTests.cs | 121 +++++------------- 1 file changed, 35 insertions(+), 86 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs index af42f9cd08d5ab..00690d4c7796da 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests { - public class ActivatorUtilitiesTests + public sealed class ActivatorUtilitiesTests { [Fact] public void CreateInstance_ClassWithABCS_UsesTheLongestAvailableConstructor() @@ -621,80 +621,61 @@ public void CreateFactory_RemoteExecutor_NoParameters_Success(bool useDynamicCod [InlineData(false)] public void CreateInstance_CollectibleAssembly(bool useDynamicCode) { - if (PlatformDetection.IsNonBundledAssemblyLoadingSupported) + if (!PlatformDetection.IsNonBundledAssemblyLoadingSupported) { - RemoteInvokeOptions options = new(); - if (!useDynamicCode) - { - DisableDynamicCode(options); - } + return; + } - using var remoteHandle = RemoteExecutor.Invoke(static () => + RemoteInvokeOptions options = new(); + if (!useDynamicCode) + { + DisableDynamicCode(options); + } + + using var remoteHandle = RemoteExecutor.Invoke(static () => + { + AssemblyLoadContext loadContext = new("CollectibleAssembly", isCollectible: true); + try { Assert.False(Collectible_IsAssemblyLoaded()); - Collectible_LoadAndCreate(useCollectibleAssembly : true, out WeakReference asmWeakRef, out WeakReference typeWeakRef); - for (int i = 0; (typeWeakRef.IsAlive || asmWeakRef.IsAlive) && (i < 10); i++) + Collectible_LoadAndCreate(loadContext, out WeakReference asmWeakRef, out WeakReference typeWeakRef); + + Assert.True(asmWeakRef.IsAlive, "asmWeakRef.IsAlive"); + Assert.True(typeWeakRef.IsAlive, "typeWeakRef.IsAlive"); + Assert.True(Collectible_IsAssemblyLoaded()); + + loadContext.Unload(); + loadContext = null; + + for (int i = 0; i < 10; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } - // These should be GC'd. + // These should be GC'd by now. Assert.False(asmWeakRef.IsAlive, "asmWeakRef.IsAlive"); Assert.False(typeWeakRef.IsAlive, "typeWeakRef.IsAlive"); Assert.False(Collectible_IsAssemblyLoaded()); - }, options); - } - } - - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData(true)] - [InlineData(false)] - public void CreateInstance_NormalAssembly(bool useDynamicCode) - { - RemoteInvokeOptions options = new(); - if (!useDynamicCode) - { - DisableDynamicCode(options); - } - - using var remoteHandle = RemoteExecutor.Invoke(static () => - { - Assert.False(Collectible_IsAssemblyLoaded()); - Collectible_LoadAndCreate(useCollectibleAssembly: false, out WeakReference asmWeakRef, out WeakReference typeWeakRef); - - for (int i = 0; (typeWeakRef.IsAlive || asmWeakRef.IsAlive) && (i < 10); i++) + } + finally { - GC.Collect(); - GC.WaitForPendingFinalizers(); + if (loadContext is not null) + { + loadContext.Unload(); + } } - - // These will not be GC'd. - Assert.True(asmWeakRef.IsAlive, "alcWeakRef.IsAlive"); - Assert.True(typeWeakRef.IsAlive, "typeWeakRef.IsAlive"); - Assert.True(Collectible_IsAssemblyLoaded()); }, options); } [MethodImpl(MethodImplOptions.NoInlining)] - static void Collectible_LoadAndCreate(bool useCollectibleAssembly, out WeakReference asmWeakRef, out WeakReference typeWeakRef) + static void Collectible_LoadAndCreate(AssemblyLoadContext loadContext, out WeakReference asmWeakRef, out WeakReference typeWeakRef) { - Assembly asm; - object obj; + Assembly asm = loadContext.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, "CollectibleAssembly.dll")); - if (useCollectibleAssembly) - { - asm = MyLoadContext.LoadAsCollectable(); - obj = CreateWithActivator(asm); - Assert.True(obj.GetType().Assembly.IsCollectible); - } - else - { - asm = MyLoadContext.LoadNormal(); - obj = CreateWithActivator(asm); - Assert.False(obj.GetType().Assembly.IsCollectible); - } + object obj = CreateWithActivator(asm); + Assert.True(obj.GetType().Assembly.IsCollectible); Assert.True(Collectible_IsAssemblyLoaded()); asmWeakRef = new WeakReference(asm); @@ -1058,36 +1039,4 @@ public ClassWithStringDefaultValue(string text = "DEFAULT") Text = text; } } - -#if NET - internal class MyLoadContext : AssemblyLoadContext - { - private MyLoadContext() : base(isCollectible: true) - { - } - - public Assembly LoadAssembly() - { - Assembly asm = LoadFromAssemblyPath(GetPath()); - Assert.Equal(GetLoadContext(asm), this); - return asm; - } - - public static Assembly LoadAsCollectable() - { - MyLoadContext alc = new MyLoadContext(); - return alc.LoadAssembly(); - } - - public static Assembly LoadNormal() - { - return Assembly.LoadFrom(GetPath()); - } - - private static string GetPath() - { - return Path.Combine(Directory.GetCurrentDirectory(), "CollectibleAssembly.dll"); - } - } -#endif } From db1b503fe16a835df72e814d55b657fb3958f38b Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 8 May 2025 11:06:38 -0500 Subject: [PATCH 4/6] Add Mono intepreter to ActiveIssue for calling assembly test --- .../tests/System.Reflection.Tests/AssemblyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs index 57a5e1277fcfe9..b4fa06fb30a48e 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs @@ -724,7 +724,7 @@ public static IEnumerable GetCallingAssembly_TestData() } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT), nameof(PlatformDetection.IsMonoInterpreter))] [ActiveIssue("https://github.com/dotnet/runtime/issues/69919", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] [MemberData(nameof(GetCallingAssembly_TestData))] public void GetCallingAssembly(Assembly assembly1, Assembly assembly2, bool expected) From d15dd74c623f4254d31864a6fcbd9705b65713a8 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 8 May 2025 15:25:58 -0500 Subject: [PATCH 5/6] Add Mono intepreter to ActiveIssue for calling assembly test 2 --- .../tests/System.Reflection.Tests/AssemblyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs index b4fa06fb30a48e..e10ef25ba23569 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs @@ -733,7 +733,7 @@ public void GetCallingAssembly(Assembly assembly1, Assembly assembly2, bool expe } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT), nameof(PlatformDetection.IsMonoInterpreter))] [ActiveIssue("https://github.com/dotnet/runtime/issues/69919", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void GetCallingAssemblyInCctor() { From 6f28121467310c04434fd7bb6f84cab2fdd830f3 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Fri, 9 May 2025 09:50:55 -0500 Subject: [PATCH 6/6] Add Mono intepreter to ActiveIssue for calling assembly test 3 --- .../tests/System.Reflection.Tests/AssemblyTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs index e10ef25ba23569..d042c9da8d44d0 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyTests.cs @@ -724,7 +724,7 @@ public static IEnumerable GetCallingAssembly_TestData() } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT), nameof(PlatformDetection.IsMonoInterpreter))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] [ActiveIssue("https://github.com/dotnet/runtime/issues/69919", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] [MemberData(nameof(GetCallingAssembly_TestData))] public void GetCallingAssembly(Assembly assembly1, Assembly assembly2, bool expected) @@ -733,7 +733,8 @@ public void GetCallingAssembly(Assembly assembly1, Assembly assembly2, bool expe } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT), nameof(PlatformDetection.IsMonoInterpreter))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51673", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/115425", TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/69919", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void GetCallingAssemblyInCctor() {