From 4284575e7d3803a2ea0d78f810f1f4675b8e3589 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Sep 2026 12:35:40 -0700 Subject: [PATCH 01/38] Use shared managed thunks for reflection invocation Port the MethodInfo.Invoke layer of #126542 toward #123864. Reuse the managed emit pipeline, promote common shared thunks after 100 calls, cache collectible fallbacks, and remove the native invocation dispatcher and CoreCLR debugger workaround. Preserve constructor invocation on existing instances and cover tier transitions and collectible lifetimes. Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib.csproj | 1 + .../Reflection/ConstructorInvoker.CoreCLR.cs | 14 +- .../Reflection/IntrinsicInvokeHelper.cs | 301 +++++++++ .../Reflection/MethodBaseInvoker.CoreCLR.cs | 28 +- .../Reflection/MethodInvoker.CoreCLR.cs | 21 +- .../src/System/RuntimeHandles.cs | 20 - .../Common/CallingConvention/ArgIterator.cs | 2 - src/coreclr/vm/appdomain.cpp | 2 + src/coreclr/vm/callingconvention.h | 2 - src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/qcallentrypoints.cpp | 1 - src/coreclr/vm/reflectioninvocation.cpp | 587 ------------------ src/coreclr/vm/runtimehandles.h | 8 - .../src/System/Reflection/InvokerEmitUtil.cs | 41 +- .../MethodBaseInvoker.Constructor.cs | 10 +- .../src/System/Reflection/MethodInvoker.cs | 4 +- .../System/Reflection/MethodInvokerCommon.cs | 32 +- .../ConstructorInfoTests.cs | 29 + .../MethodInfoTests.cs | 88 +++ .../MethodInvokerTests.cs | 38 ++ 20 files changed, 555 insertions(+), 675 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a024321cd793c7..df450044461dbb 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -172,6 +172,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 aa351553b79966..6cf00e6edab4f3 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,23 +1,19 @@ // 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.CodeAnalysis; - namespace System.Reflection { public partial class ConstructorInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; - _invokeFunc_RefArgs = InterpretedInvoke; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } - private unsafe object? InterpretedInvoke(object? obj, IntPtr* args) - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); - } + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs new file mode 100644 index 00000000000000..de2705284cf68d --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -0,0 +1,301 @@ +// 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.CodeAnalysis; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reflection +{ + // Shared, precompilable thunks avoid emitting a method-specific stub for cold invocations. + // This type is included in SystemDomain::IsReflectionInvocationMethod for caller stack walks. + internal static unsafe class IntrinsicInvokeHelper + { + private const int SpecializationThreshold = 100; + private const MethodBase.InvokerStrategy StrategyDetermined = + MethodBase.InvokerStrategy.StrategyDetermined_Obj4Args | + MethodBase.InvokerStrategy.StrategyDetermined_ObjSpanArgs | + MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + + internal struct InvokeState + { + internal IntPtr Thunk; + internal IntPtr FunctionPointer; + internal int InvocationCount; + } + + internal static object? Invoke( + ref InvokeState state, + ref MethodBase.InvokerStrategy strategy, + ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, + MethodBase method, + RuntimeType[] argumentTypes, + object? obj, + IntPtr* args, + bool backwardsCompat) + { + var thunk = (delegate*)Volatile.Read(ref state.Thunk); + if (thunk is null) + { + if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) + { + InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; + using (AssemblyBuilder.ForceAllowDynamicCode()) + { + emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); + } + + Volatile.Write(ref invokeFunc, emitDelegate); + strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + return emitDelegate(obj, args); + } + + state.FunctionPointer = functionPointer; + strategy |= StrategyDetermined; + Volatile.Write(ref state.Thunk, (IntPtr)thunk); + } + + if (RuntimeFeature.IsDynamicCodeSupported && + !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && + Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) + { + // Let the normal strategy selection specialize the next invocation's argument path. + strategy &= ~StrategyDetermined; + } + + object? result = thunk(state.FunctionPointer, obj, args, method.DeclaringType); + GC.KeepAlive(method); + return result; + } + + private static bool TryGetShape( + MethodBase method, + ReadOnlySpan argumentTypes, + out delegate* thunk, + out IntPtr functionPointer) + { + thunk = null; + functionPointer = IntPtr.Zero; + + if (method is System.Reflection.Emit.DynamicMethod || + method.ContainsGenericParameters || + (method.CallingConvention & CallingConventions.VarArgs) != 0) + { + return false; + } + + int argCount = argumentTypes.Length; + + // Only reference-typed args qualify for the object-passing fast path. + for (int i = 0; i < argCount; i++) + { + RuntimeType pt = argumentTypes[i]; + if (pt.IsByRef || pt.IsPointer || pt.IsFunctionPointer || pt.IsValueType) + { + return false; + } + } + + if (method is ConstructorInfo && + !method.IsStatic && + method.DeclaringType is Type declaringType && + !declaringType.IsValueType && + !declaringType.IsAbstract && + !declaringType.IsByRefLike && + !declaringType.ContainsGenericParameters && + declaringType != typeof(string)) // see Ctor_0 + { + thunk = argCount switch + { + 0 => &Ctor_0, + 1 => &Ctor_1, + 2 => &Ctor_2, + 3 => &Ctor_3, + _ => null, + }; + + if (thunk is null) + { + return false; + } + + functionPointer = method.MethodHandle.GetFunctionPointer(); + return true; + } + + if (method.IsStatic) + { + Type returnType = method is MethodInfo mi ? mi.ReturnType : typeof(void); + + thunk = argCount switch + { + 0 => ClassifyStatic0Return(returnType), + 1 => ClassifyStatic1Obj(returnType), + 2 => ClassifyStatic2Obj(returnType), + _ => null, + }; + + if (thunk is null) + { + return false; + } + + functionPointer = method.MethodHandle.GetFunctionPointer(); + return true; + } + + return false; + } + + // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into + // the classifier's compiled body. + + private static delegate* ClassifyStatic0Return(Type returnType) + { + if (returnType == typeof(void)) return &Static_Void_0; + if (returnType == typeof(bool)) return &Static_Bool_0; + if (returnType == typeof(byte)) return &Static_Byte_0; + if (returnType == typeof(sbyte)) return &Static_SByte_0; + if (returnType == typeof(char)) return &Static_Char_0; + if (returnType == typeof(short)) return &Static_Short_0; + if (returnType == typeof(ushort)) return &Static_UShort_0; + if (returnType == typeof(int)) return &Static_Int_0; + if (returnType == typeof(uint)) return &Static_UInt_0; + if (returnType == typeof(long)) return &Static_Long_0; + if (returnType == typeof(ulong)) return &Static_ULong_0; + if (returnType == typeof(float)) return &Static_Float_0; + if (returnType == typeof(double)) return &Static_Double_0; + if (returnType == typeof(nint) || returnType.IsFunctionPointer) return &Static_NInt_0; + if (returnType == typeof(nuint)) return &Static_NUInt_0; + + if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) + return &Static_Object_0; + + return null; + } + + private static delegate* ClassifyStatic1Obj(Type returnType) + { + if (returnType == typeof(void)) return &Static_Void_1Obj; + if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) + return &Static_Object_1Obj; + return null; + } + + private static delegate* ClassifyStatic2Obj(Type returnType) + { + if (returnType == typeof(void)) return &Static_Void_2Obj; + return null; + } + + // Per-shape thunks. JIT compiles only the ones used. + + private static object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + ((delegate*)fn)(); + return null; + } + + private static object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + private static object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + => ((delegate*)fn)(); + + private static object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + ((delegate*)fn)(Unsafe.Read((void*)args[0])); + return null; + } + + private static object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + => ((delegate*)fn)(Unsafe.Read((void*)args[0])); + + private static object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + ((delegate*)fn)( + Unsafe.Read((void*)args[0]), + Unsafe.Read((void*)args[1])); + return null; + } + + // Ctor thunks: `obj` non-null = call ctor on existing instance, null = allocate first. + // `string` excluded: `newobj String(...)` is JIT-lowered to a hidden static allocator + // (`METHOD__STRING__CTORF_*` in src/coreclr/vm/corelib.h, wired by + // `ECall::PopulateManagedStringConstructors`); the public ctor has no callable instance + // entry, and `GetUninitializedObject(typeof(string))` is runtime-rejected. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "Caller anchors the ctor MethodBase, keeping its type reachable.")] + private static object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) + { + object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + InstanceCalliHelper.Call((delegate*)fn, instance); + return obj is null ? instance : null; + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "See Ctor_0.")] + private static object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + Unsafe.Read((void*)args[0])); + return obj is null ? instance : null; + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "See Ctor_0.")] + private static object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + Unsafe.Read((void*)args[0]), + Unsafe.Read((void*)args[1])); + return obj is null ? instance : null; + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "See Ctor_0.")] + private static object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + Unsafe.Read((void*)args[0]), + Unsafe.Read((void*)args[1]), + Unsafe.Read((void*)args[2])); + return obj is null ? instance : null; + } + } +} 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 cec67b5e37c193..d7dddf1a764e8a 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,39 +1,43 @@ // 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.CodeAnalysis; using System.Reflection.Emit; namespace System.Reflection { internal partial class MethodBaseInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; _invocationFlags = method.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } internal unsafe MethodBaseInvoker(DynamicMethod method, Signature signature) : this(method, signature.Arguments) { - _signature = signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: true); - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false); + internal unsafe object? InvokeDirectByRef(object? obj, IntPtr* args) + { + if ((_strategy & MethodBase.InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + MethodInvokerCommon.DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); + } + + return _invokeFunc_RefArgs!(obj, args); + } } } 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 e9dcc90fc1b1f7..48b0e67bef659e 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,40 +1,35 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; namespace System.Reflection { public partial class MethodInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; private unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; _invocationFlags = method.ComputeAndUpdateInvocationFlags(); } private unsafe MethodInvoker(DynamicMethod method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; // No _invocationFlags for DynamicMethod. } private unsafe MethodInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invokeFunc_RefArgs = InvokeWithSharedThunk; _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + _needsByRefStrategy = true; } - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false); - - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 2308d6e7fe673c..f6f31d55359bd9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1200,26 +1200,6 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) return new MdUtf8String(name); } - [DebuggerStepThrough] - [DebuggerHidden] - [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_InvokeMethod")] - private static partial void InvokeMethod(ObjectHandleOnStack target, void** arguments, ObjectHandleOnStack sig, Interop.BOOL isConstructor, ObjectHandleOnStack result); - - [DebuggerStepThrough] - [DebuggerHidden] - internal static object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor) - { - object? result = null; - InvokeMethod( - ObjectHandleOnStack.Create(ref target), - arguments, - ObjectHandleOnStack.Create(ref sig), - isConstructor ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, - ObjectHandleOnStack.Create(ref result)); - return result; - } - /// /// For a true boxed Nullable{T}, re-box to a boxed {T} or null, otherwise just return the input. /// diff --git a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs index 76cf68a550c3a2..6402632ee1b3ca 100644 --- a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs +++ b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs @@ -1852,8 +1852,6 @@ private enum AsyncContinuationLocation ASYNC_CONTINUATION_REGISTER_ECX = 0x0080, ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0,*/ - // METHOD_INVOKE_NEEDS_ACTIVATION = 0x0040, // Flag used by ArgIteratorForMethodInvoke - // RETURN_FP_SIZE_SHIFT = 8, // The rest of the flags is cached value of GetFPReturnSize private void ComputeReturnFlags() diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 7175d34a39c686..2756ed858e9fe5 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1243,6 +1243,8 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__DYNAMICMETHOD, CLASS__DELEGATE, CLASS__METHODBASEINVOKER, + CLASS__INSTANCE_CALLI_HELPER, + CLASS__INTRINSIC_INVOKE_HELPER, CLASS__INITHELPERS, CLASS__STATICSHELPERS, }; diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 715f7348f533c3..d596c1dfc2a9da 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -1054,8 +1054,6 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0, #endif - METHOD_INVOKE_NEEDS_ACTIVATION = 0x0100, // Flag used by ArgIteratorForMethodInvoke - RETURN_FP_SIZE_SHIFT = 10, // The rest of the flags is cached value of GetFPReturnSize }; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 99cea0036ae9e0..e8e50db2bed840 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -537,6 +537,7 @@ DEFINE_CLASS(MEMBER, Reflection, MemberInfo) DEFINE_CLASS(METHODBASEINVOKER, Reflection, MethodBaseInvoker) DEFINE_CLASS(INSTANCE_CALLI_HELPER, Reflection, InstanceCalliHelper) +DEFINE_CLASS(INTRINSIC_INVOKE_HELPER, Reflection, IntrinsicInvokeHelper) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) DEFINE_FIELD_U(m_handle, ReflectMethodObject, m_pMD) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 6582a1ccf5e9f4..d79d978bbbc9f7 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -175,7 +175,6 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned) DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency) DllImportEntry(MethodBase_GetCurrentMethod) - DllImportEntry(RuntimeMethodHandle_InvokeMethod) DllImportEntry(RuntimeMethodHandle_ConstructInstantiation) DllImportEntry(RuntimeMethodHandle_GetFunctionPointer) DllImportEntry(RuntimeMethodHandle_GetMethodInstantiation) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 084b5af8c316be..c671758bad8e4e 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -149,593 +149,6 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* p END_QCALL; } -static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - // Validate the argCnt an the Rank. Also allow nested SZARRAY's. - _ASSERTE(argCnt == (int) th.GetRank() || argCnt == (int) th.GetRank() * 2 || - th.GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY); - - // Validate all of the parameters. These all typed as integers - int allocSize = 0; - if (!ClrSafeInt::multiply(sizeof(INT32), argCnt, allocSize)) - COMPlusThrow(kArgumentException, IDS_EE_SIGTOOCOMPLEX); - - INT32* indexes = (INT32*) _alloca((size_t)allocSize); - ZeroMemory(indexes, allocSize); - MethodTable* pMT = CoreLibBinder::GetElementType(ELEMENT_TYPE_I4); - - for (DWORD i=0; i<(DWORD)argCnt; i++) - { - _ASSERTE(args[i] != NULL); - - INT32 size = *(INT32*)args[i]; - ARG_SLOT value = size; - memcpyNoGCRefs(indexes + i, ArgSlotEndiannessFixup(&value, sizeof(INT32)), sizeof(INT32)); - } - - return AllocateArrayEx(th, indexes, argCnt); -} - -static BOOL IsActivationNeededForMethodInvoke(MethodDesc * pMD) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - // The activation for non-generic instance methods is covered by non-null "this pointer" - if (!pMD->IsStatic() && !pMD->HasMethodInstantiation() && !pMD->IsInterface()) - return FALSE; - - // We need to activate the instance at least once - pMD->EnsureActive(); - return FALSE; -} - -class ArgIteratorBaseForMethodInvoke -{ -protected: - SIGNATURENATIVEREF * m_ppNativeSig; - bool m_fHasThis; - -public: - FORCEINLINE CorElementType GetReturnType(TypeHandle * pthValueType) - { - WRAPPER_NO_CONTRACT; - return (*pthValueType = (*m_ppNativeSig)->GetReturnTypeHandle()).GetInternalCorElementType(); - } -protected: - - FORCEINLINE CorElementType GetNextArgumentType(DWORD iArg, TypeHandle * pthValueType) - { - WRAPPER_NO_CONTRACT; - return (*pthValueType = (*m_ppNativeSig)->GetArgumentAt(iArg)).GetInternalCorElementType(); - } - - FORCEINLINE void Reset() - { - LIMITED_METHOD_CONTRACT; - } - - FORCEINLINE BOOL IsRegPassedStruct(TypeHandle th) - { - return th.AsMethodTable()->IsRegPassedStruct(); - } - -#if defined(UNIX_AMD64_ABI) - FORCEINLINE SystemVEightByteRegistersInfo GetEightByteRegistersInfo(TypeHandle th) - { - return th.AsMethodTable()->GetClass()->GetEightByteRegistersInfo(); - } -#endif // defined(UNIX_AMD64_ABI) - -public: - FORCEINLINE BOOL IsRetBuffPassedAsFirstArg() - { - return ::IsRetBuffPassedAsFirstArg(); - } - - BOOL HasThis() - { - LIMITED_METHOD_CONTRACT; - return m_fHasThis; - } - - BOOL HasParamType() - { - LIMITED_METHOD_CONTRACT; - // param type methods are not supported for reflection invoke, so HasParamType is always false for them - return FALSE; - } - - BOOL HasAsyncContinuation() - { - LIMITED_METHOD_CONTRACT; - // async calls are also not supported for reflection invoke - return FALSE; - } - - BOOL IsVarArg() - { - LIMITED_METHOD_CONTRACT; - // vararg methods are not supported for reflection invoke, so IsVarArg is always false for them - return FALSE; - } - - DWORD NumFixedArgs() - { - LIMITED_METHOD_CONTRACT; - return (*m_ppNativeSig)->NumFixedArgs(); - } -}; - -class ArgIteratorForMethodInvoke : public ArgIteratorTemplate -{ -public: - ArgIteratorForMethodInvoke(SIGNATURENATIVEREF * ppNativeSig, BOOL fCtorOfVariableSizedObject) - { - m_ppNativeSig = ppNativeSig; - - m_fHasThis = (*m_ppNativeSig)->HasThis() && !fCtorOfVariableSizedObject; - - DWORD dwFlags = (*m_ppNativeSig)->GetArgIteratorFlags(); - - // Use the cached values if they are available - if (dwFlags & SIZE_OF_ARG_STACK_COMPUTED) - { - m_dwFlags = dwFlags; - m_nSizeOfArgStack = (*m_ppNativeSig)->GetSizeOfArgStack(); - return; - } - - // - // Compute flags and stack argument size, and cache them for next invocation - // - - ForceSigWalk(); - - if (IsActivationNeededForMethodInvoke((*m_ppNativeSig)->GetMethod())) - { - m_dwFlags |= METHOD_INVOKE_NEEDS_ACTIVATION; - } - - (*m_ppNativeSig)->SetSizeOfArgStack(m_nSizeOfArgStack); - _ASSERTE((*m_ppNativeSig)->GetSizeOfArgStack() == m_nSizeOfArgStack); - - // This has to be last - (*m_ppNativeSig)->SetArgIteratorFlags(m_dwFlags); - _ASSERTE((*m_ppNativeSig)->GetArgIteratorFlags() == m_dwFlags); - } - - BOOL IsActivationNeeded() - { - LIMITED_METHOD_CONTRACT; - return (m_dwFlags & METHOD_INVOKE_NEEDS_ACTIVATION) != 0; - } -}; - -extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod( - QCall::ObjectHandleOnStack target, - PVOID* args, // An array of byrefs - QCall::ObjectHandleOnStack pSig, - BOOL fConstructor, - QCall::ObjectHandleOnStack result, - QCallExceptionStatus* qcallError) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - Thread * pThread = GetThread(); - GCX_COOP_REGION_BEGIN(); - - struct - { - OBJECTREF target; - SIGNATURENATIVEREF pSig; - OBJECTREF retVal; - } gc; - gc.target = NULL; - gc.pSig = NULL; - gc.retVal = NULL; - GCPROTECT_BEGIN(gc); - gc.target = target.Get(); - gc.pSig = (SIGNATURENATIVEREF)pSig.Get(); - - MethodDesc* pMeth = gc.pSig->GetMethod(); - TypeHandle ownerType = gc.pSig->GetDeclaringType(); - - if (ownerType.IsSharedByGenericInstantiations()) - { - COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); - } - -#ifdef _DEBUG - if (g_pConfig->ShouldInvokeHalt(pMeth)) - { - _ASSERTE(!"InvokeHalt"); - } -#endif - - BOOL fCtorOfVariableSizedObject = FALSE; - - if (fConstructor) - { - // If we are invoking a constructor on an array then we must - // handle this specially. - if (ownerType.IsArray()) { - gc.retVal = InvokeArrayConstructor(ownerType, - args, - gc.pSig->NumFixedArgs()); - goto Done; - } - - // Variable sized objects, like String instances, allocate themselves - // so they are a special case. - MethodTable * pMT = ownerType.AsMethodTable(); - fCtorOfVariableSizedObject = pMT->HasComponentSize(); - if (!fCtorOfVariableSizedObject) - gc.retVal = pMT->Allocate(); - } - - { - ArgIteratorForMethodInvoke argit(&gc.pSig, fCtorOfVariableSizedObject); - - if (argit.IsActivationNeeded()) - pMeth->EnsureActive(); - CONSISTENCY_CHECK(pMeth->CheckActivated()); - - UINT nStackBytes = argit.SizeOfFrameArgumentArray(); - - // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here - SIZE_T nAllocaSize = TransitionBlock::GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; - - LPBYTE pAlloc = (LPBYTE)_alloca(nAllocaSize); - - LPBYTE pTransitionBlock = pAlloc + TransitionBlock::GetNegSpaceSize(); - - CallDescrData callDescrData; - - callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); - callDescrData.numStackSlots = ALIGN_UP(nStackBytes, TARGET_REGISTER_SIZE) / TARGET_REGISTER_SIZE; -#ifdef CALLDESCR_ARGREGS - callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); -#endif -#ifdef CALLDESCR_RETBUFFARGREG - callDescrData.pRetBuffArg = (UINT64*)(pTransitionBlock + TransitionBlock::GetOffsetOfRetBuffArgReg()); -#endif -#ifdef CALLDESCR_FPARGREGS - callDescrData.pFloatArgumentRegisters = NULL; -#endif -#ifdef CALLDESCR_REGTYPEMAP - callDescrData.dwRegTypeMap = 0; -#endif - callDescrData.fpReturnSize = argit.GetFPReturnSize(); -#ifdef TARGET_WASM - // WASM-TODO: this is now called from the interpreter, so the arguments layout is OK. reconsider with codegen - callDescrData.nArgsSize = nStackBytes; - callDescrData.hasThis = argit.HasThis(); - callDescrData.pTransitionBlock = (TransitionBlock*)pTransitionBlock; - - TypeHandle thValueType; - CorElementType type = argit.GetReturnType(&thValueType); - DWORD retSize = 0; - if (type == ELEMENT_TYPE_TYPEDBYREF) - { - retSize = sizeof(TypedByRef); - } - else if (type == ELEMENT_TYPE_VALUETYPE) - { - retSize = thValueType.GetSize(); - } - - callDescrData.hasRetBuff = retSize > sizeof(callDescrData.returnValue); -#endif // TARGET_WASM - - // This is duplicated logic from MethodDesc::GetCallTarget - PCODE pTarget; - { - if (pMeth->IsVtableMethod()) - { - MethodTable *pMT = gc.target->GetMethodTable(); - GCX_PREEMP(); - pTarget = pMeth->GetSingleCallableAddrOfVirtualizedCode(&gc.target, pMT, ownerType); - } - else - { - GCX_PREEMP(); - pTarget = pMeth->GetSingleCallableAddrOfCode(); - } - } - callDescrData.pTarget = pTarget; - - // Build the arguments on the stack - - GCStress::MaybeTrigger(); - - ProtectValueClassFrame *pProtectValueClassFrame = NULL; - ValueClassInfo *pValueClasses = NULL; - - // if we have the magic Value Class return, we need to allocate that class - // and place a pointer to it on the stack. - - BOOL hasRefReturnAndNeedsBoxing = FALSE; // Indicates that the method has a BYREF return type and the target type needs to be copied into a preallocated boxed object. - - TypeHandle retTH = gc.pSig->GetReturnTypeHandle(); - - TypeHandle refReturnTargetTH; // Valid only if retType == ELEMENT_TYPE_BYREF. Caches the TypeHandle of the byref target. -#ifdef TARGET_WASM - BOOL fHasRetBuffArg = callDescrData.hasRetBuff; -#else - BOOL fHasRetBuffArg = argit.HasRetBuffArg(); -#endif - CorElementType retType = retTH.GetSignatureCorElementType(); - BOOL hasValueTypeReturn = retTH.IsValueType() && retType != ELEMENT_TYPE_VOID; - _ASSERTE(hasValueTypeReturn || !fHasRetBuffArg); // only valuetypes are returned via a return buffer. - if (hasValueTypeReturn) { - gc.retVal = retTH.GetMethodTable()->Allocate(); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - refReturnTargetTH = retTH.AsTypeDesc()->GetTypeParam(); - - // If the target of the byref is a value type, we need to preallocate a boxed object to hold the managed return value. - if (refReturnTargetTH.IsValueType()) - { - _ASSERTE(refReturnTargetTH.GetSignatureCorElementType() != ELEMENT_TYPE_VOID); // Managed Reflection layer has a bouncer for "ref void" returns. - hasRefReturnAndNeedsBoxing = TRUE; - gc.retVal = refReturnTargetTH.GetMethodTable()->Allocate(); - } - } - - // Copy "this" pointer - if (!pMeth->IsStatic() && !fCtorOfVariableSizedObject) { - PVOID pThisPtr; - - if (fConstructor) - { - // Copy "this" pointer: only unbox if type is value type and method is not unboxing stub - if (ownerType.IsValueType() && !pMeth->IsUnboxingStub()) { - // Note that we create a true boxed nullabe and then convert it to a T below - pThisPtr = gc.retVal->GetData(); - } - else - pThisPtr = OBJECTREFToObject(gc.retVal); - } - else if (!pMeth->GetMethodTable()->IsValueType()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - if (pMeth->IsUnboxingStub()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - // Create a true boxed Nullable and use that as the 'this' pointer. - // since what is passed in is just a boxed T - MethodTable* pMT = pMeth->GetMethodTable(); - if (Nullable::IsNullableType(pMT)) { - OBJECTREF bufferObj = pMT->Allocate(); - void* buffer = bufferObj->GetData(); - Nullable::UnBox(buffer, gc.target, pMT); - pThisPtr = buffer; - } - else - pThisPtr = gc.target->UnBox(); - } - } - - *((LPVOID*) (pTransitionBlock + argit.GetThisOffset())) = pThisPtr; - } - - // NO GC AFTER THIS POINT. The object references in the method frame are not protected. - // - // We have already copied "this" pointer so we do not want GC to happen even sooner. Unfortunately, - // we may allocate in the process of copying this pointer that makes it hard to express using contracts. - // - // If an exception occurs a gc may happen but we are going to dump the stack anyway and we do - // not need to protect anything. - - // Allocate a local buffer for the return buffer if necessary - PVOID pLocalRetBuf = nullptr; - - { - BEGINFORBIDGC(); -#ifdef _DEBUG - GCForbidLoaderUseHolder forbidLoaderUse; -#endif - - // Take care of any return arguments - if (fHasRetBuffArg) - { - _ASSERT(hasValueTypeReturn); - PTR_MethodTable pMT = retTH.GetMethodTable(); - size_t localRetBufSize = retTH.GetSize(); - - // Allocate a local buffer. The invoked method will write the return value to this - // buffer which will be copied to gc.retVal later. - pLocalRetBuf = _alloca(localRetBufSize); - ZeroMemory(pLocalRetBuf, localRetBufSize); -#ifdef TARGET_WASM - callDescrData.pRetBuffArg = reinterpret_cast(pLocalRetBuf); -#else - *((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pLocalRetBuf; -#endif - if (pMT->ContainsGCPointers()) - { - pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pLocalRetBuf, pMT, pValueClasses); - } - } - - // copy args - UINT nNumArgs = gc.pSig->NumFixedArgs(); - for (UINT i = 0 ; i < nNumArgs; i++) { - TypeHandle th = gc.pSig->GetArgumentAt(i); - - int ofs = argit.GetNextOffset(); - _ASSERTE(ofs != TransitionBlock::InvalidOffset); - -#ifdef CALLDESCR_REGTYPEMAP - FillInRegTypeMap(ofs, argit.GetArgType(), (BYTE *)&callDescrData.dwRegTypeMap); -#endif - -#ifdef CALLDESCR_FPARGREGS - // Under CALLDESCR_FPARGREGS -ve offsets indicate arguments in floating point registers. If we have at - // least one such argument we point the call worker at the floating point area of the frame (we leave - // it null otherwise since the worker can perform a useful optimization if it knows no floating point - // registers need to be set up). - - if (TransitionBlock::HasFloatRegister(ofs, argit.GetArgLocDescForStructInRegs()) && - (callDescrData.pFloatArgumentRegisters == NULL)) - { - callDescrData.pFloatArgumentRegisters = (FloatArgumentRegisters*) (pTransitionBlock + - TransitionBlock::GetOffsetOfFloatArgumentRegisters()); - } -#endif - - UINT structSize = argit.GetArgSize(); - - ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - -#ifdef ENREGISTERED_PARAMTYPE_MAXSIZE - if (argit.IsArgPassedByRef()) - { - MethodTable* pMT = th.GetMethodTable(); - _ASSERTE(pMT && pMT->IsValueType()); - - PVOID pArgDst = argDest.GetDestinationAddress(); - - PVOID pStackCopy = _alloca(structSize); - *(PVOID *)pArgDst = pStackCopy; - - // save the info into ValueClassInfo - if (pMT->ContainsGCPointers()) - { - pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); - } - - // We need a new ArgDestination that points to the stack copy - argDest = ArgDestination(pStackCopy, 0, NULL); - } -#endif - - InvokeUtil::CopyArg(th, args[i], &argDest); - } - - ENDFORBIDGC(); - } - - if (pValueClasses != NULL) - { - pProtectValueClassFrame = new (_alloca (sizeof (ProtectValueClassFrame))) - ProtectValueClassFrame(pThread, pValueClasses); - } - - // Call the method - CallDescrWorkerWithHandler(&callDescrData); - - if (fHasRetBuffArg) - { - // Copy the return value from the return buffer to the object - if (retTH.GetMethodTable()->ContainsGCPointers()) - { - memmoveGCRefs(gc.retVal->GetData(), pLocalRetBuf, retTH.GetSize()); - } - else - { - memcpyNoGCRefs(gc.retVal->GetData(), pLocalRetBuf, retTH.GetSize()); - } - } - - // It is still illegal to do a GC here. The return type might have/contain GC pointers. - if (fConstructor) - { - // We have a special case for Strings...The object is returned... - if (fCtorOfVariableSizedObject) { - PVOID pReturnValue = &callDescrData.returnValue; - gc.retVal = ObjectToOBJECTREF(*(Object**)pReturnValue); - } - - // If it is a Nullable, box it using Nullable conventions. - // TODO: this double allocates on constructions which is wasteful - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else - if (hasValueTypeReturn || hasRefReturnAndNeedsBoxing) - { - _ASSERTE(gc.retVal != NULL); - - if (hasRefReturnAndNeedsBoxing) - { - // Method has BYREF return and the target type is one that needs boxing. We need to copy into the boxed object we have allocated for this purpose. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - CopyValueClass(gc.retVal->GetData(), pReturnedReference, gc.retVal->GetMethodTable()); - } - // if the structure is returned by value, then we need to copy in the boxed object - // we have allocated for this purpose. - else if (!fHasRetBuffArg) - { -#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) - { - FpStructInRegistersInfo info = argit.GetReturnFpStructInRegistersInfo(); - bool hasPointers = gc.retVal->GetMethodTable()->ContainsGCPointers(); - CopyReturnedFpStructFromRegisters(gc.retVal->GetData(), callDescrData.returnValue, info, hasPointers); - } - else -#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - { - CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); - } - } - // From here on out, it is OK to have GCs since the return object (which may have had - // GC pointers has been put into a GC object and thus protected. - - // TODO this creates two objects which is inefficient - // If the return type is a Nullable box it into the correct form - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - // WARNING: pReturnedReference is an unprotected inner reference so we must not trigger a GC until the referenced value has been safely captured. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(refReturnTargetTH, pReturnedReference); - } - else - { - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue); - } - - if (pProtectValueClassFrame != NULL) - pProtectValueClassFrame->Pop(pThread); - - } - -Done: - result.Set(gc.retVal); - - GCPROTECT_END(); - GCX_COOP_REGION_END(); - END_QCALL; -} - struct SkipStruct { SkipStruct(StackCrawlMark* mark, PTR_Thread thread) : pStackMark(mark) diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 0da5348907b95c..ccaae3a01976de 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -225,14 +225,6 @@ extern "C" BOOL QCALLTYPE RuntimeMethodHandle_IsCAVisibleFromDecoratedType( extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack retTypes, BOOL fAsRuntimeTypeArray, QCallExceptionStatus* qcallError); -extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod( - QCall::ObjectHandleOnStack target, - PVOID* args, - QCall::ObjectHandleOnStack pSigUNSAFE, - BOOL fConstructor, - QCall::ObjectHandleOnStack result, - QCallExceptionStatus* qcallError); - extern "C" void QCALLTYPE RuntimeMethodHandle_ConstructInstantiation(MethodDesc * pMethod, DWORD format, QCall::StringHandleOnStack retString, QCallExceptionStatus* qcallError); extern "C" void* QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * pMethod, QCallExceptionStatus* qcallError); extern "C" BOOL QCALLTYPE RuntimeMethodHandle_GetIsCollectible(MethodDesc * pMethod); 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..7abda092f10b5f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -170,8 +170,34 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, } } - // Push the arguments. ReadOnlySpan parameters = method.GetParametersAsSpan(); +#if !MONO + if (emitNew) + { + Label allocateAndInvoke = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Brfalse, allocateAndInvoke); + + il.Emit(OpCodes.Ldarg_1); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + + EmitLoadRefArguments(il, parameters); + EmitCallAndReturnHandling(il, method, emitNew: false, backwardsCompat); + il.MarkLabel(allocateAndInvoke); + } +#endif + EmitLoadRefArguments(il, parameters); + EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); + + // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); + } + + private static void EmitLoadRefArguments(ILGenerator il, ReadOnlySpan parameters) + { for (int i = 0; i < parameters.Length; i++) { il.Emit(OpCodes.Ldarg_2); @@ -189,11 +215,6 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, 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. - return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); } private static void Unbox(ILGenerator il, Type parameterType) @@ -224,6 +245,10 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, { il.Emit(OpCodes.Newobj, (ConstructorInfo)method); } + else if (method is ConstructorInfo constructor) + { + il.Emit(OpCodes.Call, constructor); + } else if (method.IsStatic || method.DeclaringType!.IsValueType) { il.Emit(OpCodes.Call, (MethodInfo)method); @@ -242,6 +267,10 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, il.Emit(OpCodes.Box, returnType); } } + else if (method is ConstructorInfo) + { + il.Emit(OpCodes.Ldnull); + } else { RuntimeType returnType; 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 index 61ce60ff5bd4ec..998f18c15a746d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs @@ -47,8 +47,11 @@ internal sealed partial class MethodBaseInvoker try { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. +#if MONO ret = InterpretedInvoke_Constructor(obj, pByRefStorage); +#else + ret = InvokeDirectByRef(obj, pByRefStorage); +#endif } catch (Exception e) when (wrapInTargetInvocationException) { @@ -69,8 +72,11 @@ internal sealed partial class MethodBaseInvoker { try { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. +#if MONO return InterpretedInvoke_Constructor(obj, null); +#else + return InvokeDirectByRef(obj, null); +#endif } catch (Exception e) when (wrapInTargetInvocationException) { 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 f03b1d420858a8..0df895bb1bb0ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -68,8 +68,10 @@ public static MethodInvoker Create(MethodBase method) // such as created from RuntimeHelpers.GetUninitializedObject(Type). MethodInvoker invoker = new MethodInvoker(rci); +#if MONO // Use the interpreted version to avoid having to generate a new method that doesn't allocate. invoker._strategy = GetStrategyForUsingInterpreted(); +#endif return invoker; } @@ -264,7 +266,7 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) } } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) { ThrowForBadInvocationFlags(); } 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..a37128e221cdcd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -18,8 +18,7 @@ internal static void Initialize( { if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) { - // Always use the native interpreted invoke. - // Useful for testing, to avoid startup overhead of emit, or for calling a ctor on already initialized object. + // Keep using the slow path instead of promoting to a specialized invoke stub. strategy = GetStrategyForUsingInterpreted(); } else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) @@ -72,7 +71,7 @@ internal static void Initialize( internal static InvokerStrategy GetStrategyForUsingInterpreted() { - // This causes the default strategy, which is interpreted, to always be used. + // Keep the default strategy instead of promoting to a specialized invoke stub. return InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs; } @@ -113,10 +112,13 @@ ref InvokeFunc_ObjSpanArgs? // If ByRefs are used, we can't use this strategy. strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; } - else if (((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0) && !Debugger.IsAttached) + else if ((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // 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. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_ObjSpanArgs; } else @@ -142,10 +144,13 @@ internal static void DetermineStrategy_Obj4Args( // If ByRefs are used, we can't use this strategy. strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; } - else if (((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0) && !Debugger.IsAttached) + else if ((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // 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. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_Obj4Args; } else @@ -165,10 +170,13 @@ internal static void DetermineStrategy_RefArgs( MethodBase method, bool backwardsCompat) { - if (((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0) && !Debugger.IsAttached) + if ((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // 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. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_RefArgs; } else diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 7823d7733e2fdf..1fab12887ef2f1 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -25,6 +25,35 @@ public override object Invoke(ConstructorInfo constructorInfo, object?[]? parame protected override bool IsExceptionWrapped => true; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Invoke_AllocatingAndExistingInstanceAcrossTiers(bool valueArgument) + { + Type argumentType = valueArgument ? typeof(int) : typeof(object); + ConstructorInfo constructor = typeof(MutableConstructorTarget).GetConstructor(new[] { argumentType }); + var existing = new MutableConstructorTarget(null); + object value = valueArgument ? 42 : new object(); + object[] arguments = { value }; + + for (int i = 0; i < 150; i++) + { + var allocated = (MutableConstructorTarget)constructor.Invoke(arguments); + Assert.NotSame(existing, allocated); + Assert.Equal(value, allocated.Value); + Assert.Null(constructor.Invoke(existing, arguments)); + Assert.Equal(value, existing.Value); + } + } + + public sealed class MutableConstructorTarget + { + public object? Value; + + public MutableConstructorTarget(object? value) => Value = value; + public MutableConstructorTarget(int value) => Value = value; + } + [Fact] public void ConstructorName() { diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 20585f722e1c94..49710ece5da056 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Xunit; @@ -21,6 +22,93 @@ public sealed class MethodInfoTests : MethodCommonTests protected override bool SupportsMissing => false; + public static IEnumerable Invoke_ReturnValueAcrossTiers_TestData() + { + yield return new object[] { typeof(bool), true }; + yield return new object[] { typeof(byte), (byte)42 }; + yield return new object[] { typeof(sbyte), (sbyte)-42 }; + yield return new object[] { typeof(char), 'x' }; + yield return new object[] { typeof(short), (short)-1234 }; + yield return new object[] { typeof(ushort), (ushort)1234 }; + yield return new object[] { typeof(int), -12345 }; + yield return new object[] { typeof(uint), 12345u }; + yield return new object[] { typeof(long), -1234567890123L }; + yield return new object[] { typeof(ulong), 1234567890123UL }; + yield return new object[] { typeof(float), 12.5f }; + yield return new object[] { typeof(double), -25.5 }; + yield return new object[] { typeof(nint), (nint)12345 }; + yield return new object[] { typeof(nuint), (nuint)54321 }; + yield return new object[] { typeof(string), "returned value" }; + yield return new object[] { typeof(object), new object() }; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [MemberData(nameof(Invoke_ReturnValueAcrossTiers_TestData))] + public void Invoke_ReturnValueAcrossTiers(Type returnType, object expected) + { + Type target = typeof(ReturnValueTarget<>).MakeGenericType(returnType); + target.GetField(nameof(ReturnValueTarget.Value)).SetValue(null, expected); + MethodInfo method = target.GetMethod(nameof(ReturnValueTarget.GetValue)); + MethodInvoker invoker = MethodInvoker.Create(method); + + for (int i = 0; i < 150; i++) + { + Assert.Equal(expected, method.Invoke(null, null)); + Assert.Equal(expected, invoker.Invoke(null)); + } + } + + public static class ReturnValueTarget + { + public static T Value; + + public static T GetValue() => Value; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsReflectionEmitSupported))] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Invoke_CollectibleMethodCanUnload(bool useMethodInvoker, bool referenceArgument) + { + WeakReference assembly = CreateAndInvokeCollectibleMethod(useMethodInvoker, referenceArgument); + for (int i = 0; i < 10 && assembly.IsAlive; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + Assert.False(assembly.IsAlive); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference CreateAndInvokeCollectibleMethod(bool useMethodInvoker, bool referenceArgument) + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(nameof(CreateAndInvokeCollectibleMethod)), AssemblyBuilderAccess.RunAndCollect); + ModuleBuilder module = assembly.DefineDynamicModule("Module"); + TypeBuilder typeBuilder = module.DefineType("Target", TypeAttributes.Public); + Type argumentType = referenceArgument ? typeof(object) : typeof(int); + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + "Echo", MethodAttributes.Public | MethodAttributes.Static, argumentType, new[] { argumentType }); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ret); + Type type = typeBuilder.CreateType(); + MethodInfo method = type.GetMethod("Echo"); + MethodInvoker? invoker = useMethodInvoker ? MethodInvoker.Create(method) : null; + object expected = referenceArgument ? new object() : 42; + object[] arguments = { expected }; + + for (int i = 0; i < 150; i++) + { + Assert.Equal(expected, invoker is not null ? invoker.Invoke(null, expected) : method.Invoke(null, arguments)); + } + + return new WeakReference(type.Assembly); + } + [Fact] public void CreateDelegate_PublicMethod() { diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 94d701507eac1d..83f40ac4c11b3f 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; using Xunit; namespace System.Reflection.Tests @@ -18,6 +19,43 @@ public class MethodInvokerTests : MethodCommonTests protected override bool SupportsMissing => false; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Constructor_ExistingInstanceAcrossTiers(bool useSpan) + { + ConstructorInfo constructor = typeof(RefConstructorTarget).GetConstructor(new[] { typeof(int).MakeByRefType() }); + MethodInvoker invoker = MethodInvoker.Create(constructor); + var target = (RefConstructorTarget)RuntimeHelpers.GetUninitializedObject(typeof(RefConstructorTarget)); + + for (int i = 0; i < 150; i++) + { + if (useSpan) + { + object[] arguments = { i }; + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(i + 1, arguments[0]); + } + else + { + Assert.Null(invoker.Invoke(target, i)); + } + + Assert.Equal(i, target.Value); + } + } + + public sealed class RefConstructorTarget + { + public int Value; + + public RefConstructorTarget(ref int value) + { + Value = value; + value++; + } + } + [Fact] public void NullTypeValidation() { From 159f7668930389504646770877a79d26deea56e6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Sep 2026 13:51:49 -0700 Subject: [PATCH 02/38] Expand shared invocation with bounded reference and primitive shapes Cover ordinary instance and delegate calls, extended reference constructors, targeted primitive and enum inputs, and reference-only static and byref patterns. Resolve virtual targets per receiver, retain emitted fallback for unsupported shapes, and preserve the existing specialization threshold. Match explicit-this calli rewriting in ReadyToRun and exercise shared selection, dispatch, copy-back, GC, exclusions, and promotion. Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Reflection/InstanceCalliHelper.cs | 56 + .../Reflection/IntrinsicInvokeHelper.cs | 623 ++++++- .../src/System/RuntimeHandles.cs | 11 + .../InstanceCalliHelperTests.cs | 86 + .../IL/Stubs/InstanceCalliHelperIntrinsics.cs | 2 +- src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/runtimehandles.cpp | 27 + src/coreclr/vm/runtimehandles.h | 1 + .../ConstructorInfoTests.cs | 486 ++++++ .../ConstructorInvokerTests.cs | 64 + .../MethodInfoTests.cs | 1446 +++++++++++++++++ .../MethodInvokerTests.cs | 46 + 12 files changed, 2784 insertions(+), 65 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs 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 08758cdf75d59f..0e16149b224058 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs @@ -156,6 +156,62 @@ 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* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7) + => fn(o, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7, object? arg8) + => fn(o, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1) + => fn(o, arg1); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3) + => fn(o, arg1, arg2, arg3); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static int Call(delegate* fn, object o, object? arg1, object? arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, int arg1, int arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, long arg1, long arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, int arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, int arg2, object? arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, bool arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, bool arg4, object? arg5) + => fn(o, arg1, arg2, arg3, arg4, arg5); + + [Intrinsic] + internal static void Call(delegate* fn, object o, float arg1, float arg2, float arg3, int arg4) + => fn(o, arg1, arg2, arg3, arg4); + [Intrinsic] internal static void Call(delegate*?, void> fn, object o, IEnumerable? arg1) => fn(o, arg1); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index de2705284cf68d..93919a2e82f2f8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -35,20 +35,17 @@ internal struct InvokeState IntPtr* args, bool backwardsCompat) { + if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) + { + return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); + } + var thunk = (delegate*)Volatile.Read(ref state.Thunk); if (thunk is null) { if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) { - InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; - using (AssemblyBuilder.ForceAllowDynamicCode()) - { - emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); - } - - Volatile.Write(ref invokeFunc, emitDelegate); - strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; - return emitDelegate(obj, args); + return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); } state.FunctionPointer = functionPointer; @@ -64,11 +61,34 @@ internal struct InvokeState strategy &= ~StrategyDetermined; } - object? result = thunk(state.FunctionPointer, obj, args, method.DeclaringType); + IntPtr target = state.FunctionPointer; + if (target == IntPtr.Zero) + { + // The same MethodInfo can be invoked on different implementations. + target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); + } + + object? result = thunk(target, obj, args, method.DeclaringType); GC.KeepAlive(method); return result; } + private static object? InvokeEmitted( + ref MethodBase.InvokerStrategy strategy, + ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, + MethodBase method, object? obj, IntPtr* args, bool backwardsCompat) + { + InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; + using (AssemblyBuilder.ForceAllowDynamicCode()) + { + emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); + } + + Volatile.Write(ref invokeFunc, emitDelegate); + strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + return emitDelegate(obj, args); + } + private static bool TryGetShape( MethodBase method, ReadOnlySpan argumentTypes, @@ -87,65 +107,236 @@ private static bool TryGetShape( int argCount = argumentTypes.Length; - // Only reference-typed args qualify for the object-passing fast path. + bool referenceArguments = true; for (int i = 0; i < argCount; i++) { - RuntimeType pt = argumentTypes[i]; - if (pt.IsByRef || pt.IsPointer || pt.IsFunctionPointer || pt.IsValueType) + if (!IsReferenceType(argumentTypes[i])) { - return false; + referenceArguments = false; + break; } } - if (method is ConstructorInfo && - !method.IsStatic && - method.DeclaringType is Type declaringType && - !declaringType.IsValueType && - !declaringType.IsAbstract && - !declaringType.IsByRefLike && - !declaringType.ContainsGenericParameters && - declaringType != typeof(string)) // see Ctor_0 + if (method is ConstructorInfo) { - thunk = argCount switch + if (method.IsStatic || + method.DeclaringType is not Type declaringType || + !IsReferenceType(declaringType) || + declaringType.IsAbstract || + declaringType.IsArray || + declaringType.ContainsGenericParameters || + declaringType == typeof(string)) + { + return false; + } + + thunk = referenceArguments ? argCount switch { 0 => &Ctor_0, 1 => &Ctor_1, 2 => &Ctor_2, 3 => &Ctor_3, + 4 => &Ctor_4, + 5 => &Ctor_5, + 6 => &Ctor_6, + 7 => &Ctor_7, + 8 => &Ctor_8, _ => null, - }; - - if (thunk is null) + } : ClassifyConstructor(argumentTypes); + } + else if (method is MethodInfo methodInfo) + { + Type returnType = methodInfo.ReturnType; + if (method.IsStatic) { - return false; + if (referenceArguments) + { + thunk = ClassifyStaticReferenceArguments(argCount, returnType); + } + else if (argCount == 1 && GetInputType(argumentTypes[0]) == typeof(int) && IsReferenceType(returnType)) + { + thunk = &Static_Object_Int; + } + else if (argCount == 2 && IsReferenceType(argumentTypes[0]) && + argumentTypes[1].IsByRef && IsReferenceType(argumentTypes[1].GetElementType()!) && + returnType == typeof(bool)) + { + thunk = &Static_Bool_ObjByRefObj; + } + } + else if (method.DeclaringType is Type declaringType && IsReferenceType(declaringType)) + { + if (referenceArguments) + { + thunk = ClassifyInstanceReferenceArguments(argCount, returnType); + } + else if (returnType == typeof(void)) + { + if (argCount == 1) + { + thunk = ClassifyInstancePrimitive(GetInputType(argumentTypes[0])); + } + else if (argCount == 4 && + argumentTypes[0] == typeof(float) && argumentTypes[1] == typeof(float) && + argumentTypes[2] == typeof(float) && GetInputType(argumentTypes[3]) == typeof(int)) + { + thunk = &Instance_Void_FloatFloatFloatInt; + } + } } + } + + if (thunk is null) + { + return false; + } + if (method.IsStatic || !method.IsVirtual || method.IsFinal) + { functionPointer = method.MethodHandle.GetFunctionPointer(); - return true; } - if (method.IsStatic) + return true; + } + + private static bool IsReferenceType(Type type) => + !type.IsValueType && !type.IsByRef && !type.IsPointer && !type.IsFunctionPointer; + + private static Type GetInputType(RuntimeType type) => + type.IsActualEnum ? type.GetEnumUnderlyingType() : type; + + private static delegate* ClassifyConstructor(ReadOnlySpan arguments) + { + if (arguments.Length == 1) + { + Type type = GetInputType(arguments[0]); + if (type == typeof(bool)) return &Ctor_Bool; + if (type == typeof(int)) return &Ctor_Int; + if (type == typeof(long)) return &Ctor_Long; + } + else if (arguments.Length == 2) { - Type returnType = method is MethodInfo mi ? mi.ReturnType : typeof(void); + Type first = GetInputType(arguments[0]); + Type second = GetInputType(arguments[1]); + if (first == typeof(int) && second == typeof(int)) return &Ctor_IntInt; + if (first == typeof(long) && second == typeof(long)) return &Ctor_LongLong; + if (IsReferenceType(first) && second == typeof(int)) return &Ctor_ObjInt; + } + else if (arguments.Length == 4 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[3])) + { + if (GetInputType(arguments[1]) == typeof(int) && IsReferenceType(arguments[2])) return &Ctor_ObjIntObjObj; + if (IsReferenceType(arguments[1]) && GetInputType(arguments[2]) == typeof(bool)) return &Ctor_ObjObjBoolObj; + } + else if (arguments.Length == 5 && + IsReferenceType(arguments[0]) && IsReferenceType(arguments[1]) && IsReferenceType(arguments[2]) && + GetInputType(arguments[3]) == typeof(bool) && IsReferenceType(arguments[4])) + { + return &Ctor_ObjObjObjBoolObj; + } - thunk = argCount switch + return null; + } + + private static delegate* ClassifyStaticReferenceArguments(int count, Type returnType) + { + if (count == 0) + { + return ClassifyStatic0Return(returnType); + } + + if (returnType == typeof(void)) + { + return count switch { - 0 => ClassifyStatic0Return(returnType), - 1 => ClassifyStatic1Obj(returnType), - 2 => ClassifyStatic2Obj(returnType), + 1 => &Static_Void_1Obj, + 2 => &Static_Void_2Obj, + 3 => &Static_Void_3Obj, + 4 => &Static_Void_4Obj, _ => null, }; + } + + return IsReferenceType(returnType) ? count switch + { + 1 => &Static_Object_1Obj, + 2 => &Static_Object_2Obj, + 3 => &Static_Object_3Obj, + 4 => &Static_Object_4Obj, + _ => null, + } : null; + } - if (thunk is null) + private static delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) + { + if (returnType == typeof(void)) + { + return count switch { - return false; - } + 0 => &Instance_Void_0, + 1 => &Instance_Void_1Obj, + 2 => &Instance_Void_2Obj, + 3 => &Instance_Void_3Obj, + 4 => &Instance_Void_4Obj, + _ => null, + }; + } - functionPointer = method.MethodHandle.GetFunctionPointer(); - return true; + if (IsReferenceType(returnType)) + { + return count switch + { + 0 => &Instance_Object_0, + 1 => &Instance_Object_1Obj, + 2 => &Instance_Object_2Obj, + 3 => &Instance_Object_3Obj, + 4 => &Instance_Object_4Obj, + _ => null, + }; + } + + if (count == 2 && returnType == typeof(int)) + { + return &Instance_Int_2Obj; + } + + if (count == 0) + { + if (returnType == typeof(bool)) return &Instance_Bool_0; + if (returnType == typeof(byte)) return &Instance_Byte_0; + if (returnType == typeof(sbyte)) return &Instance_SByte_0; + if (returnType == typeof(char)) return &Instance_Char_0; + if (returnType == typeof(short)) return &Instance_Short_0; + if (returnType == typeof(ushort)) return &Instance_UShort_0; + if (returnType == typeof(int)) return &Instance_Int_0; + if (returnType == typeof(uint)) return &Instance_UInt_0; + if (returnType == typeof(long)) return &Instance_Long_0; + if (returnType == typeof(ulong)) return &Instance_ULong_0; + if (returnType == typeof(float)) return &Instance_Float_0; + if (returnType == typeof(double)) return &Instance_Double_0; + if (returnType == typeof(nint)) return &Instance_NInt_0; + if (returnType == typeof(nuint)) return &Instance_NUInt_0; } - return false; + return null; + } + + private static delegate* ClassifyInstancePrimitive(Type type) + { + if (type == typeof(bool)) return &Instance_Void_Bool; + if (type == typeof(byte)) return &Instance_Void_Byte; + if (type == typeof(sbyte)) return &Instance_Void_SByte; + if (type == typeof(char)) return &Instance_Void_Char; + if (type == typeof(short)) return &Instance_Void_Short; + if (type == typeof(ushort)) return &Instance_Void_UShort; + if (type == typeof(int)) return &Instance_Void_Int; + if (type == typeof(uint)) return &Instance_Void_UInt; + if (type == typeof(long)) return &Instance_Void_Long; + if (type == typeof(ulong)) return &Instance_Void_ULong; + if (type == typeof(float)) return &Instance_Void_Float; + if (type == typeof(double)) return &Instance_Void_Double; + if (type == typeof(nint)) return &Instance_Void_NInt; + if (type == typeof(nuint)) return &Instance_Void_NUInt; + return null; } // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into @@ -175,20 +366,6 @@ method.DeclaringType is Type declaringType && return null; } - private static delegate* ClassifyStatic1Obj(Type returnType) - { - if (returnType == typeof(void)) return &Static_Void_1Obj; - if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) - return &Static_Object_1Obj; - return null; - } - - private static delegate* ClassifyStatic2Obj(Type returnType) - { - if (returnType == typeof(void)) return &Static_Void_2Obj; - return null; - } - // Per-shape thunks. JIT compiles only the ones used. private static object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) @@ -245,6 +422,222 @@ method.DeclaringType is Type declaringType && return null; } + private static object? ReadReference(IntPtr* args, int index) => + Unsafe.Read((void*)args[index]); + + private static T ReadPrimitive(IntPtr* args, int index) where T : unmanaged => + Unsafe.ReadUnaligned((void*)args[index]); + + private static object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => + ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1)); + + private static object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => + ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + + private static object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => + ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + + private static object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + return null; + } + + private static object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + return null; + } + + private static object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, Type? __) => + ((delegate*)fn)(ReadPrimitive(args, 0)); + + private static object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, Type? __) => + ((delegate*)fn)(ReadReference(args, 0), ref Unsafe.AsRef((void*)args[1])); + + private static object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + +#pragma warning disable CA1859 // These thunks must match the shared object-returning function-pointer signature. + private static object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => + InstanceCalliHelper.Call((delegate*)fn, obj!); + + private static object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); +#pragma warning restore CA1859 + + private static object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0)); + + private static object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); + + private static object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + + private static object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + + private static object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + InstanceCalliHelper.Call((delegate*)fn, obj!); + return null; + } + + private static object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0)); + return null; + } + + private static object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); + return null; + } + + private static object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + return null; + } + + private static object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + return null; + } + + private static object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); + return null; + } + + private static object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + InstanceCalliHelper.Call((delegate*)fn, obj!, + ReadPrimitive(args, 0), ReadPrimitive(args, 1), ReadPrimitive(args, 2), ReadPrimitive(args, 3)); + return null; + } + // Ctor thunks: `obj` non-null = call ctor on existing instance, null = allocate first. // `string` excluded: `newobj String(...)` is JIT-lowered to a hidden static allocator // (`METHOD__STRING__CTORF_*` in src/coreclr/vm/corelib.h, wired by @@ -252,18 +645,19 @@ method.DeclaringType is Type declaringType && // entry, and `GetUninitializedObject(typeof(string))` is runtime-rejected. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", Justification = "Caller anchors the ctor MethodBase, keeping its type reachable.")] + private static object GetConstructorInstance(object? obj, Type? declaringType) => + obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + private static object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) { - object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + object instance = GetConstructorInstance(obj, declaringType); InstanceCalliHelper.Call((delegate*)fn, instance); return obj is null ? instance : null; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", - Justification = "See Ctor_0.")] private static object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + object instance = GetConstructorInstance(obj, declaringType); InstanceCalliHelper.Call( (delegate*)fn, instance, @@ -271,11 +665,9 @@ method.DeclaringType is Type declaringType && return obj is null ? instance : null; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", - Justification = "See Ctor_0.")] private static object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + object instance = GetConstructorInstance(obj, declaringType); InstanceCalliHelper.Call( (delegate*)fn, instance, @@ -284,11 +676,9 @@ method.DeclaringType is Type declaringType && return obj is null ? instance : null; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", - Justification = "See Ctor_0.")] private static object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + object instance = GetConstructorInstance(obj, declaringType); InstanceCalliHelper.Call( (delegate*)fn, instance, @@ -297,5 +687,110 @@ method.DeclaringType is Type declaringType && Unsafe.Read((void*)args[2])); return obj is null ? instance : null; } + + private static object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_4Obj(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + + private static object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4)); + return obj is null ? instance : null; + } + + private static object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5)); + return obj is null ? instance : null; + } + + private static object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5), ReadReference(args, 6)); + return obj is null ? instance : null; + } + + private static object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5), ReadReference(args, 6), ReadReference(args, 7)); + return obj is null ? instance : null; + } + + private static object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Bool(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + + private static object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Int(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + + private static object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Long(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + + private static object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, ReadPrimitive(args, 0), ReadPrimitive(args, 1)); + return obj is null ? instance : null; + } + + private static object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, ReadPrimitive(args, 0), ReadPrimitive(args, 1)); + return obj is null ? instance : null; + } + + private static object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, ReadReference(args, 0), ReadPrimitive(args, 1)); + return obj is null ? instance : null; + } + + private static object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadPrimitive(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + return obj is null ? instance : null; + } + + private static object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadPrimitive(args, 2), ReadReference(args, 3)); + return obj is null ? instance : null; + } + + private static object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadPrimitive(args, 3), ReadReference(args, 4)); + return obj is null ? instance : null; + } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index f6f31d55359bd9..dd8a5d1e76b03d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1096,6 +1096,17 @@ public IntPtr GetFunctionPointer() return ptr; } + [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetVirtualFunctionPointer")] + private static partial IntPtr GetVirtualFunctionPointer(RuntimeMethodHandleInternal method, QCallTypeHandle declaringType, ObjectHandleOnStack target); + + internal static IntPtr GetVirtualFunctionPointer(RuntimeMethodInfo method, object target) + { + RuntimeType declaringType = (RuntimeType)method.DeclaringType!; + return GetVirtualFunctionPointer(IRuntimeMethodInfo.GetValue(method), new QCallTypeHandle(ref declaringType), + ObjectHandleOnStack.Create(ref target)); + } + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool IsCollectible(RuntimeMethodHandleInternal method); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs new file mode 100644 index 00000000000000..0cb8aa30dfa30c --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +extern alias crossgen2; + +using System; +using System.Collections.Generic; +using ILCompiler.ReadyToRun.Tests.TestCasesRunner; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; +using crossgen2::ILCompiler; +using crossgen2::Internal.IL; +using crossgen2::Internal.JitInterface; +using Xunit; + +namespace ILCompiler.ReadyToRun.Tests; + +public class InstanceCalliHelperTests +{ + [Fact] + public void CallOverloadsUseExplicitThis() + { + TargetArchitecture architecture = TestPaths.TargetArchitecture switch + { + "wasm" => TargetArchitecture.Wasm32, + "armel" => TargetArchitecture.ARM, + _ => Enum.Parse(TestPaths.TargetArchitecture, ignoreCase: true) + }; + TargetOS operatingSystem = Enum.Parse(TestPaths.TargetOS, ignoreCase: true); + var instructionSets = new InstructionSetSupport(default, default, architecture); + var target = new TargetDetails(architecture, operatingSystem, TargetAbi.NativeAot, instructionSets.GetVectorTSimdVector()); + var context = new ReadyToRunCompilerContext(target, SharedGenericsMode.CanonicalReferenceTypes, + bubbleIncludesCoreModule: true, targetAllowsRuntimeCodeGeneration: !TestPaths.IsWasmTarget, + instructionSets, oldTypeSystemContext: null) + { + InputFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["System.Private.CoreLib"] = TestPaths.SystemPrivateCoreLibPath + }, + ReferenceFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) + }; + var coreLib = (EcmaModule)context.GetModuleForSimpleName("System.Private.CoreLib"); + context.SetSystemModule(coreLib); + MetadataType helper = coreLib.GetType("System.Reflection"u8, "InstanceCalliHelper"u8); + int overloadCount = 0; + foreach (MethodDesc method in helper.GetMethods()) + { + if (method.Name != "Call"u8) + { + continue; + } + + overloadCount++; + MethodSignature expected = Assert.IsType(method.Signature[0]).Signature; + MethodIL il = InstanceCalliHelperIntrinsics.EmitIL(method); + var reader = new ILReader(il.GetILBytes()); + int callCount = 0; + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode == ILOpcode.calli) + { + callCount++; + MethodSignature actual = Assert.IsType(il.GetObject(reader.ReadILToken())); + Assert.False(actual.IsStatic); + Assert.True((actual.Flags & MethodSignatureFlags.ExplicitThis) != 0); + Assert.Equal(expected.ReturnType, actual.ReturnType); + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + else + { + reader.Skip(opcode); + } + } + + Assert.Equal(1, callCount); + } + + Assert.NotEqual(0, overloadCount); + } +} 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 a5d42af06d398c..2e97ddef090bdb 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"u8)) + if (method.Name == "Call"u8) { methodIL = new ExplicitThisCall(methodIL); } diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index d79d978bbbc9f7..d2a3831e0d123e 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -177,6 +177,7 @@ static const Entry s_QCall[] = DllImportEntry(MethodBase_GetCurrentMethod) DllImportEntry(RuntimeMethodHandle_ConstructInstantiation) DllImportEntry(RuntimeMethodHandle_GetFunctionPointer) + DllImportEntry(RuntimeMethodHandle_GetVirtualFunctionPointer) DllImportEntry(RuntimeMethodHandle_GetMethodInstantiation) DllImportEntry(RuntimeMethodHandle_GetTypicalMethodDefinition) DllImportEntry(RuntimeMethodHandle_StripMethodInstantiation) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 0a946c949040de..df026490e7ad32 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1318,6 +1318,33 @@ extern "C" void * QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * return funcPtr; } +extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( + MethodDesc* pMethod, QCall::TypeHandle declaringType, QCall::ObjectHandleOnStack target, QCallExceptionStatus* qcallError) +{ + QCALL_CONTRACT; + + void* result = nullptr; + BEGIN_QCALL; + + GCX_COOP(); + OBJECTREF receiver = nullptr; + GCPROTECT_BEGIN(receiver); + receiver = target.Get(); + _ASSERTE(receiver != nullptr); + MethodTable* pReceiverMT = receiver->GetMethodTable(); + { + GCX_PREEMP(); + pMethod->EnsureActive(); + result = reinterpret_cast(pMethod->IsVtableMethod() + ? pMethod->GetSingleCallableAddrOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) + : pMethod->GetSingleCallableAddrOfCode()); + } + GCPROTECT_END(); + + END_QCALL; + return result; +} + FCIMPL1(LPCUTF8, RuntimeMethodHandle::GetUtf8Name, MethodDesc* pMethod) { CONTRACTL diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index ccaae3a01976de..11f608fb282506 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -227,6 +227,7 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodInstantiation(MethodDesc extern "C" void QCALLTYPE RuntimeMethodHandle_ConstructInstantiation(MethodDesc * pMethod, DWORD format, QCall::StringHandleOnStack retString, QCallExceptionStatus* qcallError); extern "C" void* QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * pMethod, QCallExceptionStatus* qcallError); +extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer(MethodDesc* pMethod, QCall::TypeHandle declaringType, QCall::ObjectHandleOnStack target, QCallExceptionStatus* qcallError); extern "C" BOOL QCALLTYPE RuntimeMethodHandle_GetIsCollectible(MethodDesc * pMethod); extern "C" void QCALLTYPE RuntimeMethodHandle_GetTypicalMethodDefinition(MethodDesc * pMethod, QCall::ObjectHandleOnStack refMethod, QCallExceptionStatus* qcallError); extern "C" void QCALLTYPE RuntimeMethodHandle_StripMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack refMethod, QCallExceptionStatus* qcallError); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 1fab12887ef2f1..dd3ccd4344e906 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Xunit; #pragma warning disable 0414 @@ -54,6 +56,295 @@ public sealed class MutableConstructorTarget public MutableConstructorTarget(int value) => Value = value; } + public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() + { + yield return new object[] { Type.EmptyTypes, Array.Empty() }; + yield return new object[] + { + new Type[] { typeof(IIntrinsicInvokeReference) }, + new object?[] { new IntrinsicInvokeReference(1) } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(Action) }, + new object?[] { new object[] { "two" }, (Action)(() => { }) } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(IIntrinsicInvokeReference), typeof(object[]) }, + new object?[] { Task.FromResult("three"), new IntrinsicInvokeReference(3), new object[] { "three" } } + }; + yield return new object[] + { + new Type[] { typeof(object), typeof(IIntrinsicInvokeReference), typeof(Action), typeof(Task) }, + new object?[] { new object(), new IntrinsicInvokeReference(4), (Action)(() => { }), Task.FromResult(4) } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(object[]), typeof(IIntrinsicInvokeReference), typeof(Action), typeof(string) }, + new object?[] { Task.FromResult(5), new object[] { "five" }, new IntrinsicInvokeReference(5), (Action)(() => { }), "five" } + }; + yield return new object[] + { + new Type[] { typeof(object), typeof(IIntrinsicInvokeReference), typeof(object[]), typeof(Action), typeof(Task), typeof(Func) }, + new object?[] + { + new object(), + new IntrinsicInvokeReference(6), + new object[] { "six" }, + (Action)(() => { }), + Task.FromResult("six"), + (Func)(() => "six") + } + }; + yield return new object[] + { + new Type[] + { + typeof(IIntrinsicInvokeReference), + typeof(object[]), + typeof(Action), + typeof(Task), + typeof(string), + typeof(object), + typeof(Func) + }, + new object?[] + { + new IntrinsicInvokeReference(7), + new object[] { "seven" }, + (Action)(() => { }), + Task.FromResult(7), + "seven", + new object(), + (Func)(() => 7) + } + }; + yield return new object[] + { + new Type[] + { + typeof(object[]), + typeof(IIntrinsicInvokeReference), + typeof(Action), + typeof(Task), + typeof(string), + typeof(object), + typeof(Func), + typeof(Task) + }, + new object?[] + { + new object[] { "eight" }, + new IntrinsicInvokeReference(8), + (Action)(() => { }), + Task.FromResult("eight"), + "eight", + new object(), + (Func)(() => 8), + Task.FromResult(8) + } + }; + } + + [Theory] + [MemberData(nameof(Invoke_ReferenceConstructors_SharedThunk_TestData))] + public void Invoke_ReferenceConstructors_SharedThunk(Type[] parameterTypes, object?[] arguments) + { + ConstructorInfo constructor = typeof(IntrinsicInvokeReferenceConstructorTarget).GetConstructor(parameterTypes)!; + + var result = (IntrinsicInvokeReferenceConstructorTarget)constructor.Invoke(arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + Assert.Equal(arguments.Length, result.Values.Length); + for (int i = 0; i < arguments.Length; i++) + { + Assert.Same(arguments[i], result.Values[i]); + } + } + + public static IEnumerable Invoke_PrimitivePatternConstructors_SharedThunk_TestData() + { + yield return new object[] { new Type[] { typeof(bool) }, new object?[] { true } }; + yield return new object[] { new Type[] { typeof(bool) }, new object?[] { false } }; + yield return new object[] { new Type[] { typeof(int) }, new object?[] { int.MinValue + 12345 } }; + yield return new object[] { new Type[] { typeof(long) }, new object?[] { long.MaxValue - 12345 } }; + yield return new object[] { new Type[] { typeof(int), typeof(int) }, new object?[] { int.MinValue, int.MaxValue } }; + yield return new object[] { new Type[] { typeof(long), typeof(long) }, new object?[] { long.MinValue, long.MaxValue } }; + yield return new object[] + { + new Type[] { typeof(IIntrinsicInvokeReference), typeof(int) }, + new object?[] { new IntrinsicInvokeReference(7), 8 } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(int), typeof(Action), typeof(Task) }, + new object?[] { new object[] { "nine" }, 9, (Action)(() => { }), Task.FromResult("nine") } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(IIntrinsicInvokeReference), typeof(bool), typeof(Action) }, + new object?[] { Task.FromResult("ten"), new IntrinsicInvokeReference(10), true, (Action)(() => { }) } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(Action), typeof(Task), typeof(bool), typeof(IIntrinsicInvokeReference) }, + new object?[] + { + new object[] { "eleven" }, + (Action)(() => { }), + Task.FromResult("eleven"), + false, + new IntrinsicInvokeReference(11) + } + }; + } + + [Theory] + [MemberData(nameof(Invoke_PrimitivePatternConstructors_SharedThunk_TestData))] + public void Invoke_PrimitivePatternConstructors_SharedThunk(Type[] parameterTypes, object?[] arguments) + { + ConstructorInfo constructor = typeof(IntrinsicInvokePrimitiveConstructorTarget).GetConstructor(parameterTypes)!; + + var result = (IntrinsicInvokePrimitiveConstructorTarget)constructor.Invoke(arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + Assert.Equal(arguments.Length, result.Values.Length); + for (int i = 0; i < arguments.Length; i++) + { + if (parameterTypes[i].IsValueType) + { + Assert.Equal(arguments[i], result.Values[i]); + } + else + { + Assert.Same(arguments[i], result.Values[i]); + } + } + } + + public static IEnumerable Invoke_EnumConstructors_TestData() => + IntrinsicInvokeTestData.EnumValues(); + + [Theory] + [MemberData(nameof(Invoke_EnumConstructors_TestData))] + public void Invoke_EnumConstructors_MapActualUnderlyingType(Type enumType, object value) + { + Type targetType = typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(enumType); + ConstructorInfo constructor = targetType.GetConstructor(new Type[] { enumType })!; + + object result = constructor.Invoke(new object?[] { value }); + + if (enumType == typeof(IntrinsicInvokeInt32Enum) || enumType == typeof(IntrinsicInvokeInt64Enum)) + { + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + } + else + { + IntrinsicInvokeSelectionAssertions.AssertFallback(constructor); + } + + object? actual = targetType.GetField(nameof(IntrinsicInvokeEnumConstructorTarget.Value))!.GetValue(result); + Assert.NotNull(actual); + Assert.Equal(enumType, actual.GetType()); + Assert.Equal(value, actual); + } + + public static IEnumerable Invoke_ExcludedConstructors_Fallback_TestData() + { + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(DateTime) }, + new object?[] { new DateTime(2026, 9, 10) }, + new DateTime(2026, 9, 10) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(int?) }, + new object?[] { (int?)42 }, + 42 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(ValueTask) }, + new object?[] { new ValueTask(43) }, + new ValueTask(43) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(CancellationToken) }, + new object?[] { new CancellationToken(canceled: true) }, + new CancellationToken(canceled: true) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(int), typeof(int), typeof(int) }, + new object?[] { 1, 2, 3 }, + 6 + }; + yield return new object[] + { + typeof(IntrinsicInvokeStructConstructorTarget), + new Type[] { typeof(int) }, + new object?[] { 44 }, + 44 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] + { + typeof(object), typeof(object), typeof(object), typeof(object), typeof(object), + typeof(object), typeof(object), typeof(object), typeof(object) + }, + new object?[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + 9 + }; + } + + [Theory] + [MemberData(nameof(Invoke_ExcludedConstructors_Fallback_TestData))] + public void Invoke_ExcludedConstructors_Fallback( + Type declaringType, + Type[] parameterTypes, + object?[] arguments, + object expected) + { + ConstructorInfo constructor = declaringType.GetConstructor(parameterTypes)!; + + object result = constructor.Invoke(arguments); + + Assert.Equal(expected, declaringType.GetField(nameof(IntrinsicInvokeExcludedConstructorTarget.Value))!.GetValue(result)); + IntrinsicInvokeSelectionAssertions.AssertFallback(constructor); + } + + [Fact] + public void Invoke_SharedConstructorThunk_UsesNormalArgumentValidation() + { + ConstructorInfo referenceConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(IIntrinsicInvokeReference) })!; + referenceConstructor.Invoke(new object?[] { new IntrinsicInvokeReference(1) }); + IntrinsicInvokeSelectionAssertions.AssertShared(referenceConstructor); + Assert.Throws(() => referenceConstructor.Invoke(new object?[] { new object() })); + + ConstructorInfo primitiveConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(int) })!; + primitiveConstructor.Invoke(new object?[] { 1 }); + IntrinsicInvokeSelectionAssertions.AssertShared(primitiveConstructor); + Assert.Throws(() => primitiveConstructor.Invoke(new object?[] { 1L })); + + ConstructorInfo enumConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(IntrinsicInvokeInt32Enum) })!; + enumConstructor.Invoke(new object?[] { IntrinsicInvokeInt32Enum.Value }); + IntrinsicInvokeSelectionAssertions.AssertShared(enumConstructor); + Assert.Throws(() => enumConstructor.Invoke(new object?[] { IntrinsicInvokeInt64Enum.Value })); + } + [Fact] public void ConstructorName() { @@ -237,4 +528,199 @@ public StructWith1Constructor(int x, int y) this.y = y; } } + + internal sealed class IntrinsicInvokeReferenceConstructorTarget + { + internal object?[] Values { get; } + + public IntrinsicInvokeReferenceConstructorTarget() => + Values = Collect(); + + public IntrinsicInvokeReferenceConstructorTarget(IIntrinsicInvokeReference value) => + Values = Collect(value); + + public IntrinsicInvokeReferenceConstructorTarget(object[] values, Action callback) => + Values = Collect(values, callback); + + public IntrinsicInvokeReferenceConstructorTarget( + Task task, + IIntrinsicInvokeReference reference, + object[] values) => + Values = Collect(task, reference, values); + + public IntrinsicInvokeReferenceConstructorTarget( + object value, + IIntrinsicInvokeReference reference, + Action callback, + Task task) => + Values = Collect(value, reference, callback, task); + + public IntrinsicInvokeReferenceConstructorTarget( + Task task, + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + string text) => + Values = Collect(task, values, reference, callback, text); + + public IntrinsicInvokeReferenceConstructorTarget( + object value, + IIntrinsicInvokeReference reference, + object[] values, + Action callback, + Task task, + Func factory) => + Values = Collect(value, reference, values, callback, task, factory); + + public IntrinsicInvokeReferenceConstructorTarget( + IIntrinsicInvokeReference reference, + object[] values, + Action callback, + Task task, + string text, + object value, + Func factory) => + Values = Collect(reference, values, callback, task, text, value, factory); + + public IntrinsicInvokeReferenceConstructorTarget( + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + Task textTask, + string text, + object value, + Func factory, + Task valueTask) => + Values = Collect(values, reference, callback, textTask, text, value, factory, valueTask); + + private static object?[] Collect(params object?[] values) + { + GC.Collect(); + return values; + } + } + + internal sealed class IntrinsicInvokePrimitiveConstructorTarget + { + internal object?[] Values { get; } + + public IntrinsicInvokePrimitiveConstructorTarget(bool value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(int value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(long value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(int first, int second) => + Values = Collect(first, second); + + public IntrinsicInvokePrimitiveConstructorTarget(long first, long second) => + Values = Collect(first, second); + + public IntrinsicInvokePrimitiveConstructorTarget(IIntrinsicInvokeReference reference, int value) => + Values = Collect(reference, value); + + public IntrinsicInvokePrimitiveConstructorTarget( + object[] values, + int number, + Action callback, + Task task) => + Values = Collect(values, number, callback, task); + + public IntrinsicInvokePrimitiveConstructorTarget( + Task task, + IIntrinsicInvokeReference reference, + bool value, + Action callback) => + Values = Collect(task, reference, value, callback); + + public IntrinsicInvokePrimitiveConstructorTarget( + object[] values, + Action callback, + Task task, + bool value, + IIntrinsicInvokeReference reference) => + Values = Collect(values, callback, task, value, reference); + + private static object?[] Collect(params object?[] values) + { + GC.Collect(); + return values; + } + } + + internal sealed class IntrinsicInvokeEnumConstructorTarget where T : struct, Enum + { + public T Value; + + public IntrinsicInvokeEnumConstructorTarget(T value) + { + GC.Collect(); + Value = value; + } + } + + internal sealed class IntrinsicInvokeExcludedConstructorTarget + { + public object? Value; + + public IntrinsicInvokeExcludedConstructorTarget(DateTime value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(int? value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(ValueTask value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(CancellationToken value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(int first, int second, int third) => + Value = Collect(first + second + third); + + public IntrinsicInvokeExcludedConstructorTarget(object first, object second, object third, object fourth, + object fifth, object sixth, object seventh, object eighth, object ninth) => + Value = Collect(ninth); + + private static T Collect(T value) + { + GC.Collect(); + return value; + } + } + + internal struct IntrinsicInvokeStructConstructorTarget + { + public object Value; + + public IntrinsicInvokeStructConstructorTarget(int value) + { + Value = value; + GC.Collect(); + } + } + + internal sealed class IntrinsicInvokeConstructorValidationTarget + { + public IntrinsicInvokeConstructorValidationTarget(IIntrinsicInvokeReference value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public IntrinsicInvokeConstructorValidationTarget(int value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public IntrinsicInvokeConstructorValidationTarget(IntrinsicInvokeInt32Enum value) + { + GC.Collect(); + GC.KeepAlive(value); + } + } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs index f5313bde579844..26860c995b4b18 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs @@ -22,6 +22,32 @@ public override object Invoke(ConstructorInfo constructorInfo, object?[]? parame protected override bool IsExceptionWrapped => false; + [Theory] + [InlineData(1)] + [InlineData(8)] + public void SharedThunk_CachedInvokerPromotes(int argumentCount) + { + Type[] parameterTypes = new Type[argumentCount]; + Array.Fill(parameterTypes, typeof(CachedInvokerArgument)); + ConstructorInfo constructor = typeof(CachedInvokerTarget).GetConstructor(parameterTypes)!; + ConstructorInvoker invoker = ConstructorInvoker.Create(constructor); + var argument = new CachedInvokerArgument(); + object?[] arguments = new object?[argumentCount]; + Array.Fill(arguments, argument); + + for (int i = 0; i < 150; i++) + { + var result = (CachedInvokerTarget)(argumentCount == 1 ? invoker.Invoke(argument) : invoker.Invoke(arguments.AsSpan())); + Assert.Same(argument, result.Value); + if (i == 0) + { + IntrinsicInvokeSelectionAssertions.AssertShared(invoker); + } + } + + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + } + [Fact] public void Args_0() { @@ -227,5 +253,43 @@ public TestClassThrowsOnCreate(string arg1) => public TestClassThrowsOnCreate(string arg1, string arg2, string arg3, string arg4, string arg5) => throw new InvalidOperationException(); } + + private sealed class CachedInvokerArgument + { + private bool _collected; + + internal void CollectOnce() + { + if (!_collected) + { + GC.Collect(); + _collected = true; + } + } + } + + private sealed class CachedInvokerTarget + { + internal CachedInvokerArgument Value { get; } + + public CachedInvokerTarget(CachedInvokerArgument value) + { + value.CollectOnce(); + Value = value; + } + + public CachedInvokerTarget(CachedInvokerArgument first, CachedInvokerArgument second, + CachedInvokerArgument third, CachedInvokerArgument fourth, CachedInvokerArgument fifth, + CachedInvokerArgument sixth, CachedInvokerArgument seventh, CachedInvokerArgument eighth) : this(first) + { + Assert.Same(first, second); + Assert.Same(first, third); + Assert.Same(first, fourth); + Assert.Same(first, fifth); + Assert.Same(first, sixth); + Assert.Same(first, seventh); + Assert.Same(first, eighth); + } + } } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 49710ece5da056..7539059c1f71c2 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -6,6 +6,8 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.Reflection.Tests @@ -65,6 +67,700 @@ public static class ReturnValueTarget public static T GetValue() => Value; } + public static IEnumerable Invoke_InstanceReferenceVoid_SharedThunk_TestData() + { + yield return new object[] { nameof(IntrinsicInvokeReferenceTarget.Void0), Array.Empty(), -1 }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void1), + new object?[] { new IntrinsicInvokeReference(1) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void2), + new object?[] { new object[] { "array" }, (Action)(() => { }) }, + 1 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void3), + new object?[] { Task.FromResult("task"), new IntrinsicInvokeReference(3), new string[] { "array" } }, + 2 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void4), + new object?[] { new object(), new IntrinsicInvokeReference(4), (Action)(() => { }), Task.FromResult(4) }, + 3 + }; + } + + [Theory] + [MemberData(nameof(Invoke_InstanceReferenceVoid_SharedThunk_TestData))] + public void Invoke_InstanceReferenceVoid_SharedThunk(string methodName, object?[] arguments, int retainedArgument) + { + var target = new IntrinsicInvokeReferenceTarget(); + MethodInfo method = typeof(IntrinsicInvokeReferenceTarget).GetMethod(methodName)!; + + Assert.Null(method.Invoke(target, arguments)); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1, target.CallCount); + Assert.Same(retainedArgument < 0 ? target.Sentinel : arguments[retainedArgument], target.LastValue); + } + + public static IEnumerable Invoke_InstanceReferenceReturn_SharedThunk_TestData() + { + yield return new object[] { nameof(IntrinsicInvokeReferenceTarget.Return0), Array.Empty(), -1 }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return1), + new object?[] { new IntrinsicInvokeReference(1) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return2), + new object?[] { new object[] { "array" }, (Action)(() => { }) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return3), + new object?[] { Task.FromResult("task"), (Func)(() => "delegate"), new IntrinsicInvokeReference(3) }, + 1 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return4), + new object?[] { Task.FromResult("task"), new object[] { "array" }, new IntrinsicInvokeReference(4), (Action)(() => { }) }, + 0 + }; + } + + [Theory] + [MemberData(nameof(Invoke_InstanceReferenceReturn_SharedThunk_TestData))] + public void Invoke_InstanceReferenceReturn_SharedThunk(string methodName, object?[] arguments, int returnedArgument) + { + var target = new IntrinsicInvokeReferenceTarget(); + MethodInfo method = typeof(IntrinsicInvokeReferenceTarget).GetMethod(methodName)!; + + object? result = method.Invoke(target, arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(returnedArgument < 0 ? target.Sentinel : arguments[returnedArgument], result); + } + + public static IEnumerable Invoke_PrimitiveValues_TestData() => + IntrinsicInvokeTestData.PrimitiveValues(); + + [Theory] + [MemberData(nameof(Invoke_PrimitiveValues_TestData))] + public void Invoke_InstancePrimitiveReturn_SharedThunk(Type returnType, object expected) + { + Type targetType = typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(returnType); + object target = Activator.CreateInstance(targetType)!; + targetType.GetProperty(nameof(IntrinsicInvokePrimitiveReturnTarget.Value))!.SetValue(target, expected); + MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveReturnTarget.GetValue))!; + + object? result = method.Invoke(target, null); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.NotNull(result); + Assert.Equal(returnType, result.GetType()); + Assert.Equal(expected, result); + } + + public static IEnumerable Invoke_PrimitiveAndEnumValues_TestData() + { + foreach (object[] data in IntrinsicInvokeTestData.PrimitiveValues()) + { + yield return data; + } + + foreach (object[] data in IntrinsicInvokeTestData.EnumValues()) + { + yield return data; + } + } + + [Theory] + [MemberData(nameof(Invoke_PrimitiveAndEnumValues_TestData))] + public void Invoke_InstancePrimitiveArgument_SharedThunk(Type argumentType, object value) + { + Type targetType = typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(argumentType); + object target = Activator.CreateInstance(targetType)!; + MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveArgumentTarget.SetValue))!; + + Assert.Null(method.Invoke(target, new object?[] { value })); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + object? actual = targetType.GetField(nameof(IntrinsicInvokePrimitiveArgumentTarget.Value))!.GetValue(target); + Assert.NotNull(actual); + Assert.Equal(value.GetType(), actual.GetType()); + Assert.Equal(value, actual); + } + + [Fact] + public void Invoke_InstanceTwoReferencesReturningInt_SharedThunk() + { + var target = new IntrinsicInvokeInstancePatternTarget(); + var reference = new IntrinsicInvokeReference(40); + object[] array = { "first", "second" }; + MethodInfo method = typeof(IntrinsicInvokeInstancePatternTarget).GetMethod( + nameof(IntrinsicInvokeInstancePatternTarget.Sum))!; + + Assert.Equal(42, method.Invoke(target, new object?[] { reference, array })); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + + [Fact] + public void Invoke_InstanceFloatFloatFloatInt_SharedThunk() + { + var target = new IntrinsicInvokeInstancePatternTarget(); + MethodInfo method = typeof(IntrinsicInvokeInstancePatternTarget).GetMethod( + nameof(IntrinsicInvokeInstancePatternTarget.SetVector))!; + + Assert.Null(method.Invoke(target, new object?[] { 1.25f, 2.5f, 3.75f, 4 })); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1.25f, target.X); + Assert.Equal(2.5f, target.Y); + Assert.Equal(3.75f, target.Z); + Assert.Equal(4, target.W); + } + + public static IEnumerable Invoke_StaticIntReturningReference_SharedThunk_TestData() + { + yield return new object[] + { + typeof(IntrinsicInvokeStaticIntReturnTarget), + nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnString), + "42" + }; + yield return new object[] + { + typeof(IntrinsicInvokeGenericStaticIntReturnTarget), + nameof(IntrinsicInvokeGenericStaticIntReturnTarget.ReturnComparable), + 42 + }; + yield return new object[] + { + typeof(IntrinsicInvokeStaticIntReturnTarget), + nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnGeneric), + typeof(int) + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticIntReturningReference_SharedThunk_TestData))] + public void Invoke_StaticIntReturningReference_SharedThunk(Type declaringType, string methodName, object expected) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + if (method.IsGenericMethodDefinition) + { + method = method.MakeGenericMethod(typeof(int)); + } + + object? result = method.Invoke(null, new object?[] { 42 }); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(expected, result); + } + + public static IEnumerable Invoke_StaticReferenceOutReference_SharedThunk_TestData() + { + var falseInput = new IntrinsicInvokeReference(1); + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnFalse), + new object?[] { falseInput, null }, + false, + falseInput + }; + + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnNull), + new object?[] { new object[] { "input" }, (Action)(() => { }) }, + true, + null + }; + + Task trueInput = Task.FromResult("input"); + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnTrue), + new object?[] { trueInput, null }, + true, + trueInput + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticReferenceOutReference_SharedThunk_TestData))] + public void Invoke_StaticReferenceOutReference_SharedThunk( + string methodName, + object?[] arguments, + bool expectedResult, + object? expectedOutput) + { + MethodInfo method = typeof(IntrinsicInvokeByRefTarget).GetMethod(methodName)!; + + Assert.Equal(expectedResult, method.Invoke(null, arguments)); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(expectedOutput, arguments[1]); + } + + [Fact] + public void Invoke_StaticReferenceOutReference_DoesNotCopyBackAfterException() + { + MethodInfo method = typeof(IntrinsicInvokeByRefTarget).GetMethod(nameof(IntrinsicInvokeByRefTarget.ThrowAfterWrite))!; + var input = new IntrinsicInvokeReference(42); + var originalOutput = new IntrinsicInvokeReference(-1); + object?[] arguments = { input, originalOutput }; + + TargetInvocationException exception = Assert.Throws(() => method.Invoke(null, arguments)); + + Assert.IsType(exception.InnerException); + Assert.Same(originalOutput, arguments[1]); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + + public static IEnumerable Invoke_StaticReferenceReturn_SharedThunk_TestData() + { + var reference2 = new IntrinsicInvokeReference(2); + object[] array2 = { "two" }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return2), + new object?[] { reference2, array2 }, + 1 + }; + + Task task3 = Task.FromResult("three"); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return3), + new object?[] { task3, (Action)(() => { }), new IntrinsicInvokeReference(3) }, + 0 + }; + + Action callback4 = () => { }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return4), + new object?[] { callback4, new object[] { "four" }, new IntrinsicInvokeReference(4), Task.FromResult(4) }, + 0 + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticReferenceReturn_SharedThunk_TestData))] + public void Invoke_StaticReferenceReturn_SharedThunk(string methodName, object?[] arguments, int returnedArgument) + { + MethodInfo method = typeof(IntrinsicInvokeStaticReferenceTarget).GetMethod(methodName)!; + + object? result = method.Invoke(null, arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(arguments[returnedArgument], result); + } + + public static IEnumerable Invoke_StaticReferenceVoid_SharedThunk_TestData() + { + var tracker3 = new IntrinsicInvokeActionTracker(); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void3), + new object?[] { Task.FromResult("three"), tracker3.Callback, new IntrinsicInvokeReference(3) }, + tracker3 + }; + + var tracker4 = new IntrinsicInvokeActionTracker(); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void4), + new object?[] { new object[] { "four" }, new IntrinsicInvokeReference(4), tracker4.Callback, Task.FromResult(4) }, + tracker4 + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticReferenceVoid_SharedThunk_TestData))] + public void Invoke_StaticReferenceVoid_SharedThunk( + string methodName, + object?[] arguments, + object trackerObject) + { + var tracker = (IntrinsicInvokeActionTracker)trackerObject; + MethodInfo method = typeof(IntrinsicInvokeStaticReferenceTarget).GetMethod(methodName)!; + + Assert.Null(method.Invoke(null, arguments)); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1, tracker.CallCount); + } + + public static IEnumerable Invoke_VirtualDispatch_SharedThunk_TestData() + { + yield return new object[] + { + typeof(IntrinsicInvokeVirtualDispatchBase), + nameof(IntrinsicInvokeVirtualDispatchBase.Dispatch), + new object[] + { + new IntrinsicInvokeVirtualDispatchA(), + new IntrinsicInvokeVirtualDispatchB(), + new IntrinsicInvokeVirtualDispatchA(), + new IntrinsicInvokeVirtualDispatchB() + }, + new string[] { "virtual-a", "virtual-b", "virtual-a", "virtual-b" } + }; + yield return new object[] + { + typeof(IntrinsicInvokeAbstractDispatchBase), + nameof(IntrinsicInvokeAbstractDispatchBase.Dispatch), + new object[] + { + new IntrinsicInvokeAbstractDispatchA(), + new IntrinsicInvokeAbstractDispatchB(), + new IntrinsicInvokeAbstractDispatchA(), + new IntrinsicInvokeAbstractDispatchB() + }, + new string[] { "abstract-a", "abstract-b", "abstract-a", "abstract-b" } + }; + yield return new object[] + { + typeof(IIntrinsicInvokeInterfaceDispatch), + nameof(IIntrinsicInvokeInterfaceDispatch.Dispatch), + new object[] + { + new IntrinsicInvokeInterfaceDispatchA(), + new IntrinsicInvokeInterfaceDispatchB(), + new IntrinsicInvokeInterfaceDispatchA(), + new IntrinsicInvokeInterfaceDispatchB() + }, + new string[] { "interface-a", "interface-b", "interface-a", "interface-b" } + }; + yield return new object[] + { + typeof(IIntrinsicInvokeDefaultInterfaceDispatch), + nameof(IIntrinsicInvokeDefaultInterfaceDispatch.Dispatch), + new object[] + { + new IntrinsicInvokeDefaultInterfaceDispatch(), + new IntrinsicInvokeDefaultInterfaceOverride(), + new IntrinsicInvokeDefaultInterfaceDispatch(), + new IntrinsicInvokeDefaultInterfaceOverride() + }, + new string[] { "default", "override", "default", "override" } + }; + yield return new object[] + { + typeof(IntrinsicInvokeGenericVirtualDispatch), + nameof(IntrinsicInvokeGenericVirtualDispatch.Dispatch), + new object[] { new IntrinsicInvokeGenericVirtualA(), new IntrinsicInvokeGenericVirtualB() }, + new string[] { "Int32-generic-a", "Int32-generic-b" } + }; + yield return new object[] + { + typeof(IIntrinsicInvokeGenericInterfaceDispatch), + nameof(IIntrinsicInvokeGenericInterfaceDispatch.Dispatch), + new object[] { new IntrinsicInvokeGenericInterfaceA(), new IntrinsicInvokeGenericInterfaceB() }, + new string[] { "Int32-interface-a", "Int32-interface-b" } + }; + } + + [Theory] + [MemberData(nameof(Invoke_VirtualDispatch_SharedThunk_TestData))] + public void Invoke_VirtualDispatch_SharedThunk( + Type declaringType, + string methodName, + object[] receivers, + string[] expected) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + if (method.IsGenericMethodDefinition) + { + method = method.MakeGenericMethod(typeof(int)); + } + object argument = new object(); + + for (int i = 0; i < receivers.Length; i++) + { + Assert.Equal(expected[i], method.Invoke(receivers[i], new object?[] { argument })); + if (i == 0) + { + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + } + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void DynamicInvoke_DelegateInvokeMethod_SharedThunk(int callbackKind) + { + var payload = new IntrinsicInvokeDelegatePayload(); + Delegate callback; + string[] expectedLog; + + switch (callbackKind) + { + case 0: + callback = new IntrinsicInvokeStaticDynamicCallback(IntrinsicInvokeDelegateCallbacks.Static); + expectedLog = new string[] { "static" }; + break; + case 1: + var instanceTarget = new IntrinsicInvokeDelegateCallbacks("instance"); + callback = new IntrinsicInvokeInstanceDynamicCallback(instanceTarget.Instance); + expectedLog = new string[] { "instance" }; + break; + case 2: + var multicastTarget = new IntrinsicInvokeDelegateCallbacks("instance"); + IntrinsicInvokeMulticastDynamicCallback first = IntrinsicInvokeDelegateCallbacks.MulticastStatic; + IntrinsicInvokeMulticastDynamicCallback second = multicastTarget.MulticastInstance; + callback = first + second; + expectedLog = new string[] { "static", "instance" }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(callbackKind)); + } + + MethodInfo invokeMethod = callback.GetType().GetMethod(nameof(Action.Invoke))!; + object? result = callback.DynamicInvoke(new object?[] { payload }); + + Assert.Same(payload, result); + Assert.Equal(expectedLog, payload.Log.ToArray()); + IntrinsicInvokeSelectionAssertions.AssertShared(invokeMethod); + } + + public static IEnumerable Invoke_ExcludedShapes_Fallback_TestData() + { + DateTime date = new DateTime(2026, 9, 10); + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.DateTimeArgument), + null, + new object?[] { date }, + 10, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.DateTimeResult), + null, + Array.Empty(), + date, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.NullableArgument), + null, + new object?[] { (int?)42 }, + 42, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.NullableResult), + null, + Array.Empty(), + 43, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ValueTaskArgument), + null, + new object?[] { new ValueTask(44) }, + 44, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ValueTaskResult), + null, + Array.Empty(), + new ValueTask(45), + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.CancellationTokenArgument), + null, + new object?[] { new CancellationToken(canceled: true) }, + true, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.CancellationTokenResult), + null, + Array.Empty(), + new CancellationToken(canceled: true), + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ByRefValue), + null, + new object?[] { 46 }, + true, + 0, + 47 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.TwoPrimitiveArguments), + null, + new object?[] { 1, 2 }, + 3, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.FiveReferenceArguments), + null, + new object?[] { "one", "two", "three", "four", "five" }, + "five", + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeInstancePatternTarget), + nameof(IntrinsicInvokeInstancePatternTarget.FiveReferenceArguments), + new IntrinsicInvokeInstancePatternTarget(), + new object?[] { "one", "two", "three", "four", "five" }, + "five", + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeStructReceiver), + nameof(IntrinsicInvokeStructReceiver.GetValue), + new IntrinsicInvokeStructReceiver(48), + Array.Empty(), + 48, + -1, + null + }; + yield return new object[] + { + typeof(IIntrinsicInvokeStructReceiver), + nameof(IIntrinsicInvokeStructReceiver.GetValue), + new IntrinsicInvokeStructReceiver(49), + Array.Empty(), + 49, + -1, + null + }; + } + + [Theory] + [MemberData(nameof(Invoke_ExcludedShapes_Fallback_TestData))] + public void Invoke_ExcludedShapes_Fallback( + Type declaringType, + string methodName, + object? target, + object?[] arguments, + object expectedResult, + int copyBackIndex, + object? expectedCopyBack) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + + object? result = method.Invoke(target, arguments); + + Assert.Equal(expectedResult, result); + if (copyBackIndex >= 0) + { + Assert.Equal(expectedCopyBack, arguments[copyBackIndex]); + } + + IntrinsicInvokeSelectionAssertions.AssertFallback(method); + } + + public static IEnumerable Invoke_EnumResult_Fallback_TestData() + { + yield return new object[] + { + nameof(IntrinsicInvokeEnumResultTarget.InstanceResult), + new IntrinsicInvokeEnumResultTarget(), + IntrinsicInvokeInt32Enum.Value + }; + yield return new object[] + { + nameof(IntrinsicInvokeEnumResultTarget.StaticResult), + null, + IntrinsicInvokeInt64Enum.Value + }; + } + + [Theory] + [MemberData(nameof(Invoke_EnumResult_Fallback_TestData))] + public void Invoke_EnumResult_FallbackPreservesDeclaredType(string methodName, object? target, object expected) + { + MethodInfo method = typeof(IntrinsicInvokeEnumResultTarget).GetMethod(methodName)!; + + object? result = method.Invoke(target, null); + + Assert.NotNull(result); + Assert.Equal(method.ReturnType, result.GetType()); + Assert.Equal(expected, result); + IntrinsicInvokeSelectionAssertions.AssertFallback(method); + } + + [Fact] + public void Invoke_SharedThunk_UsesNormalArgumentValidation() + { + var target = new IntrinsicInvokeArgumentValidationTarget(); + + MethodInfo referenceMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Reference))!; + Assert.Null(referenceMethod.Invoke(target, new object?[] { new IntrinsicInvokeReference(1) })); + IntrinsicInvokeSelectionAssertions.AssertShared(referenceMethod); + Assert.Throws(() => referenceMethod.Invoke(target, new object?[] { new object() })); + + MethodInfo primitiveMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Primitive))!; + Assert.Null(primitiveMethod.Invoke(target, new object?[] { 1 })); + IntrinsicInvokeSelectionAssertions.AssertShared(primitiveMethod); + Assert.Throws(() => primitiveMethod.Invoke(target, new object?[] { 1L })); + + MethodInfo enumMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Enum))!; + Assert.Null(enumMethod.Invoke(target, new object?[] { IntrinsicInvokeInt32Enum.Value })); + IntrinsicInvokeSelectionAssertions.AssertShared(enumMethod); + Assert.Throws(() => enumMethod.Invoke(target, new object?[] { IntrinsicInvokeInt64Enum.Value })); + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsReflectionEmitSupported))] [InlineData(false, false)] [InlineData(false, true)] @@ -880,6 +1576,756 @@ private void DummyMethod2() } } + internal static class IntrinsicInvokeSelectionAssertions + { + private const int SpecializationThreshold = 100; + private const string ForceEmitInvokeSwitch = "Switch.System.Reflection.ForceEmitInvoke"; + private const string ForceInterpretedInvokeSwitch = "Switch.System.Reflection.ForceInterpretedInvoke"; + private const string SharedThunkMethodName = "InvokeWithSharedThunk"; + private const BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + + internal static void AssertShared(MethodBase method) + { + if (ShouldAssertSharedSelection) + { + AssertShared(GetCachedInvoker(method)); + } + } + + internal static void AssertShared(object invoker) + { + if (!ShouldAssertSharedSelection) + { + return; + } + + Assert.Equal(SharedThunkMethodName, GetRefArgsDelegate(invoker).Method.Name); + Assert.NotEqual(IntPtr.Zero, GetInvokeStateField(invoker, "Thunk")); + } + + internal static void AssertFallback(MethodBase method) + { + if (!ShouldAssertSharedSelection) + { + return; + } + + AssertFallback(GetCachedInvoker(method)); + } + + internal static void AssertFallback(object invoker) + { + if (!ShouldAssertSharedSelection) + { + return; + } + + Assert.NotEqual(SharedThunkMethodName, GetRefArgsDelegate(invoker).Method.Name); + Assert.Equal(IntPtr.Zero, GetInvokeStateField(invoker, "Thunk")); + } + + internal static void AssertPromoted(object invoker) + { + if (!ShouldAssertPromotion) + { + return; + } + + Assert.Equal(SpecializationThreshold, GetInvokeStateField(invoker, "InvocationCount")); + Assert.True( + GetRefArgsDelegate(invoker).Method.Name != SharedThunkMethodName || + GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args") is not null || + GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); + } + + private static bool ShouldAssertSharedSelection => + PlatformDetection.IsCoreCLR && !IsForceEmitOnly; + + private static bool ShouldAssertPromotion => + ShouldAssertSharedSelection && + RuntimeFeature.IsDynamicCodeSupported && + !IsForceInterpretedOnly; + + private static bool IsForceEmitOnly => + IsSwitchEnabled(ForceEmitInvokeSwitch) && !IsSwitchEnabled(ForceInterpretedInvokeSwitch); + + private static bool IsForceInterpretedOnly => + IsSwitchEnabled(ForceInterpretedInvokeSwitch) && !IsSwitchEnabled(ForceEmitInvokeSwitch); + + private static bool IsSwitchEnabled(string name) => + AppContext.TryGetSwitch(name, out bool enabled) && enabled; + + private static object GetCachedInvoker(MethodBase method) + { + FieldInfo? field = method.GetType().GetField("m_invoker", InstanceFields); + Assert.NotNull(field); + object? invoker = field.GetValue(method); + Assert.NotNull(invoker); + return invoker; + } + + private static Delegate GetRefArgsDelegate(object invoker) + { + FieldInfo? field = invoker.GetType().GetField("_invokeFunc_RefArgs", InstanceFields); + Assert.NotNull(field); + object? value = field.GetValue(invoker); + Assert.NotNull(value); + return Assert.IsAssignableFrom(value); + } + + private static Delegate? GetOptionalDelegate(object invoker, string fieldName) + { + FieldInfo? field = invoker.GetType().GetField(fieldName, InstanceFields); + return field?.GetValue(invoker) as Delegate; + } + + private static T GetInvokeStateField(object invoker, string fieldName) + { + FieldInfo? invokeStateField = invoker.GetType().GetField("_invokeState", InstanceFields); + Assert.NotNull(invokeStateField); + object? invokeState = invokeStateField.GetValue(invoker); + Assert.NotNull(invokeState); + FieldInfo? valueField = invokeState.GetType().GetField(fieldName, InstanceFields); + Assert.NotNull(valueField); + object? value = valueField.GetValue(invokeState); + Assert.NotNull(value); + return (T)value; + } + } + + internal static class IntrinsicInvokeTestData + { + internal static IEnumerable PrimitiveValues() + { + yield return new object[] { typeof(bool), true }; + yield return new object[] { typeof(byte), (byte)42 }; + yield return new object[] { typeof(sbyte), (sbyte)-42 }; + yield return new object[] { typeof(char), 'x' }; + yield return new object[] { typeof(short), (short)-1234 }; + yield return new object[] { typeof(ushort), (ushort)1234 }; + yield return new object[] { typeof(int), -12345 }; + yield return new object[] { typeof(uint), 12345u }; + yield return new object[] { typeof(long), -1234567890123L }; + yield return new object[] { typeof(ulong), 1234567890123UL }; + yield return new object[] { typeof(float), 12.5f }; + yield return new object[] { typeof(double), -25.5 }; + yield return new object[] { typeof(nint), (nint)12345 }; + yield return new object[] { typeof(nuint), (nuint)54321 }; + } + + internal static IEnumerable EnumValues() + { + yield return new object[] { typeof(IntrinsicInvokeByteEnum), IntrinsicInvokeByteEnum.Value }; + yield return new object[] { typeof(IntrinsicInvokeSByteEnum), IntrinsicInvokeSByteEnum.Value }; + yield return new object[] { typeof(IntrinsicInvokeInt16Enum), IntrinsicInvokeInt16Enum.Value }; + yield return new object[] { typeof(IntrinsicInvokeUInt16Enum), IntrinsicInvokeUInt16Enum.Value }; + yield return new object[] { typeof(IntrinsicInvokeInt32Enum), IntrinsicInvokeInt32Enum.Value }; + yield return new object[] { typeof(IntrinsicInvokeUInt32Enum), IntrinsicInvokeUInt32Enum.Value }; + yield return new object[] { typeof(IntrinsicInvokeInt64Enum), IntrinsicInvokeInt64Enum.Value }; + yield return new object[] { typeof(IntrinsicInvokeUInt64Enum), IntrinsicInvokeUInt64Enum.Value }; + } + } + + internal interface IIntrinsicInvokeReference + { + int Value { get; } + } + + internal sealed class IntrinsicInvokeReference : IIntrinsicInvokeReference + { + internal IntrinsicInvokeReference(int value) => Value = value; + + public int Value { get; } + } + + internal sealed class IntrinsicInvokeReferenceTarget + { + internal int CallCount { get; private set; } + internal object? LastValue { get; private set; } + internal object Sentinel { get; } = new object(); + + public void Void0() => Record(Sentinel); + + public void Void1(IIntrinsicInvokeReference value) => Record(value); + + public void Void2(object[] values, Action callback) => Record(callback, values); + + public void Void3(Task task, IIntrinsicInvokeReference value, string[] values) => + Record(values, task, value); + + public void Void4(object value, IIntrinsicInvokeReference reference, Action callback, Task task) => + Record(task, value, reference, callback); + + public object Return0() => CollectAndReturn(Sentinel); + + public IIntrinsicInvokeReference Return1(IIntrinsicInvokeReference value) => CollectAndReturn(value); + + public object[] Return2(object[] values, Action callback) => CollectAndReturn(values, callback); + + public Func Return3(Task task, Func callback, IIntrinsicInvokeReference value) => + CollectAndReturn(callback, task, value); + + public Task Return4( + Task task, + object[] values, + IIntrinsicInvokeReference reference, + Action callback) => + CollectAndReturn(task, values, reference, callback); + + [MethodImpl(MethodImplOptions.NoInlining)] + private void Record(object? value, params object?[] keepAlive) + { + GC.Collect(); + CallCount++; + LastValue = value; + GC.KeepAlive(keepAlive); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T CollectAndReturn(T value, params object?[] keepAlive) + { + GC.Collect(); + GC.KeepAlive(keepAlive); + return value; + } + } + + internal sealed class IntrinsicInvokePrimitiveReturnTarget + { + public T Value { get; set; } + + public T GetValue() + { + GC.Collect(); + return Value; + } + } + + internal sealed class IntrinsicInvokePrimitiveArgumentTarget + { + public object? Value; + + public void SetValue(T value) + { + GC.Collect(); + Value = value; + } + } + + internal sealed class IntrinsicInvokeInstancePatternTarget + { + internal float X { get; private set; } + internal float Y { get; private set; } + internal float Z { get; private set; } + internal int W { get; private set; } + + public int Sum(IIntrinsicInvokeReference reference, object[] values) + { + GC.Collect(); + return reference.Value + values.Length; + } + + public void SetVector(float x, float y, float z, int w) + { + GC.Collect(); + X = x; + Y = y; + Z = z; + W = w; + } + + public object FiveReferenceArguments(object first, object second, object third, object fourth, object fifth) + { + GC.Collect(); + GC.KeepAlive(first); + GC.KeepAlive(second); + GC.KeepAlive(third); + GC.KeepAlive(fourth); + return fifth; + } + } + + internal static class IntrinsicInvokeStaticIntReturnTarget + { + public static string ReturnString(int value) + { + GC.Collect(); + return value.ToString(); + } + + public static Type? ReturnGeneric(int value) + { + GC.Collect(); + return value == 42 ? typeof(T) : null; + } + } + + internal static class IntrinsicInvokeGenericStaticIntReturnTarget where T : IComparable + { + public static IComparable ReturnComparable(int value) + { + T result = (T)(object)value; + GC.Collect(); + return result; + } + } + + internal static class IntrinsicInvokeByRefTarget + { + public static bool ReturnFalse(IIntrinsicInvokeReference input, out IIntrinsicInvokeReference output) + { + GC.Collect(); + output = input; + return false; + } + + public static bool ReturnNull(object[] input, out Action? output) + { + GC.Collect(); + output = null; + GC.KeepAlive(input); + return true; + } + + public static bool ReturnTrue(Task input, out Task output) + { + GC.Collect(); + output = input; + return true; + } + + public static bool ThrowAfterWrite(IIntrinsicInvokeReference input, out IIntrinsicInvokeReference output) + { + output = input; + GC.Collect(); + throw new InvalidOperationException(); + } + } + + internal static class IntrinsicInvokeStaticReferenceTarget + { + public static object[] Return2(IIntrinsicInvokeReference reference, object[] values) + { + GC.Collect(); + GC.KeepAlive(reference); + return values; + } + + public static Task Return3(Task task, Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + GC.KeepAlive(callback); + GC.KeepAlive(reference); + return task; + } + + public static Action Return4( + Action callback, + object[] values, + IIntrinsicInvokeReference reference, + Task task) + { + GC.Collect(); + GC.KeepAlive(values); + GC.KeepAlive(reference); + GC.KeepAlive(task); + return callback; + } + + public static void Void3(Task task, Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + callback(); + GC.KeepAlive(task); + GC.KeepAlive(reference); + } + + public static void Void4( + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + Task task) + { + GC.Collect(); + callback(); + GC.KeepAlive(values); + GC.KeepAlive(reference); + GC.KeepAlive(task); + } + } + + internal sealed class IntrinsicInvokeActionTracker + { + internal int CallCount { get; private set; } + internal Action Callback => Invoke; + + private void Invoke() => CallCount++; + } + + internal class IntrinsicInvokeVirtualDispatchBase + { + public virtual object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "virtual-base"; + } + } + + internal sealed class IntrinsicInvokeVirtualDispatchA : IntrinsicInvokeVirtualDispatchBase + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "virtual-a"; + } + } + + internal sealed class IntrinsicInvokeVirtualDispatchB : IntrinsicInvokeVirtualDispatchBase + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "virtual-b"; + } + } + + internal abstract class IntrinsicInvokeAbstractDispatchBase + { + public abstract object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeAbstractDispatchA : IntrinsicInvokeAbstractDispatchBase + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "abstract-a"; + } + } + + internal sealed class IntrinsicInvokeAbstractDispatchB : IntrinsicInvokeAbstractDispatchBase + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "abstract-b"; + } + } + + internal interface IIntrinsicInvokeInterfaceDispatch + { + object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeInterfaceDispatchA : IIntrinsicInvokeInterfaceDispatch + { + public object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "interface-a"; + } + } + + internal sealed class IntrinsicInvokeInterfaceDispatchB : IIntrinsicInvokeInterfaceDispatch + { + public object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "interface-b"; + } + } + + internal interface IIntrinsicInvokeDefaultInterfaceDispatch + { + object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "default"; + } + } + + internal sealed class IntrinsicInvokeDefaultInterfaceDispatch : IIntrinsicInvokeDefaultInterfaceDispatch + { + } + + internal sealed class IntrinsicInvokeDefaultInterfaceOverride : IIntrinsicInvokeDefaultInterfaceDispatch + { + public object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "override"; + } + } + + internal delegate object? IntrinsicInvokeStaticDynamicCallback(object? value); + internal delegate object? IntrinsicInvokeInstanceDynamicCallback(object? value); + internal delegate object? IntrinsicInvokeMulticastDynamicCallback(object? value); + + internal abstract class IntrinsicInvokeGenericVirtualDispatch + { + public abstract object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeGenericVirtualA : IntrinsicInvokeGenericVirtualDispatch + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-generic-a"; + } + } + + internal sealed class IntrinsicInvokeGenericVirtualB : IntrinsicInvokeGenericVirtualDispatch + { + public override object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-generic-b"; + } + } + + internal interface IIntrinsicInvokeGenericInterfaceDispatch + { + object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeGenericInterfaceA : IIntrinsicInvokeGenericInterfaceDispatch + { + public object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-interface-a"; + } + } + + internal sealed class IntrinsicInvokeGenericInterfaceB : IIntrinsicInvokeGenericInterfaceDispatch + { + public object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-interface-b"; + } + } + + internal sealed class IntrinsicInvokeDelegatePayload + { + internal List Log { get; } = new List(); + } + + internal sealed class IntrinsicInvokeDelegateCallbacks + { + private readonly string _label; + + internal IntrinsicInvokeDelegateCallbacks(string label) => _label = label; + + internal static object? Static(object? value) => Record(value, "static"); + + internal object? Instance(object? value) => Record(value, _label); + + internal static object? MulticastStatic(object? value) => Record(value, "static"); + + internal object? MulticastInstance(object? value) => Record(value, _label); + + private static object? Record(object? value, string label) + { + GC.Collect(); + ((IntrinsicInvokeDelegatePayload)value!).Log.Add(label); + return value; + } + } + + internal static class IntrinsicInvokeExcludedMethodTarget + { + public static object DateTimeArgument(DateTime value) + { + GC.Collect(); + return value.Day; + } + + public static DateTime DateTimeResult() + { + GC.Collect(); + return new DateTime(2026, 9, 10); + } + + public static object NullableArgument(int? value) + { + GC.Collect(); + return value.GetValueOrDefault(); + } + + public static int? NullableResult() + { + GC.Collect(); + return 43; + } + + public static object ValueTaskArgument(ValueTask value) + { + GC.Collect(); + return value.Result; + } + + public static ValueTask ValueTaskResult() + { + GC.Collect(); + return new ValueTask(45); + } + + public static object CancellationTokenArgument(CancellationToken value) + { + GC.Collect(); + return value.IsCancellationRequested; + } + + public static CancellationToken CancellationTokenResult() + { + GC.Collect(); + return new CancellationToken(canceled: true); + } + + public static bool ByRefValue(ref int value) + { + GC.Collect(); + value++; + return true; + } + + public static object TwoPrimitiveArguments(int first, int second) + { + GC.Collect(); + return first + second; + } + + public static object FiveReferenceArguments( + object first, + object second, + object third, + object fourth, + object fifth) + { + GC.Collect(); + GC.KeepAlive(first); + GC.KeepAlive(second); + GC.KeepAlive(third); + GC.KeepAlive(fourth); + return fifth; + } + } + + internal interface IIntrinsicInvokeStructReceiver + { + object GetValue(); + } + + internal readonly struct IntrinsicInvokeStructReceiver : IIntrinsicInvokeStructReceiver + { + internal IntrinsicInvokeStructReceiver(int value) => Value = value; + + internal int Value { get; } + + public object GetValue() + { + GC.Collect(); + return Value; + } + + public override string ToString() + { + GC.Collect(); + return Value.ToString(); + } + } + + internal sealed class IntrinsicInvokeEnumResultTarget + { + public IntrinsicInvokeInt32Enum InstanceResult() + { + GC.Collect(); + return IntrinsicInvokeInt32Enum.Value; + } + + public static IntrinsicInvokeInt64Enum StaticResult() + { + GC.Collect(); + return IntrinsicInvokeInt64Enum.Value; + } + } + + internal sealed class IntrinsicInvokeArgumentValidationTarget + { + public void Reference(IIntrinsicInvokeReference value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public void Primitive(int value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public void Enum(IntrinsicInvokeInt32Enum value) + { + GC.Collect(); + GC.KeepAlive(value); + } + } + + internal enum IntrinsicInvokeByteEnum : byte + { + Value = 211 + } + + internal enum IntrinsicInvokeSByteEnum : sbyte + { + Value = -91 + } + + internal enum IntrinsicInvokeInt16Enum : short + { + Value = -30001 + } + + internal enum IntrinsicInvokeUInt16Enum : ushort + { + Value = 60001 + } + + internal enum IntrinsicInvokeInt32Enum : int + { + Value = -123456789 + } + + internal enum IntrinsicInvokeUInt32Enum : uint + { + Value = 0xFEDCBA98 + } + + internal enum IntrinsicInvokeInt64Enum : long + { + Value = -1234567890123456789 + } + + internal enum IntrinsicInvokeUInt64Enum : ulong + { + Value = 0xFEDCBA9876543210 + } + #pragma warning disable 0414 public interface MI_Interface { diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 83f40ac4c11b3f..1cc562aa64fa2c 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -19,6 +19,37 @@ public class MethodInvokerTests : MethodCommonTests protected override bool SupportsMissing => false; + [Fact] + public void SharedThunk_CachedInvokerPromotes() + { + MethodInfo method = typeof(CachedInvokerTarget).GetMethod(nameof(CachedInvokerTarget.Echo))!; + MethodInvoker invoker = MethodInvoker.Create(method); + var target = new CachedInvokerTarget(); + object argument = new object(); + + for (int i = 0; i < 150; i++) + { + Assert.Same(argument, invoker.Invoke(target, argument)); + if (i == 0) + { + IntrinsicInvokeSelectionAssertions.AssertShared(invoker); + } + } + + Assert.Equal(150, target.CallCount); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + } + + [Fact] + public void SharedThunk_ObjectMethodOnBoxedValueReceiverFallsBack() + { + MethodInfo method = typeof(object).GetMethod(nameof(object.ToString))!; + MethodInvoker invoker = MethodInvoker.Create(method); + + Assert.Equal("50", invoker.Invoke(new IntrinsicInvokeStructReceiver(50))); + IntrinsicInvokeSelectionAssertions.AssertFallback(invoker); + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -335,6 +366,21 @@ public void VerifyThisObj_Null() public static IEnumerable Invoke_TestData() => MethodInfoTests.Invoke_TestData(); + private sealed class CachedInvokerTarget + { + internal int CallCount { get; private set; } + + public object Echo(object value) + { + if (CallCount++ == 0) + { + GC.Collect(); + } + + return value; + } + } + private class TestClass { private int _i = 42; From e830840ff7e03c358184159098dcacb4e7401ff9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Sep 2026 14:32:47 -0700 Subject: [PATCH 03/38] Document the reference-result TryParse invocation thunk Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 93919a2e82f2f8..0a9ce8ab383e89 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -161,6 +161,7 @@ method.DeclaringType is not Type declaringType || argumentTypes[1].IsByRef && IsReferenceType(argumentTypes[1].GetElementType()!) && returnType == typeof(bool)) { + // Thunk for the .NET TryParse pattern with reference-type results. thunk = &Static_Bool_ObjByRefObj; } } From f37fc61a5c8b6b518924c36708ab665df178b99a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Sep 2026 15:34:31 -0700 Subject: [PATCH 04/38] Document ASP.NET Core callers of specialized invocation shapes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Reflection/IntrinsicInvokeHelper.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 0a9ce8ab383e89..f806a3cf980548 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -130,6 +130,8 @@ method.DeclaringType is not Type declaringType || return false; } + // ActivatorUtilities.CreateInstance reaches StaticFileMiddleware (4), SessionMiddleware and + // RateLimitingMiddleware (5), OutputCacheMiddleware (6), and EndpointRoutingMiddleware (8). thunk = referenceArguments ? argCount switch { 0 => &Ctor_0, @@ -155,6 +157,7 @@ method.DeclaringType is not Type declaringType || } else if (argCount == 1 && GetInputType(argumentTypes[0]) == typeof(int) && IsReferenceType(returnType)) { + // SignalR BuildStream(int streamBufferCapacity). thunk = &Static_Object_Int; } else if (argCount == 2 && IsReferenceType(argumentTypes[0]) && @@ -162,6 +165,7 @@ method.DeclaringType is not Type declaringType || returnType == typeof(bool)) { // Thunk for the .NET TryParse pattern with reference-type results. + // Typed-header TryParse(string, out T) and TryParseList(IList, out IList). thunk = &Static_Bool_ObjByRefObj; } } @@ -175,12 +179,15 @@ method.DeclaringType is not Type declaringType || { if (argCount == 1) { + // Named attribute setters (Duration, NoStore, Location, CaptureUnmatchedValues) + // and Blazor RemoveRootComponent(int). thunk = ClassifyInstancePrimitive(GetInputType(argumentTypes[0])); } else if (argCount == 4 && argumentTypes[0] == typeof(float) && argumentTypes[1] == typeof(float) && argumentTypes[2] == typeof(float) && GetInputType(argumentTypes[3]) == typeof(int)) { + // Blazor VirtualizeJsInterop.OnSpacerBeforeVisible and OnSpacerAfterVisible. thunk = &Instance_Void_FloatFloatFloatInt; } } @@ -211,27 +218,35 @@ private static Type GetInputType(RuntimeType type) => if (arguments.Length == 1) { Type type = GetInputType(arguments[0]); + // StreamRenderingAttribute and RequireAntiforgeryTokenAttribute. if (type == typeof(bool)) return &Ctor_Bool; + // Length-based route constraints and BindingBehaviorAttribute's enum constructor. if (type == typeof(int)) return &Ctor_Int; + // Min/Max route constraints and RequestSizeLimitAttribute. if (type == typeof(long)) return &Ctor_Long; } else if (arguments.Length == 2) { Type first = GetInputType(arguments[0]); Type second = GetInputType(arguments[1]); + // LengthRouteConstraint(int, int) and RangeRouteConstraint(long, long). if (first == typeof(int) && second == typeof(int)) return &Ctor_IntInt; if (first == typeof(long) && second == typeof(long)) return &Ctor_LongLong; + // ProducesResponseTypeAttribute(Type, int). if (IsReferenceType(first) && second == typeof(int)) return &Ctor_ObjInt; } else if (arguments.Length == 4 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[3])) { + // ProducesResponseTypeAttribute(Type, int, string, string[]). if (GetInputType(arguments[1]) == typeof(int) && IsReferenceType(arguments[2])) return &Ctor_ObjIntObjObj; + // MVC ArrayModelBinder and CollectionModelBinder. if (IsReferenceType(arguments[1]) && GetInputType(arguments[2]) == typeof(bool)) return &Ctor_ObjObjBoolObj; } else if (arguments.Length == 5 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[1]) && IsReferenceType(arguments[2]) && GetInputType(arguments[3]) == typeof(bool) && IsReferenceType(arguments[4])) { + // MVC DictionaryModelBinder. return &Ctor_ObjObjObjBoolObj; } @@ -297,6 +312,7 @@ private static Type GetInputType(RuntimeType type) => if (count == 2 && returnType == typeof(int)) { + // Blazor AddRootComponent(string, string). return &Instance_Int_2Obj; } From a0db90a44d1f3d3f07133be21e2513eb2a2a903a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Sep 2026 16:17:35 -0700 Subject: [PATCH 05/38] Delay reflection invoke specialization until 10000 calls Require compiled dynamic-code support both for shared-thunk promotion and for all specialized argument strategies. Preserve the emitted fallback needed for otherwise unsupported signatures. Exercise the exact shared/specialized boundary and retain shared invocation beyond it when compilation is unavailable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 4 +- .../System/Reflection/MethodInvokerCommon.cs | 8 ++-- .../ConstructorInfoTests.cs | 2 +- .../ConstructorInvokerTests.cs | 6 +-- .../MethodInfoTests.cs | 29 ++++++++++++--- .../MethodInvokerTests.cs | 37 ++++++++++++++----- 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index f806a3cf980548..c177444466ec6a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -12,7 +12,7 @@ namespace System.Reflection // This type is included in SystemDomain::IsReflectionInvocationMethod for caller stack walks. internal static unsafe class IntrinsicInvokeHelper { - private const int SpecializationThreshold = 100; + private const int SpecializationThreshold = 10_000; private const MethodBase.InvokerStrategy StrategyDetermined = MethodBase.InvokerStrategy.StrategyDetermined_Obj4Args | MethodBase.InvokerStrategy.StrategyDetermined_ObjSpanArgs | @@ -53,7 +53,7 @@ internal struct InvokeState Volatile.Write(ref state.Thunk, (IntPtr)thunk); } - if (RuntimeFeature.IsDynamicCodeSupported && + if (RuntimeFeature.IsDynamicCodeCompiled && !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) { 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 a37128e221cdcd..f328f1425acd37 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -23,7 +23,7 @@ internal static void Initialize( } else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. + // Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing. strategy = GetStrategyForUsingEmit(); } else @@ -123,7 +123,7 @@ ref InvokeFunc_ObjSpanArgs? } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_ObjSpanArgs = CreateInvokeDelegate_ObjSpanArgs(method, backwardsCompat); } @@ -155,7 +155,7 @@ internal static void DetermineStrategy_Obj4Args( } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_Obj4Args = CreateInvokeDelegate_Obj4Args(method, backwardsCompat); } @@ -181,7 +181,7 @@ internal static void DetermineStrategy_RefArgs( } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_RefArgs = CreateInvokeDelegate_RefArgs(method, backwardsCompat); } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index dd3ccd4344e906..f1a6c76be5cba9 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -38,7 +38,7 @@ public void Invoke_AllocatingAndExistingInstanceAcrossTiers(bool valueArgument) object value = valueArgument ? 42 : new object(); object[] arguments = { value }; - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { var allocated = (MutableConstructorTarget)constructor.Invoke(arguments); Assert.NotSame(existing, allocated); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs index 26860c995b4b18..30efd2a744bf49 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs @@ -35,13 +35,13 @@ public void SharedThunk_CachedInvokerPromotes(int argumentCount) object?[] arguments = new object?[argumentCount]; Array.Fill(arguments, argument); - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { var result = (CachedInvokerTarget)(argumentCount == 1 ? invoker.Invoke(argument) : invoker.Invoke(arguments.AsSpan())); Assert.Same(argument, result.Value); - if (i == 0) + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) { - IntrinsicInvokeSelectionAssertions.AssertShared(invoker); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 7539059c1f71c2..d9cf1675860afa 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -53,7 +53,7 @@ public void Invoke_ReturnValueAcrossTiers(Type returnType, object expected) MethodInfo method = target.GetMethod(nameof(ReturnValueTarget.GetValue)); MethodInvoker invoker = MethodInvoker.Create(method); - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { Assert.Equal(expected, method.Invoke(null, null)); Assert.Equal(expected, invoker.Invoke(null)); @@ -797,7 +797,7 @@ private static WeakReference CreateAndInvokeCollectibleMethod(bool useMethodInvo object expected = referenceArgument ? new object() : 42; object[] arguments = { expected }; - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { Assert.Equal(expected, invoker is not null ? invoker.Invoke(null, expected) : method.Invoke(null, arguments)); } @@ -1578,7 +1578,7 @@ private void DummyMethod2() internal static class IntrinsicInvokeSelectionAssertions { - private const int SpecializationThreshold = 100; + internal const int SpecializationThreshold = 10_000; private const string ForceEmitInvokeSwitch = "Switch.System.Reflection.ForceEmitInvoke"; private const string ForceInterpretedInvokeSwitch = "Switch.System.Reflection.ForceInterpretedInvoke"; private const string SharedThunkMethodName = "InvokeWithSharedThunk"; @@ -1626,8 +1626,14 @@ internal static void AssertFallback(object invoker) internal static void AssertPromoted(object invoker) { + if (!ShouldAssertSharedSelection) + { + return; + } + if (!ShouldAssertPromotion) { + AssertNotPromoted(invoker, 0); return; } @@ -1638,12 +1644,25 @@ internal static void AssertPromoted(object invoker) GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); } + internal static void AssertNotPromoted(object invoker, int invocationCount) + { + if (!ShouldAssertSharedSelection) + { + return; + } + + AssertShared(invoker); + Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args")); + Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs")); + Assert.Equal(ShouldAssertPromotion ? invocationCount : 0, GetInvokeStateField(invoker, "InvocationCount")); + } + private static bool ShouldAssertSharedSelection => - PlatformDetection.IsCoreCLR && !IsForceEmitOnly; + PlatformDetection.IsCoreCLR && (!IsForceEmitOnly || !RuntimeFeature.IsDynamicCodeCompiled); private static bool ShouldAssertPromotion => ShouldAssertSharedSelection && - RuntimeFeature.IsDynamicCodeSupported && + RuntimeFeature.IsDynamicCodeCompiled && !IsForceInterpretedOnly; private static bool IsForceEmitOnly => diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 1cc562aa64fa2c..731df6fa96f510 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -19,24 +19,37 @@ public class MethodInvokerTests : MethodCommonTests protected override bool SupportsMissing => false; - [Fact] - public void SharedThunk_CachedInvokerPromotes() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SharedThunk_CachedInvokerPromotes(bool useByRef) { - MethodInfo method = typeof(CachedInvokerTarget).GetMethod(nameof(CachedInvokerTarget.Echo))!; + MethodInfo method = typeof(CachedInvokerTarget).GetMethod( + useByRef ? nameof(CachedInvokerTarget.TryGetValue) : nameof(CachedInvokerTarget.Echo))!; MethodInvoker invoker = MethodInvoker.Create(method); var target = new CachedInvokerTarget(); object argument = new object(); + object?[] arguments = { target, null }; - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { - Assert.Same(argument, invoker.Invoke(target, argument)); - if (i == 0) + if (useByRef) + { + Assert.Equal(true, invoker.Invoke(null, arguments.AsSpan())); + Assert.Same(target, arguments[1]); + } + else { - IntrinsicInvokeSelectionAssertions.AssertShared(invoker); + Assert.Same(argument, invoker.Invoke(target, argument)); + } + + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) + { + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); } } - Assert.Equal(150, target.CallCount); + Assert.Equal(IntrinsicInvokeSelectionAssertions.SpecializationThreshold + 1, target.CallCount); IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); } @@ -59,7 +72,7 @@ public void Constructor_ExistingInstanceAcrossTiers(bool useSpan) MethodInvoker invoker = MethodInvoker.Create(constructor); var target = (RefConstructorTarget)RuntimeHelpers.GetUninitializedObject(typeof(RefConstructorTarget)); - for (int i = 0; i < 150; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) { if (useSpan) { @@ -370,6 +383,12 @@ private sealed class CachedInvokerTarget { internal int CallCount { get; private set; } + public static bool TryGetValue(CachedInvokerTarget target, out object result) + { + result = target.Echo(target); + return true; + } + public object Echo(object value) { if (CallCount++ == 0) From ac47d396dc1d7010aca8ea11e35ba214f06ab319 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 12 Sep 2026 17:46:43 -0700 Subject: [PATCH 06/38] Prepare virtual invocation entrypoints on portable runtimes Prepare resolved virtual and delegate Invoke targets for R2R callers before returning their portable entrypoints. Cover a cold emitted virtual method without relying on a previously initialized target thunk. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/runtimehandles.cpp | 9 ++++++-- .../MethodInfoTests.cs | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index df026490e7ad32..3d44ed34106e43 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1335,9 +1335,14 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( { GCX_PREEMP(); pMethod->EnsureActive(); - result = reinterpret_cast(pMethod->IsVtableMethod() + PCODE callTarget = pMethod->IsVtableMethod() ? pMethod->GetSingleCallableAddrOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) - : pMethod->GetSingleCallableAddrOfCode()); + : pMethod->GetSingleCallableAddrOfCode(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Virtual dispatch can return an entrypoint whose R2R-to-interpreter thunk is not prepared yet. + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(callTarget); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + result = reinterpret_cast(callTarget); } GCPROTECT_END(); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index d9cf1675860afa..be9349b4fe6486 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -498,6 +498,27 @@ public void Invoke_VirtualDispatch_SharedThunk( } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsReflectionEmitSupported))] + public void Invoke_EmittedVirtualMethod_SharedThunk() + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(nameof(Invoke_EmittedVirtualMethod_SharedThunk)), AssemblyBuilderAccess.RunAndCollect); + TypeBuilder typeBuilder = assembly.DefineDynamicModule("Module").DefineType("Target", TypeAttributes.Public); + const string MethodName = "GetValue"; + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + MethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot, + typeof(int), Type.EmptyTypes); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4, 42); + il.Emit(OpCodes.Ret); + Type targetType = typeBuilder.CreateType()!; + object target = Activator.CreateInstance(targetType)!; + MethodInfo method = targetType.GetMethod(MethodName)!; + + Assert.Equal(42, method.Invoke(target, null)); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + [Theory] [InlineData(0)] [InlineData(1)] From f0d58a263befb1508306d9387ffef293873ed647 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 12 Sep 2026 17:46:56 -0700 Subject: [PATCH 07/38] Fix NativeAOT invocation roots and constructor argument counts Root the reflected primitive, enum, and generic dispatch fixtures so NativeAOT runs the shared invocation coverage. Initialize MethodInvoker's constructor parameter count just as the method and ConstructorInvoker paths already do. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/MethodInvoker.cs | 1 + .../System.Reflection.Tests/default.rd.xml | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index bf8331776d7a32..58e39783ffd282 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -25,6 +25,7 @@ internal MethodInvoker(RuntimeMethodInfo method) internal MethodInvoker(RuntimeConstructorInfo constructor) { _methodBaseInvoker = constructor.MethodInvoker; + _parameterCount = constructor.GetParametersAsSpan().Length; } public static MethodInvoker Create(MethodBase method) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml b/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml index 31a940f6e8540e..a39cc46c62f2ad 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml @@ -46,6 +46,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cb0a26ebc2ce7ce22e82884d1bb6cc09b18ba599 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Sep 2026 13:12:15 -0700 Subject: [PATCH 08/38] Make reflection invocation test instantiations trim-analyzable Replace generic theory cases with explicit facts and remove their NativeAOT reflection roots. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ConstructorInfoTests.cs | 43 ++- .../MethodInfoTests.cs | 274 ++++++++++++------ .../System.Reflection.Tests/default.rd.xml | 83 ------ 3 files changed, 227 insertions(+), 173 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index f1a6c76be5cba9..9171a0fd9014ff 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -223,14 +224,44 @@ public void Invoke_PrimitivePatternConstructors_SharedThunk(Type[] parameterType } } - public static IEnumerable Invoke_EnumConstructors_TestData() => - IntrinsicInvokeTestData.EnumValues(); + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Byte() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); - [Theory] - [MemberData(nameof(Invoke_EnumConstructors_TestData))] - public void Invoke_EnumConstructors_MapActualUnderlyingType(Type enumType, object value) + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_SByte() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int16() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt16() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int32() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt32() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int64() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt64() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + + private static void Invoke_EnumConstructors_MapActualUnderlyingType( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields)] Type targetType, + object value) { - Type targetType = typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(enumType); + Type enumType = value.GetType(); ConstructorInfo constructor = targetType.GetConstructor(new Type[] { enumType })!; object result = constructor.Invoke(new object?[] { value }); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index be9349b4fe6486..5cae407826fd02 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection.Emit; using System.Runtime.CompilerServices; @@ -151,14 +152,67 @@ public void Invoke_InstanceReferenceReturn_SharedThunk(string methodName, object Assert.Same(returnedArgument < 0 ? target.Sentinel : arguments[returnedArgument], result); } - public static IEnumerable Invoke_PrimitiveValues_TestData() => - IntrinsicInvokeTestData.PrimitiveValues(); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Boolean() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(bool)), true); - [Theory] - [MemberData(nameof(Invoke_PrimitiveValues_TestData))] - public void Invoke_InstancePrimitiveReturn_SharedThunk(Type returnType, object expected) + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Byte() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(byte)), (byte)42); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_SByte() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Char() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(char)), 'x'); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int16() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(short)), (short)-1234); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt16() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int32() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(int)), -12345); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt32() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(uint)), 12345u); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int64() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(long)), -1234567890123L); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt64() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Single() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(float)), 12.5f); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Double() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(double)), -25.5); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_IntPtr() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UIntPtr() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + + private static void Invoke_InstancePrimitiveReturn_SharedThunk( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, + object expected) { - Type targetType = typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(returnType); object target = Activator.CreateInstance(targetType)!; targetType.GetProperty(nameof(IntrinsicInvokePrimitiveReturnTarget.Value))!.SetValue(target, expected); MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveReturnTarget.GetValue))!; @@ -167,28 +221,103 @@ public void Invoke_InstancePrimitiveReturn_SharedThunk(Type returnType, object e IntrinsicInvokeSelectionAssertions.AssertShared(method); Assert.NotNull(result); - Assert.Equal(returnType, result.GetType()); + Assert.Equal(expected.GetType(), result.GetType()); Assert.Equal(expected, result); } - public static IEnumerable Invoke_PrimitiveAndEnumValues_TestData() - { - foreach (object[] data in IntrinsicInvokeTestData.PrimitiveValues()) - { - yield return data; - } + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Boolean() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(bool)), true); - foreach (object[] data in IntrinsicInvokeTestData.EnumValues()) - { - yield return data; - } - } + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Byte() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(byte)), (byte)42); - [Theory] - [MemberData(nameof(Invoke_PrimitiveAndEnumValues_TestData))] - public void Invoke_InstancePrimitiveArgument_SharedThunk(Type argumentType, object value) + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_SByte() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Char() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(char)), 'x'); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(short)), (short)-1234); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(int)), -12345); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(uint)), 12345u); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(long)), -1234567890123L); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Single() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(float)), 12.5f); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Double() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(double)), -25.5); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_IntPtr() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UIntPtr() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_ByteEnum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_SByteEnum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + + private static void Invoke_InstancePrimitiveArgument_SharedThunk( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields)] Type targetType, + object value) { - Type targetType = typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(argumentType); object target = Activator.CreateInstance(targetType)!; MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveArgumentTarget.SetValue))!; @@ -244,12 +373,6 @@ public static IEnumerable Invoke_StaticIntReturningReference_SharedThu nameof(IntrinsicInvokeGenericStaticIntReturnTarget.ReturnComparable), 42 }; - yield return new object[] - { - typeof(IntrinsicInvokeStaticIntReturnTarget), - nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnGeneric), - typeof(int) - }; } [Theory] @@ -257,17 +380,24 @@ public static IEnumerable Invoke_StaticIntReturningReference_SharedThu public void Invoke_StaticIntReturningReference_SharedThunk(Type declaringType, string methodName, object expected) { MethodInfo method = declaringType.GetMethod(methodName)!; - if (method.IsGenericMethodDefinition) - { - method = method.MakeGenericMethod(typeof(int)); - } - object? result = method.Invoke(null, new object?[] { 42 }); IntrinsicInvokeSelectionAssertions.AssertShared(method); Assert.Equal(expected, result); } + [Fact] + public void Invoke_StaticGenericIntReturningReference_SharedThunk() + { + MethodInfo method = typeof(IntrinsicInvokeStaticIntReturnTarget) + .GetMethod(nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnGeneric))!.MakeGenericMethod(typeof(int)); + + object? result = method.Invoke(null, new object?[] { 42 }); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(typeof(int), result); + } + public static IEnumerable Invoke_StaticReferenceOutReference_SharedThunk_TestData() { var falseInput = new IntrinsicInvokeReference(1); @@ -457,20 +587,6 @@ public static IEnumerable Invoke_VirtualDispatch_SharedThunk_TestData( }, new string[] { "default", "override", "default", "override" } }; - yield return new object[] - { - typeof(IntrinsicInvokeGenericVirtualDispatch), - nameof(IntrinsicInvokeGenericVirtualDispatch.Dispatch), - new object[] { new IntrinsicInvokeGenericVirtualA(), new IntrinsicInvokeGenericVirtualB() }, - new string[] { "Int32-generic-a", "Int32-generic-b" } - }; - yield return new object[] - { - typeof(IIntrinsicInvokeGenericInterfaceDispatch), - nameof(IIntrinsicInvokeGenericInterfaceDispatch.Dispatch), - new object[] { new IntrinsicInvokeGenericInterfaceA(), new IntrinsicInvokeGenericInterfaceB() }, - new string[] { "Int32-interface-a", "Int32-interface-b" } - }; } [Theory] @@ -482,10 +598,33 @@ public void Invoke_VirtualDispatch_SharedThunk( string[] expected) { MethodInfo method = declaringType.GetMethod(methodName)!; - if (method.IsGenericMethodDefinition) - { - method = method.MakeGenericMethod(typeof(int)); - } + AssertVirtualDispatchSharedThunk(method, receivers, expected); + } + + [Fact] + public void Invoke_GenericVirtualDispatch_SharedThunk() + { + MethodInfo method = typeof(IntrinsicInvokeGenericVirtualDispatch) + .GetMethod(nameof(IntrinsicInvokeGenericVirtualDispatch.Dispatch))!.MakeGenericMethod(typeof(int)); + + AssertVirtualDispatchSharedThunk(method, + new object[] { new IntrinsicInvokeGenericVirtualA(), new IntrinsicInvokeGenericVirtualB() }, + new string[] { "Int32-generic-a", "Int32-generic-b" }); + } + + [Fact] + public void Invoke_GenericInterfaceDispatch_SharedThunk() + { + MethodInfo method = typeof(IIntrinsicInvokeGenericInterfaceDispatch) + .GetMethod(nameof(IIntrinsicInvokeGenericInterfaceDispatch.Dispatch))!.MakeGenericMethod(typeof(int)); + + AssertVirtualDispatchSharedThunk(method, + new object[] { new IntrinsicInvokeGenericInterfaceA(), new IntrinsicInvokeGenericInterfaceB() }, + new string[] { "Int32-interface-a", "Int32-interface-b" }); + } + + private static void AssertVirtualDispatchSharedThunk(MethodInfo method, object[] receivers, string[] expected) + { object argument = new object(); for (int i = 0; i < receivers.Length; i++) @@ -1733,39 +1872,6 @@ private static T GetInvokeStateField(object invoker, string fieldName) } } - internal static class IntrinsicInvokeTestData - { - internal static IEnumerable PrimitiveValues() - { - yield return new object[] { typeof(bool), true }; - yield return new object[] { typeof(byte), (byte)42 }; - yield return new object[] { typeof(sbyte), (sbyte)-42 }; - yield return new object[] { typeof(char), 'x' }; - yield return new object[] { typeof(short), (short)-1234 }; - yield return new object[] { typeof(ushort), (ushort)1234 }; - yield return new object[] { typeof(int), -12345 }; - yield return new object[] { typeof(uint), 12345u }; - yield return new object[] { typeof(long), -1234567890123L }; - yield return new object[] { typeof(ulong), 1234567890123UL }; - yield return new object[] { typeof(float), 12.5f }; - yield return new object[] { typeof(double), -25.5 }; - yield return new object[] { typeof(nint), (nint)12345 }; - yield return new object[] { typeof(nuint), (nuint)54321 }; - } - - internal static IEnumerable EnumValues() - { - yield return new object[] { typeof(IntrinsicInvokeByteEnum), IntrinsicInvokeByteEnum.Value }; - yield return new object[] { typeof(IntrinsicInvokeSByteEnum), IntrinsicInvokeSByteEnum.Value }; - yield return new object[] { typeof(IntrinsicInvokeInt16Enum), IntrinsicInvokeInt16Enum.Value }; - yield return new object[] { typeof(IntrinsicInvokeUInt16Enum), IntrinsicInvokeUInt16Enum.Value }; - yield return new object[] { typeof(IntrinsicInvokeInt32Enum), IntrinsicInvokeInt32Enum.Value }; - yield return new object[] { typeof(IntrinsicInvokeUInt32Enum), IntrinsicInvokeUInt32Enum.Value }; - yield return new object[] { typeof(IntrinsicInvokeInt64Enum), IntrinsicInvokeInt64Enum.Value }; - yield return new object[] { typeof(IntrinsicInvokeUInt64Enum), IntrinsicInvokeUInt64Enum.Value }; - } - } - internal interface IIntrinsicInvokeReference { int Value { get; } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml b/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml index a39cc46c62f2ad..31a940f6e8540e 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/default.rd.xml @@ -46,89 +46,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 00b06d51cb3cddf9f6c70b65187936abd4d00f2b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Sep 2026 15:14:55 -0700 Subject: [PATCH 09/38] Use typed pointer reads in shared reflection thunks Replace reference and primitive read helpers with direct typed pointer loads. Scope unsafe contexts to methods and CS8500 suppressions to object-pointer reads. Cover the two-reference static void thunk. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 1434 +++++++++++------ .../MethodInfoTests.cs | 20 + 2 files changed, 949 insertions(+), 505 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index c177444466ec6a..dc467a81eef993 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -10,7 +10,7 @@ namespace System.Reflection { // Shared, precompilable thunks avoid emitting a method-specific stub for cold invocations. // This type is included in SystemDomain::IsReflectionInvocationMethod for caller stack walks. - internal static unsafe class IntrinsicInvokeHelper + internal static class IntrinsicInvokeHelper { private const int SpecializationThreshold = 10_000; private const MethodBase.InvokerStrategy StrategyDetermined = @@ -25,7 +25,7 @@ internal struct InvokeState internal int InvocationCount; } - internal static object? Invoke( + internal static unsafe object? Invoke( ref InvokeState state, ref MethodBase.InvokerStrategy strategy, ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, @@ -35,176 +35,185 @@ internal struct InvokeState IntPtr* args, bool backwardsCompat) { - if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) + unsafe { - return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); - } - - var thunk = (delegate*)Volatile.Read(ref state.Thunk); - if (thunk is null) - { - if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) + if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) { return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); } - state.FunctionPointer = functionPointer; - strategy |= StrategyDetermined; - Volatile.Write(ref state.Thunk, (IntPtr)thunk); - } + var thunk = (delegate*)Volatile.Read(ref state.Thunk); + if (thunk is null) + { + if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) + { + return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); + } - if (RuntimeFeature.IsDynamicCodeCompiled && - !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && - Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) - { - // Let the normal strategy selection specialize the next invocation's argument path. - strategy &= ~StrategyDetermined; - } + state.FunctionPointer = functionPointer; + strategy |= StrategyDetermined; + Volatile.Write(ref state.Thunk, (IntPtr)thunk); + } - IntPtr target = state.FunctionPointer; - if (target == IntPtr.Zero) - { - // The same MethodInfo can be invoked on different implementations. - target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); - } + if (RuntimeFeature.IsDynamicCodeCompiled && + !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && + Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) + { + // Let the normal strategy selection specialize the next invocation's argument path. + strategy &= ~StrategyDetermined; + } + + IntPtr target = state.FunctionPointer; + if (target == IntPtr.Zero) + { + // The same MethodInfo can be invoked on different implementations. + target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); + } - object? result = thunk(target, obj, args, method.DeclaringType); - GC.KeepAlive(method); - return result; + object? result = thunk(target, obj, args, method.DeclaringType); + GC.KeepAlive(method); + return result; + } } - private static object? InvokeEmitted( + private static unsafe object? InvokeEmitted( ref MethodBase.InvokerStrategy strategy, ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, MethodBase method, object? obj, IntPtr* args, bool backwardsCompat) { - InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; - using (AssemblyBuilder.ForceAllowDynamicCode()) + unsafe { - emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); - } + InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; + using (AssemblyBuilder.ForceAllowDynamicCode()) + { + emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); + } - Volatile.Write(ref invokeFunc, emitDelegate); - strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; - return emitDelegate(obj, args); + Volatile.Write(ref invokeFunc, emitDelegate); + strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + return emitDelegate(obj, args); + } } - private static bool TryGetShape( + private static unsafe bool TryGetShape( MethodBase method, ReadOnlySpan argumentTypes, out delegate* thunk, out IntPtr functionPointer) { - thunk = null; - functionPointer = IntPtr.Zero; - - if (method is System.Reflection.Emit.DynamicMethod || - method.ContainsGenericParameters || - (method.CallingConvention & CallingConventions.VarArgs) != 0) + unsafe { - return false; - } - - int argCount = argumentTypes.Length; + thunk = null; + functionPointer = IntPtr.Zero; - bool referenceArguments = true; - for (int i = 0; i < argCount; i++) - { - if (!IsReferenceType(argumentTypes[i])) - { - referenceArguments = false; - break; - } - } - - if (method is ConstructorInfo) - { - if (method.IsStatic || - method.DeclaringType is not Type declaringType || - !IsReferenceType(declaringType) || - declaringType.IsAbstract || - declaringType.IsArray || - declaringType.ContainsGenericParameters || - declaringType == typeof(string)) + if (method is System.Reflection.Emit.DynamicMethod || + method.ContainsGenericParameters || + (method.CallingConvention & CallingConventions.VarArgs) != 0) { return false; } - // ActivatorUtilities.CreateInstance reaches StaticFileMiddleware (4), SessionMiddleware and - // RateLimitingMiddleware (5), OutputCacheMiddleware (6), and EndpointRoutingMiddleware (8). - thunk = referenceArguments ? argCount switch - { - 0 => &Ctor_0, - 1 => &Ctor_1, - 2 => &Ctor_2, - 3 => &Ctor_3, - 4 => &Ctor_4, - 5 => &Ctor_5, - 6 => &Ctor_6, - 7 => &Ctor_7, - 8 => &Ctor_8, - _ => null, - } : ClassifyConstructor(argumentTypes); - } - else if (method is MethodInfo methodInfo) - { - Type returnType = methodInfo.ReturnType; - if (method.IsStatic) + int argCount = argumentTypes.Length; + + bool referenceArguments = true; + for (int i = 0; i < argCount; i++) { - if (referenceArguments) + if (!IsReferenceType(argumentTypes[i])) { - thunk = ClassifyStaticReferenceArguments(argCount, returnType); + referenceArguments = false; + break; } - else if (argCount == 1 && GetInputType(argumentTypes[0]) == typeof(int) && IsReferenceType(returnType)) + } + + if (method is ConstructorInfo) + { + if (method.IsStatic || + method.DeclaringType is not Type declaringType || + !IsReferenceType(declaringType) || + declaringType.IsAbstract || + declaringType.IsArray || + declaringType.ContainsGenericParameters || + declaringType == typeof(string)) { - // SignalR BuildStream(int streamBufferCapacity). - thunk = &Static_Object_Int; + return false; } - else if (argCount == 2 && IsReferenceType(argumentTypes[0]) && - argumentTypes[1].IsByRef && IsReferenceType(argumentTypes[1].GetElementType()!) && - returnType == typeof(bool)) + + // ActivatorUtilities.CreateInstance reaches StaticFileMiddleware (4), SessionMiddleware and + // RateLimitingMiddleware (5), OutputCacheMiddleware (6), and EndpointRoutingMiddleware (8). + thunk = referenceArguments ? argCount switch { - // Thunk for the .NET TryParse pattern with reference-type results. - // Typed-header TryParse(string, out T) and TryParseList(IList, out IList). - thunk = &Static_Bool_ObjByRefObj; - } + 0 => &Ctor_0, + 1 => &Ctor_1, + 2 => &Ctor_2, + 3 => &Ctor_3, + 4 => &Ctor_4, + 5 => &Ctor_5, + 6 => &Ctor_6, + 7 => &Ctor_7, + 8 => &Ctor_8, + _ => null, + } : ClassifyConstructor(argumentTypes); } - else if (method.DeclaringType is Type declaringType && IsReferenceType(declaringType)) + else if (method is MethodInfo methodInfo) { - if (referenceArguments) + Type returnType = methodInfo.ReturnType; + if (method.IsStatic) { - thunk = ClassifyInstanceReferenceArguments(argCount, returnType); + if (referenceArguments) + { + thunk = ClassifyStaticReferenceArguments(argCount, returnType); + } + else if (argCount == 1 && GetInputType(argumentTypes[0]) == typeof(int) && IsReferenceType(returnType)) + { + // SignalR BuildStream(int streamBufferCapacity). + thunk = &Static_Object_Int; + } + else if (argCount == 2 && IsReferenceType(argumentTypes[0]) && + argumentTypes[1].IsByRef && IsReferenceType(argumentTypes[1].GetElementType()!) && + returnType == typeof(bool)) + { + // Thunk for the .NET TryParse pattern with reference-type results. + // Typed-header TryParse(string, out T) and TryParseList(IList, out IList). + thunk = &Static_Bool_ObjByRefObj; + } } - else if (returnType == typeof(void)) + else if (method.DeclaringType is Type declaringType && IsReferenceType(declaringType)) { - if (argCount == 1) + if (referenceArguments) { - // Named attribute setters (Duration, NoStore, Location, CaptureUnmatchedValues) - // and Blazor RemoveRootComponent(int). - thunk = ClassifyInstancePrimitive(GetInputType(argumentTypes[0])); + thunk = ClassifyInstanceReferenceArguments(argCount, returnType); } - else if (argCount == 4 && - argumentTypes[0] == typeof(float) && argumentTypes[1] == typeof(float) && - argumentTypes[2] == typeof(float) && GetInputType(argumentTypes[3]) == typeof(int)) + else if (returnType == typeof(void)) { - // Blazor VirtualizeJsInterop.OnSpacerBeforeVisible and OnSpacerAfterVisible. - thunk = &Instance_Void_FloatFloatFloatInt; + if (argCount == 1) + { + // Named attribute setters (Duration, NoStore, Location, CaptureUnmatchedValues) + // and Blazor RemoveRootComponent(int). + thunk = ClassifyInstancePrimitive(GetInputType(argumentTypes[0])); + } + else if (argCount == 4 && + argumentTypes[0] == typeof(float) && argumentTypes[1] == typeof(float) && + argumentTypes[2] == typeof(float) && GetInputType(argumentTypes[3]) == typeof(int)) + { + // Blazor VirtualizeJsInterop.OnSpacerBeforeVisible and OnSpacerAfterVisible. + thunk = &Instance_Void_FloatFloatFloatInt; + } } } } - } - if (thunk is null) - { - return false; - } + if (thunk is null) + { + return false; + } - if (method.IsStatic || !method.IsVirtual || method.IsFinal) - { - functionPointer = method.MethodHandle.GetFunctionPointer(); - } + if (method.IsStatic || !method.IsVirtual || method.IsFinal) + { + functionPointer = method.MethodHandle.GetFunctionPointer(); + } - return true; + return true; + } } private static bool IsReferenceType(Type type) => @@ -213,446 +222,785 @@ private static bool IsReferenceType(Type type) => private static Type GetInputType(RuntimeType type) => type.IsActualEnum ? type.GetEnumUnderlyingType() : type; - private static delegate* ClassifyConstructor(ReadOnlySpan arguments) + private static unsafe delegate* ClassifyConstructor(ReadOnlySpan arguments) { - if (arguments.Length == 1) + unsafe { - Type type = GetInputType(arguments[0]); - // StreamRenderingAttribute and RequireAntiforgeryTokenAttribute. - if (type == typeof(bool)) return &Ctor_Bool; - // Length-based route constraints and BindingBehaviorAttribute's enum constructor. - if (type == typeof(int)) return &Ctor_Int; - // Min/Max route constraints and RequestSizeLimitAttribute. - if (type == typeof(long)) return &Ctor_Long; + if (arguments.Length == 1) + { + Type type = GetInputType(arguments[0]); + // StreamRenderingAttribute and RequireAntiforgeryTokenAttribute. + if (type == typeof(bool)) return &Ctor_Bool; + // Length-based route constraints and BindingBehaviorAttribute's enum constructor. + if (type == typeof(int)) return &Ctor_Int; + // Min/Max route constraints and RequestSizeLimitAttribute. + if (type == typeof(long)) return &Ctor_Long; + } + else if (arguments.Length == 2) + { + Type first = GetInputType(arguments[0]); + Type second = GetInputType(arguments[1]); + // LengthRouteConstraint(int, int) and RangeRouteConstraint(long, long). + if (first == typeof(int) && second == typeof(int)) return &Ctor_IntInt; + if (first == typeof(long) && second == typeof(long)) return &Ctor_LongLong; + // ProducesResponseTypeAttribute(Type, int). + if (IsReferenceType(first) && second == typeof(int)) return &Ctor_ObjInt; + } + else if (arguments.Length == 4 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[3])) + { + // ProducesResponseTypeAttribute(Type, int, string, string[]). + if (GetInputType(arguments[1]) == typeof(int) && IsReferenceType(arguments[2])) return &Ctor_ObjIntObjObj; + // MVC ArrayModelBinder and CollectionModelBinder. + if (IsReferenceType(arguments[1]) && GetInputType(arguments[2]) == typeof(bool)) return &Ctor_ObjObjBoolObj; + } + else if (arguments.Length == 5 && + IsReferenceType(arguments[0]) && IsReferenceType(arguments[1]) && IsReferenceType(arguments[2]) && + GetInputType(arguments[3]) == typeof(bool) && IsReferenceType(arguments[4])) + { + // MVC DictionaryModelBinder. + return &Ctor_ObjObjObjBoolObj; + } + + return null; } - else if (arguments.Length == 2) + } + + private static unsafe delegate* ClassifyStaticReferenceArguments(int count, Type returnType) + { + unsafe { - Type first = GetInputType(arguments[0]); - Type second = GetInputType(arguments[1]); - // LengthRouteConstraint(int, int) and RangeRouteConstraint(long, long). - if (first == typeof(int) && second == typeof(int)) return &Ctor_IntInt; - if (first == typeof(long) && second == typeof(long)) return &Ctor_LongLong; - // ProducesResponseTypeAttribute(Type, int). - if (IsReferenceType(first) && second == typeof(int)) return &Ctor_ObjInt; + if (count == 0) + { + return ClassifyStatic0Return(returnType); + } + + if (returnType == typeof(void)) + { + return count switch + { + 1 => &Static_Void_1Obj, + 2 => &Static_Void_2Obj, + 3 => &Static_Void_3Obj, + 4 => &Static_Void_4Obj, + _ => null, + }; + } + + return IsReferenceType(returnType) ? count switch + { + 1 => &Static_Object_1Obj, + 2 => &Static_Object_2Obj, + 3 => &Static_Object_3Obj, + 4 => &Static_Object_4Obj, + _ => null, + } : null; } - else if (arguments.Length == 4 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[3])) + } + + private static unsafe delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) + { + unsafe { - // ProducesResponseTypeAttribute(Type, int, string, string[]). - if (GetInputType(arguments[1]) == typeof(int) && IsReferenceType(arguments[2])) return &Ctor_ObjIntObjObj; - // MVC ArrayModelBinder and CollectionModelBinder. - if (IsReferenceType(arguments[1]) && GetInputType(arguments[2]) == typeof(bool)) return &Ctor_ObjObjBoolObj; + if (returnType == typeof(void)) + { + return count switch + { + 0 => &Instance_Void_0, + 1 => &Instance_Void_1Obj, + 2 => &Instance_Void_2Obj, + 3 => &Instance_Void_3Obj, + 4 => &Instance_Void_4Obj, + _ => null, + }; + } + + if (IsReferenceType(returnType)) + { + return count switch + { + 0 => &Instance_Object_0, + 1 => &Instance_Object_1Obj, + 2 => &Instance_Object_2Obj, + 3 => &Instance_Object_3Obj, + 4 => &Instance_Object_4Obj, + _ => null, + }; + } + + if (count == 2 && returnType == typeof(int)) + { + // Blazor AddRootComponent(string, string). + return &Instance_Int_2Obj; + } + + if (count == 0) + { + if (returnType == typeof(bool)) return &Instance_Bool_0; + if (returnType == typeof(byte)) return &Instance_Byte_0; + if (returnType == typeof(sbyte)) return &Instance_SByte_0; + if (returnType == typeof(char)) return &Instance_Char_0; + if (returnType == typeof(short)) return &Instance_Short_0; + if (returnType == typeof(ushort)) return &Instance_UShort_0; + if (returnType == typeof(int)) return &Instance_Int_0; + if (returnType == typeof(uint)) return &Instance_UInt_0; + if (returnType == typeof(long)) return &Instance_Long_0; + if (returnType == typeof(ulong)) return &Instance_ULong_0; + if (returnType == typeof(float)) return &Instance_Float_0; + if (returnType == typeof(double)) return &Instance_Double_0; + if (returnType == typeof(nint)) return &Instance_NInt_0; + if (returnType == typeof(nuint)) return &Instance_NUInt_0; + } + + return null; } - else if (arguments.Length == 5 && - IsReferenceType(arguments[0]) && IsReferenceType(arguments[1]) && IsReferenceType(arguments[2]) && - GetInputType(arguments[3]) == typeof(bool) && IsReferenceType(arguments[4])) + } + + private static unsafe delegate* ClassifyInstancePrimitive(Type type) + { + unsafe { - // MVC DictionaryModelBinder. - return &Ctor_ObjObjObjBoolObj; + if (type == typeof(bool)) return &Instance_Void_Bool; + if (type == typeof(byte)) return &Instance_Void_Byte; + if (type == typeof(sbyte)) return &Instance_Void_SByte; + if (type == typeof(char)) return &Instance_Void_Char; + if (type == typeof(short)) return &Instance_Void_Short; + if (type == typeof(ushort)) return &Instance_Void_UShort; + if (type == typeof(int)) return &Instance_Void_Int; + if (type == typeof(uint)) return &Instance_Void_UInt; + if (type == typeof(long)) return &Instance_Void_Long; + if (type == typeof(ulong)) return &Instance_Void_ULong; + if (type == typeof(float)) return &Instance_Void_Float; + if (type == typeof(double)) return &Instance_Void_Double; + if (type == typeof(nint)) return &Instance_Void_NInt; + if (type == typeof(nuint)) return &Instance_Void_NUInt; + return null; } - - return null; } - private static delegate* ClassifyStaticReferenceArguments(int count, Type returnType) + // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into + // the classifier's compiled body. + + private static unsafe delegate* ClassifyStatic0Return(Type returnType) { - if (count == 0) + unsafe { - return ClassifyStatic0Return(returnType); + if (returnType == typeof(void)) return &Static_Void_0; + if (returnType == typeof(bool)) return &Static_Bool_0; + if (returnType == typeof(byte)) return &Static_Byte_0; + if (returnType == typeof(sbyte)) return &Static_SByte_0; + if (returnType == typeof(char)) return &Static_Char_0; + if (returnType == typeof(short)) return &Static_Short_0; + if (returnType == typeof(ushort)) return &Static_UShort_0; + if (returnType == typeof(int)) return &Static_Int_0; + if (returnType == typeof(uint)) return &Static_UInt_0; + if (returnType == typeof(long)) return &Static_Long_0; + if (returnType == typeof(ulong)) return &Static_ULong_0; + if (returnType == typeof(float)) return &Static_Float_0; + if (returnType == typeof(double)) return &Static_Double_0; + if (returnType == typeof(nint) || returnType.IsFunctionPointer) return &Static_NInt_0; + if (returnType == typeof(nuint)) return &Static_NUInt_0; + + if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) + return &Static_Object_0; + + return null; } + } - if (returnType == typeof(void)) + // Per-shape thunks. JIT compiles only the ones used. + + private static unsafe object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe { - return count switch - { - 1 => &Static_Void_1Obj, - 2 => &Static_Void_2Obj, - 3 => &Static_Void_3Obj, - 4 => &Static_Void_4Obj, - _ => null, - }; + ((delegate*)fn)(); + return null; } + } - return IsReferenceType(returnType) ? count switch + private static unsafe object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe { - 1 => &Static_Object_1Obj, - 2 => &Static_Object_2Obj, - 3 => &Static_Object_3Obj, - 4 => &Static_Object_4Obj, - _ => null, - } : null; + return ((delegate*)fn)(); + } } - private static delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) + private static unsafe object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) { - if (returnType == typeof(void)) + unsafe { - return count switch - { - 0 => &Instance_Void_0, - 1 => &Instance_Void_1Obj, - 2 => &Instance_Void_2Obj, - 3 => &Instance_Void_3Obj, - 4 => &Instance_Void_4Obj, - _ => null, - }; + return ((delegate*)fn)(); } + } - if (IsReferenceType(returnType)) + private static unsafe object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe { - return count switch - { - 0 => &Instance_Object_0, - 1 => &Instance_Object_1Obj, - 2 => &Instance_Object_2Obj, - 3 => &Instance_Object_3Obj, - 4 => &Instance_Object_4Obj, - _ => null, - }; + return ((delegate*)fn)(); } + } - if (count == 2 && returnType == typeof(int)) + private static unsafe object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe { - // Blazor AddRootComponent(string, string). - return &Instance_Int_2Obj; + return ((delegate*)fn)(); } + } - if (count == 0) + private static unsafe object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe { - if (returnType == typeof(bool)) return &Instance_Bool_0; - if (returnType == typeof(byte)) return &Instance_Byte_0; - if (returnType == typeof(sbyte)) return &Instance_SByte_0; - if (returnType == typeof(char)) return &Instance_Char_0; - if (returnType == typeof(short)) return &Instance_Short_0; - if (returnType == typeof(ushort)) return &Instance_UShort_0; - if (returnType == typeof(int)) return &Instance_Int_0; - if (returnType == typeof(uint)) return &Instance_UInt_0; - if (returnType == typeof(long)) return &Instance_Long_0; - if (returnType == typeof(ulong)) return &Instance_ULong_0; - if (returnType == typeof(float)) return &Instance_Float_0; - if (returnType == typeof(double)) return &Instance_Double_0; - if (returnType == typeof(nint)) return &Instance_NInt_0; - if (returnType == typeof(nuint)) return &Instance_NUInt_0; + return ((delegate*)fn)(); } - - return null; } - private static delegate* ClassifyInstancePrimitive(Type type) + private static unsafe object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, Type? ___) { - if (type == typeof(bool)) return &Instance_Void_Bool; - if (type == typeof(byte)) return &Instance_Void_Byte; - if (type == typeof(sbyte)) return &Instance_Void_SByte; - if (type == typeof(char)) return &Instance_Void_Char; - if (type == typeof(short)) return &Instance_Void_Short; - if (type == typeof(ushort)) return &Instance_Void_UShort; - if (type == typeof(int)) return &Instance_Void_Int; - if (type == typeof(uint)) return &Instance_Void_UInt; - if (type == typeof(long)) return &Instance_Void_Long; - if (type == typeof(ulong)) return &Instance_Void_ULong; - if (type == typeof(float)) return &Instance_Void_Float; - if (type == typeof(double)) return &Instance_Void_Double; - if (type == typeof(nint)) return &Instance_Void_NInt; - if (type == typeof(nuint)) return &Instance_Void_NUInt; - return null; + unsafe + { + return ((delegate*)fn)(); + } } - // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into - // the classifier's compiled body. + private static unsafe object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } - private static delegate* ClassifyStatic0Return(Type returnType) + private static unsafe object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) { - if (returnType == typeof(void)) return &Static_Void_0; - if (returnType == typeof(bool)) return &Static_Bool_0; - if (returnType == typeof(byte)) return &Static_Byte_0; - if (returnType == typeof(sbyte)) return &Static_SByte_0; - if (returnType == typeof(char)) return &Static_Char_0; - if (returnType == typeof(short)) return &Static_Short_0; - if (returnType == typeof(ushort)) return &Static_UShort_0; - if (returnType == typeof(int)) return &Static_Int_0; - if (returnType == typeof(uint)) return &Static_UInt_0; - if (returnType == typeof(long)) return &Static_Long_0; - if (returnType == typeof(ulong)) return &Static_ULong_0; - if (returnType == typeof(float)) return &Static_Float_0; - if (returnType == typeof(double)) return &Static_Double_0; - if (returnType == typeof(nint) || returnType.IsFunctionPointer) return &Static_NInt_0; - if (returnType == typeof(nuint)) return &Static_NUInt_0; + unsafe + { + return ((delegate*)fn)(); + } + } - if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) - return &Static_Object_0; + private static unsafe object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } - return null; + private static unsafe object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } } - // Per-shape thunks. JIT compiles only the ones used. + private static unsafe object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } - private static object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, Type? ___) { - ((delegate*)fn)(); - return null; + unsafe + { + return ((delegate*)fn)(); + } } - private static object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); - private static object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, Type? ___) - => ((delegate*)fn)(); + private static unsafe object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } - private static object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) { - ((delegate*)fn)(Unsafe.Read((void*)args[0])); - return null; + unsafe + { + return ((delegate*)fn)(); + } } - private static object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) - => ((delegate*)fn)(Unsafe.Read((void*)args[0])); + private static unsafe object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } - private static object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) { - ((delegate*)fn)( - Unsafe.Read((void*)args[0]), - Unsafe.Read((void*)args[1])); - return null; + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0]); + return null; +#pragma warning restore CS8500 + } } - private static object? ReadReference(IntPtr* args, int index) => - Unsafe.Read((void*)args[index]); + private static unsafe object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0]); +#pragma warning restore CS8500 + } + } - private static T ReadPrimitive(IntPtr* args, int index) where T : unmanaged => - Unsafe.ReadUnaligned((void*)args[index]); + private static unsafe object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)( + *(object?*)(void*)args[0], + *(object?*)(void*)args[1]); + return null; +#pragma warning restore CS8500 + } + } - private static object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => - ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1)); + private static unsafe object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } - private static object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => - ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + private static unsafe object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); +#pragma warning restore CS8500 + } + } - private static object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) => - ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + private static unsafe object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); +#pragma warning restore CS8500 + } + } - private static object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) { - ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); - return null; + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); + return null; +#pragma warning restore CS8500 + } } - private static object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) { - ((delegate*)fn)(ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); - return null; + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return null; +#pragma warning restore CS8500 + } } - private static object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, Type? __) => - ((delegate*)fn)(ReadPrimitive(args, 0)); + private static unsafe object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { + return ((delegate*)fn)(*(int*)(void*)args[0]); + } + } - private static object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, Type? __) => - ((delegate*)fn)(ReadReference(args, 0), ref Unsafe.AsRef((void*)args[1])); + private static unsafe object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], ref Unsafe.AsRef((void*)args[1])); +#pragma warning restore CS8500 + } + } - private static object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } #pragma warning disable CA1859 // These thunks must match the shared object-returning function-pointer signature. - private static object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) => - InstanceCalliHelper.Call((delegate*)fn, obj!); + private static unsafe object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } - private static object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); + private static unsafe object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } #pragma warning restore CA1859 - private static object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0)); + private static unsafe object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0]); +#pragma warning restore CS8500 + } + } - private static object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); + private static unsafe object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } - private static object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); + private static unsafe object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); +#pragma warning restore CS8500 + } + } - private static object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) => - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); + private static unsafe object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); +#pragma warning restore CS8500 + } + } - private static object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, Type? __) { - InstanceCalliHelper.Call((delegate*)fn, obj!); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!); + return null; + } } - private static object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0)); - return null; + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0]); + return null; +#pragma warning restore CS8500 + } } - private static object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1)); - return null; + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); + return null; +#pragma warning restore CS8500 + } } - private static object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2)); - return null; + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); + return null; +#pragma warning restore CS8500 + } } - private static object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3)); - return null; + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return null; +#pragma warning restore CS8500 + } } - private static object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(bool*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(byte*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(sbyte*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(char*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(short*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(ushort*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(int*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(uint*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(long*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(ulong*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(float*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(double*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(nint*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, ReadPrimitive(args, 0)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(nuint*)(void*)args[0]); + return null; + } } - private static object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, Type? _) { - InstanceCalliHelper.Call((delegate*)fn, obj!, - ReadPrimitive(args, 0), ReadPrimitive(args, 1), ReadPrimitive(args, 2), ReadPrimitive(args, 3)); - return null; + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, + *(float*)(void*)args[0], *(float*)(void*)args[1], *(float*)(void*)args[2], *(int*)(void*)args[3]); + return null; + } } // Ctor thunks: `obj` non-null = call ctor on existing instance, null = allocate first. @@ -665,149 +1013,225 @@ private static T ReadPrimitive(IntPtr* args, int index) where T : unmanaged = private static object GetConstructorInstance(object? obj, Type? declaringType) => obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); - private static object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) + private static unsafe object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance); + return obj is null ? instance : null; + } } - private static object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call( - (delegate*)fn, - instance, - Unsafe.Read((void*)args[0])); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call( - (delegate*)fn, - instance, - Unsafe.Read((void*)args[0]), - Unsafe.Read((void*)args[1])); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0], + *(object?*)(void*)args[1]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call( - (delegate*)fn, - instance, - Unsafe.Read((void*)args[0]), - Unsafe.Read((void*)args[1]), - Unsafe.Read((void*)args[2])); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0], + *(object?*)(void*)args[1], + *(object?*)(void*)args[2]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - Instance_Void_4Obj(fn, instance, args, declaringType); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_4Obj(fn, instance, args, declaringType); + return obj is null ? instance : null; + } } - private static object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5), ReadReference(args, 6)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5], *(object?*)(void*)args[6]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadReference(args, 3), ReadReference(args, 4), ReadReference(args, 5), ReadReference(args, 6), ReadReference(args, 7)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5], *(object?*)(void*)args[6], *(object?*)(void*)args[7]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - Instance_Void_Bool(fn, instance, args, declaringType); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Bool(fn, instance, args, declaringType); + return obj is null ? instance : null; + } } - private static object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - Instance_Void_Int(fn, instance, args, declaringType); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Int(fn, instance, args, declaringType); + return obj is null ? instance : null; + } } - private static object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - Instance_Void_Long(fn, instance, args, declaringType); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Long(fn, instance, args, declaringType); + return obj is null ? instance : null; + } } - private static object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, ReadPrimitive(args, 0), ReadPrimitive(args, 1)); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(int*)(void*)args[0], *(int*)(void*)args[1]); + return obj is null ? instance : null; + } } - private static object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, ReadPrimitive(args, 0), ReadPrimitive(args, 1)); - return obj is null ? instance : null; + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(long*)(void*)args[0], *(long*)(void*)args[1]); + return obj is null ? instance : null; + } } - private static object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, ReadReference(args, 0), ReadPrimitive(args, 1)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(object?*)(void*)args[0], *(int*)(void*)args[1]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadPrimitive(args, 1), ReadReference(args, 2), ReadReference(args, 3)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(int*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadPrimitive(args, 2), ReadReference(args, 3)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(bool*)(void*)args[2], *(object?*)(void*)args[3]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } - private static object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) { - object instance = GetConstructorInstance(obj, declaringType); - InstanceCalliHelper.Call((delegate*)fn, instance, - ReadReference(args, 0), ReadReference(args, 1), ReadReference(args, 2), ReadPrimitive(args, 3), ReadReference(args, 4)); - return obj is null ? instance : null; + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(bool*)(void*)args[3], *(object?*)(void*)args[4]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } } } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 5cae407826fd02..8c0aedd156e21d 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -500,6 +500,20 @@ public void Invoke_StaticReferenceReturn_SharedThunk(string methodName, object?[ public static IEnumerable Invoke_StaticReferenceVoid_SharedThunk_TestData() { + var tracker2 = new IntrinsicInvokeActionTracker(); + var reference2 = new IntrinsicInvokeReference(2); + Action callback2 = reference => + { + Assert.Same(reference2, reference); + tracker2.Callback(); + }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void2), + new object?[] { callback2, reference2 }, + tracker2 + }; + var tracker3 = new IntrinsicInvokeActionTracker(); yield return new object[] { @@ -2078,6 +2092,12 @@ public static Action Return4( return callback; } + public static void Void2(Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + callback(reference); + } + public static void Void3(Task task, Action callback, IIntrinsicInvokeReference reference) { GC.Collect(); From 2346fce1db4aa061585fc10ef2e7391b52aedf00 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 Sep 2026 15:52:17 -0700 Subject: [PATCH 10/38] Address CCR feedback: exclude P/Invoke and UnmanagedCallersOnly methods from shared-thunk classification TryGetShape's GetFunctionPointer call can resolve a raw unmanaged entrypoint for methods that are P/Invoke-implemented or UnmanagedCallersOnly, bypassing the managed calling-convention validation that the emitted-invoker path relies on to correctly throw NotSupportedException. Exclude such methods from the shared managed-calli shapes and fall back to the existing emitted path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index dc467a81eef993..081fc6c1ba4f64 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; namespace System.Reflection @@ -108,8 +109,14 @@ private static unsafe bool TryGetShape( if (method is System.Reflection.Emit.DynamicMethod || method.ContainsGenericParameters || - (method.CallingConvention & CallingConventions.VarArgs) != 0) + (method.CallingConvention & CallingConventions.VarArgs) != 0 || + (method.Attributes & MethodAttributes.PinvokeImpl) != 0 || + method.IsDefined(typeof(UnmanagedCallersOnlyAttribute), inherit: false)) { + // P/Invoke and UnmanagedCallersOnly methods can resolve to an unmanaged entrypoint + // (see GetFunctionPointer's use of CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE), which is + // not compatible with the shared managed-calli shapes below. Fall back to the + // emitted/validation path, which correctly rejects unsupported combinations. return false; } From 9f7925ef30482f96c7479bacd799ec9860792e16 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 16 Sep 2026 13:12:41 -0700 Subject: [PATCH 11/38] Tune reflection invoke specialization thresholds Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 21 +- .../ConstructorInfoTests.cs | 2 +- .../ConstructorInvokerTests.cs | 4 +- .../MethodInfoTests.cs | 180 +++++++++++++++++- .../MethodInvokerTests.cs | 8 +- 5 files changed, 202 insertions(+), 13 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 081fc6c1ba4f64..71afeaa6c2ced9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -13,7 +13,9 @@ namespace System.Reflection // This type is included in SystemDomain::IsReflectionInvocationMethod for caller stack walks. internal static class IntrinsicInvokeHelper { - private const int SpecializationThreshold = 10_000; + private const int CachedTargetSpecializationThreshold = 10_000; + private const int ClassVirtualSpecializationThreshold = 3_000; + private const int InterfaceOrGenericVirtualSpecializationThreshold = 1_000; private const MethodBase.InvokerStrategy StrategyDetermined = MethodBase.InvokerStrategy.StrategyDetermined_Obj4Args | MethodBase.InvokerStrategy.StrategyDetermined_ObjSpanArgs | @@ -24,6 +26,7 @@ internal struct InvokeState internal IntPtr Thunk; internal IntPtr FunctionPointer; internal int InvocationCount; + internal int SpecializationThreshold; } internal static unsafe object? Invoke( @@ -52,13 +55,14 @@ internal struct InvokeState } state.FunctionPointer = functionPointer; + state.SpecializationThreshold = GetSpecializationThreshold(method, functionPointer); strategy |= StrategyDetermined; Volatile.Write(ref state.Thunk, (IntPtr)thunk); } if (RuntimeFeature.IsDynamicCodeCompiled && !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && - Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) + Interlocked.Increment(ref state.InvocationCount) >= state.SpecializationThreshold) { // Let the normal strategy selection specialize the next invocation's argument path. strategy &= ~StrategyDetermined; @@ -77,6 +81,19 @@ internal struct InvokeState } } + private static int GetSpecializationThreshold(MethodBase method, IntPtr functionPointer) + { + if (functionPointer != IntPtr.Zero) + { + return CachedTargetSpecializationThreshold; + } + + RuntimeMethodInfo runtimeMethod = (RuntimeMethodInfo)method; + return runtimeMethod.DeclaringType!.IsInterface || runtimeMethod.IsGenericMethod + ? InterfaceOrGenericVirtualSpecializationThreshold + : ClassVirtualSpecializationThreshold; + } + private static unsafe object? InvokeEmitted( ref MethodBase.InvokerStrategy strategy, ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 9171a0fd9014ff..66f98f56eb0855 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -39,7 +39,7 @@ public void Invoke_AllocatingAndExistingInstanceAcrossTiers(bool valueArgument) object value = valueArgument ? 42 : new object(); object[] arguments = { value }; - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { var allocated = (MutableConstructorTarget)constructor.Invoke(arguments); Assert.NotSame(existing, allocated); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs index 30efd2a744bf49..888f2184b1cd65 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs @@ -35,11 +35,11 @@ public void SharedThunk_CachedInvokerPromotes(int argumentCount) object?[] arguments = new object?[argumentCount]; Array.Fill(arguments, argument); - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { var result = (CachedInvokerTarget)(argumentCount == 1 ? invoker.Invoke(argument) : invoker.Invoke(arguments.AsSpan())); Assert.Same(argument, result.Value); - if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold - 1) { IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 8c0aedd156e21d..e2f9dcacd072e4 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -54,7 +54,7 @@ public void Invoke_ReturnValueAcrossTiers(Type returnType, object expected) MethodInfo method = target.GetMethod(nameof(ReturnValueTarget.GetValue)); MethodInvoker invoker = MethodInvoker.Create(method); - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { Assert.Equal(expected, method.Invoke(null, null)); Assert.Equal(expected, invoker.Invoke(null)); @@ -615,6 +615,94 @@ public void Invoke_VirtualDispatch_SharedThunk( AssertVirtualDispatchSharedThunk(method, receivers, expected); } + [Theory] + [InlineData( + typeof(IntrinsicInvokeThresholdCachedTarget), + nameof(IntrinsicInvokeThresholdCachedTarget.Echo), + typeof(IntrinsicInvokeThresholdCachedTarget), + false, + IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold, + false)] + [InlineData( + typeof(IntrinsicInvokeThresholdVirtualBase), + nameof(IntrinsicInvokeThresholdVirtualBase.Echo), + typeof(IntrinsicInvokeThresholdVirtualDerived), + false, + IntrinsicInvokeSelectionAssertions.ClassVirtualSpecializationThreshold, + true)] + [InlineData( + typeof(IIntrinsicInvokeThresholdInterface), + nameof(IIntrinsicInvokeThresholdInterface.Echo), + typeof(IntrinsicInvokeThresholdInterfaceTarget), + false, + IntrinsicInvokeSelectionAssertions.InterfaceOrGenericVirtualSpecializationThreshold, + true)] + [InlineData( + typeof(IntrinsicInvokeThresholdGenericMethodBase), + nameof(IntrinsicInvokeThresholdGenericMethodBase.Echo), + typeof(IntrinsicInvokeThresholdGenericMethodDerived), + true, + IntrinsicInvokeSelectionAssertions.InterfaceOrGenericVirtualSpecializationThreshold, + true)] + [InlineData( + typeof(IntrinsicInvokeThresholdGenericTypeBase), + nameof(IntrinsicInvokeThresholdGenericTypeBase.Echo), + typeof(IntrinsicInvokeThresholdGenericTypeDerived), + false, + IntrinsicInvokeSelectionAssertions.ClassVirtualSpecializationThreshold, + true)] + [InlineData( + typeof(IntrinsicInvokeThresholdFinalGenericMethodDerived), + nameof(IntrinsicInvokeThresholdFinalGenericMethodDerived.Echo), + typeof(IntrinsicInvokeThresholdFinalGenericMethodDerived), + true, + IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold, + false)] + public void Invoke_SharedThunkPromotesAtDispatchPathThreshold( + Type declaringType, + string methodName, + Type targetType, + bool makeGenericMethod, + int threshold, + bool useVirtualResolution) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + if (makeGenericMethod) + { + method = method.MakeGenericMethod(typeof(int)); + } + + object target = Activator.CreateInstance(targetType)!; + MethodInvoker invoker = MethodInvoker.Create(method); + object argument = new object(); + object?[] arguments = { argument }; + + for (int i = 0; i < threshold; i++) + { + Assert.Same(argument, method.Invoke(target, arguments)); + Assert.Same(argument, invoker.Invoke(target, argument)); + } + + if (useVirtualResolution) + { + IntrinsicInvokeSelectionAssertions.AssertUsesVirtualResolution(method); + IntrinsicInvokeSelectionAssertions.AssertUsesVirtualResolution(invoker); + } + else + { + IntrinsicInvokeSelectionAssertions.AssertUsesCachedTarget(method); + IntrinsicInvokeSelectionAssertions.AssertUsesCachedTarget(invoker); + } + + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(method, threshold); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, threshold); + + Assert.Same(argument, method.Invoke(target, arguments)); + Assert.Same(argument, invoker.Invoke(target, argument)); + IntrinsicInvokeSelectionAssertions.AssertPromoted(method); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + } + [Fact] public void Invoke_GenericVirtualDispatch_SharedThunk() { @@ -971,7 +1059,7 @@ private static WeakReference CreateAndInvokeCollectibleMethod(bool useMethodInvo object expected = referenceArgument ? new object() : 42; object[] arguments = { expected }; - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { Assert.Equal(expected, invoker is not null ? invoker.Invoke(null, expected) : method.Invoke(null, arguments)); } @@ -1752,7 +1840,9 @@ private void DummyMethod2() internal static class IntrinsicInvokeSelectionAssertions { - internal const int SpecializationThreshold = 10_000; + internal const int CachedTargetSpecializationThreshold = 10_000; + internal const int ClassVirtualSpecializationThreshold = 3_000; + internal const int InterfaceOrGenericVirtualSpecializationThreshold = 1_000; private const string ForceEmitInvokeSwitch = "Switch.System.Reflection.ForceEmitInvoke"; private const string ForceInterpretedInvokeSwitch = "Switch.System.Reflection.ForceInterpretedInvoke"; private const string SharedThunkMethodName = "InvokeWithSharedThunk"; @@ -1811,13 +1901,21 @@ internal static void AssertPromoted(object invoker) return; } - Assert.Equal(SpecializationThreshold, GetInvokeStateField(invoker, "InvocationCount")); + Assert.Equal( + GetInvokeStateField(invoker, "SpecializationThreshold"), + GetInvokeStateField(invoker, "InvocationCount")); Assert.True( GetRefArgsDelegate(invoker).Method.Name != SharedThunkMethodName || GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args") is not null || GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); } + internal static void AssertPromoted(MethodBase method) => + AssertPromoted(GetCachedInvoker(method)); + + internal static void AssertNotPromoted(MethodBase method, int invocationCount) => + AssertNotPromoted(GetCachedInvoker(method), invocationCount); + internal static void AssertNotPromoted(object invoker, int invocationCount) { if (!ShouldAssertSharedSelection) @@ -1831,6 +1929,22 @@ internal static void AssertNotPromoted(object invoker, int invocationCount) Assert.Equal(ShouldAssertPromotion ? invocationCount : 0, GetInvokeStateField(invoker, "InvocationCount")); } + internal static void AssertUsesCachedTarget(object stateOwner) + { + if (ShouldAssertSharedSelection) + { + Assert.NotEqual(IntPtr.Zero, GetInvokeStateField(GetInvoker(stateOwner), "FunctionPointer")); + } + } + + internal static void AssertUsesVirtualResolution(object stateOwner) + { + if (ShouldAssertSharedSelection) + { + Assert.Equal(IntPtr.Zero, GetInvokeStateField(GetInvoker(stateOwner), "FunctionPointer")); + } + } + private static bool ShouldAssertSharedSelection => PlatformDetection.IsCoreCLR && (!IsForceEmitOnly || !RuntimeFeature.IsDynamicCodeCompiled); @@ -1857,6 +1971,9 @@ private static object GetCachedInvoker(MethodBase method) return invoker; } + private static object GetInvoker(object stateOwner) => + stateOwner is MethodBase method ? GetCachedInvoker(method) : stateOwner; + private static Delegate GetRefArgsDelegate(object invoker) { FieldInfo? field = invoker.GetType().GetField("_invokeFunc_RefArgs", InstanceFields); @@ -1898,6 +2015,61 @@ internal sealed class IntrinsicInvokeReference : IIntrinsicInvokeReference public int Value { get; } } + internal sealed class IntrinsicInvokeThresholdCachedTarget + { + public object Echo(object value) => value; + } + + internal class IntrinsicInvokeThresholdVirtualBase + { + public virtual object Echo(object value) => value; + } + + internal sealed class IntrinsicInvokeThresholdVirtualDerived : IntrinsicInvokeThresholdVirtualBase + { + public override object Echo(object value) => value; + } + + internal interface IIntrinsicInvokeThresholdInterface + { + object Echo(object value); + } + + internal sealed class IntrinsicInvokeThresholdInterfaceTarget : IIntrinsicInvokeThresholdInterface + { + public object Echo(object value) => value; + } + + internal class IntrinsicInvokeThresholdGenericMethodBase + { + public virtual object Echo(object value) => value; + } + + internal sealed class IntrinsicInvokeThresholdGenericMethodDerived : IntrinsicInvokeThresholdGenericMethodBase + { + public override object Echo(object value) => value; + } + + internal class IntrinsicInvokeThresholdFinalGenericMethodBase + { + public virtual object Echo(object value) => value; + } + + internal sealed class IntrinsicInvokeThresholdFinalGenericMethodDerived : IntrinsicInvokeThresholdFinalGenericMethodBase + { + public sealed override object Echo(object value) => value; + } + + internal class IntrinsicInvokeThresholdGenericTypeBase + { + public virtual object Echo(object value) => value; + } + + internal sealed class IntrinsicInvokeThresholdGenericTypeDerived : IntrinsicInvokeThresholdGenericTypeBase + { + public override object Echo(object value) => value; + } + internal sealed class IntrinsicInvokeReferenceTarget { internal int CallCount { get; private set; } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 731df6fa96f510..22dc9a4c48bfed 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -31,7 +31,7 @@ public void SharedThunk_CachedInvokerPromotes(bool useByRef) object argument = new object(); object?[] arguments = { target, null }; - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { if (useByRef) { @@ -43,13 +43,13 @@ public void SharedThunk_CachedInvokerPromotes(bool useByRef) Assert.Same(argument, invoker.Invoke(target, argument)); } - if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold - 1) { IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); } } - Assert.Equal(IntrinsicInvokeSelectionAssertions.SpecializationThreshold + 1, target.CallCount); + Assert.Equal(IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold + 1, target.CallCount); IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); } @@ -72,7 +72,7 @@ public void Constructor_ExistingInstanceAcrossTiers(bool useSpan) MethodInvoker invoker = MethodInvoker.Create(constructor); var target = (RefConstructorTarget)RuntimeHelpers.GetUninitializedObject(typeof(RefConstructorTarget)); - for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold; i++) { if (useSpan) { From 4d7dc9d35be99b5062d7a741d4071d1ceb3352fa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 16 Sep 2026 16:04:13 -0700 Subject: [PATCH 12/38] Remove InstanceCalliHelper tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../InstanceCalliHelperTests.cs | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs deleted file mode 100644 index 0cb8aa30dfa30c..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -extern alias crossgen2; - -using System; -using System.Collections.Generic; -using ILCompiler.ReadyToRun.Tests.TestCasesRunner; -using Internal.IL; -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; -using crossgen2::ILCompiler; -using crossgen2::Internal.IL; -using crossgen2::Internal.JitInterface; -using Xunit; - -namespace ILCompiler.ReadyToRun.Tests; - -public class InstanceCalliHelperTests -{ - [Fact] - public void CallOverloadsUseExplicitThis() - { - TargetArchitecture architecture = TestPaths.TargetArchitecture switch - { - "wasm" => TargetArchitecture.Wasm32, - "armel" => TargetArchitecture.ARM, - _ => Enum.Parse(TestPaths.TargetArchitecture, ignoreCase: true) - }; - TargetOS operatingSystem = Enum.Parse(TestPaths.TargetOS, ignoreCase: true); - var instructionSets = new InstructionSetSupport(default, default, architecture); - var target = new TargetDetails(architecture, operatingSystem, TargetAbi.NativeAot, instructionSets.GetVectorTSimdVector()); - var context = new ReadyToRunCompilerContext(target, SharedGenericsMode.CanonicalReferenceTypes, - bubbleIncludesCoreModule: true, targetAllowsRuntimeCodeGeneration: !TestPaths.IsWasmTarget, - instructionSets, oldTypeSystemContext: null) - { - InputFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["System.Private.CoreLib"] = TestPaths.SystemPrivateCoreLibPath - }, - ReferenceFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) - }; - var coreLib = (EcmaModule)context.GetModuleForSimpleName("System.Private.CoreLib"); - context.SetSystemModule(coreLib); - MetadataType helper = coreLib.GetType("System.Reflection"u8, "InstanceCalliHelper"u8); - int overloadCount = 0; - foreach (MethodDesc method in helper.GetMethods()) - { - if (method.Name != "Call"u8) - { - continue; - } - - overloadCount++; - MethodSignature expected = Assert.IsType(method.Signature[0]).Signature; - MethodIL il = InstanceCalliHelperIntrinsics.EmitIL(method); - var reader = new ILReader(il.GetILBytes()); - int callCount = 0; - while (reader.HasNext) - { - ILOpcode opcode = reader.ReadILOpcode(); - if (opcode == ILOpcode.calli) - { - callCount++; - MethodSignature actual = Assert.IsType(il.GetObject(reader.ReadILToken())); - Assert.False(actual.IsStatic); - Assert.True((actual.Flags & MethodSignatureFlags.ExplicitThis) != 0); - Assert.Equal(expected.ReturnType, actual.ReturnType); - Assert.Equal(expected.Length, actual.Length); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - else - { - reader.Skip(opcode); - } - } - - Assert.Equal(1, callCount); - } - - Assert.NotEqual(0, overloadCount); - } -} From e51260fe09eb1df5b67b83b3331047589db26d77 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Sep 2026 11:09:42 -0700 Subject: [PATCH 13/38] Reword the comment and add some asserts --- .../Reflection/IntrinsicInvokeHelper.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 71afeaa6c2ced9..43c7e7ae1c1e42 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -1027,15 +1027,20 @@ private static Type GetInputType(RuntimeType type) => } } - // Ctor thunks: `obj` non-null = call ctor on existing instance, null = allocate first. - // `string` excluded: `newobj String(...)` is JIT-lowered to a hidden static allocator - // (`METHOD__STRING__CTORF_*` in src/coreclr/vm/corelib.h, wired by - // `ECall::PopulateManagedStringConstructors`); the public ctor has no callable instance - // entry, and `GetUninitializedObject(typeof(string))` is runtime-rejected. + // When calling a constuctor, we can be in one of two scenarios: + // 1. The user has specified an instance and we are calling the constructor like a regular method + // on such instance. + // 2. The user has not specified an instance and we need to allocate one and then call the constructor. + // + // This method unifies the two paths so our shared constructor thunks will always have a "this" instance + // when they go to invoke the constructor. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", Justification = "Caller anchors the ctor MethodBase, keeping its type reachable.")] - private static object GetConstructorInstance(object? obj, Type? declaringType) => - obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + private static object GetConstructorInstance(object? obj, Type? declaringType) + { + Debug.Assert(declaringType != typeof(string)); + return obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + } private static unsafe object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) { From 2f974d36528f82749965faa6ef5726a97da8b6d2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Sep 2026 11:32:40 -0700 Subject: [PATCH 14/38] Handle special reflection constructors Avoid emitting direct existing-instance constructor calls for string and array constructors. Keep those special constructor forms on the allocation-style emitted path so reflection invoke does not call VM special constructors as ordinary instance entrypoints. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 2 ++ .../src/System/Reflection/InvokerEmitUtil.cs | 8 ++++- .../ConstructorInfoTests.cs | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 43c7e7ae1c1e42..0461eca7c06fe6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -1,6 +1,7 @@ // 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; @@ -1039,6 +1040,7 @@ private static Type GetInputType(RuntimeType type) => private static object GetConstructorInstance(object? obj, Type? declaringType) { Debug.Assert(declaringType != typeof(string)); + Debug.Assert(!declaringType!.IsArray); return obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); } 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 7abda092f10b5f..63b42f345be1ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -172,8 +172,11 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, ReadOnlySpan parameters = method.GetParametersAsSpan(); #if !MONO - if (emitNew) + if (emitNew && CanCallConstructorOnExistingInstance(method.DeclaringType!)) { + Debug.Assert(method.DeclaringType != typeof(string)); + Debug.Assert(!method.DeclaringType!.IsArray); + Label allocateAndInvoke = il.DefineLabel(); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Brfalse, allocateAndInvoke); @@ -196,6 +199,9 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); } + private static bool CanCallConstructorOnExistingInstance(Type declaringType) => + declaringType != typeof(string) && !declaringType.IsArray; + private static void EmitLoadRefArguments(ILGenerator il, ReadOnlySpan parameters) { for (int i = 0; i < parameters.Length; i++) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 66f98f56eb0855..9082e9e6e27e19 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -57,6 +57,41 @@ public sealed class MutableConstructorTarget public MutableConstructorTarget(int value) => Value = value; } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Invoke_SpecialAllocatingConstructorsWithExistingInstance(bool useString) + { + ConstructorInfo constructor; + object target; + object?[] arguments; + + if (useString) + { + constructor = typeof(string).GetConstructor(new[] { typeof(char[]) })!; + target = "existing"; + arguments = new object?[] { new[] { 'n', 'e', 'w' } }; + } + else + { + constructor = typeof(object[]).GetConstructor(new[] { typeof(int) })!; + target = new object[1]; + arguments = new object?[] { 3 }; + } + + object? result = constructor.Invoke(target, arguments); + Assert.NotNull(result); + Assert.NotSame(target, result); + if (useString) + { + Assert.Equal("new", result); + } + else + { + Assert.Equal(3, Assert.IsType(result).Length); + } + } + public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() { yield return new object[] { Type.EmptyTypes, Array.Empty() }; From fdfe85663cb2e21cd3153d7bc90ad8180af7fecd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Sep 2026 11:38:47 -0700 Subject: [PATCH 15/38] Preserve special constructor invoke result Keep string and array existing-instance constructor invocation on the no-op constructor result path while avoiding direct emitted calls to their special VM constructor entrypoints. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/InvokerEmitUtil.cs | 29 ++++++++++++------- .../ConstructorInfoTests.cs | 12 +------- 2 files changed, 20 insertions(+), 21 deletions(-) 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 63b42f345be1ca..14a3ea86cb9b53 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -172,24 +172,33 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, ReadOnlySpan parameters = method.GetParametersAsSpan(); #if !MONO - if (emitNew && CanCallConstructorOnExistingInstance(method.DeclaringType!)) + if (emitNew) { - Debug.Assert(method.DeclaringType != typeof(string)); - Debug.Assert(!method.DeclaringType!.IsArray); - Label allocateAndInvoke = il.DefineLabel(); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Brfalse, allocateAndInvoke); - il.Emit(OpCodes.Ldarg_1); - if (method.DeclaringType!.IsValueType) + if (!CanCallConstructorOnExistingInstance(method.DeclaringType!)) { - il.Emit(OpCodes.Unbox, method.DeclaringType); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Ret); + il.MarkLabel(allocateAndInvoke); } + else + { + Debug.Assert(method.DeclaringType != typeof(string)); + Debug.Assert(!method.DeclaringType!.IsArray); + + il.Emit(OpCodes.Ldarg_1); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } - EmitLoadRefArguments(il, parameters); - EmitCallAndReturnHandling(il, method, emitNew: false, backwardsCompat); - il.MarkLabel(allocateAndInvoke); + EmitLoadRefArguments(il, parameters); + EmitCallAndReturnHandling(il, method, emitNew: false, backwardsCompat); + il.MarkLabel(allocateAndInvoke); + } } #endif EmitLoadRefArguments(il, parameters); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 9082e9e6e27e19..481254c7b33f16 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -79,17 +79,7 @@ public void Invoke_SpecialAllocatingConstructorsWithExistingInstance(bool useStr arguments = new object?[] { 3 }; } - object? result = constructor.Invoke(target, arguments); - Assert.NotNull(result); - Assert.NotSame(target, result); - if (useString) - { - Assert.Equal("new", result); - } - else - { - Assert.Equal(3, Assert.IsType(result).Length); - } + Assert.Null(constructor.Invoke(target, arguments)); } public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() From cb282b2f29f28c208784284f4eb21aa3ddc65fe5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Sep 2026 14:04:59 -0700 Subject: [PATCH 16/38] Address CCR feedback: prepare virtual dispatch entrypoint and fix typo - RuntimeMethodHandle_GetVirtualFunctionPointer now calls PrepareForUseAsAFunctionPointer() after EnsureActive() before resolving the virtual target, matching RuntimeMethodHandle_GetFunctionPointer's contract that value-type parameters be loaded before a raw entrypoint is handed out for a managed calli. - Fix "constuctor" -> "constructor" typo in a comment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 2 +- src/coreclr/vm/runtimehandles.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 0461eca7c06fe6..9e834c60946ac7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -1028,7 +1028,7 @@ private static Type GetInputType(RuntimeType type) => } } - // When calling a constuctor, we can be in one of two scenarios: + // When calling a constructor, we can be in one of two scenarios: // 1. The user has specified an instance and we are calling the constructor like a regular method // on such instance. // 2. The user has not specified an instance and we need to allocate one and then call the constructor. diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 3d44ed34106e43..110c6a24104d1d 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1335,6 +1335,7 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( { GCX_PREEMP(); pMethod->EnsureActive(); + pMethod->PrepareForUseAsAFunctionPointer(); PCODE callTarget = pMethod->IsVtableMethod() ? pMethod->GetSingleCallableAddrOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) : pMethod->GetSingleCallableAddrOfCode(); From 0e4237a79d72d4cfd3aa362d6f87ade771de8562 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 18 Sep 2026 11:59:31 -0700 Subject: [PATCH 17/38] Fix test-only Mono/WASM CI failure: guard MethodBase overloads before caching invoker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AssertPromoted(MethodBase) and AssertNotPromoted(MethodBase, int) called GetCachedInvoker(method) directly as part of evaluating the argument expression, before the ShouldAssertSharedSelection guard (which requires PlatformDetection.IsCoreCLR) could short-circuit — unlike the sibling AssertShared(MethodBase)/AssertFallback(MethodBase) overloads, which check the flag first. On Mono (all failing System.Reflection.Tests / InvokeEmit.Tests / InvokeInterpreted.Tests WASM CI legs run on Mono), RuntimeMethodInfo has no CoreCLR-only "m_invoker" cached-invoker field, so GetCachedInvoker's Assert.NotNull failed immediately regardless of platform. No product code changes; this only affects test-helper gating. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Reflection.Tests/MethodInfoTests.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index e2f9dcacd072e4..d0c7fe088595cd 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -1910,11 +1910,21 @@ internal static void AssertPromoted(object invoker) GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); } - internal static void AssertPromoted(MethodBase method) => - AssertPromoted(GetCachedInvoker(method)); + internal static void AssertPromoted(MethodBase method) + { + if (ShouldAssertSharedSelection) + { + AssertPromoted(GetCachedInvoker(method)); + } + } - internal static void AssertNotPromoted(MethodBase method, int invocationCount) => - AssertNotPromoted(GetCachedInvoker(method), invocationCount); + internal static void AssertNotPromoted(MethodBase method, int invocationCount) + { + if (ShouldAssertSharedSelection) + { + AssertNotPromoted(GetCachedInvoker(method), invocationCount); + } + } internal static void AssertNotPromoted(object invoker, int invocationCount) { From 8bd7e24fb390b031337596bdd83b9ec4eab1d656 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 18 Sep 2026 12:38:06 -0700 Subject: [PATCH 18/38] Address CCR feedback: drop redundant P/Invoke/UCO and generic-declaring-type checks TryGetShape's P/Invoke and [UnmanagedCallersOnly] exclusions were unnecessary: the shared-calli path resolves the same stable entry point as the emitted-invoker path (MethodDesc::GetMultiCallableAddrOfCode), so a P/Invoke target dispatches correctly through either path, and an UnmanagedCallersOnly target fails identically in both (the reverse-P/Invoke check lives at the callee's entry point, not in the caller). Also removes the redundant declaringType.ContainsGenericParameters check on constructors: method.ContainsGenericParameters, checked earlier in the same method, already covers the declaring type for constructors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 9e834c60946ac7..bd100d040c2f19 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -127,14 +127,8 @@ private static unsafe bool TryGetShape( if (method is System.Reflection.Emit.DynamicMethod || method.ContainsGenericParameters || - (method.CallingConvention & CallingConventions.VarArgs) != 0 || - (method.Attributes & MethodAttributes.PinvokeImpl) != 0 || - method.IsDefined(typeof(UnmanagedCallersOnlyAttribute), inherit: false)) + (method.CallingConvention & CallingConventions.VarArgs) != 0) { - // P/Invoke and UnmanagedCallersOnly methods can resolve to an unmanaged entrypoint - // (see GetFunctionPointer's use of CORINFO_ACCESS_UNMANAGED_CALLER_MAYBE), which is - // not compatible with the shared managed-calli shapes below. Fall back to the - // emitted/validation path, which correctly rejects unsupported combinations. return false; } @@ -157,7 +151,6 @@ method.DeclaringType is not Type declaringType || !IsReferenceType(declaringType) || declaringType.IsAbstract || declaringType.IsArray || - declaringType.ContainsGenericParameters || declaringType == typeof(string)) { return false; From 2ff69a69577cb0d7863f71224c7c8f1075933dc6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Sep 2026 11:24:32 -0700 Subject: [PATCH 19/38] Limit special constructor invocation test to CoreCLR The test covers CoreCLR's handling of string and array pseudo-constructors. Those constructors are not uniformly reflectable on Mono and NativeAOT targets, where this CoreCLR-specific behavior is not implemented. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tests/System.Reflection.Tests/ConstructorInfoTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 481254c7b33f16..214d877f17e721 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -57,7 +57,7 @@ public sealed class MutableConstructorTarget public MutableConstructorTarget(int value) => Value = value; } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] [InlineData(false)] [InlineData(true)] public void Invoke_SpecialAllocatingConstructorsWithExistingInstance(bool useString) From 68a8db6f91bfcafdf3c6c529ef7158624abd9c04 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Sep 2026 11:51:59 -0700 Subject: [PATCH 20/38] Preserve special constructor invocation behavior Keep the shared invocation strategy while matching the previous reflection dispatcher's results for pseudo-constructors invoked with a target. String constructors remain a no-op that returns null; array constructors throw the same InvalidProgramException that reflection wraps in TargetInvocationException. Split the regression coverage so each distinct baseline contract is explicit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/InvokerEmitUtil.cs | 14 ++++++++ .../ConstructorInfoTests.cs | 36 +++++++++---------- 2 files changed, 30 insertions(+), 20 deletions(-) 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 14a3ea86cb9b53..a6f0ccb6da9323 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -180,6 +180,11 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, if (!CanCallConstructorOnExistingInstance(method.DeclaringType!)) { + if (method.DeclaringType!.IsArray) + { + il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_InvalidProgramException()); + } + il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ret); il.MarkLabel(allocateAndInvoke); @@ -361,6 +366,11 @@ public static void Throw_NullReference_InvokeNullRefReturned() { throw new NullReferenceException(SR.NullReference_InvokeNullRefReturned); } + + public static void Throw_InvalidProgramException() + { + throw new InvalidProgramException(); + } } private static class Methods @@ -377,6 +387,10 @@ public static MethodInfo Span_get_Item() => public static MethodInfo ThrowHelper_Throw_NullReference_InvokeNullRefReturned() => s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_NullReference_InvokeNullRefReturned))!; + private static MethodInfo? s_ThrowHelper_Throw_InvalidProgramException; + public static MethodInfo ThrowHelper_Throw_InvalidProgramException() => + s_ThrowHelper_Throw_InvalidProgramException ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_InvalidProgramException))!; + private static MethodInfo? s_Object_GetRawData; public static MethodInfo Object_GetRawData() => s_Object_GetRawData ??= typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetRawData), BindingFlags.NonPublic | BindingFlags.Static)!; diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 214d877f17e721..2f43ea764c2f9e 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -57,31 +57,27 @@ public sealed class MutableConstructorTarget public MutableConstructorTarget(int value) => Value = value; } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] - [InlineData(false)] - [InlineData(true)] - public void Invoke_SpecialAllocatingConstructorsWithExistingInstance(bool useString) + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_StringConstructorWithExistingInstance() { - ConstructorInfo constructor; - object target; - object?[] arguments; - - if (useString) - { - constructor = typeof(string).GetConstructor(new[] { typeof(char[]) })!; - target = "existing"; - arguments = new object?[] { new[] { 'n', 'e', 'w' } }; - } - else - { - constructor = typeof(object[]).GetConstructor(new[] { typeof(int) })!; - target = new object[1]; - arguments = new object?[] { 3 }; - } + ConstructorInfo constructor = typeof(string).GetConstructor(new[] { typeof(char[]) })!; + object target = "existing"; + object?[] arguments = new object?[] { new[] { 'n', 'e', 'w' } }; Assert.Null(constructor.Invoke(target, arguments)); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ArrayConstructorWithExistingInstance() + { + ConstructorInfo constructor = typeof(object[]).GetConstructor(new[] { typeof(int) })!; + object target = new object[1]; + object?[] arguments = new object?[] { 3 }; + + TargetInvocationException exception = Assert.Throws(() => constructor.Invoke(target, arguments)); + Assert.IsType(exception.InnerException); + } + public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() { yield return new object[] { Type.EmptyTypes, Array.Empty() }; From 4a3d122115d8b2f23457b554c51b431577fe7f4a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 14:02:41 -0700 Subject: [PATCH 21/38] Reject special constructors with existing targets Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/InvokerEmitUtil.cs | 16 ++++++---------- .../ConstructorInfoTests.cs | 5 +++-- 2 files changed, 9 insertions(+), 12 deletions(-) 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 a6f0ccb6da9323..4faf7220b929a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -180,11 +180,7 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, if (!CanCallConstructorOnExistingInstance(method.DeclaringType!)) { - if (method.DeclaringType!.IsArray) - { - il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_InvalidProgramException()); - } - + il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_NotSupportedException()); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ret); il.MarkLabel(allocateAndInvoke); @@ -367,9 +363,9 @@ public static void Throw_NullReference_InvokeNullRefReturned() throw new NullReferenceException(SR.NullReference_InvokeNullRefReturned); } - public static void Throw_InvalidProgramException() + public static void Throw_NotSupportedException() { - throw new InvalidProgramException(); + throw new NotSupportedException(); } } @@ -387,9 +383,9 @@ public static MethodInfo Span_get_Item() => public static MethodInfo ThrowHelper_Throw_NullReference_InvokeNullRefReturned() => s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_NullReference_InvokeNullRefReturned))!; - private static MethodInfo? s_ThrowHelper_Throw_InvalidProgramException; - public static MethodInfo ThrowHelper_Throw_InvalidProgramException() => - s_ThrowHelper_Throw_InvalidProgramException ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_InvalidProgramException))!; + private static MethodInfo? s_ThrowHelper_Throw_NotSupportedException; + public static MethodInfo ThrowHelper_Throw_NotSupportedException() => + s_ThrowHelper_Throw_NotSupportedException ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_NotSupportedException))!; private static MethodInfo? s_Object_GetRawData; public static MethodInfo Object_GetRawData() => diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 2f43ea764c2f9e..41d45db54b8161 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -64,7 +64,8 @@ public void Invoke_StringConstructorWithExistingInstance() object target = "existing"; object?[] arguments = new object?[] { new[] { 'n', 'e', 'w' } }; - Assert.Null(constructor.Invoke(target, arguments)); + TargetInvocationException exception = Assert.Throws(() => constructor.Invoke(target, arguments)); + Assert.IsType(exception.InnerException); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] @@ -75,7 +76,7 @@ public void Invoke_ArrayConstructorWithExistingInstance() object?[] arguments = new object?[] { 3 }; TargetInvocationException exception = Assert.Throws(() => constructor.Invoke(target, arguments)); - Assert.IsType(exception.InnerException); + Assert.IsType(exception.InnerException); } public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() From 1c51f17a47867116bd399d9e9a05589207866d9b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 24 Sep 2026 11:24:15 -0700 Subject: [PATCH 22/38] Use closed generic types in reflection tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ConstructorInfoTests.cs | 16 +- .../MethodInfoTests.cs | 158 ++++++++++++------ 2 files changed, 112 insertions(+), 62 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 41d45db54b8161..22d9141c6c1208 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -248,35 +248,35 @@ public void Invoke_PrimitivePatternConstructors_SharedThunk(Type[] parameterType [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_Byte() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeByteEnum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_SByte() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeSByteEnum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_Int16() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeInt16Enum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt16() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeUInt16Enum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_Int32() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeInt32Enum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt32() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeUInt32Enum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_Int64() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeInt64Enum.Value); [Fact] public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt64() => - Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget), IntrinsicInvokeUInt64Enum.Value); private static void Invoke_EnumConstructors_MapActualUnderlyingType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index d0c7fe088595cd..e8888d159c66a9 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -27,29 +27,79 @@ public sealed class MethodInfoTests : MethodCommonTests public static IEnumerable Invoke_ReturnValueAcrossTiers_TestData() { - yield return new object[] { typeof(bool), true }; - yield return new object[] { typeof(byte), (byte)42 }; - yield return new object[] { typeof(sbyte), (sbyte)-42 }; - yield return new object[] { typeof(char), 'x' }; - yield return new object[] { typeof(short), (short)-1234 }; - yield return new object[] { typeof(ushort), (ushort)1234 }; - yield return new object[] { typeof(int), -12345 }; - yield return new object[] { typeof(uint), 12345u }; - yield return new object[] { typeof(long), -1234567890123L }; - yield return new object[] { typeof(ulong), 1234567890123UL }; - yield return new object[] { typeof(float), 12.5f }; - yield return new object[] { typeof(double), -25.5 }; - yield return new object[] { typeof(nint), (nint)12345 }; - yield return new object[] { typeof(nuint), (nuint)54321 }; - yield return new object[] { typeof(string), "returned value" }; - yield return new object[] { typeof(object), new object() }; + yield return new object[] { typeof(ReturnValueTarget), "returned value" }; + yield return new object[] { typeof(ReturnValueTarget), new object() }; } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] [MemberData(nameof(Invoke_ReturnValueAcrossTiers_TestData))] - public void Invoke_ReturnValueAcrossTiers(Type returnType, object expected) + public void Invoke_ReturnValueAcrossTiers( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.PublicMethods)] Type target, + object expected) => + Verify_ReturnValueAcrossTiers(target, expected); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Boolean() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), true); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Byte() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (byte)42); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_SByte() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (sbyte)-42); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Char() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), 'x'); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Int16() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (short)-1234); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_UInt16() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (ushort)1234); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Int32() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), -12345); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_UInt32() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), 12345u); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Int64() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), -1234567890123L); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_UInt64() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), 1234567890123UL); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Single() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), 12.5f); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_Double() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), -25.5); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_IntPtr() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (nint)12345); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Invoke_ReturnValueAcrossTiers_UIntPtr() => + Verify_ReturnValueAcrossTiers(typeof(ReturnValueTarget), (nuint)54321); + + private static void Verify_ReturnValueAcrossTiers( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.PublicMethods)] Type target, + object expected) { - Type target = typeof(ReturnValueTarget<>).MakeGenericType(returnType); target.GetField(nameof(ReturnValueTarget.Value)).SetValue(null, expected); MethodInfo method = target.GetMethod(nameof(ReturnValueTarget.GetValue)); MethodInvoker invoker = MethodInvoker.Create(method); @@ -154,59 +204,59 @@ public void Invoke_InstanceReferenceReturn_SharedThunk(string methodName, object [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Boolean() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(bool)), true); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), true); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Byte() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(byte)), (byte)42); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (byte)42); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_SByte() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (sbyte)-42); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Char() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(char)), 'x'); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), 'x'); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Int16() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(short)), (short)-1234); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (short)-1234); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt16() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (ushort)1234); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Int32() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(int)), -12345); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), -12345); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt32() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(uint)), 12345u); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), 12345u); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Int64() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(long)), -1234567890123L); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), -1234567890123L); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt64() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), 1234567890123UL); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Single() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(float)), 12.5f); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), 12.5f); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_Double() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(double)), -25.5); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), -25.5); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_IntPtr() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (nint)12345); [Fact] public void Invoke_InstancePrimitiveReturn_SharedThunk_UIntPtr() => - Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget), (nuint)54321); private static void Invoke_InstancePrimitiveReturn_SharedThunk( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | @@ -227,91 +277,91 @@ private static void Invoke_InstancePrimitiveReturn_SharedThunk( [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Boolean() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(bool)), true); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), true); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Byte() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(byte)), (byte)42); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (byte)42); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_SByte() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (sbyte)-42); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Char() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(char)), 'x'); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), 'x'); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(short)), (short)-1234); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (short)-1234); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (ushort)1234); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(int)), -12345); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), -12345); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(uint)), 12345u); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), 12345u); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(long)), -1234567890123L); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), -1234567890123L); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), 1234567890123UL); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Single() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(float)), 12.5f); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), 12.5f); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Double() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(double)), -25.5); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), -25.5); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_IntPtr() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (nint)12345); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UIntPtr() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), (nuint)54321); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_ByteEnum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeByteEnum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_SByteEnum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeSByteEnum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeInt16Enum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeUInt16Enum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeInt32Enum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeUInt32Enum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeInt64Enum.Value); [Fact] public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64Enum() => - Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget), IntrinsicInvokeUInt64Enum.Value); private static void Invoke_InstancePrimitiveArgument_SharedThunk( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | From 62fbb60071f0b149bb2fcc2a24e706f658427d84 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 24 Sep 2026 15:22:29 -0700 Subject: [PATCH 23/38] Use closed generic methods in reflection tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../MethodInfoTests.cs | 98 +++++++++++-------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index e8888d159c66a9..d0930977688842 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -670,59 +670,68 @@ public void Invoke_VirtualDispatch_SharedThunk( typeof(IntrinsicInvokeThresholdCachedTarget), nameof(IntrinsicInvokeThresholdCachedTarget.Echo), typeof(IntrinsicInvokeThresholdCachedTarget), - false, IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold, false)] [InlineData( typeof(IntrinsicInvokeThresholdVirtualBase), nameof(IntrinsicInvokeThresholdVirtualBase.Echo), typeof(IntrinsicInvokeThresholdVirtualDerived), - false, IntrinsicInvokeSelectionAssertions.ClassVirtualSpecializationThreshold, true)] [InlineData( typeof(IIntrinsicInvokeThresholdInterface), nameof(IIntrinsicInvokeThresholdInterface.Echo), typeof(IntrinsicInvokeThresholdInterfaceTarget), - false, - IntrinsicInvokeSelectionAssertions.InterfaceOrGenericVirtualSpecializationThreshold, - true)] - [InlineData( - typeof(IntrinsicInvokeThresholdGenericMethodBase), - nameof(IntrinsicInvokeThresholdGenericMethodBase.Echo), - typeof(IntrinsicInvokeThresholdGenericMethodDerived), - true, IntrinsicInvokeSelectionAssertions.InterfaceOrGenericVirtualSpecializationThreshold, true)] [InlineData( typeof(IntrinsicInvokeThresholdGenericTypeBase), nameof(IntrinsicInvokeThresholdGenericTypeBase.Echo), typeof(IntrinsicInvokeThresholdGenericTypeDerived), - false, IntrinsicInvokeSelectionAssertions.ClassVirtualSpecializationThreshold, true)] - [InlineData( - typeof(IntrinsicInvokeThresholdFinalGenericMethodDerived), - nameof(IntrinsicInvokeThresholdFinalGenericMethodDerived.Echo), - typeof(IntrinsicInvokeThresholdFinalGenericMethodDerived), - true, - IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold, - false)] public void Invoke_SharedThunkPromotesAtDispatchPathThreshold( Type declaringType, string methodName, Type targetType, - bool makeGenericMethod, int threshold, bool useVirtualResolution) { MethodInfo method = declaringType.GetMethod(methodName)!; - if (makeGenericMethod) - { - method = method.MakeGenericMethod(typeof(int)); - } - object target = Activator.CreateInstance(targetType)!; + AssertSharedThunkPromotesAtDispatchPathThreshold(method, target, threshold, useVirtualResolution); + } + + [Fact] + public void Invoke_GenericMethodSharedThunkPromotesAtDispatchPathThreshold() + { + MethodInfo method = typeof(IntrinsicInvokeThresholdGenericMethodBase) + .GetMethod(nameof(IntrinsicInvokeThresholdGenericMethodBase.Echo))!.MakeGenericMethod(typeof(int)); + AssertSharedThunkPromotesAtDispatchPathThreshold( + method, + new IntrinsicInvokeThresholdGenericMethodDerived(), + IntrinsicInvokeSelectionAssertions.InterfaceOrGenericVirtualSpecializationThreshold, + true); + } + + [Fact] + public void Invoke_FinalGenericMethodSharedThunkPromotesAtDispatchPathThreshold() + { + MethodInfo method = typeof(IntrinsicInvokeThresholdFinalGenericMethodDerived) + .GetMethod(nameof(IntrinsicInvokeThresholdFinalGenericMethodDerived.Echo))!.MakeGenericMethod(typeof(int)); + AssertSharedThunkPromotesAtDispatchPathThreshold( + method, + new IntrinsicInvokeThresholdFinalGenericMethodDerived(), + IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold, + false); + } + + private static void AssertSharedThunkPromotesAtDispatchPathThreshold( + MethodInfo method, + object target, + int threshold, + bool useVirtualResolution) + { MethodInvoker invoker = MethodInvoker.Create(method); object argument = new object(); object?[] arguments = { argument }; @@ -1204,8 +1213,8 @@ public void CreateDelegate_GenericMethod() MethodInfo miMethod1String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod1)); MethodInfo miMethod2String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod3)); - MethodInfo miMethod2IntGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(int) }); - MethodInfo miMethod2StringGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(string) }); + MethodInfo miMethod2IntGeneric = miMethod2String.MakeGenericMethod(typeof(int)); + MethodInfo miMethod2StringGeneric = miMethod2String.MakeGenericMethod(typeof(string)); Delegate methodDelegate = miMethod1String.CreateDelegate(typeof(Delegate_GC_T_T)); object returnValue = ((Delegate_GC_T_T)methodDelegate).DynamicInvoke(new object[] { genericClass, "TestGeneric" }); @@ -1582,13 +1591,24 @@ public void Invoke_CustomBinder_ResultRequiringPrimitiveWidening_DoesNotCopyBack Assert.Equal("10", Assert.IsType(args[0])); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), new Type[] { typeof(int) })] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), new Type[] { typeof(string), typeof(int) })] - public void MakeGenericMethod(Type type, string name, Type[] typeArguments) + [Fact] + public void MakeGenericMethod_OneArgument() + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)); + MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeof(int)); + AssertConstructedGenericMethod(methodInfo, genericMethodInfo); + } + + [Fact] + public void MakeGenericMethod_TwoArguments() + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2)); + MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeof(string), typeof(int)); + AssertConstructedGenericMethod(methodInfo, genericMethodInfo); + } + + private static void AssertConstructedGenericMethod(MethodInfo methodInfo, MethodInfo genericMethodInfo) { - MethodInfo methodInfo = GetMethod(type, name); - MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeArguments); Assert.True(genericMethodInfo.IsGenericMethod); Assert.False(genericMethodInfo.IsGenericMethodDefinition); @@ -1797,17 +1817,11 @@ public void ToStringTest(Type type, string name, string expected) Assert.Equal(expected, methodInfo.ToString()); } - public static IEnumerable ToString_TestData() - { - MethodInfo genericMethodInfo = GetMethod(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2)).MakeGenericMethod(new Type[] { typeof(DateTime) }); - yield return new object[] { genericMethodInfo, "System.String GenericMethod2[DateTime](System.DateTime, System.String, System.String)" }; - } - - [Theory] - [MemberData(nameof(ToString_TestData))] - public void ToStringTest_ByMethodInfo(MethodInfo methodInfo, string expected) + [Fact] + public void ToStringTest_ByConstructedGenericMethodInfo() { - Assert.Equal(expected, methodInfo.ToString()); + MethodInfo genericMethodInfo = GetMethod(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2)).MakeGenericMethod(typeof(DateTime)); + Assert.Equal("System.String GenericMethod2[DateTime](System.DateTime, System.String, System.String)", genericMethodInfo.ToString()); } public static IEnumerable MethodNameAndArguments() From 03617816441411b5392fe90d6f0d69265f962d4c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 28 Sep 2026 16:18:01 -0700 Subject: [PATCH 24/38] Allow invoking abstract constructors on existing instances Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/InvokerEmitUtil.cs | 2 +- .../src/System/Reflection/MethodInvoker.cs | 3 +- .../MethodInvokerTests.cs | 41 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) 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 4faf7220b929a3..76eb5fe19eb6f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -144,7 +144,7 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, { Debug.Assert(!method.ContainsGenericParameters); - bool emitNew = method is RuntimeConstructorInfo; + bool emitNew = method is RuntimeConstructorInfo && !method.DeclaringType!.IsAbstract; 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. 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 0df895bb1bb0ce..53125d20d783a6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -266,7 +266,8 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) } } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0 || + (_invocationFlags & InvocationFlags.NoConstructorInvoke) != 0 && (_isStatic || obj is null)) { ThrowForBadInvocationFlags(); } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 22dc9a4c48bfed..c33765e397fde1 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -100,6 +100,47 @@ public RefConstructorTarget(ref int value) } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Constructor_AbstractDeclaringTypeWithExistingInstance() + { + ConstructorInfo constructor = typeof(AbstractRefConstructorTarget).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + new[] { typeof(int).MakeByRefType() }, + modifiers: null)!; + MethodInvoker invoker = MethodInvoker.Create(constructor); + int initialValue = -1; + var target = new ConcreteRefConstructorTarget(ref initialValue); + object?[] arguments = { 42 }; + + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(42, target.Value); + Assert.Equal(43, arguments[0]); + + object?[] constructorArguments = { 84 }; + Assert.Null(constructor.Invoke(target, constructorArguments)); + Assert.Equal(84, target.Value); + Assert.Equal(85, constructorArguments[0]); + } + + public abstract class AbstractRefConstructorTarget + { + public int Value; + + protected AbstractRefConstructorTarget(ref int value) + { + Value = value; + value++; + } + } + + public sealed class ConcreteRefConstructorTarget : AbstractRefConstructorTarget + { + public ConcreteRefConstructorTarget(ref int value) : base(ref value) + { + } + } + [Fact] public void NullTypeValidation() { From da89d0b7fad7b48f6ec53d585977331c69313d78 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Sep 2026 13:06:36 -0700 Subject: [PATCH 25/38] Fix abstract constructor invocation across invoker paths Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/InvokerEmitUtil.cs | 4 +- .../src/System/Reflection/MethodInvoker.cs | 7 +- .../MethodInvokerTests.cs | 76 +++++++++++++++++++ 3 files changed, 82 insertions(+), 5 deletions(-) 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 76eb5fe19eb6f0..f9b96c6a86e7be 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -20,7 +20,7 @@ public static InvokeFunc_Obj4Args CreateInvokeDelegate_Obj4Args(MethodBase metho { Debug.Assert(!method.ContainsGenericParameters); - bool emitNew = method is RuntimeConstructorInfo; + bool emitNew = method is RuntimeConstructorInfo && !method.DeclaringType!.IsAbstract; bool hasThis = !emitNew && !method.IsStatic; Type[] delegateParameters = [typeof(object), typeof(object), typeof(object), typeof(object), typeof(object)]; @@ -87,7 +87,7 @@ public static InvokeFunc_ObjSpanArgs CreateInvokeDelegate_ObjSpanArgs(MethodBase { Debug.Assert(!method.ContainsGenericParameters); - bool emitNew = method is RuntimeConstructorInfo; + bool emitNew = method is RuntimeConstructorInfo && !method.DeclaringType!.IsAbstract; 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. 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 53125d20d783a6..de474447fe69bc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -188,7 +188,8 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) private object? InvokeImpl(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) { - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0 && + ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0 || _isStatic || obj is null)) { ThrowForBadInvocationFlags(); } @@ -266,8 +267,8 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) } } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0 || - (_invocationFlags & InvocationFlags.NoConstructorInvoke) != 0 && (_isStatic || obj is null)) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0 && + ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0 || _isStatic || obj is null)) { ThrowForBadInvocationFlags(); } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index c33765e397fde1..95521eefbc9f28 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -123,6 +123,60 @@ public void Constructor_AbstractDeclaringTypeWithExistingInstance() Assert.Equal(85, constructorArguments[0]); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Constructor_AbstractDeclaringTypeWithExistingInstance_RegularArguments() + { + ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + new[] { typeof(int) }, + modifiers: null)!; + MethodInvoker invoker = MethodInvoker.Create(constructor); + var target = new ConcreteRegularConstructorTarget(0); + + Assert.Null(invoker.Invoke(target, 42)); + Assert.Equal(42, target.Value); + Assert.Null(invoker.Invoke(target, 43)); + Assert.Equal(43, target.Value); + + object?[] arguments = { 44 }; + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(44, target.Value); + arguments[0] = 45; + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(45, target.Value); + + Assert.Throws(() => invoker.Invoke(null, 46)); + object?[] nullTargetArguments = { 47 }; + Assert.Throws(() => invoker.Invoke(null, nullTargetArguments.AsSpan())); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + public void Constructor_AbstractDeclaringTypeWithExistingInstance_ManyRegularArguments() + { + ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + new[] + { + typeof(int), + typeof(int), + typeof(int), + typeof(int), + typeof(int) + }, + modifiers: null)!; + MethodInvoker invoker = MethodInvoker.Create(constructor); + var target = new ConcreteRegularConstructorTarget(0); + object?[] arguments = { 1, 2, 3, 4, 5 }; + + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(15, target.Value); + arguments[0] = 10; + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(24, target.Value); + } + public abstract class AbstractRefConstructorTarget { public int Value; @@ -141,6 +195,28 @@ public ConcreteRefConstructorTarget(ref int value) : base(ref value) } } + public abstract class AbstractRegularConstructorTarget + { + public int Value; + + protected AbstractRegularConstructorTarget(int value) + { + Value = value; + } + + protected AbstractRegularConstructorTarget(int a, int b, int c, int d, int e) + { + Value = a + b + c + d + e; + } + } + + public sealed class ConcreteRegularConstructorTarget : AbstractRegularConstructorTarget + { + public ConcreteRegularConstructorTarget(int value) : base(value) + { + } + } + [Fact] public void NullTypeValidation() { From b69e73e0c38320a9db3d3dfb79b161e2081356ae Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Sep 2026 15:37:23 -0700 Subject: [PATCH 26/38] Prepare resolved virtual targets Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/method.cpp | 16 +++++++++++++--- src/coreclr/vm/method.hpp | 3 ++- src/coreclr/vm/runtimehandles.cpp | 17 ++++++++++++++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 4bec95cd2d20df..5a8c6f6d664232 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2106,7 +2106,8 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly() } //******************************************************************************* -PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH) +PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode( + OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH, MethodDesc* pResolvedTargetMD) { CONTRACTL { @@ -2122,7 +2123,11 @@ PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, Meth if (HasMethodInstantiation()) { CheckRestore(); - MethodDesc *pResultMD = ResolveGenericVirtualMethod(orThis, pMTOfThis); + _ASSERTE(pResolvedTargetMD == nullptr || + pResolvedTargetMD == ResolveGenericVirtualMethod(orThis, pMTOfThis)); + MethodDesc *pResultMD = pResolvedTargetMD != nullptr + ? pResolvedTargetMD + : ResolveGenericVirtualMethod(orThis, pMTOfThis); // If we're remoting this call we can't call directly on the returned // method desc, we need to go through a stub that guarantees we end up @@ -2137,10 +2142,15 @@ PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, Meth if (IsInterface()) { - MethodDesc * pTargetMD = MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH,this,orThis, pMTOfThis); + _ASSERTE(pResolvedTargetMD == nullptr || + pResolvedTargetMD == MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, this, orThis, pMTOfThis)); + MethodDesc * pTargetMD = pResolvedTargetMD != nullptr + ? pResolvedTargetMD + : MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, this, orThis, pMTOfThis); return pTargetMD->GetSingleCallableAddrOfCode(); } + // A class virtual slot can contain a dispatch stub; use it rather than the resolved method's entrypoint. return pMTOfThis->GetRestoredSlot(GetSlot()); } diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index c49974094cd398..74794fa369c887 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1622,7 +1622,8 @@ class MethodDesc // The code that implements these was taken verbatim from elsewhere in the // codebase, and there may be subtle differences between the two, e.g. with // regard to thunking. - PCODE GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH); + PCODE GetSingleCallableAddrOfVirtualizedCode( + OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH, MethodDesc* pResolvedTargetMD = nullptr); PCODE GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH); #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 110c6a24104d1d..c07eaaa5512327 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1336,8 +1336,23 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( GCX_PREEMP(); pMethod->EnsureActive(); pMethod->PrepareForUseAsAFunctionPointer(); + MethodDesc* pResolvedTargetMD = nullptr; +#ifdef FEATURE_TYPEEQUIVALENCE + if (pMethod->IsVtableMethod() && !pMethod->DoesNotHaveEquivalentValuetypeParameters()) + { + pResolvedTargetMD = pMethod->GetMethodDescOfVirtualizedCode( + &receiver, pReceiverMT, declaringType.AsTypeHandle()); + if (pResolvedTargetMD != pMethod) + { + // The resolved implementation can have value-type parameters distinct from the declaration. + pResolvedTargetMD->EnsureActive(); + pResolvedTargetMD->PrepareForUseAsAFunctionPointer(); + } + } +#endif PCODE callTarget = pMethod->IsVtableMethod() - ? pMethod->GetSingleCallableAddrOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) + ? pMethod->GetSingleCallableAddrOfVirtualizedCode( + &receiver, pReceiverMT, declaringType.AsTypeHandle(), pResolvedTargetMD) : pMethod->GetSingleCallableAddrOfCode(); #ifdef FEATURE_PORTABLE_ENTRYPOINTS // Virtual dispatch can return an entrypoint whose R2R-to-interpreter thunk is not prepared yet. From aaabd4d8cf710697926dcf66dbff584754d13963 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Sep 2026 16:21:49 -0700 Subject: [PATCH 27/38] Track unsupported reflection tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Reflection.Tests/ConstructorInfoTests.cs | 8 ++++++-- .../System.Reflection.Tests/MethodInvokerTests.cs | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 22d9141c6c1208..ba52afedd8dcf0 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -57,7 +57,9 @@ public sealed class MutableConstructorTarget public MutableConstructorTarget(int value) => Value = value; } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_StringConstructorWithExistingInstance() { ConstructorInfo constructor = typeof(string).GetConstructor(new[] { typeof(char[]) })!; @@ -68,7 +70,9 @@ public void Invoke_StringConstructorWithExistingInstance() Assert.IsType(exception.InnerException); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_ArrayConstructorWithExistingInstance() { ConstructorInfo constructor = typeof(object[]).GetConstructor(new[] { typeof(int) })!; diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 95521eefbc9f28..b81d7a5a37565c 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -100,7 +100,9 @@ public RefConstructorTarget(ref int value) } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance() { ConstructorInfo constructor = typeof(AbstractRefConstructorTarget).GetConstructor( @@ -123,7 +125,9 @@ public void Constructor_AbstractDeclaringTypeWithExistingInstance() Assert.Equal(85, constructorArguments[0]); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance_RegularArguments() { ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( @@ -151,7 +155,9 @@ public void Constructor_AbstractDeclaringTypeWithExistingInstance_RegularArgumen Assert.Throws(() => invoker.Invoke(null, nullTargetArguments.AsSpan())); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance_ManyRegularArguments() { ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( From 8720b49eb587f4e1f31662fbd131fc18f108ffd9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Sep 2026 16:22:59 -0700 Subject: [PATCH 28/38] Correct reflection issue mappings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Reflection.Tests/ConstructorInfoTests.cs | 8 ++++---- .../System.Reflection.Tests/MethodInvokerTests.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index ba52afedd8dcf0..f3d8ce1870be5f 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -58,8 +58,8 @@ public sealed class MutableConstructorTarget } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_StringConstructorWithExistingInstance() { ConstructorInfo constructor = typeof(string).GetConstructor(new[] { typeof(char[]) })!; @@ -71,8 +71,8 @@ public void Invoke_StringConstructorWithExistingInstance() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134902", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_ArrayConstructorWithExistingInstance() { ConstructorInfo constructor = typeof(object[]).GetConstructor(new[] { typeof(int) })!; diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index b81d7a5a37565c..087d5db80091d0 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -101,8 +101,8 @@ public RefConstructorTarget(ref int value) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance() { ConstructorInfo constructor = typeof(AbstractRefConstructorTarget).GetConstructor( @@ -126,8 +126,8 @@ public void Constructor_AbstractDeclaringTypeWithExistingInstance() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance_RegularArguments() { ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( @@ -156,8 +156,8 @@ public void Constructor_AbstractDeclaringTypeWithExistingInstance_RegularArgumen } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134900", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134901", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/134903", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Constructor_AbstractDeclaringTypeWithExistingInstance_ManyRegularArguments() { ConstructorInfo constructor = typeof(AbstractRegularConstructorTarget).GetConstructor( From bb9f2f3c5ea32c8d892fa4e888630ebfc8799381 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Sep 2026 15:15:24 -0700 Subject: [PATCH 29/38] Simplify virtual target preparation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/method.cpp | 24 +++--------------------- src/coreclr/vm/method.hpp | 3 +-- src/coreclr/vm/runtimehandles.cpp | 24 ++++++------------------ 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 5a8c6f6d664232..53b94e2a120bbf 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2106,8 +2106,7 @@ PCODE MethodDesc::GetSingleCallableAddrOfCodeForUnmanagedCallersOnly() } //******************************************************************************* -PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode( - OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH, MethodDesc* pResolvedTargetMD) +PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH) { CONTRACTL { @@ -2123,34 +2122,17 @@ PCODE MethodDesc::GetSingleCallableAddrOfVirtualizedCode( if (HasMethodInstantiation()) { CheckRestore(); - _ASSERTE(pResolvedTargetMD == nullptr || - pResolvedTargetMD == ResolveGenericVirtualMethod(orThis, pMTOfThis)); - MethodDesc *pResultMD = pResolvedTargetMD != nullptr - ? pResolvedTargetMD - : ResolveGenericVirtualMethod(orThis, pMTOfThis); - - // If we're remoting this call we can't call directly on the returned - // method desc, we need to go through a stub that guarantees we end up - // in the remoting handler. The stub we use below is normally just for - // non-virtual calls on virtual methods (that have the same problem - // where we could end up bypassing the remoting system), but it serves - // our purpose here (basically pushes our correctly instantiated, - // resolved method desc on the stack and calls the remoting code). + MethodDesc *pResultMD = ResolveGenericVirtualMethod(orThis, pMTOfThis); return pResultMD->GetSingleCallableAddrOfCode(); } if (IsInterface()) { - _ASSERTE(pResolvedTargetMD == nullptr || - pResolvedTargetMD == MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, this, orThis, pMTOfThis)); - MethodDesc * pTargetMD = pResolvedTargetMD != nullptr - ? pResolvedTargetMD - : MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, this, orThis, pMTOfThis); + MethodDesc * pTargetMD = MethodTable::GetMethodDescForInterfaceMethodAndServer(staticTH, this, orThis, pMTOfThis); return pTargetMD->GetSingleCallableAddrOfCode(); } - // A class virtual slot can contain a dispatch stub; use it rather than the resolved method's entrypoint. return pMTOfThis->GetRestoredSlot(GetSlot()); } diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 74794fa369c887..c49974094cd398 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1622,8 +1622,7 @@ class MethodDesc // The code that implements these was taken verbatim from elsewhere in the // codebase, and there may be subtle differences between the two, e.g. with // regard to thunking. - PCODE GetSingleCallableAddrOfVirtualizedCode( - OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH, MethodDesc* pResolvedTargetMD = nullptr); + PCODE GetSingleCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH); PCODE GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, MethodTable* pMTOfThis, TypeHandle staticTH); #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index c07eaaa5512327..fa2d9efeb22b59 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1336,24 +1336,12 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( GCX_PREEMP(); pMethod->EnsureActive(); pMethod->PrepareForUseAsAFunctionPointer(); - MethodDesc* pResolvedTargetMD = nullptr; -#ifdef FEATURE_TYPEEQUIVALENCE - if (pMethod->IsVtableMethod() && !pMethod->DoesNotHaveEquivalentValuetypeParameters()) - { - pResolvedTargetMD = pMethod->GetMethodDescOfVirtualizedCode( - &receiver, pReceiverMT, declaringType.AsTypeHandle()); - if (pResolvedTargetMD != pMethod) - { - // The resolved implementation can have value-type parameters distinct from the declaration. - pResolvedTargetMD->EnsureActive(); - pResolvedTargetMD->PrepareForUseAsAFunctionPointer(); - } - } -#endif - PCODE callTarget = pMethod->IsVtableMethod() - ? pMethod->GetSingleCallableAddrOfVirtualizedCode( - &receiver, pReceiverMT, declaringType.AsTypeHandle(), pResolvedTargetMD) - : pMethod->GetSingleCallableAddrOfCode(); + MethodDesc* pTargetMethod = pMethod->IsVtableMethod() + ? pMethod->GetMethodDescOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) + : pMethod; + pTargetMethod->EnsureActive(); + pTargetMethod->PrepareForUseAsAFunctionPointer(); + PCODE callTarget = pTargetMethod->GetSingleCallableAddrOfCode(); #ifdef FEATURE_PORTABLE_ENTRYPOINTS // Virtual dispatch can return an entrypoint whose R2R-to-interpreter thunk is not prepared yet. MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(callTarget); From 3584422f15d6b175ea33b236b97e01236494be3a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Sep 2026 16:42:11 -0700 Subject: [PATCH 30/38] Merge invocation counter, cache declaring type, explain value-type fallback Address jkotas review feedback on IntrinsicInvokeHelper: - Merge InvocationCount/SpecializationThreshold into a single InvocationsUntilSpecialization countdown field, replacing the Interlocked.Increment with a plain decrement. Dropped decrements under concurrent invocation are acceptable since this only delays the heuristic specialization trigger. - Cache method.DeclaringType (a virtual property read) into InvokeState.DeclaringType at shape-resolution time instead of re-reading it on every invocation; only the constructor thunks consume this value. - Add an explanatory comment for the boxed value-type receiver fallback: virtual/interface dispatch against it can resolve to an unboxing (or combined unboxing/instantiating) stub whose calling convention isn't guaranteed to match the shared thunk signature. Update the reflection-invocation test helpers to match: the merged counter is no longer independently readable as a stored threshold, so AssertPromoted/AssertNotPromoted now take the threshold explicitly from the caller instead of comparing against a state-embedded value. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 19 ++++++++---- .../ConstructorInvokerTests.cs | 4 +-- .../MethodInfoTests.cs | 30 +++++++++---------- .../MethodInvokerTests.cs | 4 +-- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index bd100d040c2f19..bcf22830abe808 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -26,8 +26,11 @@ internal struct InvokeState { internal IntPtr Thunk; internal IntPtr FunctionPointer; - internal int InvocationCount; - internal int SpecializationThreshold; + internal Type? DeclaringType; + // Counts down from the specialization threshold to zero. A plain decrement is used instead of + // Interlocked.Decrement: this is a heuristic trigger, so occasionally dropped decrements under + // concurrent invocation are acceptable and simply delay specialization slightly. + internal int InvocationsUntilSpecialization; } internal static unsafe object? Invoke( @@ -44,6 +47,11 @@ internal struct InvokeState { if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) { + // A virtual/interface method resolved against a boxed value-type receiver can resolve to an + // unboxing stub or, for generic value types, a combined unboxing/instantiating stub whose + // calling convention (this-pointer adjustment, implicit generic context) isn't guaranteed to + // match the shared thunk signature. Fall back to the emitted invoker, which handles this + // correctly, rather than risk calling through an incompatible entry point. return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); } @@ -56,14 +64,15 @@ internal struct InvokeState } state.FunctionPointer = functionPointer; - state.SpecializationThreshold = GetSpecializationThreshold(method, functionPointer); + state.DeclaringType = method.DeclaringType; + state.InvocationsUntilSpecialization = GetSpecializationThreshold(method, functionPointer); strategy |= StrategyDetermined; Volatile.Write(ref state.Thunk, (IntPtr)thunk); } if (RuntimeFeature.IsDynamicCodeCompiled && !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && - Interlocked.Increment(ref state.InvocationCount) >= state.SpecializationThreshold) + --state.InvocationsUntilSpecialization <= 0) { // Let the normal strategy selection specialize the next invocation's argument path. strategy &= ~StrategyDetermined; @@ -76,7 +85,7 @@ internal struct InvokeState target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); } - object? result = thunk(target, obj, args, method.DeclaringType); + object? result = thunk(target, obj, args, state.DeclaringType); GC.KeepAlive(method); return result; } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs index 888f2184b1cd65..a67463c01d7157 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs @@ -41,11 +41,11 @@ public void SharedThunk_CachedInvokerPromotes(int argumentCount) Assert.Same(argument, result.Value); if (i == 0 || i == IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold - 1) { - IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1, IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold); } } - IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker, IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index d0930977688842..7e9a9c470bfade 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -753,13 +753,13 @@ private static void AssertSharedThunkPromotesAtDispatchPathThreshold( IntrinsicInvokeSelectionAssertions.AssertUsesCachedTarget(invoker); } - IntrinsicInvokeSelectionAssertions.AssertNotPromoted(method, threshold); - IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, threshold); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(method, threshold, threshold); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, threshold, threshold); Assert.Same(argument, method.Invoke(target, arguments)); Assert.Same(argument, invoker.Invoke(target, argument)); - IntrinsicInvokeSelectionAssertions.AssertPromoted(method); - IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + IntrinsicInvokeSelectionAssertions.AssertPromoted(method, threshold); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker, threshold); } [Fact] @@ -1952,7 +1952,7 @@ internal static void AssertFallback(object invoker) Assert.Equal(IntPtr.Zero, GetInvokeStateField(invoker, "Thunk")); } - internal static void AssertPromoted(object invoker) + internal static void AssertPromoted(object invoker, int threshold) { if (!ShouldAssertSharedSelection) { @@ -1961,36 +1961,35 @@ internal static void AssertPromoted(object invoker) if (!ShouldAssertPromotion) { - AssertNotPromoted(invoker, 0); + AssertNotPromoted(invoker, 0, threshold); return; } - Assert.Equal( - GetInvokeStateField(invoker, "SpecializationThreshold"), - GetInvokeStateField(invoker, "InvocationCount")); + // The countdown reaches zero (or below, under racing decrements) once the threshold is hit. + Assert.True(GetInvokeStateField(invoker, "InvocationsUntilSpecialization") <= 0); Assert.True( GetRefArgsDelegate(invoker).Method.Name != SharedThunkMethodName || GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args") is not null || GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); } - internal static void AssertPromoted(MethodBase method) + internal static void AssertPromoted(MethodBase method, int threshold) { if (ShouldAssertSharedSelection) { - AssertPromoted(GetCachedInvoker(method)); + AssertPromoted(GetCachedInvoker(method), threshold); } } - internal static void AssertNotPromoted(MethodBase method, int invocationCount) + internal static void AssertNotPromoted(MethodBase method, int invocationCount, int threshold) { if (ShouldAssertSharedSelection) { - AssertNotPromoted(GetCachedInvoker(method), invocationCount); + AssertNotPromoted(GetCachedInvoker(method), invocationCount, threshold); } } - internal static void AssertNotPromoted(object invoker, int invocationCount) + internal static void AssertNotPromoted(object invoker, int invocationCount, int threshold) { if (!ShouldAssertSharedSelection) { @@ -2000,7 +1999,8 @@ internal static void AssertNotPromoted(object invoker, int invocationCount) AssertShared(invoker); Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args")); Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs")); - Assert.Equal(ShouldAssertPromotion ? invocationCount : 0, GetInvokeStateField(invoker, "InvocationCount")); + int expectedRemaining = ShouldAssertPromotion ? threshold - invocationCount : threshold; + Assert.Equal(expectedRemaining, GetInvokeStateField(invoker, "InvocationsUntilSpecialization")); } internal static void AssertUsesCachedTarget(object stateOwner) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 087d5db80091d0..7cf848afc303e2 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -45,12 +45,12 @@ public void SharedThunk_CachedInvokerPromotes(bool useByRef) if (i == 0 || i == IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold - 1) { - IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1, IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold); } } Assert.Equal(IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold + 1, target.CallCount); - IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker, IntrinsicInvokeSelectionAssertions.CachedTargetSpecializationThreshold); } [Fact] From 06250ca0e34963d3d04b69b1fc5bc662d09ffdf2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Sep 2026 16:42:24 -0700 Subject: [PATCH 31/38] Remove redundant PrepareForUseAsAFunctionPointer call pTargetMethod (pMethod itself for non-virtual methods, or the resolved virtual target) is already separately prepared via pTargetMethod->PrepareForUseAsAFunctionPointer() below, making the earlier unconditional call on pMethod redundant. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/runtimehandles.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index fa2d9efeb22b59..58fd203d3d4b10 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1335,7 +1335,6 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( { GCX_PREEMP(); pMethod->EnsureActive(); - pMethod->PrepareForUseAsAFunctionPointer(); MethodDesc* pTargetMethod = pMethod->IsVtableMethod() ? pMethod->GetMethodDescOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) : pMethod; From 382f9f870aa7a9567ef2d389e0a110f71dab031b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 10:51:00 -0700 Subject: [PATCH 32/38] Cache DeclaringType as RuntimeType and reuse for GetVirtualFunctionPointer InvokeState.DeclaringType is now stored as RuntimeType? instead of Type?, so GetConstructorInstance can call RuntimeType.GetUninitializedObject() directly instead of going through the public RuntimeHelpers.GetUninitializedObject(Type) API's redundant type-check/cast. The cached RuntimeType is also now passed into RuntimeMethodHandle.GetVirtualFunctionPointer instead of having it re-derive the declaring type from method.DeclaringType (a virtual property read) on every virtual/interface dispatch. Also documents why the ForceEmitInvoke/ForceInterpretedInvoke guard is needed: ForceEmitInvoke alone never reaches this method because MethodInvokerCommon replaces the shared-thunk delegate before it is ever entered, but the guard still matters when both switches are set simultaneously. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Reflection/IntrinsicInvokeHelper.cs | 201 +++++++++--------- .../src/System/RuntimeHandles.cs | 3 +- 2 files changed, 103 insertions(+), 101 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index bcf22830abe808..2c8f9d22a9c9b4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -2,7 +2,6 @@ // 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 System.Runtime.InteropServices; @@ -26,7 +25,7 @@ internal struct InvokeState { internal IntPtr Thunk; internal IntPtr FunctionPointer; - internal Type? DeclaringType; + internal RuntimeType? DeclaringType; // Counts down from the specialization threshold to zero. A plain decrement is used instead of // Interlocked.Decrement: this is a heuristic trigger, so occasionally dropped decrements under // concurrent invocation are acceptable and simply delay specialization slightly. @@ -55,7 +54,7 @@ internal struct InvokeState return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); } - var thunk = (delegate*)Volatile.Read(ref state.Thunk); + var thunk = (delegate*)Volatile.Read(ref state.Thunk); if (thunk is null) { if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) @@ -64,13 +63,19 @@ internal struct InvokeState } state.FunctionPointer = functionPointer; - state.DeclaringType = method.DeclaringType; + state.DeclaringType = (RuntimeType?)method.DeclaringType; state.InvocationsUntilSpecialization = GetSpecializationThreshold(method, functionPointer); strategy |= StrategyDetermined; Volatile.Write(ref state.Thunk, (IntPtr)thunk); } if (RuntimeFeature.IsDynamicCodeCompiled && + // ForceEmitInvoke alone never reaches here: MethodInvokerCommon.Initialize pre-selects the + // emit-only strategy at construction, which replaces the shared-thunk delegate with a plain + // emitted invoker before this method is ever entered. The one case where ForceEmitInvoke can + // still be true here is when both switches are set simultaneously (a contradictory + // configuration); Initialize then falls back to the default/undetermined strategy, so + // invocation proceeds through the shared thunk and both switches remain readable below. !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && --state.InvocationsUntilSpecialization <= 0) { @@ -82,7 +87,7 @@ internal struct InvokeState if (target == IntPtr.Zero) { // The same MethodInfo can be invoked on different implementations. - target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); + target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, state.DeclaringType!, obj!); } object? result = thunk(target, obj, args, state.DeclaringType); @@ -126,7 +131,7 @@ private static int GetSpecializationThreshold(MethodBase method, IntPtr function private static unsafe bool TryGetShape( MethodBase method, ReadOnlySpan argumentTypes, - out delegate* thunk, + out delegate* thunk, out IntPtr functionPointer) { unsafe @@ -249,7 +254,7 @@ private static bool IsReferenceType(Type type) => private static Type GetInputType(RuntimeType type) => type.IsActualEnum ? type.GetEnumUnderlyingType() : type; - private static unsafe delegate* ClassifyConstructor(ReadOnlySpan arguments) + private static unsafe delegate* ClassifyConstructor(ReadOnlySpan arguments) { unsafe { @@ -292,7 +297,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe delegate* ClassifyStaticReferenceArguments(int count, Type returnType) + private static unsafe delegate* ClassifyStaticReferenceArguments(int count, Type returnType) { unsafe { @@ -324,7 +329,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) + private static unsafe delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) { unsafe { @@ -382,7 +387,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe delegate* ClassifyInstancePrimitive(Type type) + private static unsafe delegate* ClassifyInstancePrimitive(Type type) { unsafe { @@ -407,7 +412,7 @@ private static Type GetInputType(RuntimeType type) => // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into // the classifier's compiled body. - private static unsafe delegate* ClassifyStatic0Return(Type returnType) + private static unsafe delegate* ClassifyStatic0Return(Type returnType) { unsafe { @@ -436,7 +441,7 @@ private static Type GetInputType(RuntimeType type) => // Per-shape thunks. JIT compiles only the ones used. - private static unsafe object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -445,7 +450,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -453,7 +458,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -461,7 +466,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -469,7 +474,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -477,7 +482,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -485,7 +490,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -493,7 +498,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -501,7 +506,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -509,7 +514,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -517,7 +522,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -525,7 +530,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -533,7 +538,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -541,7 +546,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -549,7 +554,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -557,7 +562,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + private static unsafe object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, RuntimeType? ___) { unsafe { @@ -565,7 +570,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -576,7 +581,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -586,7 +591,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -599,7 +604,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -609,7 +614,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -619,7 +624,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -629,7 +634,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -640,7 +645,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -651,7 +656,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -659,7 +664,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, Type? __) + private static unsafe object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, RuntimeType? __) { unsafe { @@ -669,7 +674,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -678,7 +683,7 @@ private static Type GetInputType(RuntimeType type) => } #pragma warning disable CA1859 // These thunks must match the shared object-returning function-pointer signature. - private static unsafe object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -686,7 +691,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -694,7 +699,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -702,7 +707,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -710,7 +715,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -718,7 +723,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -726,7 +731,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -734,7 +739,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -742,7 +747,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -750,7 +755,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -758,7 +763,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -766,7 +771,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -774,7 +779,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -782,7 +787,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -790,7 +795,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -801,7 +806,7 @@ private static Type GetInputType(RuntimeType type) => } #pragma warning restore CA1859 - private static unsafe object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -811,7 +816,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -821,7 +826,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -831,7 +836,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -841,7 +846,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + private static unsafe object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? __) { unsafe { @@ -850,7 +855,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -861,7 +866,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -872,7 +877,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -883,7 +888,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -894,7 +899,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -903,7 +908,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -912,7 +917,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -921,7 +926,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -930,7 +935,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -939,7 +944,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -948,7 +953,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -957,7 +962,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -966,7 +971,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -975,7 +980,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -984,7 +989,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -993,7 +998,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -1002,7 +1007,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -1011,7 +1016,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -1020,7 +1025,7 @@ private static Type GetInputType(RuntimeType type) => } } - private static unsafe object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + private static unsafe object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? _) { unsafe { @@ -1037,16 +1042,14 @@ private static Type GetInputType(RuntimeType type) => // // This method unifies the two paths so our shared constructor thunks will always have a "this" instance // when they go to invoke the constructor. - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", - Justification = "Caller anchors the ctor MethodBase, keeping its type reachable.")] - private static object GetConstructorInstance(object? obj, Type? declaringType) + private static object GetConstructorInstance(object? obj, RuntimeType? declaringType) { Debug.Assert(declaringType != typeof(string)); Debug.Assert(!declaringType!.IsArray); - return obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + return obj ?? declaringType!.GetUninitializedObject(); } - private static unsafe object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) + private static unsafe object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, RuntimeType? declaringType) { unsafe { @@ -1056,7 +1059,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1071,7 +1074,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1087,7 +1090,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1104,7 +1107,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1114,7 +1117,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1127,7 +1130,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1140,7 +1143,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1153,7 +1156,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1166,7 +1169,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1176,7 +1179,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1186,7 +1189,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1196,7 +1199,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1206,7 +1209,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1216,7 +1219,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1228,7 +1231,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1241,7 +1244,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { @@ -1254,7 +1257,7 @@ private static object GetConstructorInstance(object? obj, Type? declaringType) } } - private static unsafe object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + private static unsafe object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, RuntimeType? declaringType) { unsafe { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index dd8a5d1e76b03d..791f12174cc4b5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1100,9 +1100,8 @@ public IntPtr GetFunctionPointer() [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetVirtualFunctionPointer")] private static partial IntPtr GetVirtualFunctionPointer(RuntimeMethodHandleInternal method, QCallTypeHandle declaringType, ObjectHandleOnStack target); - internal static IntPtr GetVirtualFunctionPointer(RuntimeMethodInfo method, object target) + internal static IntPtr GetVirtualFunctionPointer(RuntimeMethodInfo method, RuntimeType declaringType, object target) { - RuntimeType declaringType = (RuntimeType)method.DeclaringType!; return GetVirtualFunctionPointer(IRuntimeMethodInfo.GetValue(method), new QCallTypeHandle(ref declaringType), ObjectHandleOnStack.Create(ref target)); } From d0e099415377cfd6f9558e8a2c36924df27261ec Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 10:51:06 -0700 Subject: [PATCH 33/38] Fix corelib.h column alignment for IntrinsicInvokeHelper entry Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/corelib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index e8e50db2bed840..3eed63e441edc7 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -537,7 +537,7 @@ DEFINE_CLASS(MEMBER, Reflection, MemberInfo) DEFINE_CLASS(METHODBASEINVOKER, Reflection, MethodBaseInvoker) DEFINE_CLASS(INSTANCE_CALLI_HELPER, Reflection, InstanceCalliHelper) -DEFINE_CLASS(INTRINSIC_INVOKE_HELPER, Reflection, IntrinsicInvokeHelper) +DEFINE_CLASS(INTRINSIC_INVOKE_HELPER, Reflection, IntrinsicInvokeHelper) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) DEFINE_FIELD_U(m_handle, ReflectMethodObject, m_pMD) From 94dee58d4d53a7389713ff1debab5e1dd0a2e437 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 11:22:10 -0700 Subject: [PATCH 34/38] Remove stale ForceEmitInvoke explanatory comment per jkotas feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 2c8f9d22a9c9b4..6e24fb19660097 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -70,12 +70,6 @@ internal struct InvokeState } if (RuntimeFeature.IsDynamicCodeCompiled && - // ForceEmitInvoke alone never reaches here: MethodInvokerCommon.Initialize pre-selects the - // emit-only strategy at construction, which replaces the shared-thunk delegate with a plain - // emitted invoker before this method is ever entered. The one case where ForceEmitInvoke can - // still be true here is when both switches are set simultaneously (a contradictory - // configuration); Initialize then falls back to the default/undetermined strategy, so - // invocation proceeds through the shared thunk and both switches remain readable below. !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && --state.InvocationsUntilSpecialization <= 0) { From 99429722542d5a1f155e16be83958c289c6d3400 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 11:27:42 -0700 Subject: [PATCH 35/38] Remove redundant EnsureActive call in GetVirtualFunctionPointer pTargetMethod->EnsureActive() already covers both branches: when pMethod->IsVtableMethod() is true, pTargetMethod is the resolved virtual target; otherwise pTargetMethod == pMethod. The prior pMethod->EnsureActive() call before resolution was therefore redundant. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/runtimehandles.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 58fd203d3d4b10..3a9bfb4e0fd14d 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1334,7 +1334,6 @@ extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( MethodTable* pReceiverMT = receiver->GetMethodTable(); { GCX_PREEMP(); - pMethod->EnsureActive(); MethodDesc* pTargetMethod = pMethod->IsVtableMethod() ? pMethod->GetMethodDescOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) : pMethod; From 2e305158a394d90c9268cda950bedbf986c00f41 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 11:51:26 -0700 Subject: [PATCH 36/38] Remove unneeded boxed value-type special-case guard in IntrinsicInvokeHelper Tracing MethodDesc::GetMethodDescOfVirtualizedCode and MethodTable::GetMethodDescForSlot / GetMethodDescForInterfaceMethod shows that virtual/interface dispatch against a boxed value-type receiver always resolves to the unboxing(-and-instantiating) stub's own MethodDesc, and GetMethodEntryPoint returns that stub's entry point directly. The stub is fully self-contained (this-pointer adjustment and any instantiation argument are supplied internally), so its calling convention already matches the shared thunk's normal (this, args...) signature. The guard's premise of a calling-convention mismatch does not hold, so it is unnecessary and is removed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index 6e24fb19660097..beb935ca906b94 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -44,16 +44,6 @@ internal struct InvokeState { unsafe { - if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) - { - // A virtual/interface method resolved against a boxed value-type receiver can resolve to an - // unboxing stub or, for generic value types, a combined unboxing/instantiating stub whose - // calling convention (this-pointer adjustment, implicit generic context) isn't guaranteed to - // match the shared thunk signature. Fall back to the emitted invoker, which handles this - // correctly, rather than risk calling through an incompatible entry point. - return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); - } - var thunk = (delegate*)Volatile.Read(ref state.Thunk); if (thunk is null) { From 1def6ea77ea3e5a9d61bf99d90777db4d55d5c6f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 12:29:15 -0700 Subject: [PATCH 37/38] Update reflection invoke tests for shared-thunk boxed value-type dispatch Following removal of the obj.GetType().IsValueType special-case guard in IntrinsicInvokeHelper.Invoke, virtual/interface dispatch on a boxed value-type receiver now resolves through the shared thunk rather than falling back to the emitted invoker. Update the affected test expectations to assert the new shared-thunk selection while preserving the existing functional result coverage: - MethodInfoTests: move the IIntrinsicInvokeStructReceiver.GetValue (interface-declared, struct receiver) case out of the excluded-shapes fallback data into a new fact asserting AssertShared. - MethodInvokerTests: SharedThunk_ObjectMethodOnBoxedValueReceiverFallsBack is renamed to ...UsesSharedThunk and asserts AssertShared. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../MethodInfoTests.cs | 24 +++++++++++-------- .../MethodInvokerTests.cs | 6 +++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 7e9a9c470bfade..61a8f99cdbb874 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -992,16 +992,6 @@ public static IEnumerable Invoke_ExcludedShapes_Fallback_TestData() -1, null }; - yield return new object[] - { - typeof(IIntrinsicInvokeStructReceiver), - nameof(IIntrinsicInvokeStructReceiver.GetValue), - new IntrinsicInvokeStructReceiver(49), - Array.Empty(), - 49, - -1, - null - }; } [Theory] @@ -1028,6 +1018,20 @@ public void Invoke_ExcludedShapes_Fallback( IntrinsicInvokeSelectionAssertions.AssertFallback(method); } + [Fact] + public void Invoke_InterfaceMethodOnBoxedValueReceiver_SharedThunk() + { + // The virtual dispatch for an interface method on a boxed value-type receiver resolves to + // the struct's own unboxing(-and-instantiating) stub, which is self-contained and + // call-compatible with the shared thunk's normal (this, args...) convention. + MethodInfo method = typeof(IIntrinsicInvokeStructReceiver).GetMethod(nameof(IIntrinsicInvokeStructReceiver.GetValue))!; + + object? result = method.Invoke(new IntrinsicInvokeStructReceiver(49), Array.Empty()); + + Assert.Equal(49, result); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + public static IEnumerable Invoke_EnumResult_Fallback_TestData() { yield return new object[] diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 7cf848afc303e2..bfe9adedbe695c 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -54,13 +54,15 @@ public void SharedThunk_CachedInvokerPromotes(bool useByRef) } [Fact] - public void SharedThunk_ObjectMethodOnBoxedValueReceiverFallsBack() + public void SharedThunk_ObjectMethodOnBoxedValueReceiverUsesSharedThunk() { MethodInfo method = typeof(object).GetMethod(nameof(object.ToString))!; MethodInvoker invoker = MethodInvoker.Create(method); + // The virtual dispatch resolves to the struct's own unboxing(-and-instantiating) stub, + // which is self-contained and call-compatible with the shared thunk. Assert.Equal("50", invoker.Invoke(new IntrinsicInvokeStructReceiver(50))); - IntrinsicInvokeSelectionAssertions.AssertFallback(invoker); + IntrinsicInvokeSelectionAssertions.AssertShared(invoker); } [Theory] From bbe673323b77e96e89b5b96ed94e6d52ab57ad08 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 21:10:43 -0700 Subject: [PATCH 38/38] Fix condition for dynamic code compilation check --- .../src/System/Reflection/IntrinsicInvokeHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs index beb935ca906b94..99e5b883132946 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -60,7 +60,7 @@ internal struct InvokeState } if (RuntimeFeature.IsDynamicCodeCompiled && - !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && + !LocalAppContextSwitches.ForceInterpretedInvoke && --state.InvocationsUntilSpecialization <= 0) { // Let the normal strategy selection specialize the next invocation's argument path.