diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 1dd3eb3291ca9a..f99a2842e7fcad 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -185,6 +185,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs index 05f69d2670e3ed..f58e24742dc500 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,18 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - namespace System.Reflection { - internal partial class ConstructorInvoker + public partial class ConstructorInvoker { - public InvocationFlags _invocationFlags; + private readonly Signature? _signature; + + internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) + { + _signature = constructor.Signature; + _invokeFunc_RefArgs = InterpretedInvoke; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments) + private unsafe object? InterpretedInvoke(object? obj, IntPtr* args) { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _method.Signature, isConstructor: obj is null)!; + return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs index dc764971c2604d..0a4ebb1c5140c8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs @@ -24,7 +24,7 @@ public sealed partial class DynamicMethod : MethodInfo private Module _module; internal bool _skipVisibility; internal RuntimeType? _typeOwner; - private MethodInvoker? _invoker; + private MethodBaseInvoker? _invoker; private Signature? _signature; private string _name; private MethodAttributes _attributes; @@ -85,12 +85,12 @@ internal RuntimeMethodHandle GetMethodDescriptor() return new RuntimeMethodHandle(_methodHandle!); } - private MethodInvoker Invoker + private MethodBaseInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return _invoker ??= new MethodInvoker(this, Signature); + return _invoker ??= new MethodBaseInvoker(this, Signature); } } @@ -134,136 +134,28 @@ Signature LazyCreateSignature() throw new TargetParameterCountException(SR.Arg_ParmCnt); object? retValue; - - unsafe + switch (argCount) { - if (argCount == 0) - { - retValue = Invoker.InlinedInvoke(obj, args: default, invokeAttr); - } - else if (argCount > MaxStackAllocArgCount) - { - Debug.Assert(parameters != null); - retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); - } - else - { - Debug.Assert(parameters != null); - StackAllocedArguments argStorage = default; - Span copyOfParameters = argStorage._args.AsSpan(argCount); - Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); - - StackAllocatedByRefs byrefStorage = default; -#pragma warning disable CS8500 - IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; -#pragma warning restore CS8500 - - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - Signature.Arguments, - binder, - culture, - invokeAttr); - - retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - } + case 0: + retValue = Invoker.InvokeWithNoArgs(obj, invokeAttr); + break; + case 1: + retValue = Invoker.InvokeWithOneArg(obj, invokeAttr, binder, parameters!, culture); + break; + case 2: + case 3: + case 4: + retValue = Invoker.InvokeWithFewArgs(obj, invokeAttr, binder, parameters!, culture); + break; + default: + retValue = Invoker.InvokeWithManyArgs(obj, invokeAttr, binder, parameters!, culture); + break; } GC.KeepAlive(this); return retValue; } - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - // This is a separate method to support better performance for the faster paths. - private static unsafe object? InvokeWithManyArguments( - DynamicMethod mi, - int argCount, - object? obj, - BindingFlags invokeAttr, - Binder? binder, - object?[] parameters, - CultureInfo? culture) - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new(objHolder, 0, argCount); - - // We don't check a max stack size since we are invoking a method which - // naturally requires a stack size that is dependent on the arg count\size. - IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; - NativeMemory.Clear(pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - - ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; - Span shouldCopyBackParameters = new(copyBackActions, argCount); - - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - - object? retValue; - try - { - RegisterForGCReporting(®); - mi.CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - mi.Signature.Arguments, - binder, - culture, - invokeAttr); - - retValue = mi.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - - return retValue; - } - public DynamicILInfo GetDynamicILInfo() { if (_dynamicILInfo == 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 new file mode 100644 index 00000000000000..85e9a63d6ecc63 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Emit; + +namespace System.Reflection +{ + internal partial class MethodBaseInvoker + { + private readonly Signature? _signature; + + internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) + { + _signature = method.Signature; + _invocationFlags = method.ComputeAndUpdateInvocationFlags(); + _invokeFunc_RefArgs = InterpretedInvoke_Method; + } + + internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) + { + _signature = constructor.Signature; + _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + } + + internal unsafe MethodBaseInvoker(DynamicMethod method, Signature signature) : this(method, signature.Arguments) + { + _signature = signature; + _invokeFunc_RefArgs = InterpretedInvoke_Method; + } + + private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + + private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) => + RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor : false); + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs index 671206f80e821c..644364a77266e2 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,36 +1,39 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; +using System.Reflection.Emit; namespace System.Reflection { - internal partial class MethodInvoker + public partial class MethodInvoker { - private readonly Signature _signature; - internal InvocationFlags _invocationFlags; + private readonly Signature? _signature; - public MethodInvoker(MethodBase method, Signature signature) + private unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) { - _method = method; - _signature = signature; + _signature = method.Signature; + _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invocationFlags = method.ComputeAndUpdateInvocationFlags(); + } - if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) - { - // Always use the native invoke; useful for testing. - _strategyDetermined = true; - } - else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) - { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. - _invoked = true; - } + private unsafe MethodInvoker(DynamicMethod method) : this(method, method.Signature.Arguments) + { + _signature = method.Signature; + _invokeFunc_RefArgs = InterpretedInvoke_Method; + // No _invocationFlags for DynamicMethod. } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments) + private unsafe MethodInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _signature, isConstructor: false); + _signature = constructor.Signature; + _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); } + + 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); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index a77102fc2b51fe..2059daa80674ac 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -194,18 +194,17 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); - ParameterCopyBackAction _ref = default; RuntimeType fieldType = (RuntimeType)FieldType; if (value is null) { if (RuntimeTypeHandle.IsValueType(fieldType)) { - fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, binder, culture, invokeAttr); } } else if (!ReferenceEquals(value.GetType(), fieldType)) { - fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, binder, culture, invokeAttr); } bool domainInitialized = false; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs index 2fa7952e07a82d..500581553a3cb1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Text; @@ -29,7 +28,7 @@ internal sealed partial class RuntimeConstructorInfo : ConstructorInfo, IRuntime private readonly MethodAttributes m_methodAttributes; private readonly BindingFlags m_bindingFlags; private Signature? m_signature; - private ConstructorInvoker? m_invoker; + private MethodBaseInvoker? m_invoker; internal InvocationFlags InvocationFlags { @@ -37,20 +36,17 @@ internal InvocationFlags InvocationFlags get { InvocationFlags flags = Invoker._invocationFlags; - if ((flags & InvocationFlags.Initialized) == 0) - { - flags = ComputeAndUpdateInvocationFlags(this, ref Invoker._invocationFlags); - } + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } } - private ConstructorInvoker Invoker + private MethodBaseInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_invoker ??= new ConstructorInvoker(this); + m_invoker ??= new MethodBaseInvoker(this); return m_invoker; } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs index 7ec37002150389..ce9182cc4785bc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs @@ -1259,7 +1259,7 @@ private static void AddCustomAttributes( continue; } - setMethod.InvokeOneParameter(attribute, BindingFlags.Default, null, value, null); + setMethod.InvokePropertySetter(attribute, BindingFlags.Default, null, value, null); } else { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index 24634ed9715fe2..1fd8da7fe5f66d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -27,7 +27,7 @@ internal sealed partial class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo private Signature? m_signature; private readonly RuntimeType m_declaringType; private readonly object? m_keepalive; - private MethodInvoker? m_invoker; + private MethodBaseInvoker? m_invoker; internal InvocationFlags InvocationFlags { @@ -35,20 +35,17 @@ internal InvocationFlags InvocationFlags get { InvocationFlags flags = Invoker._invocationFlags; - if ((flags & InvocationFlags.Initialized) == 0) - { - flags = ComputeAndUpdateInvocationFlags(this, ref Invoker._invocationFlags); - } + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } } - private MethodInvoker Invoker + private MethodBaseInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_invoker ??= new MethodInvoker(this, Signature); + m_invoker ??= new MethodBaseInvoker(this); return m_invoker; } } @@ -287,7 +284,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() #region Invocation Logic [DebuggerStepThrough] [DebuggerHidden] - internal object? InvokeOneParameter(object? obj, BindingFlags invokeAttr, Binder? binder, object? parameter, CultureInfo? culture) + internal void InvokePropertySetter(object? obj, BindingFlags invokeAttr, Binder? binder, object? parameter, CultureInfo? culture) { // ContainsStackPointers means that the struct (either the declaring type or the return type) // contains pointers that point to the stack. This is either a ByRef or a TypedReference. These structs cannot @@ -298,7 +295,10 @@ public override MethodImplAttributes GetMethodImplementationFlags() } // check basic method consistency. This call will throw if there are problems in the target/method relationship - ValidateInvokeTarget(obj); + if (!IsStatic) + { + MethodInvokerCommon.ValidateInvokeTarget(obj, this); + } Signature sig = Signature; if (sig.Arguments.Length != 1) @@ -306,38 +306,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - object? retValue; - - unsafe - { - StackAllocedArguments argStorage = default; - Span copyOfParameters = argStorage._args.AsSpan(1); - ReadOnlySpan parameters = new(in parameter); - Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(1); - - StackAllocatedByRefs byrefStorage = default; -#pragma warning disable 8500 - IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; -#pragma warning restore 8500 - - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - -#if MONO // Temporary until Mono is updated. - retValue = Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); -#else - retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); -#endif - } - - return retValue; + Invoker.InvokePropertySetter(obj, invokeAttr, binder, parameter, culture); } #endregion diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs index d2731883b02f89..eb83e8f410d6c8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs @@ -372,7 +372,7 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt if (index is null) { - m.InvokeOneParameter(obj, invokeAttr, binder, value, culture); + m.InvokePropertySetter(obj, invokeAttr, binder, value, culture); } else { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 831582694aa534..86f1f7d235609a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -380,34 +380,6 @@ private static unsafe void DispatchTailCalls( } } } - -#pragma warning disable 0414, IDE0044 - // Type that represents a managed view of the unmanaged GCFrame - // data structure in coreclr. The type layouts between the two should match. - internal unsafe ref struct GCFrameRegistration - { - private nuint m_reserved1; - private nuint m_reserved2; - private void* m_pObjRefs; - private uint m_numObjRefs; - private int m_MaybeInterior; - - public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) - { - m_reserved1 = 0; - m_reserved2 = 0; - m_pObjRefs = allocation; - m_numObjRefs = elemCount; - m_MaybeInterior = areByRefs ? 1 : 0; - } - } -#pragma warning restore 0414, IDE0044 - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration); } // Helper class to assist with unsafe pinning of arbitrary objects. // It's used by VM code. diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 12b85edffacb6f..b56d32a0905922 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3639,37 +3639,33 @@ public override Type MakeArrayType(int rank) private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object AllocateValueType(RuntimeType type, object? value); + internal static extern object AllocateValueType(RuntimeType type, object? value); - private CheckValueStatus TryChangeTypeSpecial( - ref object value, - out bool isValueType) + private CheckValueStatus TryChangeTypeSpecial(ref object value) { - Pointer? pointer = value as Pointer; - RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); + Pointer? pointer = value as Pointer; + RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); - if (!CanValueSpecialCast(srcType, this)) - { - isValueType = false; - return CheckValueStatus.ArgumentException; - } + if (!CanValueSpecialCast(srcType, this)) + { + return CheckValueStatus.ArgumentException; + } - if (pointer != null) - { - value = pointer.GetPointerValue(); // Convert source pointer to IntPtr - } - else + if (pointer != null) + { + value = pointer.GetPointerValue(); // Convert source pointer to IntPtr + } + else + { + CorElementType srcElementType = GetUnderlyingType(srcType); + CorElementType dstElementType = GetUnderlyingType(this); + if (dstElementType != srcElementType) { - CorElementType srcElementType = GetUnderlyingType(srcType); - CorElementType dstElementType = GetUnderlyingType(this); - if (dstElementType != srcElementType) - { - value = InvokeUtils.ConvertOrWiden(srcType, value, this, dstElementType); - } + value = InvokeUtils.ConvertOrWiden(srcType, value, this, dstElementType); } + } - isValueType = true; - return CheckValueStatus.Success; + return CheckValueStatus.Success; } #endregion diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index c7905dd7141c63..c410fee4e530de 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -686,7 +686,7 @@ CP0001 - T:Internal.Reflection.Core.Execution.MethodInvoker + T:Internal.Reflection.Core.Execution.MethodBaseInvoker CP0001 diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs index cf93e6169f91b8..fee9f2509cead5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs @@ -72,7 +72,7 @@ public abstract class ExecutionEnvironment //============================================================================================== // Invoke and field access support. //============================================================================================== - public abstract MethodInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles); + public abstract MethodBaseInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles); public abstract FieldAccessor TryGetFieldAccessor(MetadataReader reader, RuntimeTypeHandle declaringTypeHandle, RuntimeTypeHandle fieldTypeHandle, FieldHandle fieldHandle); //============================================================================================== @@ -96,12 +96,12 @@ public abstract class ExecutionEnvironment //============================================================================================== public abstract FieldAccessor CreateLiteralFieldAccessor(object value, RuntimeTypeHandle fieldTypeHandle); public abstract void GetEnumInfo(RuntimeTypeHandle typeHandle, out string[] names, out object[] values, out bool isFlags); - public abstract IntPtr GetDynamicInvokeThunk(MethodInvoker invoker); + public abstract IntPtr GetDynamicInvokeThunk(MethodBaseInvoker invoker); //============================================================================================== // Non-public methods //============================================================================================== - internal MethodInvoker GetMethodInvoker(RuntimeTypeInfo declaringType, QMethodDefinition methodHandle, RuntimeTypeInfo[] genericMethodTypeArguments, MemberInfo exceptionPertainant, out Exception exception) + internal MethodBaseInvoker GetMethodInvoker(RuntimeTypeInfo declaringType, QMethodDefinition methodHandle, RuntimeTypeInfo[] genericMethodTypeArguments, MemberInfo exceptionPertainant, out Exception exception) { exception = null; @@ -120,13 +120,13 @@ internal MethodInvoker GetMethodInvoker(RuntimeTypeInfo declaringType, QMethodDe { genericMethodTypeArgumentHandles[i] = genericMethodTypeArguments[i].TypeHandle; } - MethodInvoker methodInvoker = TryGetMethodInvoker(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles); + MethodBaseInvoker methodInvoker = TryGetMethodInvoker(typeDefinitionHandle, methodHandle, genericMethodTypeArgumentHandles); if (methodInvoker == null) exception = ReflectionCoreExecution.ExecutionDomain.CreateNonInvokabilityException(exceptionPertainant); return methodInvoker; } - protected MethodInvoker GetMethodInvoker(MethodInfo methodInfo) + protected MethodBaseInvoker GetMethodInvoker(MethodInfo methodInfo) { return ((RuntimeMethodInfo)methodInfo).MethodInvoker; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodBaseInvoker.cs similarity index 80% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodInvoker.cs rename to src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodBaseInvoker.cs index a208715adc9489..19589a2be4e6c5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/MethodBaseInvoker.cs @@ -13,12 +13,12 @@ namespace Internal.Reflection.Core.Execution { // - // This class polymorphically implements the MethodBase.Invoke() api and its close cousins. MethodInvokers are designed to be built once and cached + // This class polymorphically implements the MethodBase.Invoke() api and its close cousins. MethodBaseInvokers are designed to be built once and cached // for maximum Invoke() throughput. // - public abstract class MethodInvoker + public abstract class MethodBaseInvoker { - protected MethodInvoker() { } + protected MethodBaseInvoker() { } [DebuggerGuidedStepThrough] public object? Invoke(object thisObject, object?[] arguments, Binder? binder, BindingFlags invokeAttr, CultureInfo? cultureInfo) @@ -26,7 +26,7 @@ protected MethodInvoker() { } BinderBundle binderBundle = binder.ToBinderBundle(invokeAttr, cultureInfo); bool wrapInTargetInvocationException = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; object? result = Invoke(thisObject, arguments, binderBundle, wrapInTargetInvocationException); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result; } @@ -42,7 +42,11 @@ public object CreateInstance(object?[] arguments, Binder? binder, BindingFlags i protected abstract object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException); protected abstract object CreateInstance(object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException); + protected internal abstract object CreateInstance(Span arguments); + protected internal abstract object CreateInstanceWithFewArgs(Span arguments); public abstract Delegate CreateDelegate(RuntimeTypeHandle delegateType, object target, bool isStatic, bool isVirtual, bool isOpen); + protected internal abstract object? Invoke(object? thisObject, Span arguments); + protected internal abstract object? InvokeDirectWithFewArgs(object? thisObject, Span arguments); // This property is used to retrieve the target method pointer. It is used by the RuntimeMethodHandle.GetFunctionPointer API public abstract IntPtr LdFtnResult { get; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 39fda81c8a8388..3aaf11fd0ef336 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -678,7 +678,7 @@ public static bool CanPrimitiveWiden(RuntimeTypeHandle srcType, RuntimeTypeHandl return true; } - public static object CheckArgument(object srcObject, RuntimeTypeHandle dstType, BinderBundle binderBundle) + public static object CheckArgument(object srcObject, RuntimeTypeHandle dstType, BinderBundle? binderBundle) { return InvokeUtils.CheckArgument(srcObject, dstType.ToEETypePtr(), InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 896b92604d1a08..681f2b8557cb72 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -136,6 +136,7 @@ + @@ -149,6 +150,7 @@ + @@ -483,7 +485,7 @@ - + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs new file mode 100644 index 00000000000000..9410dc44610afd --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Reflection.Core.Execution; +using System.Reflection.Runtime.MethodInfos; +using static System.Reflection.DynamicInvokeInfo; + +namespace System.Reflection +{ + public sealed class ConstructorInvoker + { + private readonly MethodBaseInvoker _methodBaseInvoker; + private readonly RuntimeTypeHandle _declaringTypeHandle; + + internal ConstructorInvoker(RuntimeConstructorInfo constructor) + { + _methodBaseInvoker = constructor.MethodInvoker; + _declaringTypeHandle = constructor.DeclaringType.TypeHandle; + } + + public static ConstructorInvoker Create(ConstructorInfo constructor) + { + if (constructor is not RuntimeConstructorInfo runtimeConstructor) + { + throw new ArgumentException(SR.Argument_MustBeRuntimeConstructorInfo, nameof(constructor)); + } + + return new ConstructorInvoker(runtimeConstructor); + } + + public object? Invoke() + { + return _methodBaseInvoker.CreateInstanceWithFewArgs(new Span()); + } + + public object? Invoke(object? arg1) + { + return _methodBaseInvoker.CreateInstanceWithFewArgs(new Span(ref arg1)); + } + + public object? Invoke(object? arg1, object? arg2) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + return _methodBaseInvoker.CreateInstanceWithFewArgs(argStorage._args.AsSpan(2)); + } + + public object? Invoke(object? arg1, object? arg2, object? arg3) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + argStorage._args.Set(2, arg3); + return _methodBaseInvoker.CreateInstanceWithFewArgs(argStorage._args.AsSpan(3)); + } + + public object? Invoke(object? arg1, object? arg2, object? arg3, object? arg4) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + argStorage._args.Set(2, arg3); + argStorage._args.Set(3, arg4); + return _methodBaseInvoker.CreateInstanceWithFewArgs(argStorage._args.AsSpan(4)); + } + + public object? Invoke(Span arguments) + { + + return _methodBaseInvoker.CreateInstance(arguments); + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs index 698b7a726cef97..2cda655a816d6a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -177,7 +178,7 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk) public bool IsSupportedSignature => _argumentCount >= 0; - [DebuggerGuidedStepThroughAttribute] + [DebuggerGuidedStepThrough] public unsafe object? Invoke( object? thisPtr, IntPtr methodToCall, @@ -188,14 +189,7 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk) int argCount = parameters?.Length ?? 0; if (argCount != _argumentCount) { - if (_argumentCount < 0) - { - if (_argumentCount == ArgumentCount_NotSupported_ByRefLike) - throw new NotSupportedException(SR.NotSupported_ByRefLike); - throw new NotSupportedException(); - } - - throw new TargetParameterCountException(SR.Arg_ParmCnt); + ThrowForArgCountMismatch(); } object? returnObject = null; @@ -241,38 +235,166 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk) } else if (argCount > MaxStackAllocArgCount) { - ret = ref InvokeWithManyArguments( methodToCall, ref thisArg, ref ret, + ret = ref InvokeWithManyArguments(methodToCall, ref thisArg, ref ret, parameters, binderBundle, wrapInTargetInvocationException); } else { - StackAllocedArguments argStorage = default; - StackAllocatedByRefs byrefStorage = default; + ret = ref InvokeWithFewArguments(methodToCall, ref thisArg, ref ret, + parameters, binderBundle, wrapInTargetInvocationException); + } -#pragma warning disable 8500 - CheckArguments(ref argStorage._arg0!, (ByReference*)&byrefStorage, parameters, binderBundle); -#pragma warning restore 8500 + return ((_returnTransform & (Transform.Nullable | Transform.Pointer | Transform.FunctionPointer | Transform.ByRef)) != 0) ? + ReturnTransform(ref ret, wrapInTargetInvocationException) : returnObject; + } - try - { -#pragma warning disable 8500 - ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, &byrefStorage); -#pragma warning restore 8500 - DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); - } - catch (Exception e) when (wrapInTargetInvocationException) + [DebuggerGuidedStepThrough] + public unsafe object? Invoke( + object? thisPtr, + IntPtr methodToCall, + Span parameters) + { + int argCount = parameters.Length; + if (argCount != _argumentCount) + { + ThrowForArgCountMismatch(); + } + + object? returnObject = null; + + scoped ref byte thisArg = ref Unsafe.NullRef(); + if (!_isStatic) + { + // The caller is expected to validate this + Debug.Assert(thisPtr != null); + + // See TODO comment in DynamicInvokeMethodThunk.NormalizeSignature + // if (_isValueTypeInstanceMethod) + // { + // // thisArg is a raw data byref for valuetype instance methods + // thisArg = ref thisPtr.GetRawData(); + // } + // else { - throw new TargetInvocationException(e); + thisArg = ref Unsafe.As(ref thisPtr); } - finally + } + + scoped ref byte ret = ref Unsafe.As(ref returnObject); + if ((_returnTransform & Transform.AllocateReturnBox) != 0) + { + returnObject = RuntimeImports.RhNewObject( + (_returnTransform & (Transform.Pointer | Transform.FunctionPointer)) != 0 ? + EETypePtr.EETypePtrOf() : _returnType); + ret = ref returnObject.GetRawData(); + } + + if (argCount == 0) + { + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, null); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + } + else if (argCount > MaxStackAllocArgCount) + { + ret = ref InvokeWithManyArguments(methodToCall, ref thisArg, ref ret, parameters); + } + else + { + ret = ref InvokeWithFewArguments(methodToCall, ref thisArg, ref ret, parameters); + } + + return ((_returnTransform & (Transform.Nullable | Transform.Pointer | Transform.FunctionPointer | Transform.ByRef)) != 0) ? + ReturnTransform(ref ret, wrapInTargetInvocationException: false) : returnObject; + } + + + [DebuggerGuidedStepThrough] + // This method is equivalent to the one above except that it takes 'Span' instead of 'object[]' + // for the parameters, does not require a copy of the parameters or CopyBack, and does not require + // re-throw capability. + public unsafe object? InvokeDirectWithFewArgs( + object? thisPtr, + IntPtr methodToCall, + Span parameters) + { + int argCount = parameters.Length; + + if (argCount != _argumentCount) + { + ThrowForArgCountMismatch(); + } + + Debug.Assert(_argumentCount <= MaxStackAllocArgCount); + + object? returnObject = null; + + scoped ref byte thisArg = ref Unsafe.NullRef(); + if (!_isStatic) + { + // The caller is expected to validate this + Debug.Assert(thisPtr != null); + + // See TODO comment in DynamicInvokeMethodThunk.NormalizeSignature + // if (_isValueTypeInstanceMethod) + // { + // // thisArg is a raw data byref for valuetype instance methods + // thisArg = ref thisPtr.GetRawData(); + // } + // else { - if (_needsCopyBack) - CopyBack(ref argStorage._arg0!, parameters); + thisArg = ref Unsafe.As(ref thisPtr); } } + scoped ref byte ret = ref Unsafe.As(ref returnObject); + if ((_returnTransform & Transform.AllocateReturnBox) != 0) + { + returnObject = RuntimeImports.RhNewObject( + (_returnTransform & (Transform.Pointer | Transform.FunctionPointer)) != 0 ? + EETypePtr.EETypePtrOf() : _returnType); + ret = ref returnObject.GetRawData(); + } + + if (argCount == 0) + { + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, null); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + } + else if (argCount == 1) + { + ByReference br = ByReference.Create(ref parameters[0]); +#pragma warning disable CS8500 + void* pByrefStorage = &br; +#pragma warning restore CS8500 + + // Since no copy of args is required, pass 'parameters' for both arguments. + CheckArguments(parameters, pByrefStorage, parameters); + + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByrefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + + // No need to call CopyBack here since there are no ref values. + } + else + { + ret = ref InvokeDirectWithFewArguments(methodToCall, ref thisArg, ref ret, parameters); + } + return ((_returnTransform & (Transform.Nullable | Transform.Pointer | Transform.FunctionPointer | Transform.ByRef)) != 0) ? - ReturnTransform(ref ret, wrapInTargetInvocationException) : returnObject; + ReturnTransform(ref ret, wrapInTargetInvocationException: false) : returnObject; + } + + private void ThrowForArgCountMismatch() + { + if (_argumentCount < 0) + { + if (_argumentCount == ArgumentCount_NotSupported_ByRefLike) + throw new NotSupportedException(SR.NotSupported_ByRefLike); + + throw new NotSupportedException(); + } + + throw new TargetParameterCountException(SR.Arg_ParmCnt); } private unsafe ref byte InvokeWithManyArguments( @@ -290,15 +412,16 @@ private unsafe ref byte InvokeWithManyArguments( void* pByRefStorage = (ByReference*)(pStorage + argCount); #pragma warning restore 8500 - RuntimeImports.GCFrameRegistration regArgStorage = new(pStorage, (uint)argCount, areByRefs: false); - RuntimeImports.GCFrameRegistration regByRefStorage = new(pByRefStorage, (uint)argCount, areByRefs: true); + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); try { RuntimeImports.RhRegisterForGCReporting(®ArgStorage); RuntimeImports.RhRegisterForGCReporting(®ByRefStorage); - CheckArguments(ref Unsafe.As(ref *pStorage), pByRefStorage, parameters, binderBundle); + Span copyOfParameters = new(ref Unsafe.As(ref *pStorage), argCount); + CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle); try { @@ -312,7 +435,7 @@ private unsafe ref byte InvokeWithManyArguments( finally { if (_needsCopyBack) - CopyBack(ref Unsafe.As(ref *pStorage), parameters); + CopyBackToArray(ref Unsafe.As(ref *pStorage), parameters); } } finally @@ -324,6 +447,136 @@ private unsafe ref byte InvokeWithManyArguments( return ref ret; } + // This method is equivalent to the one above except that it takes 'Span' instead of 'object[]' + // for the parameters and does not require re-throw capability. + private unsafe ref byte InvokeWithManyArguments( + IntPtr methodToCall, ref byte thisArg, ref byte ret, Span parameters) + { + int argCount = _argumentCount; + + // We don't check a max stack size since we are invoking a method which + // naturally requires a stack size that is dependent on the arg count\size. + IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); + +#pragma warning disable 8500 + void* pByRefStorage = (ByReference*)(pStorage + argCount); +#pragma warning restore 8500 + + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); + + try + { + RuntimeImports.RhRegisterForGCReporting(®ArgStorage); + RuntimeImports.RhRegisterForGCReporting(®ByRefStorage); + + Span copyOfParameters = new(ref Unsafe.As(ref *pStorage), argCount); + CheckArguments(copyOfParameters, pByRefStorage, parameters); + + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + + if (_needsCopyBack) + CopyBackToSpan(copyOfParameters, parameters); + } + finally + { + RuntimeImports.RhUnregisterForGCReporting(®ByRefStorage); + RuntimeImports.RhUnregisterForGCReporting(®ArgStorage); + } + + return ref ret; + } + + // This is a separate method to localize the overhead of stack allocs for 'StackAllocatedByRefs' and 'StackAllocatedByRefs'. + private unsafe ref byte InvokeWithFewArguments( + IntPtr methodToCall, ref byte thisArg, ref byte ret, + object?[] parameters, BinderBundle? binderBundle, bool wrapInTargetInvocationException) + { + Debug.Assert(_argumentCount <= MaxStackAllocArgCount); + int argCount = _argumentCount; + + StackAllocatedArguments argStorage = default; + Span copyOfParameters = argStorage._args.AsSpan(argCount); + StackAllocatedByRefs byrefStorage = default; +#pragma warning disable CS8500 + void* pByRefStorage = (ByReference*)&byrefStorage; +#pragma warning restore CS8500 + + CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle); + + try + { + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + } + catch (Exception e) when (wrapInTargetInvocationException) + { + throw new TargetInvocationException(e); + } + finally + { + if (_needsCopyBack) + CopyBackToArray(ref copyOfParameters[0], parameters); + } + + return ref ret; + } + + // This method is equivalent to the one above except that it takes 'Span' instead of 'object[]' + // for the parameters and does not require 'BinderBundle' or re-throw capability. + private unsafe ref byte InvokeWithFewArguments( + IntPtr methodToCall, ref byte thisArg, ref byte ret, Span parameters) + { + Debug.Assert(_argumentCount <= MaxStackAllocArgCount); + int argCount = _argumentCount; + + StackAllocatedArguments argStorage = default; + Span copyOfParameters = argStorage._args.AsSpan(argCount); + StackAllocatedByRefs byrefStorage = default; +#pragma warning disable CS8500 + void* pByRefStorage = (ByReference*)&byrefStorage; +#pragma warning restore CS8500 + + CheckArguments(copyOfParameters, pByRefStorage, parameters); + + try + { + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + } + finally + { + if (_needsCopyBack) + CopyBackToSpan(copyOfParameters, parameters); + } + + return ref ret; + } + + // This method is equivalent to the one above except that it does not require a copy of the args or CopyBack. + private unsafe ref byte InvokeDirectWithFewArguments( + IntPtr methodToCall, ref byte thisArg, ref byte ret, Span parameters) + { + Debug.Assert(_argumentCount <= MaxStackAllocArgCount); + + StackAllocatedByRefs byrefStorage = default; +#pragma warning disable CS8500 + void* pByRefStorage = (ByReference*)&byrefStorage; +#pragma warning restore CS8500 + + // Since no copy of args is required, pass 'parameters' for both arguments. + CheckArguments(parameters, pByRefStorage, parameters); + + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + + // No need to call CopyBack here since there are no ref values. + + return ref ret; + } + private unsafe object? GetCoercedDefaultValue(int index, in ArgumentInfo argumentInfo) { object? defaultValue = Method.GetParametersNoCopy()[index].DefaultValue; @@ -345,10 +598,10 @@ private unsafe ref byte InvokeWithManyArguments( } private unsafe void CheckArguments( - ref object copyOfParameters, + Span copyOfParameters, void* byrefParameters, object?[] parameters, - BinderBundle binderBundle) + BinderBundle? binderBundle) { for (int i = 0; i < parameters.Length; i++) { @@ -416,16 +669,84 @@ private unsafe void CheckArguments( // is specified for value types, and also used to hold the results from conversions such as // from Int16 to Int32. - Unsafe.Add(ref copyOfParameters, i) = arg!; + copyOfParameters[i] = arg!; #pragma warning disable 8500, 9094 ((ByReference*)byrefParameters)[i] = new ByReference(ref (argumentInfo.Transform & Transform.Reference) != 0 ? - ref Unsafe.As(ref Unsafe.Add(ref copyOfParameters, i)) : ref arg.GetRawData()); + ref Unsafe.As(ref copyOfParameters[i]) : ref arg.GetRawData()); #pragma warning restore 8500, 9094 } } - private unsafe void CopyBack(ref object copyOfParameters, object?[] parameters) + // This method is equivalent to the one above except that it takes 'Span' instead of 'object[]' + // for the parameters and does not require the use of 'BinderBundle' or check for 'Type.Missing'. + private unsafe void CheckArguments( + Span copyOfParameters, + void* byrefParameters, + Span parameters) + { + for (int i = 0; i < parameters.Length; i++) + { + object? arg = parameters[i]; + + ref readonly ArgumentInfo argumentInfo = ref _arguments[i]; + + if (arg is null) + { + // null is substituded by zero-initialized value for non-reference type + if ((argumentInfo.Transform & Transform.Reference) == 0) + arg = RuntimeImports.RhNewObject( + (argumentInfo.Transform & (Transform.Pointer | Transform.FunctionPointer)) != 0 ? + EETypePtr.EETypePtrOf() : argumentInfo.Type); + } + else + { + EETypePtr srcEEType = arg.GetEETypePtr(); + EETypePtr dstEEType = argumentInfo.Type; + + if (!(srcEEType.RawValue == dstEEType.RawValue || + RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) || + (dstEEType.IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable + && castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false)))) + { + // ByRefs have to be exact match + if ((argumentInfo.Transform & Transform.ByRef) != 0) + throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true); + + arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle: null); + } + + if ((argumentInfo.Transform & Transform.Reference) == 0) + { + if ((argumentInfo.Transform & (Transform.ByRef | Transform.Nullable)) != 0) + { + // Rebox the value to avoid mutating the original box. This also takes care of + // T -> Nullable transformation as side-effect. + object box = RuntimeImports.RhNewObject(argumentInfo.Type); + RuntimeImports.RhUnbox(arg, ref box.GetRawData(), argumentInfo.Type); + arg = box; + } + } + } + + // We need to perform type safety validation against the incoming arguments, but we also need + // to be resilient against the possibility that some other thread (or even the binder itself!) + // may mutate the array after we've validated the arguments but before we've properly invoked + // the method. The solution is to copy the arguments to a different, not-user-visible buffer + // as we validate them. This separate array is also used to hold default values when 'null' + // is specified for value types, and also used to hold the results from conversions such as + // from Int16 to Int32. + + copyOfParameters[i] = arg; + +#pragma warning disable 8500, 9094 + ((ByReference*)byrefParameters)[i] = new ByReference(ref (argumentInfo.Transform & Transform.Reference) != 0 ? + ref Unsafe.As(ref copyOfParameters[i]) : ref arg.GetRawData()); +#pragma warning restore 8500, 9094 + } + } + + private unsafe void CopyBackToArray(ref object? src, object?[] dest) { ArgumentInfo[] arguments = _arguments; @@ -438,7 +759,7 @@ private unsafe void CopyBack(ref object copyOfParameters, object?[] parameters) if ((transform & Transform.ByRef) == 0) continue; - object obj = Unsafe.Add(ref copyOfParameters, i); + object? obj = Unsafe.Add(ref src, i); if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) { @@ -458,7 +779,44 @@ private unsafe void CopyBack(ref object copyOfParameters, object?[] parameters) } } - parameters[i] = obj; + dest[i] = obj; + } + } + + private unsafe void CopyBackToSpan(Span src, Span dest) + { + ArgumentInfo[] arguments = _arguments; + + for (int i = 0; i < arguments.Length; i++) + { + ref readonly ArgumentInfo argumentInfo = ref arguments[i]; + + Transform transform = argumentInfo.Transform; + + if ((transform & Transform.ByRef) == 0) + continue; + + object? obj = src[i]; + + if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) + { + if ((transform & Transform.Pointer) != 0) + { + Type type = Type.GetTypeFromMethodTable(argumentInfo.Type.ToPointer()); + Debug.Assert(type.IsPointer); + obj = Pointer.Box((void*)Unsafe.As(ref obj.GetRawData()), type); + } + if ((transform & Transform.FunctionPointer) != 0) + { + obj = RuntimeImports.RhBox(EETypePtr.EETypePtrOf(), ref obj.GetRawData()); + } + else + { + obj = RuntimeImports.RhBox(argumentInfo.Type, ref obj.GetRawData()); + } + } + + dest[i] = obj; } } @@ -495,20 +853,39 @@ private unsafe object ReturnTransform(ref byte byref, bool wrapInTargetInvocatio private const int MaxStackAllocArgCount = 4; + [InlineArray(MaxStackAllocArgCount)] + internal struct ArgumentData + { + private T _arg0; + + [UnscopedRef] + public Span AsSpan(int length) + { + Debug.Assert((uint)length <= MaxStackAllocArgCount); + return new Span(ref _arg0, length); + } + + public void Set(int index, T value) + { + Debug.Assert((uint)index < MaxStackAllocArgCount); + Unsafe.Add(ref _arg0, index) = value; + } + } + // Helper struct to avoid intermediate object[] allocation in calls to the native reflection stack. // When argument count <= MaxStackAllocArgCount, define a local of type default(StackAllocatedByRefs) // and pass it to CheckArguments(). // For argument count > MaxStackAllocArgCount, do a stackalloc of void* pointers along with // GCReportingRegistration to safely track references. - [InlineArray(MaxStackAllocArgCount)] - private ref struct StackAllocedArguments + [StructLayout(LayoutKind.Sequential)] + internal ref struct StackAllocatedArguments { - internal object? _arg0; + internal ArgumentData _args; } // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. [InlineArray(MaxStackAllocArgCount)] - private ref struct StackAllocatedByRefs + internal ref struct StackAllocatedByRefs { internal ref byte _arg0; } 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 new file mode 100644 index 00000000000000..93efbe1f0470f4 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Reflection.Core.Execution; +using System.Diagnostics; +using System.Reflection.Runtime.MethodInfos; +using static System.Reflection.DynamicInvokeInfo; + +namespace System.Reflection +{ + public sealed class MethodInvoker + { + private readonly MethodBaseInvoker _methodBaseInvoker; + + internal MethodInvoker(RuntimeMethodInfo method) + { + _methodBaseInvoker = method.MethodInvoker; + } + + internal MethodInvoker(RuntimeConstructorInfo constructor) + { + _methodBaseInvoker = constructor.MethodInvoker; + } + + public static MethodInvoker Create(MethodBase method) + { + ArgumentNullException.ThrowIfNull(method, nameof(method)); + + if (method is RuntimeMethodInfo rmi) + { + return new MethodInvoker(rmi); + } + + if (method is RuntimeConstructorInfo rci) + { + // This is useful for calling a constructor on an already-initialized object + // such as created from RuntimeHelpers.GetUninitializedObject(Type). + return new MethodInvoker(rci); + } + + throw new ArgumentException(SR.Argument_MustBeRuntimeMethod, nameof(method)); + } + + public object? Invoke(object? obj) + { + object? result = _methodBaseInvoker.InvokeDirectWithFewArgs(obj, new Span()); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + public object? Invoke(object? obj, object? arg1) + { + object? result = _methodBaseInvoker.InvokeDirectWithFewArgs(obj, new Span(ref arg1)); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + public object? Invoke(object? obj, object? arg1, object? arg2) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + + object? result = _methodBaseInvoker.InvokeDirectWithFewArgs(obj, argStorage._args.AsSpan(2)); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + argStorage._args.Set(2, arg3); + + object? result = _methodBaseInvoker.InvokeDirectWithFewArgs(obj, argStorage._args.AsSpan(3)); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) + { + StackAllocatedArguments argStorage = default; + argStorage._args.Set(0, arg1); + argStorage._args.Set(1, arg2); + argStorage._args.Set(2, arg3); + argStorage._args.Set(3, arg4); + + object? result = _methodBaseInvoker.InvokeDirectWithFewArgs(obj, argStorage._args.AsSpan(4)); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + public object? Invoke(object? obj, Span arguments) + { + object? result = _methodBaseInvoker.Invoke(obj, arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs index 11d820cfd054ad..c156d8e938d186 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs @@ -431,7 +431,7 @@ public sealed override DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type) RuntimeMethodInfo invokeMethod = runtimeType.GetInvokeMethod(); - MethodInvoker methodInvoker = invokeMethod.MethodInvoker; + MethodBaseInvoker methodInvoker = invokeMethod.MethodInvoker; IntPtr invokeThunk = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetDynamicInvokeThunk(methodInvoker); info = new DynamicInvokeInfo(invokeMethod, invokeThunk); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs index 6d71dacfe5246c..ead65e3d6b15ba 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs @@ -11,7 +11,7 @@ namespace System.Reflection.Runtime.MethodInfos // // Custom invoker for edge case scenarios not handled by the toolchain. Examples: Strings and Nullables. // - internal sealed class CustomMethodInvoker : MethodInvoker + internal sealed class CustomMethodInvoker : MethodBaseInvoker { public CustomMethodInvoker(Type thisType, Type[] parameterTypes, InvokerOptions options, CustomMethodInvokerAction action) { @@ -21,7 +21,16 @@ public CustomMethodInvoker(Type thisType, Type[] parameterTypes, InvokerOptions _parameterTypes = parameterTypes; } - protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) + protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) => + InvokeSpecial(thisObject, arguments, binderBundle, wrapInTargetInvocationException); + + protected internal sealed override object? Invoke(object? thisObject, Span arguments) => + InvokeSpecial(thisObject, arguments, binderBundle: null, wrapInTargetInvocationException: false); + + protected internal sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) => + InvokeSpecial(thisObject, arguments, binderBundle: null, wrapInTargetInvocationException: false); + + private object? InvokeSpecial(object? thisObject, ReadOnlySpan arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { // This does not handle optional parameters. None of the methods we use custom invocation for have them. if (!(thisObject == null && 0 != (_options & InvokerOptions.AllowNullThis))) @@ -55,6 +64,20 @@ protected sealed override object CreateInstance(object?[]? arguments, BinderBund return Invoke(null, arguments, binderBundle, wrapInTargetInvocationException); } + protected internal sealed override object CreateInstance(Span arguments) + { + // Custom method invokers need to also create the instance, so we just pass a null this. + Debug.Assert((_options & InvokerOptions.AllowNullThis) != 0); + return Invoke(null, arguments); + } + + protected internal sealed override object CreateInstanceWithFewArgs(Span arguments) + { + // Custom method invokers need to also create the instance, so we just pass a null this. + Debug.Assert((_options & InvokerOptions.AllowNullThis) != 0); + return InvokeDirectWithFewArgs(null, arguments); + } + public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, object target, bool isStatic, bool isVirtual, bool isOpen) { if (_thisType.IsConstructedGenericType && _thisType.GetGenericTypeDefinition() == typeof(Nullable<>)) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodMapper.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodMapper.cs index 6b81aaaeb2bf21..0de913d3bc8855 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodMapper.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodMapper.cs @@ -14,7 +14,7 @@ internal static partial class CustomMethodMapper // // Certain types and methods are edge-cases that require special handling. // - public static MethodInvoker GetCustomMethodInvokerIfNeeded(this MethodBase methodBase) + public static MethodBaseInvoker GetCustomMethodInvokerIfNeeded(this MethodBase methodBase) { Type declaringType = methodBase.DeclaringType!; bool isNullable = declaringType.IsConstructedGenericType && declaringType.GetGenericTypeDefinition() == typeof(Nullable<>); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/IRuntimeMethodCommon.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/IRuntimeMethodCommon.cs index 47cd8f18860a13..142c1d4fdca4cc 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/IRuntimeMethodCommon.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/IRuntimeMethodCommon.cs @@ -45,7 +45,7 @@ internal interface IRuntimeMethodCommon where TRuntimeMeth string Name { get; } - MethodInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant, out Exception exception); + MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant, out Exception exception); bool IsGenericMethodDefinition { get; } int GenericParameterCount { get; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/NativeFormat/NativeFormatMethodCommon.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/NativeFormat/NativeFormatMethodCommon.cs index 99be841a46b4b8..e126de60ca8fdf 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/NativeFormat/NativeFormatMethodCommon.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/NativeFormat/NativeFormatMethodCommon.cs @@ -25,7 +25,7 @@ internal struct NativeFormatMethodCommon : IRuntimeMethodCommon GenericParameterCount != 0; - public MethodInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant, out Exception exception) + public MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant, out Exception exception) { return ReflectionCoreExecution.ExecutionEnvironment.GetMethodInvoker(DeclaringType, new QMethodDefinition(Reader, MethodHandle), methodArguments, exceptionPertainant, out exception); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/OpenMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/OpenMethodInvoker.cs index cf81ce9d200144..5a8b74225ee16a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/OpenMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/OpenMethodInvoker.cs @@ -12,7 +12,7 @@ namespace System.Reflection.Runtime.MethodInfos { - internal sealed class OpenMethodInvoker : MethodInvoker + internal sealed class OpenMethodInvoker : MethodBaseInvoker { protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { @@ -24,6 +24,26 @@ protected sealed override object CreateInstance(object?[]? arguments, BinderBund throw new InvalidOperationException(SR.Arg_UnboundGenParam); } + protected internal sealed override object CreateInstance(Span arguments) + { + throw new InvalidOperationException(SR.Arg_UnboundGenParam); + } + + protected internal sealed override object CreateInstanceWithFewArgs(Span arguments) + { + throw new InvalidOperationException(SR.Arg_UnboundGenParam); + } + + protected internal sealed override object? Invoke(object? thisObject, Span arguments) + { + throw new InvalidOperationException(SR.Arg_UnboundGenParam); + } + + protected internal sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) + { + throw new InvalidOperationException(SR.Arg_UnboundGenParam); + } + public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, object target, bool isStatic, bool isVirtual, bool isOpen) { throw new InvalidOperationException(SR.Arg_UnboundGenParam); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructedGenericMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructedGenericMethodInfo.cs index 53cf1f0f963cca..793e85fd935e77 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructedGenericMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructedGenericMethodInfo.cs @@ -166,7 +166,7 @@ public sealed override RuntimeMethodHandle MethodHandle } } - protected sealed override MethodInvoker UncachedMethodInvoker + protected sealed override MethodBaseInvoker UncachedMethodInvoker { get { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs index 22eb2a62767293..1b56abb1e39b11 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeConstructorInfo.cs @@ -1,18 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Reflection; +using Internal.Reflection.Core.Execution; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Collections.Generic; -using System.Reflection.Runtime.General; -using System.Reflection.Runtime.TypeInfos; using System.Reflection.Runtime.ParameterInfos; -using Internal.Reflection.Core.Execution; - namespace System.Reflection.Runtime.MethodInfos { // @@ -72,7 +67,7 @@ public sealed override ParameterInfo[] GetParametersNoCopy() public sealed override object Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { parameters ??= Array.Empty(); - MethodInvoker methodInvoker; + MethodBaseInvoker methodInvoker; try { methodInvoker = this.MethodInvoker; @@ -98,7 +93,7 @@ public sealed override object Invoke(object? obj, BindingFlags invokeAttr, Binde } object? result = methodInvoker.Invoke(obj, parameters, binder, invokeAttr, culture); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result!; } @@ -162,7 +157,7 @@ public sealed override Type ReflectedType public abstract override RuntimeMethodHandle MethodHandle { get; } - protected MethodInvoker MethodInvoker + protected internal MethodBaseInvoker MethodInvoker { get { @@ -174,8 +169,8 @@ protected MethodInvoker MethodInvoker protected abstract RuntimeParameterInfo[] RuntimeParameters { get; } - protected abstract MethodInvoker UncachedMethodInvoker { get; } + protected abstract MethodBaseInvoker UncachedMethodInvoker { get; } - private volatile MethodInvoker _lazyMethodInvoker; + private volatile MethodBaseInvoker _lazyMethodInvoker; } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeDummyMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeDummyMethodInfo.cs index 409186ad654295..ddf736da1be642 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeDummyMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeDummyMethodInfo.cs @@ -38,14 +38,14 @@ private RuntimeDummyMethodInfo() { } public sealed override MethodBase MetadataDefinitionMethod { get { throw NotImplemented.ByDesign; } } public sealed override int MetadataToken { get { throw NotImplemented.ByDesign; } } public sealed override RuntimeMethodHandle MethodHandle { get { throw NotImplemented.ByDesign; } } - protected sealed override MethodInvoker UncachedMethodInvoker { get { throw NotImplemented.ByDesign; } } + protected sealed override MethodBaseInvoker UncachedMethodInvoker { get { throw NotImplemented.ByDesign; } } internal sealed override RuntimeParameterInfo[] GetRuntimeParameters(RuntimeMethodInfo contextMethod, out RuntimeParameterInfo returnParameter) { throw NotImplemented.ByDesign; } internal sealed override RuntimeTypeInfo RuntimeDeclaringType { get { throw NotImplemented.ByDesign; } } internal sealed override string RuntimeName { get { throw NotImplemented.ByDesign; } } internal sealed override RuntimeTypeInfo[] RuntimeGenericArgumentsOrParameters { get { throw NotImplemented.ByDesign; } } protected internal sealed override string ComputeToString(RuntimeMethodInfo contextMethod) { throw NotImplemented.ByDesign; } - internal sealed override MethodInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant) { throw NotImplemented.ByDesign; } + internal sealed override MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant) { throw NotImplemented.ByDesign; } internal sealed override RuntimeMethodHandle GetRuntimeMethodHandle(Type[] genericArgs) { throw NotImplemented.ByDesign; } internal sealed override RuntimeMethodInfo WithReflectedTypeSetToDeclaringType { get { throw NotImplemented.ByDesign; } } public static readonly RuntimeDummyMethodInfo Instance = new RuntimeDummyMethodInfo(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodInfo.cs index 8b5ff9baa6fc68..c22697cac28d88 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodInfo.cs @@ -158,12 +158,12 @@ public sealed override ParameterInfo[] GetParametersNoCopy() public abstract override bool HasSameMetadataDefinitionAs(MemberInfo other); - [DebuggerGuidedStepThroughAttribute] + [DebuggerGuidedStepThrough] public sealed override object? Invoke(object? obj, BindingFlags invokeAttr, Binder binder, object?[]? parameters, CultureInfo culture) { - MethodInvoker methodInvoker = this.MethodInvoker; + MethodBaseInvoker methodInvoker = this.MethodInvoker; object? result = methodInvoker.Invoke(obj, parameters, binder, invokeAttr, culture); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result; } @@ -245,7 +245,7 @@ internal abstract string RuntimeName internal abstract RuntimeMethodInfo WithReflectedTypeSetToDeclaringType { get; } - protected abstract MethodInvoker UncachedMethodInvoker { get; } + protected abstract MethodBaseInvoker UncachedMethodInvoker { get; } // // The non-public version of MethodInfo.GetGenericArguments() (does not array-copy and has a more truthful name.) @@ -290,7 +290,7 @@ internal RuntimeParameterInfo RuntimeReturnParameter private volatile RuntimeParameterInfo[] _lazyParameters; private volatile RuntimeParameterInfo _lazyReturnParameter; - internal MethodInvoker MethodInvoker + internal MethodBaseInvoker MethodInvoker { get { @@ -300,7 +300,7 @@ internal MethodInvoker MethodInvoker internal IntPtr LdFtnResult => MethodInvoker.LdFtnResult; - private volatile MethodInvoker _lazyMethodInvoker; + private volatile MethodBaseInvoker _lazyMethodInvoker; /// /// Common CreateDelegate worker. NOTE: If the method signature is not compatible, this method returns null rather than throwing an ArgumentException. diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs index 773412a70ff90f..25a0e21f9c9850 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs @@ -19,7 +19,7 @@ namespace System.Reflection.Runtime.MethodInfos internal abstract class RuntimeNamedMethodInfo : RuntimeMethodInfo { protected internal abstract string ComputeToString(RuntimeMethodInfo contextMethod); - internal abstract MethodInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant); + internal abstract MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant); internal abstract RuntimeMethodHandle GetRuntimeMethodHandle(Type[] methodArguments); } @@ -144,7 +144,7 @@ public sealed override MethodInfo MakeGenericMethod(params Type[] typeArguments) if (typeArguments.Length != GenericTypeParameters.Length) throw new ArgumentException(SR.Format(SR.Argument_NotEnoughGenArguments, typeArguments.Length, GenericTypeParameters.Length)); RuntimeMethodInfo methodInfo = (RuntimeMethodInfo)RuntimeConstructedGenericMethodInfo.GetRuntimeConstructedGenericMethodInfo(this, genericTypeArguments); - MethodInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any missing metadata exceptions now rather than later. + MethodBaseInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any missing metadata exceptions now rather than later. return methodInfo; } @@ -292,20 +292,20 @@ private RuntimeTypeInfo[] GenericTypeParameters } } - internal sealed override MethodInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant) + internal sealed override MethodBaseInvoker GetUncachedMethodInvoker(RuntimeTypeInfo[] methodArguments, MemberInfo exceptionPertainant) { - MethodInvoker invoker = _common.GetUncachedMethodInvoker(methodArguments, exceptionPertainant, out Exception exception); + MethodBaseInvoker invoker = _common.GetUncachedMethodInvoker(methodArguments, exceptionPertainant, out Exception exception); if (invoker == null) throw exception; return invoker; } - protected sealed override MethodInvoker UncachedMethodInvoker + protected sealed override MethodBaseInvoker UncachedMethodInvoker { get { - MethodInvoker invoker = this.GetCustomMethodInvokerIfNeeded(); + MethodBaseInvoker invoker = this.GetCustomMethodInvokerIfNeeded(); if (invoker != null) return invoker; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimePlainConstructorInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimePlainConstructorInfo.cs index a4a8fd6e360a79..4544281a81455a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimePlainConstructorInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimePlainConstructorInfo.cs @@ -152,7 +152,7 @@ protected sealed override RuntimeParameterInfo[] RuntimeParameters } } - protected sealed override MethodInvoker UncachedMethodInvoker + protected sealed override MethodBaseInvoker UncachedMethodInvoker { get { @@ -162,7 +162,7 @@ protected sealed override MethodInvoker UncachedMethodInvoker if (this.IsStatic) throw new MemberAccessException(SR.Acc_NotClassInit); - MethodInvoker invoker = this.GetCustomMethodInvokerIfNeeded(); + MethodBaseInvoker invoker = this.GetCustomMethodInvokerIfNeeded(); if (invoker != null) return invoker; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticConstructorInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticConstructorInfo.cs index e74fdeb1694c4d..ddab93409dc8b6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticConstructorInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticConstructorInfo.cs @@ -80,7 +80,7 @@ public sealed override int MetadataToken public sealed override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { object ctorAllocatedObject = this.MethodInvoker.Invoke(null, parameters, binder, invokeAttr, culture)!; - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return ctorAllocatedObject; } @@ -158,7 +158,7 @@ protected sealed override RuntimeParameterInfo[] RuntimeParameters } } - protected sealed override MethodInvoker UncachedMethodInvoker => new CustomMethodInvoker(_declaringType, _runtimeParameterTypes, _options, _action); + protected sealed override MethodBaseInvoker UncachedMethodInvoker => new CustomMethodInvoker(_declaringType, _runtimeParameterTypes, _options, _action); private volatile RuntimeParameterInfo[] _lazyParameters; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticMethodInfo.cs index e7fc8b9a74e646..a10b14b3f0594c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeSyntheticMethodInfo.cs @@ -171,7 +171,7 @@ public sealed override RuntimeMethodHandle MethodHandle } } - protected sealed override MethodInvoker UncachedMethodInvoker => new CustomMethodInvoker(_declaringType, _runtimeParameterTypes, _options, _action); + protected sealed override MethodBaseInvoker UncachedMethodInvoker => new CustomMethodInvoker(_declaringType, _runtimeParameterTypes, _options, _action); internal sealed override RuntimeTypeInfo[] RuntimeGenericArgumentsOrParameters { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs index b9eec1e328abff..5a3584f16e6725 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs @@ -334,8 +334,8 @@ private object GetConstantValue(bool raw) return defaultValue; } - private volatile MethodInvoker _lazyGetterInvoker; - private volatile MethodInvoker _lazySetterInvoker; + private volatile MethodBaseInvoker _lazyGetterInvoker; + private volatile MethodBaseInvoker _lazySetterInvoker; private volatile RuntimeNamedMethodInfo _lazyGetter; private volatile RuntimeNamedMethodInfo _lazySetter; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index c6ac7d60c9990b..93a33d9f298834 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -630,24 +630,6 @@ internal static IntPtr RhGetModuleSection(TypeManagerHandle module, ReadyToRunSe [RuntimeImport(RuntimeLibrary, "RhBulkMoveWithWriteBarrier")] internal static extern unsafe void RhBulkMoveWithWriteBarrier(ref byte dmem, ref byte smem, nuint size); - internal unsafe struct GCFrameRegistration - { - private nuint m_reserved1; - private nuint m_reserved2; - private void* m_pObjRefs; - private uint m_numObjRefs; - private int m_MaybeInterior; - - public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) - { - m_reserved1 = 0; - m_reserved2 = 0; - m_pObjRefs = allocation; - m_numObjRefs = elemCount; - m_MaybeInterior = areByRefs ? 1 : 0; - } - } - [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhRegisterForGCReporting")] internal static extern unsafe void RhRegisterForGCReporting(GCFrameRegistration* pRegistration); diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs index 35d9cde9222faf..1fd37d6d7a90fa 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs @@ -25,7 +25,6 @@ using CanonicalFormKind = global::Internal.TypeSystem.CanonicalFormKind; - using Debug = System.Diagnostics.Debug; namespace Internal.Reflection.Execution @@ -304,7 +303,7 @@ public sealed override unsafe bool TryGetConstructedGenericTypeForComponentsNoCo return TypeLoaderEnvironment.Instance.TryGetConstructedGenericTypeForComponents(genericTypeDefinitionHandle, genericTypeArgumentHandles, out runtimeTypeHandle); } - public sealed override MethodInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles) + public sealed override MethodBaseInvoker TryGetMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, RuntimeTypeHandle[] genericMethodTypeArgumentHandles) { MethodBase methodInfo = ReflectionCoreExecution.ExecutionDomain.GetMethod(declaringTypeHandle, methodHandle, genericMethodTypeArgumentHandles); diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs index 31eb0cf6656bd1..d2427e2eeff955 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs @@ -152,7 +152,7 @@ public sealed override void GetEnumInfo(RuntimeTypeHandle typeHandle, out string return; } - public override IntPtr GetDynamicInvokeThunk(MethodInvoker invoker) + public override IntPtr GetDynamicInvokeThunk(MethodBaseInvoker invoker) { return ((MethodInvokerWithMethodInvokeInfo)invoker).MethodInvokeInfo.InvokeThunk ; diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/InstanceMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/InstanceMethodInvoker.cs index ad032363454e01..e07cf71b6dd192 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/InstanceMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/InstanceMethodInvoker.cs @@ -2,16 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using global::System; -using global::System.Threading; using global::System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using global::System.Diagnostics; -using global::System.Collections.Generic; using global::Internal.Runtime.Augments; -using global::Internal.Reflection.Execution; -using global::Internal.Reflection.Core.Execution; using global::Internal.Runtime.CompilerServices; namespace Internal.Reflection.Execution.MethodInvokers @@ -44,7 +40,7 @@ private static object ThrowTargetException(IntPtr _) throw new TargetException(); } - [DebuggerGuidedStepThroughAttribute] + [DebuggerGuidedStepThrough] protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { if (MethodInvokeInfo.IsSupportedSignature) // Workaround to match expected argument validation order @@ -58,7 +54,39 @@ private static object ThrowTargetException(IntPtr _) arguments, binderBundle, wrapInTargetInvocationException); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + protected sealed override object? Invoke(object? thisObject, Span arguments) + { + if (MethodInvokeInfo.IsSupportedSignature) // Workaround to match expected argument validation order + { + ValidateThis(thisObject, _declaringTypeHandle); + } + + object? result = MethodInvokeInfo.Invoke( + thisObject, + MethodInvokeInfo.LdFtnResult, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + protected sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) + { + if (MethodInvokeInfo.IsSupportedSignature) // Workaround to match expected argument validation order + { + ValidateThis(thisObject, _declaringTypeHandle); + } + + object? result = MethodInvokeInfo.InvokeDirectWithFewArgs( + thisObject, + MethodInvokeInfo.LdFtnResult, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result; } @@ -76,6 +104,20 @@ protected sealed override object CreateInstance(object[] arguments, BinderBundle return thisObject; } + protected sealed override object CreateInstance(Span arguments) + { + object thisObject = RawCalliHelper.Call(_allocatorMethod, _declaringTypeHandle.Value); + Invoke(thisObject, arguments); + return thisObject; + } + + protected sealed override object CreateInstanceWithFewArgs(Span arguments) + { + object thisObject = RawCalliHelper.Call(_allocatorMethod, _declaringTypeHandle.Value); + InvokeDirectWithFewArgs(thisObject, arguments); + return thisObject; + } + public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, object target, bool isStatic, bool isVirtual, bool isOpen) { if (isOpen) diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/MethodInvokerWithMethodInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/MethodInvokerWithMethodInvokeInfo.cs index 04bd56ea52c785..6877a0f46ddaf7 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/MethodInvokerWithMethodInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/MethodInvokerWithMethodInvokeInfo.cs @@ -12,7 +12,7 @@ namespace Internal.Reflection.Execution.MethodInvokers { - internal abstract class MethodInvokerWithMethodInvokeInfo : MethodInvoker + internal abstract class MethodInvokerWithMethodInvokeInfo : MethodBaseInvoker { public MethodInvokerWithMethodInvokeInfo(MethodInvokeInfo methodInvokeInfo) { @@ -32,7 +32,7 @@ public override Delegate CreateDelegate(RuntimeTypeHandle delegateType, object t // // Creates the appropriate flavor of Invoker depending on the calling convention "shape" (static, instance or virtual.) // - internal static MethodInvoker CreateMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, MethodInvokeInfo methodInvokeInfo) + internal static MethodBaseInvoker CreateMethodInvoker(RuntimeTypeHandle declaringTypeHandle, QMethodDefinition methodHandle, MethodInvokeInfo methodInvokeInfo) { bool isStatic = false; diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/StaticMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/StaticMethodInvoker.cs index 3e7a71a80862ea..2443e1848ef902 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/StaticMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/StaticMethodInvoker.cs @@ -2,14 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using global::System; -using global::System.Threading; using global::System.Reflection; using global::System.Diagnostics; -using global::System.Collections.Generic; - -using global::Internal.Runtime.Augments; -using global::Internal.Reflection.Execution; -using global::Internal.Reflection.Core.Execution; namespace Internal.Reflection.Execution.MethodInvokers { @@ -23,7 +17,7 @@ public StaticMethodInvoker(MethodInvokeInfo methodInvokeInfo) { } - [DebuggerGuidedStepThroughAttribute] + [DebuggerGuidedStepThrough] protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { object? result = MethodInvokeInfo.Invoke( @@ -32,15 +26,48 @@ public StaticMethodInvoker(MethodInvokeInfo methodInvokeInfo) arguments, binderBundle, wrapInTargetInvocationException); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + protected sealed override object? Invoke(object? thisObject, Span arguments) + { + object? result = MethodInvokeInfo.Invoke( + null, // this pointer is ignored for static methods + MethodInvokeInfo.LdFtnResult, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result; } + [DebuggerGuidedStepThrough] + protected sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) + { + object? result = MethodInvokeInfo.InvokeDirectWithFewArgs( + null, // this pointer is ignored for static methods + MethodInvokeInfo.LdFtnResult, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + protected sealed override object CreateInstance(object[] arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { throw NotImplemented.ByDesign; } + protected sealed override object CreateInstance(Span arguments) + { + throw NotImplemented.ByDesign; + } + + protected sealed override object CreateInstanceWithFewArgs(Span arguments) + { + throw NotImplemented.ByDesign; + } + public sealed override IntPtr LdFtnResult => MethodInvokeInfo.LdFtnResult; } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/VirtualMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/VirtualMethodInvoker.cs index 09c3c53b24a216..f92c1ef3d6f700 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/VirtualMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/MethodInvokers/VirtualMethodInvoker.cs @@ -2,15 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using global::System; -using global::System.Threading; using global::System.Reflection; using global::System.Diagnostics; -using global::System.Collections.Generic; using global::Internal.Runtime.Augments; using global::Internal.Runtime.CompilerServices; -using global::Internal.Reflection.Execution; -using global::Internal.Reflection.Core.Execution; namespace Internal.Reflection.Execution.MethodInvokers { @@ -50,7 +46,7 @@ public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, o } } - [DebuggerGuidedStepThroughAttribute] + [DebuggerGuidedStepThrough] protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) { IntPtr resolvedVirtual = IntPtr.Zero; @@ -75,7 +71,45 @@ public sealed override Delegate CreateDelegate(RuntimeTypeHandle delegateType, o arguments, binderBundle, wrapInTargetInvocationException); - System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + protected sealed override object? Invoke(object? thisObject, Span arguments) + { + IntPtr resolvedVirtual = IntPtr.Zero; + + if (MethodInvokeInfo.IsSupportedSignature) // Workaround to match expected argument validation order + { + ValidateThis(thisObject, _declaringTypeHandle); + resolvedVirtual = OpenMethodResolver.ResolveMethod(MethodInvokeInfo.VirtualResolveData, thisObject); + } + + object? result = MethodInvokeInfo.Invoke( + thisObject, + resolvedVirtual, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + return result; + } + + [DebuggerGuidedStepThrough] + protected sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) + { + IntPtr resolvedVirtual = IntPtr.Zero; + + if (MethodInvokeInfo.IsSupportedSignature) // Workaround to match expected argument validation order + { + ValidateThis(thisObject, _declaringTypeHandle); + resolvedVirtual = OpenMethodResolver.ResolveMethod(MethodInvokeInfo.VirtualResolveData, thisObject); + } + + object? result = MethodInvokeInfo.InvokeDirectWithFewArgs( + thisObject, + resolvedVirtual, + arguments); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); return result; } @@ -84,6 +118,16 @@ protected sealed override object CreateInstance(object[] arguments, BinderBundle throw NotImplemented.ByDesign; } + protected sealed override object CreateInstance(Span arguments) + { + throw NotImplemented.ByDesign; + } + + protected sealed override object CreateInstanceWithFewArgs(Span arguments) + { + throw NotImplemented.ByDesign; + } + internal IntPtr ResolveTarget(RuntimeTypeHandle type) { return OpenMethodResolver.ResolveMethod(MethodInvokeInfo.VirtualResolveData, type); diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index c11627614ea07c..5b13a735343a83 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1548,8 +1548,7 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__DYNAMICMETHOD, CLASS__DELEGATE, CLASS__MULTICAST_DELEGATE, - CLASS__METHOD_INVOKER, - CLASS__CONSTRUCTOR_INVOKER, + CLASS__METHODBASEINVOKER, }; static bool fInited = false; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0fa94138ed46c9..a70f07936ce5fe 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -492,8 +492,7 @@ DEFINE_CLASS(VECTORT, Numerics, Vector`1) DEFINE_CLASS(MEMBER, Reflection, MemberInfo) -DEFINE_CLASS(METHOD_INVOKER, Reflection, MethodInvoker) -DEFINE_CLASS(CONSTRUCTOR_INVOKER, Reflection, ConstructorInvoker) +DEFINE_CLASS(METHODBASEINVOKER, Reflection, MethodBaseInvoker) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) DEFINE_FIELD_U(m_handle, ReflectMethodObject, m_pMD) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 9fc399c31cd5a3..39e9a3a44d1cd4 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -435,6 +435,11 @@ FCFuncStart(gBufferFuncs) FCFuncElement("__BulkMoveWithWriteBarrier", Buffer::BulkMoveWithWriteBarrier) FCFuncEnd() +FCFuncStart(gGCFrameRegistration) + FCFuncElement("RegisterForGCReporting", GCReporting::Register) + FCFuncElement("UnregisterForGCReporting", GCReporting::Unregister) +FCFuncEnd() + FCFuncStart(gGCInterfaceFuncs) FCFuncElement("GetGenerationWR", GCInterface::GetGenerationWR) FCFuncElement("_RegisterForFullGCNotification", GCInterface::RegisterForFullGCNotification) @@ -556,8 +561,6 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack) FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer) FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo) - FCFuncElement("RegisterForGCReporting", GCReporting::Register) - FCFuncElement("UnregisterForGCReporting", GCReporting::Unregister) FCFuncElement("Box", JIT_Box) FCFuncEnd() @@ -715,6 +718,7 @@ FCClassElement("Enum", "System", gEnumFuncs) FCClassElement("Environment", "System", gEnvironmentFuncs) FCClassElement("Exception", "System", gExceptionFuncs) FCClassElement("GC", "System", gGCInterfaceFuncs) +FCClassElement("GCFrameRegistration", "System.Runtime", gGCFrameRegistration) FCClassElement("GCHandle", "System.Runtime.InteropServices", gGCHandleFuncs) FCClassElement("GCSettings", "System.Runtime", gGCSettingsFuncs) #ifdef FEATURE_COMINTEROP diff --git a/src/libraries/Common/tests/System/Reflection/InvokeEmitTests.cs b/src/libraries/Common/tests/System/Reflection/InvokeEmitTests.cs index e95598dd47a666..149be11a982597 100644 --- a/src/libraries/Common/tests/System/Reflection/InvokeEmitTests.cs +++ b/src/libraries/Common/tests/System/Reflection/InvokeEmitTests.cs @@ -8,7 +8,7 @@ namespace System.Reflection.Tests { public class InvokeEmitTests { - [ConditionalFact(typeof(InvokeEmitTests), nameof(InvokeEmitTests.IsEmitInvokeSupported))] + [ConditionalFact(typeof(InvokeEmitTests), nameof(IsEmitInvokeSupported))] public static void VerifyInvokeIsUsingEmit_Method() { MethodInfo method = typeof(TestClassThatThrows).GetMethod(nameof(TestClassThatThrows.Throw))!; @@ -17,10 +17,10 @@ public static void VerifyInvokeIsUsingEmit_Method() Assert.Contains("Here", exInner.ToString()); Assert.Contains("InvokeStub_TestClassThatThrows", exInner.ToString()); - Assert.DoesNotContain(InterpretedMethodName, exInner.ToString()); + Assert.DoesNotContain("InterpretedInvoke_Method", exInner.ToString()); } - [ConditionalFact(typeof(InvokeEmitTests), nameof(InvokeEmitTests.IsEmitInvokeSupported))] + [ConditionalFact(typeof(InvokeEmitTests), nameof(IsEmitInvokeSupported))] public static void VerifyInvokeIsUsingEmit_Constructor() { ConstructorInfo ctor = typeof(TestClassThatThrows).GetConstructor(Type.EmptyTypes)!; @@ -29,7 +29,7 @@ public static void VerifyInvokeIsUsingEmit_Constructor() Assert.Contains("Here", exInner.ToString()); Assert.Contains("InvokeStub_TestClassThatThrows", exInner.ToString()); - Assert.DoesNotContain(InterpretedMethodName, exInner.ToString()); + Assert.DoesNotContain("InterpretedInvoke_Constructor", exInner.ToString()); } private static bool IsEmitInvokeSupported() @@ -38,11 +38,7 @@ private static bool IsEmitInvokeSupported() return RuntimeFeature.IsDynamicCodeSupported; } - private static string InterpretedMethodName => PlatformDetection.IsMonoRuntime ? - "System.Reflection.MethodInvoker.InterpretedInvoke" : - "System.RuntimeMethodHandle.InvokeMethod"; - - private class TestClassThatThrows + private class TestClassThatThrows { public TestClassThatThrows() { diff --git a/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs b/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs index 355c154119cc13..573211876b0fb7 100644 --- a/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs +++ b/src/libraries/Common/tests/System/Reflection/InvokeInterpretedTests.cs @@ -9,38 +9,38 @@ public class InvokeInterpretedTests { [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/50957", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoAOT))] - public static void VerifyInvokeIsUsingEmit_Method() + public static void VerifyInvokeIsUsingInterpreter_Method() { MethodInfo method = typeof(TestClassThatThrows).GetMethod(nameof(TestClassThatThrows.Throw))!; TargetInvocationException ex = Assert.Throws(() => method.Invoke(null, null)); Exception exInner = ex.InnerException; Assert.Contains("Here", exInner.ToString()); - Assert.Contains(InterpretedMethodName(), exInner.ToString()); + Assert.Contains(InterpretedMethodName, exInner.ToString()); Assert.DoesNotContain("InvokeStub_TestClassThatThrows", exInner.ToString()); - - string InterpretedMethodName() => PlatformDetection.IsMonoRuntime ? - "System.Reflection.MethodInvoker.InterpretedInvoke" : - "System.RuntimeMethodHandle.InvokeMethod"; } [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/50957", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoAOT))] - public static void VerifyInvokeIsUsingEmit_Constructor() + public static void VerifyInvokeIsUsingInterpreter_Constructor() { ConstructorInfo ctor = typeof(TestClassThatThrows).GetConstructor(Type.EmptyTypes)!; TargetInvocationException ex = Assert.Throws(() => ctor.Invoke(null)); Exception exInner = ex.InnerException; Assert.Contains("Here", exInner.ToString()); - Assert.Contains(InterpretedMethodName(), exInner.ToString()); + Assert.Contains(InterpretedConstructorName, exInner.ToString()); Assert.DoesNotContain("InvokeStub_TestClassThatThrows", exInner.ToString()); - - string InterpretedMethodName() => PlatformDetection.IsMonoRuntime ? - "System.Reflection.ConstructorInvoker.InterpretedInvoke" : - "System.RuntimeMethodHandle.InvokeMethod"; } + private static string InterpretedConstructorName => PlatformDetection.IsMonoRuntime ? + "InterpretedInvoke_Constructor" : + "System.RuntimeMethodHandle.InvokeMethod"; + + private static string InterpretedMethodName => PlatformDetection.IsMonoRuntime ? + "InterpretedInvoke_Method" : + "System.RuntimeMethodHandle.InvokeMethod"; + private class TestClassThatThrows { public TestClassThatThrows() diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 60ef1fbf28cad2..0b723db4398664 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1393,9 +1393,15 @@ FieldInfo must be a runtime FieldInfo object. + + ConstructorInfo must be a runtime ConstructorInfo object. + MethodInfo must be a runtime MethodInfo object. + + Method must be a runtime MethodInfo, ConstructorInfo or DynamicMethod object. + Type must be a runtime Type object. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 5cc1e4823e5e81..c0e88c56753452 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -684,11 +684,14 @@ + + + @@ -701,7 +704,6 @@ - @@ -857,6 +859,7 @@ + @@ -2587,11 +2590,11 @@ - + - - + + @@ -2661,4 +2664,4 @@ - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 27ac00870c28a7..f728ac96d0f2e0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -3,95 +3,332 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; +using static System.Reflection.MethodInvokerCommon; namespace System.Reflection { - internal sealed partial class ConstructorInvoker + public sealed partial class ConstructorInvoker { + private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; + private InvokeFunc_Obj4Args? _invokeFunc_Obj4Args; + private InvokeFunc_RefArgs? _invokeFunc_RefArgs; + private InvokerStrategy _strategy; + private readonly int _argCount; + private readonly RuntimeType[] _argTypes; + private readonly InvocationFlags _invocationFlags; + private readonly InvokerArgFlags[] _invokerArgFlags; private readonly RuntimeConstructorInfo _method; + private readonly bool _needsByRefStrategy; - private bool _invoked; - private bool _strategyDetermined; - private InvokerEmitUtil.InvokeFunc? _invokeFunc; + public static ConstructorInvoker Create(ConstructorInfo constructor) + { + ArgumentNullException.ThrowIfNull(constructor, nameof(constructor)); + + if (constructor is not RuntimeConstructorInfo runtimeConstructor) + { + throw new ArgumentException(SR.Argument_MustBeRuntimeConstructorInfo, nameof(constructor)); + } + + return new ConstructorInvoker(runtimeConstructor); + } + + private ConstructorInvoker(RuntimeConstructorInfo constructor, RuntimeType[] argumentTypes) + { + _method = constructor; + _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + _argTypes = argumentTypes; + _argCount = _argTypes.Length; + + Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + } - public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) + public object? Invoke() => Invoke(null, null, null, null); + public object? Invoke(object? arg1) => Invoke(arg1, null, null, null); + public object? Invoke(object? arg1, object? arg2) => Invoke(arg1, arg2, null, null); + public object? Invoke(object? arg1, object? arg2, object? arg3) => Invoke(arg1, arg2, arg3, null); + public object? Invoke(object? arg1, object? arg2, object? arg3, object? arg4) { - _method = constructorInfo; + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + { + _method.ThrowNoInvokeException(); + } + + // Allow additional non-used arguments to simplify caller's logic. + if (_argCount > MaxStackAllocArgCount) + { + MethodBaseInvoker.ThrowTargetParameterCountException(); + } - if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) + switch (_argCount) { - // Always use the native invoke; useful for testing. - _strategyDetermined = true; + case 4: + CheckArgument(ref arg4, 3); + goto case 3; + case 3: + CheckArgument(ref arg3, 2); + goto case 2; + case 2: + CheckArgument(ref arg2, 1); + goto case 1; + case 1: + CheckArgument(ref arg1, 0); + break; } - else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) + + // Check fast path first. + if (_invokeFunc_Obj4Args is not null) { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. - _invoked = true; + return _invokeFunc_Obj4Args(obj: null, arg1, arg2, arg3, arg4); + } + + if ((_strategy & InvokerStrategy.StrategyDetermined_Obj4Args) == 0) + { + DetermineStrategy_Obj4Args(ref _strategy, ref _invokeFunc_Obj4Args, _method, _needsByRefStrategy, backwardsCompat: false); + if (_invokeFunc_Obj4Args is not null) + { + return _invokeFunc_Obj4Args(obj: null, arg1, arg2, arg3, arg4); + } } + + return InvokeDirectByRef(arg1, arg2, arg3, arg4); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe object? InlinedInvoke(object? obj, IntPtr* args, BindingFlags invokeAttr) + public object? Invoke(Span arguments) + { + if (!_needsByRefStrategy) + { + // Switch to fast path if possible. + switch (_argCount) + { + case 0: + return Invoke(null, null, null, null); + case 1: + return Invoke(arguments[0], null, null, null); + case 2: + return Invoke(arguments[0], arguments[1], null, null); + case 3: + return Invoke(arguments[0], arguments[1], arguments[2], null); + case 4: + return Invoke(arguments[0], arguments[1], arguments[2], arguments[3]); + default: + break; + } + } + + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + { + _method.ThrowNoInvokeException(); + } + + if (arguments.Length != _argCount) + { + throw new TargetParameterCountException(SR.Arg_ParmCnt); + } + + if (arguments.Length > MaxStackAllocArgCount) + { + return InvokeWithManyArgs(arguments); + } + + return InvokeWithFewArgs(arguments); + } + + internal object? InvokeWithFewArgs(Span arguments) + { + Debug.Assert(_argCount <= MaxStackAllocArgCount); + + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = stackArgStorage._args.AsSpan(_argCount); + scoped Span shouldCopyBack = stackArgStorage._shouldCopyBack.AsSpan(_argCount); + + for (int i = 0; i < _argCount; i++) + { + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + } + + // Check fast path first. + if (_invokeFunc_ObjSpanArgs is not null) + { + return _invokeFunc_ObjSpanArgs(obj : null, copyOfArgs); + // No need to call CopyBack here since there are no ref values. + } + + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); + if (_invokeFunc_ObjSpanArgs is not null) + { + return _invokeFunc_ObjSpanArgs(obj: null, copyOfArgs); + } + } + + object? ret = InvokeDirectByRefWithFewArgs(copyOfArgs); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return ret; + } + + internal object? InvokeDirectByRef(object? arg1 = null, object? arg2 = null, object? arg3 = null, object? arg4 = null) { - if (_invokeFunc != null && (invokeAttr & BindingFlags.DoNotWrapExceptions) != 0 && obj == null) + StackAllocatedArguments stackStorage = new(arg1, arg2, arg3, arg4); + return InvokeDirectByRefWithFewArgs(stackStorage._args.AsSpan(_argCount)); + } + + internal unsafe object? InvokeDirectByRefWithFewArgs(Span copyOfArgs) + { + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); + } + + StackAllocatedByRefs byrefs = default; +#pragma warning disable CS8500 + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; +#pragma warning restore CS8500 + + for (int i = 0; i < _argCount; i++) { - return _invokeFunc(target: null, args); +#pragma warning disable CS8500 + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? +#pragma warning restore CS8500 + ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : + ByReference.Create(ref copyOfArgs[i]); } - return Invoke(obj, args, invokeAttr); + + return _invokeFunc_RefArgs!(obj: null, pByRefFixedStorage); } - [DebuggerStepThrough] - [DebuggerHidden] - private unsafe object? Invoke(object? obj, IntPtr* args, BindingFlags invokeAttr) + internal unsafe object? InvokeWithManyArgs(Span arguments) { - if (!_strategyDetermined) + Span copyOfArgs; + GCFrameRegistration regArgStorage; + object? ret; + + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); + } + + if (_invokeFunc_ObjSpanArgs is not null) { - if (!_invoked) + IntPtr* pArgStorage = stackalloc IntPtr[_argCount]; + NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr)); + copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); + regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + + for (int i = 0; i < _argCount; i++) + { + object? arg = arguments[i]; + CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + } + + ret = _invokeFunc_ObjSpanArgs(obj: null, copyOfArgs); + // No need to call CopyBack here since there are no ref values. + } + finally { - // The first time, ignoring race conditions, use the slow path. - _invoked = true; + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } - else + } + else + { + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) { - if (RuntimeFeature.IsDynamicCodeSupported) + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); + } + + IntPtr* pStorage = stackalloc IntPtr[2 * _argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * _argCount) * (nuint)sizeof(IntPtr)); + copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); + + IntPtr* pByRefStorage = pStorage + _argCount; + scoped Span shouldCopyBack = stackalloc bool[_argCount]; + + regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + + for (int i = 0; i < _argCount; i++) { - _invokeFunc = InvokerEmitUtil.CreateInvokeDelegate(_method); + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + #pragma warning disable CS8500 + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + #pragma warning restore CS8500 + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); } - _strategyDetermined = true; + + ret = _invokeFunc_RefArgs!(obj: null, pByRefStorage); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } } - object? ret; - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // Copy modified values out. This is only done with ByRef parameters. + internal void CopyBack(Span dest, Span copyOfParameters, Span shouldCopyBack) + { + for (int i = 0; i < dest.Length; i++) { - try + if (shouldCopyBack[i]) { - // For the rarely used scenario of calling the constructor directly through MethodBase.Invoke() - // with a non-null 'obj', we use the slow path to avoid having two emit-based delegates. - if (_invokeFunc != null && obj == null) + if ((_invokerArgFlags[i] & InvokerArgFlags.IsNullableOfT) != 0) { - ret = _invokeFunc(target: null, args); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + dest![i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); } else { - ret = InterpretedInvoke(obj, args); + dest![i] = copyOfParameters[i]; } } - catch (Exception e) - { - throw new TargetInvocationException(e); - } } - else if (_invokeFunc != null && obj == null) + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CheckArgument(ref object? arg, int i) + { + RuntimeType sigType = _argTypes[i]; + + // Convert the type if necessary. + // Note that Type.Missing is not supported. + if (arg is null) { - ret = _invokeFunc(target: null, args); + if ((_invokerArgFlags[i] & InvokerArgFlags.IsValueType_ByRef_Or_Pointer) != 0) + { + return sigType.CheckValue(ref arg); + } } - else + else if (!ReferenceEquals(arg.GetType(), sigType)) { - ret = InterpretedInvoke(obj, args); + return sigType.CheckValue(ref arg); } - return ret; + return false; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs index 1e0622e385931f..8a756b1ef1b0a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs @@ -413,6 +413,8 @@ public bool InitLocals set => _initLocals = value; } + internal RuntimeType[] ArgumentTypes => _parameterTypes; + private RuntimeParameterInfo[] LoadParameters() { if (_parameters == null) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs index 1471d1d977ef1e..b128d7068b82d7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -111,12 +111,18 @@ public static object ConvertOrWiden(Type srcType, object srcObject, Type dstType private static bool TryConvertPointer(object srcObject, [NotNullWhen(true)] out object? dstPtr) { - if (srcObject is IntPtr or UIntPtr) + if (srcObject is IntPtr) { dstPtr = srcObject; return true; } + if (srcObject is UIntPtr) + { + dstPtr = (IntPtr)(UIntPtr)srcObject; + return true; + } + // The source pointer should already have been converted to an IntPtr. Debug.Assert(srcObject is not Pointer); 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 0dac01f77a0e42..e99a79bc85b134 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.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; @@ -13,9 +12,135 @@ internal static class InvokerEmitUtil // If changed, update native stack walking code that also uses this prefix to ignore reflection frames. private const string InvokeStubPrefix = "InvokeStub_"; - internal unsafe delegate object? InvokeFunc(object? target, IntPtr* arguments); + internal unsafe delegate object? InvokeFunc_RefArgs(object? obj, IntPtr* refArguments); + internal delegate object? InvokeFunc_ObjSpanArgs(object? obj, Span arguments); + internal delegate object? InvokeFunc_Obj4Args(object? obj, object? arg1, object? arg2, object? arg3, object? arg4); - public static unsafe InvokeFunc CreateInvokeDelegate(MethodBase method) + public static unsafe InvokeFunc_Obj4Args CreateInvokeDelegate_Obj4Args(MethodBase method, bool backwardsCompat) + { + Debug.Assert(!method.ContainsGenericParameters); + + bool emitNew = method is RuntimeConstructorInfo; + bool hasThis = !emitNew && !method.IsStatic; + + Type[] delegateParameters = new Type[5] { typeof(object), typeof(object), typeof(object), typeof(object), typeof(object) }; + + string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; + var dm = new DynamicMethod( + InvokeStubPrefix + declaringTypeName + method.Name, + returnType: typeof(object), + delegateParameters, + typeof(object).Module, // Use system module to identify our DynamicMethods. + skipVisibility: true); + + ILGenerator il = dm.GetILGenerator(); + + // Handle instance methods. + if (hasThis) + { + il.Emit(OpCodes.Ldarg_0); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + } + + // Push the arguments. + ParameterInfo[] parameters = method.GetParametersNoCopy(); + for (int i = 0; i < parameters.Length; i++) + { + RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType; + + switch (i) + { + case 0: + il.Emit(OpCodes.Ldarg_1); + break; + case 1: + il.Emit(OpCodes.Ldarg_2); + break; + case 2: + il.Emit(OpCodes.Ldarg_3); + break; + default: + il.Emit(OpCodes.Ldarg_S, i + 1); + break; + } + + if (parameterType.IsPointer) + { + il.Emit(OpCodes.Unbox_Any, typeof(IntPtr)); + } + else if (parameterType.IsValueType) + { + il.Emit(OpCodes.Unbox_Any, parameterType); + } + } + + EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); + + // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + return (InvokeFunc_Obj4Args)dm.CreateDelegate(typeof(InvokeFunc_Obj4Args), target: null); + } + + public static unsafe InvokeFunc_ObjSpanArgs CreateInvokeDelegate_ObjSpanArgs(MethodBase method, bool backwardsCompat) + { + Debug.Assert(!method.ContainsGenericParameters); + + bool emitNew = method is RuntimeConstructorInfo; + bool hasThis = !emitNew && !method.IsStatic; + + // The first parameter is unused but supports treating the DynamicMethod as an instance method which is slightly faster than a static. + Type[] delegateParameters = new Type[2] { typeof(object), typeof(Span) }; + + string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; + var dm = new DynamicMethod( + InvokeStubPrefix + declaringTypeName + method.Name, + returnType: typeof(object), + delegateParameters, + typeof(object).Module, // Use system module to identify our DynamicMethods. + skipVisibility: true); + + ILGenerator il = dm.GetILGenerator(); + + // Handle instance methods. + if (hasThis) + { + il.Emit(OpCodes.Ldarg_0); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + } + + // Push the arguments. + ParameterInfo[] parameters = method.GetParametersNoCopy(); + for (int i = 0; i < parameters.Length; i++) + { + RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType; + + il.Emit(OpCodes.Ldarga_S, 1); + il.Emit(OpCodes.Ldc_I4, i); + il.Emit(OpCodes.Call, Methods.Span_get_Item()); + il.Emit(OpCodes.Ldind_Ref); + + if (parameterType.IsPointer) + { + il.Emit(OpCodes.Unbox_Any, typeof(IntPtr)); + } + else if (parameterType.IsValueType) + { + il.Emit(OpCodes.Unbox_Any, parameterType); + } + } + + EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); + + // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + return (InvokeFunc_ObjSpanArgs)dm.CreateDelegate(typeof(InvokeFunc_ObjSpanArgs), target: null); + } + + public static unsafe InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, bool backwardsCompat) { Debug.Assert(!method.ContainsGenericParameters); @@ -65,9 +190,17 @@ public static unsafe InvokeFunc CreateInvokeDelegate(MethodBase method) } } + 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 EmitCallAndReturnHandling(ILGenerator il, MethodBase method, bool emitNew, bool backwardsCompat) + { // For CallStack reasons, don't inline target method. // Mono interpreter does not support\need this. - if (RuntimeFeature.IsDynamicCodeCompiled) + if (backwardsCompat && RuntimeFeature.IsDynamicCodeCompiled) { #if MONO il.Emit(OpCodes.Call, Methods.DisableInline()); @@ -159,9 +292,6 @@ public static unsafe InvokeFunc CreateInvokeDelegate(MethodBase method) } il.Emit(OpCodes.Ret); - - // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. - return (InvokeFunc)dm.CreateDelegate(typeof(InvokeFunc), target: null); } private static class ThrowHelper @@ -178,6 +308,10 @@ private static class Methods public static FieldInfo ByReferenceOfByte_Value() => s_ByReferenceOfByte_Value ??= typeof(ByReference).GetField("Value")!; + private static MethodInfo? s_Span_get_Item; + public static MethodInfo Span_get_Item() => + s_Span_get_Item ??= typeof(Span).GetProperty("Item")!.GetGetMethod()!; + private static MethodInfo? s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned; public static MethodInfo ThrowHelper_Throw_NullReference_InvokeNullRefReturned() => s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned ??= typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_NullReference_InvokeNullRefReturned))!; diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 2a8349acb0061b..fb07f75f779b0e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -12,6 +12,8 @@ namespace System.Reflection { public abstract partial class MethodBase : MemberInfo { + internal const int MaxStackAllocArgCount = 4; + protected MethodBase() { } public abstract ParameterInfo[] GetParameters(); @@ -137,174 +139,103 @@ internal virtual Type[] GetParameterTypes() } #if !NATIVEAOT - private protected void ValidateInvokeTarget(object? target) + internal static object? HandleTypeMissing(ParameterInfo paramInfo, RuntimeType sigType) { - // Confirm member invocation has an instance and is of the correct type - if (!IsStatic) + if (paramInfo.DefaultValue == DBNull.Value) { - if (target == null) - { - throw new TargetException(SR.RFLCT_Targ_StatMethReqTarg); - } - - if (!DeclaringType!.IsInstanceOfType(target)) - { - throw new TargetException(SR.RFLCT_Targ_ITargMismatch); - } + throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); } - } - private protected unsafe void CheckArguments( - Span copyOfParameters, - IntPtr* byrefParameters, - Span shouldCopyBack, - ReadOnlySpan parameters, - RuntimeType[] sigTypes, - Binder? binder, - CultureInfo? culture, - BindingFlags invokeAttr - ) - { - Debug.Assert(parameters.Length > 0); + object? arg = paramInfo.DefaultValue; - ParameterInfo[]? paramInfos = null; - for (int i = 0; i < parameters.Length; i++) + if (sigType.IsNullableOfT) { - ParameterCopyBackAction copyBackArg = default; - bool isValueType = false; - object? arg = parameters[i]; - RuntimeType sigType = sigTypes[i]; - - // Convert a Type.Missing to the default value. - if (ReferenceEquals(arg, Type.Missing)) + if (arg is not null) { - paramInfos ??= GetParametersNoCopy(); - ParameterInfo paramInfo = paramInfos[i]; - - if (paramInfo.DefaultValue == DBNull.Value) - { - throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); - } - - arg = paramInfo.DefaultValue; - - if (sigType.IsNullableOfT) - { - copyBackArg = ParameterCopyBackAction.CopyNullable; - - if (arg is not null) - { - // For nullable Enum types, the ParameterInfo.DefaultValue returns a raw value which - // needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924 - Type argumentType = sigType.GetGenericArguments()[0]; - if (argumentType.IsEnum) - { - arg = Enum.ToObject(argumentType, arg); - } - } - } - else + // For nullable Enum types, the ParameterInfo.DefaultValue returns a raw value which + // needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924 + Type argumentType = sigType.GetGenericArguments()[0]; + if (argumentType.IsEnum) { - copyBackArg = ParameterCopyBackAction.Copy; + arg = Enum.ToObject(argumentType, arg); } } + } - if (arg is null) - { - // Fast path for null reference types. - isValueType = RuntimeTypeHandle.IsValueType(sigType); - if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) - { - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); - } - } - else - { - RuntimeType argType = (RuntimeType)arg.GetType(); + return arg; + } - if (ReferenceEquals(argType, sigType)) - { - // Fast path when the value's type matches the signature type. - isValueType = RuntimeTypeHandle.IsValueType(argType); - } - else if (sigType.TryByRefFastPath(ref arg, ref isValueType)) - { - // Fast path when the value's type matches the signature type of a byref parameter. - copyBackArg = ParameterCopyBackAction.Copy; - } - else - { - // Slow path that supports type conversions. - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); - } - } + [Flags] + internal enum InvokerStrategy : int + { + HasBeenInvoked_ObjSpanArgs = 0x1, + StrategyDetermined_ObjSpanArgs = 0x2, - // We need to perform type safety validation against the incoming arguments, but we also need - // to be resilient against the possibility that some other thread (or even the binder itself!) - // may mutate the array after we've validated the arguments but before we've properly invoked - // the method. The solution is to copy the arguments to a different, not-user-visible buffer - // as we validate them. n.b. This disallows use of ArrayPool, as ArrayPool-rented arrays are - // considered user-visible to threads which may still be holding on to returned instances. - // This separate array is also used to hold default values when 'null' is specified for value - // types, and also used to hold the results from conversions such as from Int16 to Int32. For - // compat, these default values and conversions are not be applied to the incoming arguments. - shouldCopyBack[i] = copyBackArg; - copyOfParameters[i] = arg; - -#pragma warning disable 8500 - if (isValueType) - { - Debug.Assert(arg != null); - Debug.Assert( - arg.GetType() == sigType || - (sigType.IsPointer && (arg.GetType() == typeof(IntPtr) || arg.GetType() == typeof(UIntPtr))) || - (sigType.IsByRef && arg.GetType() == RuntimeTypeHandle.GetElementType(sigType)) || - ((sigType.IsEnum || arg.GetType().IsEnum) && RuntimeType.GetUnderlyingType((RuntimeType)arg.GetType()) == RuntimeType.GetUnderlyingType(sigType))); - ByReference valueTypeRef = ByReference.Create(ref copyOfParameters[i]!.GetRawData()); - *(ByReference*)(byrefParameters + i) = valueTypeRef; - } - else - { - ByReference objRef = ByReference.Create(ref copyOfParameters[i]); - *(ByReference*)(byrefParameters + i) = objRef; - } -#pragma warning restore 8500 - } + HasBeenInvoked_Obj4Args = 0x4, + StrategyDetermined_Obj4Args = 0x8, + + HasBeenInvoked_RefArgs = 0x10, + StrategyDetermined_RefArgs = 0x20, } - internal const int MaxStackAllocArgCount = 4; + [Flags] + internal enum InvokerArgFlags : int + { + IsValueType = 0x1, + IsValueType_ByRef_Or_Pointer = 0x2, + IsNullableOfT = 0x4, + } [InlineArray(MaxStackAllocArgCount)] - private protected struct ArgumentData + internal struct ArgumentData { private T _arg0; [UnscopedRef] public Span AsSpan(int length) { - Debug.Assert((uint)length <= (uint) MaxStackAllocArgCount); + Debug.Assert((uint)length <= MaxStackAllocArgCount); return new Span(ref _arg0, length); } + + public void Set(int index, T value) + { + Debug.Assert((uint)index < MaxStackAllocArgCount); + Unsafe.Add(ref _arg0, index) = value; + } } // Helper struct to avoid intermediate object[] allocation in calls to the native reflection stack. - // When argument count <= MaxStackAllocArgCount, define a local of type default(StackAllocatedByRefs) - // and pass it to CheckArguments(). + // When argument count <= MaxStackAllocArgCount, define a local of these helper structs. // For argument count > MaxStackAllocArgCount, do a stackalloc of void* pointers along with // GCReportingRegistration to safely track references. [StructLayout(LayoutKind.Sequential)] - private protected ref struct StackAllocedArguments + internal ref struct StackAllocatedArguments { + public StackAllocatedArguments(object? obj1, object? obj2, object? obj3, object? obj4) + { + _args.Set(0, obj1); + _args.Set(1, obj2); + _args.Set(2, obj3); + _args.Set(3, obj4); + } + internal ArgumentData _args; - internal ArgumentData _copyBacks; + } + + [StructLayout(LayoutKind.Sequential)] + internal ref struct StackAllocatedArgumentsWithCopyBack + { + internal ArgumentData _args; + internal ArgumentData _shouldCopyBack; } // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. [InlineArray(MaxStackAllocArgCount)] - private protected ref struct StackAllocatedByRefs + internal ref struct StackAllocatedByRefs { internal ref byte _arg0; } #endif - } + } } 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 new file mode 100644 index 00000000000000..2202fabfdc821b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static System.Reflection.MethodBase; + +namespace System.Reflection +{ + internal sealed partial class MethodBaseInvoker + { + // The rarely used scenario of calling the constructor on an existing instance. + internal unsafe object? InvokeConstructorWithoutAlloc( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + bool wrapInTargetInvocationException = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; + object? ret; + int argCount = _argCount; + + scoped Span shouldCopyBack = stackalloc bool[argCount]; + IntPtr* pStorage = stackalloc IntPtr[2 * argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * argCount) * (nuint)sizeof(IntPtr)); + Span copyOfArgs = new(ref Unsafe.AsRef(pStorage), argCount); + GCFrameRegistration regArgStorage = new((void**)pStorage, (uint)argCount, areByRefs: false); + IntPtr* pByRefStorage = pStorage + argCount; + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)argCount, areByRefs: true); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + + CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + for (int i = 0; i < argCount; i++) + { +#pragma warning disable CS8500 + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? +#pragma warning restore CS8500 + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); + } + + try + { + // Use the interpreted version to avoid having to generate a new method that doesn't allocate. + ret = InterpretedInvoke_Constructor(obj, pByRefStorage); + } + catch (Exception e) when (wrapInTargetInvocationException) + { + throw new TargetInvocationException(e); + } + + CopyBack(parameters, copyOfArgs, shouldCopyBack); + return ret; + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + } + } + + internal unsafe object? InvokeConstructorWithoutAlloc(object? obj, bool wrapInTargetInvocationException) + { + try + { + // Use the interpreted version to avoid having to generate a new method that doesn't allocate. + return InterpretedInvoke_Constructor(obj, null); + } + catch (Exception e) when (wrapInTargetInvocationException) + { + throw new TargetInvocationException(e); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs new file mode 100644 index 00000000000000..d82b71886a72f7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs @@ -0,0 +1,414 @@ +// 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.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; +using static System.Reflection.MethodInvokerCommon; + +namespace System.Reflection +{ + internal sealed partial class MethodBaseInvoker + { + internal const int MaxStackAllocArgCount = 4; + + private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; + private InvokeFunc_RefArgs? _invokeFunc_RefArgs; + private InvokerStrategy _strategy; + internal readonly InvocationFlags _invocationFlags; + private readonly InvokerArgFlags[] _invokerArgFlags; + private readonly RuntimeType[] _argTypes; + private readonly MethodBase _method; + private readonly int _argCount; + private readonly bool _needsByRefStrategy; + + private MethodBaseInvoker(MethodBase method, RuntimeType[] argumentTypes) + { + _method = method; + _argTypes = argumentTypes; + _argCount = _argTypes.Length; + + Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + } + + [DoesNotReturn] + internal static void ThrowTargetParameterCountException() + { + throw new TargetParameterCountException(SR.Arg_ParmCnt); + } + + + internal unsafe object? InvokeWithNoArgs(object? obj, BindingFlags invokeAttr) + { + Debug.Assert(_argCount == 0); + + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); + } + + try + { + return _invokeFunc_RefArgs!(obj, refArguments: null); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + } + + internal unsafe object? InvokeWithOneArg( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + Debug.Assert(_argCount == 1); + + object? arg = parameters[0]; + ReadOnlySpan parametersSpan = new(arg); + + object? copyOfArg = null; + Span copyOfArgs = new(ref copyOfArg); + + bool copyBack = false; + Span shouldCopyBack = new(ref copyBack); + + CheckArguments(parametersSpan, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + object? ret; + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); + } + + if (_invokeFunc_ObjSpanArgs is not null) + { + try + { + ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + + CopyBack(parameters, copyOfArgs, shouldCopyBack); + return ret; + } + + ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + CopyBack(parameters, copyOfArgs, shouldCopyBack); + return ret; + } + + internal unsafe object? InvokeWithFewArgs( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + Debug.Assert(_argCount <= MaxStackAllocArgCount); + + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = stackArgStorage._args.AsSpan(_argCount); + Span shouldCopyBack = stackArgStorage._shouldCopyBack.AsSpan(_argCount); + + CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + object? ret; + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); + } + + if (_invokeFunc_ObjSpanArgs is not null) + { + try + { + ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + + CopyBack(parameters, copyOfArgs, shouldCopyBack); + return ret; + } + + ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + CopyBack(parameters, copyOfArgs, shouldCopyBack); + return ret; + } + + internal unsafe object? InvokeDirectByRefWithFewArgs(object? obj, Span copyOfArgs, BindingFlags invokeAttr) + { + Debug.Assert(_argCount <= MaxStackAllocArgCount); + + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); + } + + StackAllocatedByRefs byrefs = default; +#pragma warning disable CS8500 + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; +#pragma warning restore CS8500 + + for (int i = 0; i < _argCount; i++) + { +#pragma warning disable CS8500 + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? +#pragma warning restore CS8500 + ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : + ByReference.Create(ref copyOfArgs[i]); + } + + try + { + return _invokeFunc_RefArgs!(obj, pByRefFixedStorage); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + } + + internal unsafe object? InvokeWithManyArgs( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + Debug.Assert(_argCount > MaxStackAllocArgCount); + + Span copyOfArgs; + object? ret; + GCFrameRegistration regArgStorage; + Span shouldCopyBack; + + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); + } + + if (_invokeFunc_ObjSpanArgs is not null) + { + IntPtr* pArgStorage = stackalloc IntPtr[_argCount * 2]; + NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr) * 2); + copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); + regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); + shouldCopyBack = new Span(pArgStorage + _argCount, _argCount); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + + CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + try + { + ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + + CopyBack(parameters, copyOfArgs, shouldCopyBack); + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + } + } + else + { + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); + } + + IntPtr* pStorage = stackalloc IntPtr[3 * _argCount]; + NativeMemory.Clear(pStorage, (nuint)(3 * _argCount) * (nuint)sizeof(IntPtr)); + copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); + regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); + IntPtr* pByRefStorage = pStorage + _argCount; + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); + shouldCopyBack = new Span(pStorage + _argCount * 2, _argCount); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + + CheckArguments(parameters, copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + for (int i = 0; i < _argCount; i++) + { + #pragma warning disable CS8500 + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + #pragma warning restore CS8500 + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); + } + + try + { + ret = _invokeFunc_RefArgs!(obj, pByRefStorage); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + + CopyBack(parameters, copyOfArgs, shouldCopyBack); + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + } + } + + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void InvokePropertySetter( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object? parameter, + CultureInfo? culture) + { + Debug.Assert(_argCount == 1); + + object? copyOfArg = null; + Span copyOfArgs = new(ref copyOfArg, 1); + + bool copyBack = false; + Span shouldCopyBack = new(ref copyBack, 1); // Not used for setters + + CheckArguments(new ReadOnlySpan(parameter), copyOfArgs, shouldCopyBack, binder, culture, invokeAttr); + + if (_invokeFunc_ObjSpanArgs is not null) // Fast path check + { + try + { + _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + } + catch (Exception e) when ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + throw new TargetInvocationException(e); + } + } + else + { + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + // Initialize for next time. + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: true); + } + + InvokeDirectByRefWithFewArgs(obj, copyOfArgs, invokeAttr); + } + } + + // Copy modified values out. This is done with ByRef, Type.Missing and parameters changed by the Binder. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CopyBack(object?[] dest, Span copyOfParameters, Span shouldCopyBack) + { + for (int i = 0; i < dest.Length; i++) + { + if (shouldCopyBack[i]) + { + if ((_invokerArgFlags[i] & InvokerArgFlags.IsNullableOfT) != 0) + { + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + dest![i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } + else + { + dest![i] = copyOfParameters[i]; + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CheckArguments( + ReadOnlySpan parameters, + Span copyOfParameters, + Span shouldCopyBack, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr + ) + { + for (int i = 0; i < parameters.Length; i++) + { + object? arg = parameters[i]; + RuntimeType sigType = _argTypes[i]; + + // Convert a Type.Missing to the default value. + if (ReferenceEquals(arg, Type.Missing)) + { + arg = HandleTypeMissing(_method.GetParametersNoCopy()[i], sigType); + shouldCopyBack[i] = true; + } + + // Convert the type if necessary. + if (arg is null) + { + if ((_invokerArgFlags[i] & InvokerArgFlags.IsValueType_ByRef_Or_Pointer) != 0) + { + shouldCopyBack[i] = sigType.CheckValue(ref arg, binder, culture, invokeAttr); + } + } + else if (!ReferenceEquals(arg.GetType(), sigType)) + { + // Determine if we can use the fast path for byref types + if (TryByRefFastPath(sigType, ref arg)) + { + // Fast path when the value's type matches the signature type of a byref parameter. + shouldCopyBack[i] = true; + } + else + { + shouldCopyBack[i] = sigType.CheckValue(ref arg, binder, culture, invokeAttr); + } + } + + copyOfParameters[i] = arg; + } + } + + private static bool TryByRefFastPath(RuntimeType type, ref object arg) + { + if (RuntimeType.TryGetByRefElementType(type, out RuntimeType? sigElementType) && + ReferenceEquals(sigElementType, arg.GetType())) + { + if (sigElementType.IsValueType) + { + // Make a copy to prevent the boxed instance from being directly modified by the method. + arg = RuntimeType.AllocateValueType(sigElementType, arg); + } + + return true; + } + + return false; + } + } +} 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 15a0c195308286..76483f646c53a1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -3,77 +3,367 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime; +using System.Reflection.Emit; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; +using static System.Reflection.MethodInvokerCommon; namespace System.Reflection { - internal sealed partial class MethodInvoker + public sealed partial class MethodInvoker { + private InvokeFunc_ObjSpanArgs? _invokeFunc_ObjSpanArgs; + private InvokeFunc_Obj4Args? _invokeFunc_Obj4Args; + private InvokeFunc_RefArgs? _invokeFunc_RefArgs; + private InvokerStrategy _strategy; + private readonly int _argCount; + private readonly RuntimeType[] _argTypes; + private readonly InvocationFlags _invocationFlags; + private readonly InvokerArgFlags[] _invokerArgFlags; private readonly MethodBase _method; + private readonly bool _needsByRefStrategy; + private readonly bool _isStatic; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe object? InlinedInvoke(object? obj, IntPtr* args, BindingFlags invokeAttr) + public static MethodInvoker Create(MethodBase method) { - if (_invokeFunc != null && (invokeAttr & BindingFlags.DoNotWrapExceptions) != 0) + ArgumentNullException.ThrowIfNull(method, nameof(method)); + + if (method is RuntimeMethodInfo rmi) + { + return new MethodInvoker(rmi); + } + + if (method is DynamicMethod dm) { - return _invokeFunc(obj, args); + return new MethodInvoker(dm); } - return Invoke(obj, args, invokeAttr); + + if (method is RuntimeConstructorInfo rci) + { + // This is useful for calling a constructor on an already-initialized object + // such as created from RuntimeHelpers.GetUninitializedObject(Type). + return new MethodInvoker(rci); + } + + throw new ArgumentException(SR.Argument_MustBeRuntimeMethod, nameof(method)); } - private bool _invoked; - private bool _strategyDetermined; - private InvokerEmitUtil.InvokeFunc? _invokeFunc; + private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) + { + _method = method; + _argTypes = argumentTypes; + _argCount = _argTypes.Length; + _isStatic = _method.IsStatic; + + Initialize(argumentTypes, out _strategy, out _invokerArgFlags, out _needsByRefStrategy); + } - [DebuggerStepThrough] - [DebuggerHidden] - private unsafe object? Invoke(object? obj, IntPtr* args, BindingFlags invokeAttr) + public object? Invoke(object? obj) => Invoke(obj, null, null, null, null); + public object? Invoke(object? obj, object? arg1) => Invoke(obj, arg1, null, null, null); + public object? Invoke(object? obj, object? arg1, object? arg2) => Invoke(obj, arg1, arg2, null, null); + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3) => Invoke(obj, arg1, arg2, arg3, null); + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) { - if (!_strategyDetermined) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + { + ThrowForBadInvocationFlags(); + } + + // Allow additional non-used arguments to simplify caller's logic. + if (_argCount > MaxStackAllocArgCount) + { + MethodBaseInvoker.ThrowTargetParameterCountException(); + } + + if (!_isStatic) { - if (!_invoked) + ValidateInvokeTarget(obj, _method); + } + + switch (_argCount) + { + case 4: + CheckArgument(ref arg4, 3); + goto case 3; + case 3: + CheckArgument(ref arg3, 2); + goto case 2; + case 2: + CheckArgument(ref arg2, 1); + goto case 1; + case 1: + CheckArgument(ref arg1, 0); + break; + } + + // Check fast path first. + if (_invokeFunc_Obj4Args is not null) + { + return _invokeFunc_Obj4Args(obj, arg1, arg2, arg3, arg4); + } + + if ((_strategy & InvokerStrategy.StrategyDetermined_Obj4Args) == 0) + { + DetermineStrategy_Obj4Args(ref _strategy, ref _invokeFunc_Obj4Args, _method, _needsByRefStrategy, backwardsCompat: false); + if (_invokeFunc_Obj4Args is not null) { - // The first time, ignoring race conditions, use the slow path. - _invoked = true; + return _invokeFunc_Obj4Args(obj, arg1, arg2, arg3, arg4); } - else + } + + return InvokeDirectByRef(obj, arg1, arg2, arg3, arg4); + } + + public object? Invoke(object? obj, Span arguments) + { + if (!_needsByRefStrategy) + { + // Switch to fast path if possible. + switch (_argCount) { - if (RuntimeFeature.IsDynamicCodeSupported) - { - _invokeFunc = InvokerEmitUtil.CreateInvokeDelegate(_method); - } - _strategyDetermined = true; + case 0: + return Invoke(obj, null, null, null, null); + case 1: + return Invoke(obj, arguments[0], null, null, null); + case 2: + return Invoke(obj, arguments[0], arguments[1], null, null); + case 3: + return Invoke(obj, arguments[0], arguments[1], arguments[2], null); + case 4: + return Invoke(obj, arguments[0], arguments[1], arguments[2], arguments[3]); + default: + break; + } + } + + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + { + ThrowForBadInvocationFlags(); + } + + if (arguments.Length != _argCount) + { + throw new TargetParameterCountException(SR.Arg_ParmCnt); + } + + if (!_isStatic) + { + ValidateInvokeTarget(obj, _method); + } + + if (arguments.Length > MaxStackAllocArgCount) + { + return InvokeWithManyArgs(obj, arguments); + } + + return InvokeWithFewArgs(obj, arguments); + } + + private void ThrowForBadInvocationFlags() + { + if (_method is RuntimeMethodInfo rmi) + { + rmi.ThrowNoInvokeException(); + } + + Debug.Assert(_method is RuntimeConstructorInfo); + ((RuntimeConstructorInfo)_method).ThrowNoInvokeException(); + } + + internal object? InvokeWithFewArgs(object? obj, Span arguments) + { + Debug.Assert(_argCount <= MaxStackAllocArgCount); + + StackAllocatedArgumentsWithCopyBack stackArgStorage = default; + Span copyOfArgs = stackArgStorage._args.AsSpan(_argCount); + scoped Span shouldCopyBack = stackArgStorage._shouldCopyBack.AsSpan(_argCount); + + for (int i = 0; i < _argCount; i++) + { + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + } + + // Check fast path first. + if (_invokeFunc_ObjSpanArgs is not null) + { + return _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + // No need to call CopyBack here since there are no ref values. + } + + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) + { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); + if (_invokeFunc_ObjSpanArgs is not null) + { + return _invokeFunc_ObjSpanArgs(obj, copyOfArgs); } } + object? ret = InvokeDirectByRefWithFewArgs(obj, copyOfArgs); + CopyBack(arguments, copyOfArgs, shouldCopyBack); + return ret; + } + + internal object? InvokeDirectByRef(object? obj, object? arg1 = null, object? arg2 = null, object? arg3 = null, object? arg4 = null) + { + StackAllocatedArguments stackStorage = new(arg1, arg2, arg3, arg4); + return InvokeDirectByRefWithFewArgs(obj, stackStorage._args.AsSpan(_argCount)); + } + + internal unsafe object? InvokeDirectByRefWithFewArgs(object? obj, Span copyOfArgs) + { + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); + } + + StackAllocatedByRefs byrefs = default; +#pragma warning disable CS8500 + IntPtr* pByRefFixedStorage = (IntPtr*)&byrefs; +#pragma warning restore CS8500 + + for (int i = 0; i < _argCount; i++) + { +#pragma warning disable CS8500 + *(ByReference*)(pByRefFixedStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? +#pragma warning restore CS8500 + ByReference.Create(ref copyOfArgs[i]!.GetRawData()) : + ByReference.Create(ref copyOfArgs[i]); + } + + return _invokeFunc_RefArgs!(obj, pByRefFixedStorage); + } + + internal unsafe object? InvokeWithManyArgs(object? obj, Span arguments) + { + Span copyOfArgs; + GCFrameRegistration regArgStorage; object? ret; - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + + if ((_strategy & InvokerStrategy.StrategyDetermined_ObjSpanArgs) == 0) { + DetermineStrategy_ObjSpanArgs(ref _strategy, ref _invokeFunc_ObjSpanArgs, _method, _needsByRefStrategy, backwardsCompat: false); + } + + if (_invokeFunc_ObjSpanArgs is not null) + { + IntPtr* pArgStorage = stackalloc IntPtr[_argCount]; + NativeMemory.Clear(pArgStorage, (nuint)_argCount * (nuint)sizeof(IntPtr)); + copyOfArgs = new(ref Unsafe.AsRef(pArgStorage), _argCount); + regArgStorage = new((void**)pArgStorage, (uint)_argCount, areByRefs: false); + try { - if (_invokeFunc != null) + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + + for (int i = 0; i < _argCount; i++) { - ret = _invokeFunc(obj, args); + object? arg = arguments[i]; + CheckArgument(ref arg, i); + copyOfArgs[i] = arg; } - else + + ret = _invokeFunc_ObjSpanArgs(obj, copyOfArgs); + // No need to call CopyBack here since there are no ref values. + } + finally + { + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); + } + } + else + { + if ((_strategy & InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: false); + } + + IntPtr* pStorage = stackalloc IntPtr[2 * _argCount]; + NativeMemory.Clear(pStorage, (nuint)(2 * _argCount) * (nuint)sizeof(IntPtr)); + copyOfArgs = new(ref Unsafe.AsRef(pStorage), _argCount); + + IntPtr* pByRefStorage = pStorage + _argCount; + scoped Span shouldCopyBack = stackalloc bool[_argCount]; + + regArgStorage = new((void**)pStorage, (uint)_argCount, areByRefs: false); + GCFrameRegistration regByRefStorage = new((void**)pByRefStorage, (uint)_argCount, areByRefs: true); + + try + { + GCFrameRegistration.RegisterForGCReporting(®ArgStorage); + GCFrameRegistration.RegisterForGCReporting(®ByRefStorage); + + for (int i = 0; i < _argCount; i++) { - ret = InterpretedInvoke(obj, args); + object? arg = arguments[i]; + shouldCopyBack[i] = CheckArgument(ref arg, i); + copyOfArgs[i] = arg; + #pragma warning disable CS8500 + *(ByReference*)(pByRefStorage + i) = (_invokerArgFlags[i] & InvokerArgFlags.IsValueType) != 0 ? + #pragma warning restore CS8500 + ByReference.Create(ref Unsafe.AsRef(pStorage + i).GetRawData()) : + ByReference.Create(ref Unsafe.AsRef(pStorage + i)); } + + ret = _invokeFunc_RefArgs!(obj, pByRefStorage); + CopyBack(arguments, copyOfArgs, shouldCopyBack); } - catch (Exception e) + finally { - throw new TargetInvocationException(e); + GCFrameRegistration.UnregisterForGCReporting(®ByRefStorage); + GCFrameRegistration.UnregisterForGCReporting(®ArgStorage); } } - else if (_invokeFunc != null) + + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // Copy modified values out. This is only done with ByRef parameters. + internal void CopyBack(Span dest, Span copyOfParameters, Span shouldCopyBack) + { + for (int i = 0; i < dest.Length; i++) { - ret = _invokeFunc(obj, args); + if (shouldCopyBack[i]) + { + if ((_invokerArgFlags[i] & InvokerArgFlags.IsNullableOfT) != 0) + { + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + dest![i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } + else + { + dest![i] = copyOfParameters[i]; + } + } } - else + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CheckArgument(ref object? arg, int i) + { + RuntimeType sigType = _argTypes[i]; + + // Convert the type if necessary. + // Note that Type.Missing is not supported. + if (arg is null) + { + if ((_invokerArgFlags[i] & InvokerArgFlags.IsValueType_ByRef_Or_Pointer) != 0) + { + return sigType.CheckValue(ref arg); + } + } + else if (!ReferenceEquals(arg.GetType(), sigType)) { - ret = InterpretedInvoke(obj, args); + return sigType.CheckValue(ref arg); } - return ret; + return false; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs new file mode 100644 index 00000000000000..b21900ad00b271 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using static System.Reflection.InvokerEmitUtil; +using static System.Reflection.MethodBase; + +namespace System.Reflection +{ + internal static class MethodInvokerCommon + { + internal static void Initialize( + RuntimeType[] argumentTypes, + out InvokerStrategy strategy, + out InvokerArgFlags[] invokerFlags, + out bool needsByRefStrategy) + { + if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) + { + // Always use the native invoke; useful for testing. + strategy = InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs; + } + else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) + { + // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. + strategy = InvokerStrategy.HasBeenInvoked_Obj4Args | InvokerStrategy.HasBeenInvoked_ObjSpanArgs | InvokerStrategy.HasBeenInvoked_RefArgs; + } + else + { + strategy = default; + } + + int argCount = argumentTypes.Length; + invokerFlags = new InvokerArgFlags[argCount]; + needsByRefStrategy = false; + + for (int i = 0; i < argCount; i++) + { + RuntimeType type = argumentTypes[i]; + if (RuntimeTypeHandle.IsByRef(type)) + { + type = (RuntimeType)type.GetElementType(); + invokerFlags[i] |= InvokerArgFlags.IsValueType_ByRef_Or_Pointer; + needsByRefStrategy = true; + if (type.IsNullableOfT) + { + invokerFlags[i] |= InvokerArgFlags.IsNullableOfT; + } + } + + if (RuntimeTypeHandle.IsPointer(type)) + { + invokerFlags[i] |= InvokerArgFlags.IsValueType | InvokerArgFlags.IsValueType_ByRef_Or_Pointer; + } + else if (RuntimeTypeHandle.IsFunctionPointer(type)) + { + invokerFlags[i] |= InvokerArgFlags.IsValueType; + } + else if (RuntimeTypeHandle.IsValueType(type)) + { + invokerFlags[i] |= InvokerArgFlags.IsValueType | InvokerArgFlags.IsValueType_ByRef_Or_Pointer; + + if (type.IsNullableOfT) + { + invokerFlags[i] |= InvokerArgFlags.IsNullableOfT; + } + } + } + } + + /// + /// Confirm member invocation has an instance and is of the correct type + /// + internal static void ValidateInvokeTarget(object? target, MethodBase method) + { + Debug.Assert(!method.IsStatic); + + if (target == null) + { + throw new TargetException(SR.RFLCT_Targ_StatMethReqTarg); + } + + if (!method.DeclaringType!.IsInstanceOfType(target)) + { + throw new TargetException(SR.RFLCT_Targ_ITargMismatch); + } + } + + internal static void DetermineStrategy_ObjSpanArgs( + ref InvokerStrategy strategy, + ref InvokeFunc_ObjSpanArgs? + invokeFunc_ObjSpanArgs, + MethodBase method, + bool needsByRefStrategy, + bool backwardsCompat) + { + if (needsByRefStrategy) + { + // If ByRefs are used, we can't use this strategy. + strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; + } + else if ((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0) + { + // The first time, ignoring race conditions, use the slow path. + strategy |= InvokerStrategy.HasBeenInvoked_ObjSpanArgs; + } + else + { +#if !MONO // Temporary until Mono can unbox a true Nullable + if (RuntimeFeature.IsDynamicCodeSupported) + { + invokeFunc_ObjSpanArgs = CreateInvokeDelegate_ObjSpanArgs(method, backwardsCompat); + } +#endif + strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; + } + } + + internal static void DetermineStrategy_Obj4Args( + ref InvokerStrategy strategy, + ref InvokeFunc_Obj4Args? invokeFunc_Obj4Args, + MethodBase method, + bool needsByRefStrategy, + bool backwardsCompat) + { + if (needsByRefStrategy) + { + // If ByRefs are used, we can't use this strategy. + strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; + } + else if ((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0) + { + // The first time, ignoring race conditions, use the slow path. + strategy |= InvokerStrategy.HasBeenInvoked_Obj4Args; + } + else + { +#if !MONO // Temporary until Mono can unbox a true Nullable + if (RuntimeFeature.IsDynamicCodeSupported) + { + invokeFunc_Obj4Args = CreateInvokeDelegate_Obj4Args(method, backwardsCompat); + } +#endif + strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; + } + } + + internal static void DetermineStrategy_RefArgs( + ref InvokerStrategy strategy, + ref InvokeFunc_RefArgs? invokeFunc_RefArgs, + MethodBase method, + bool backwardsCompat) + { + if ((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0) + { + // The first time, ignoring race conditions, use the slow path. + strategy |= InvokerStrategy.HasBeenInvoked_RefArgs; + } + else + { + if (RuntimeFeature.IsDynamicCodeSupported) + { + invokeFunc_RefArgs = CreateInvokeDelegate_RefArgs(method, backwardsCompat); + } + + strategy |= InvokerStrategy.StrategyDetermined_RefArgs; + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs deleted file mode 100644 index e7a9692a7449a2..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Reflection -{ - /// - /// Determines how an invoke parameter needs to be copied back to the caller's object[] parameters. - /// - internal enum ParameterCopyBackAction : byte - { - None = 0, - Copy = 1, - CopyNullable = 2 - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index d9b5ae564bafe4..af741f377b55b7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -4,29 +4,27 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; -using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection { internal sealed partial class RuntimeConstructorInfo : ConstructorInfo { [MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path - private static InvocationFlags ComputeAndUpdateInvocationFlags(ConstructorInfo constructorInfo, ref InvocationFlags flagsToUpdate) + internal InvocationFlags ComputeAndUpdateInvocationFlags() { InvocationFlags invocationFlags = InvocationFlags.IsConstructor; // this is a given - Type? declaringType = constructorInfo.DeclaringType; + Type? declaringType = DeclaringType; if (declaringType == typeof(void) || declaringType != null && declaringType.ContainsGenericParameters // Enclosing type has unbound generics - || (constructorInfo.CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs + || (CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs ) { invocationFlags |= InvocationFlags.NoInvoke; } - else if (constructorInfo.IsStatic) + else if (IsStatic) { invocationFlags |= InvocationFlags.RunClassConstructor | InvocationFlags.NoConstructorInvoke; } @@ -41,12 +39,11 @@ private static InvocationFlags ComputeAndUpdateInvocationFlags(ConstructorInfo c invocationFlags |= InvocationFlags.ContainsStackPointers; // Check for attempt to create a delegate class. - if (typeof(Delegate).IsAssignableFrom(constructorInfo.DeclaringType)) + if (typeof(Delegate).IsAssignableFrom(DeclaringType)) invocationFlags |= InvocationFlags.IsDelegateConstructor; } invocationFlags |= InvocationFlags.Initialized; - flagsToUpdate = invocationFlags; // accesses are guaranteed atomic return invocationFlags; } @@ -98,6 +95,7 @@ internal void ThrowNoInvokeException() [DebuggerStepThrough] [DebuggerHidden] + // This is a rarely-used Invoke since it calls a constructor on an existing instance. public override object? Invoke( object? obj, BindingFlags invokeAttr, @@ -108,7 +106,10 @@ internal void ThrowNoInvokeException() if ((InvocationFlags & InvocationFlags.NoInvoke) != 0) ThrowNoInvokeException(); - ValidateInvokeTarget(obj); + if (!IsStatic) + { + MethodInvokerCommon.ValidateInvokeTarget(obj, this); + } // Correct number of arguments supplied int argCount = (parameters is null) ? 0 : parameters.Length; @@ -125,133 +126,9 @@ internal void ThrowNoInvokeException() return null; } - Debug.Assert(obj != null); - - unsafe - { - if (argCount == 0) - { - Invoker.InlinedInvoke(obj, args: default, invokeAttr); - } - else if (argCount > MaxStackAllocArgCount) - { - Debug.Assert(parameters != null); - InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); - } - else - { - Debug.Assert(parameters != null); - StackAllocedArguments argStorage = default; - Span copyOfParameters = argStorage._args.AsSpan(argCount); - Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); - - StackAllocatedByRefs byrefStorage = default; -#pragma warning disable 8500 - IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; -#pragma warning restore 8500 - - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - } - } - - return null; - } - - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - // This is a separate method to support better performance for the faster paths. - [DebuggerStepThrough] - [DebuggerHidden] - private static unsafe void InvokeWithManyArguments( - RuntimeConstructorInfo ci, - int argCount, - object? obj, - BindingFlags invokeAttr, - Binder? binder, - object?[] parameters, - CultureInfo? culture) - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new(objHolder, 0, argCount); - - // We don't check a max stack size since we are invoking a method which - // naturally requires a stack size that is dependent on the arg count\size. - IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; - NativeMemory.Clear(pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - - ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; - Span shouldCopyBackParameters = new(copyBackActions, argCount); - - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - - try - { - RegisterForGCReporting(®); - ci.CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ci.ArgumentTypes, - binder, - culture, - invokeAttr); - - ci.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } + return argCount == 0 ? + Invoker.InvokeConstructorWithoutAlloc(obj!, (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) : + Invoker.InvokeConstructorWithoutAlloc(obj!, invokeAttr, binder, parameters!, culture); } [DebuggerStepThrough] @@ -273,137 +150,19 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] throw new TargetParameterCountException(SR.Arg_ParmCnt); } - object? retValue; - - unsafe - { - if (argCount == 0) - { - retValue = Invoker.InlinedInvoke(obj: null, args: default, invokeAttr); - } - else if (argCount > MaxStackAllocArgCount) - { - retValue = InvokeWithManyArguments(this, argCount, invokeAttr, binder, parameters, culture); - } - else - { - Debug.Assert(parameters != null); - StackAllocedArguments argStorage = default; - Span copyOfParameters = argStorage._args.AsSpan(argCount); - Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); - - StackAllocatedByRefs byrefStorage = default; -#pragma warning disable 8500 - IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; -#pragma warning restore 8500 - - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InlinedInvoke(obj: null, pByRefStorage, invokeAttr); - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - } - } - - Debug.Assert(retValue != null); - return retValue; - } - - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - // This is a separate method to encourage more efficient IL for the faster paths. - [DebuggerStepThrough] - [DebuggerHidden] - private static unsafe object? InvokeWithManyArguments( - RuntimeConstructorInfo ci, - int argCount, - BindingFlags invokeAttr, - Binder? binder, - object?[]? parameters, - CultureInfo? culture) - { - Debug.Assert(parameters != null); - - object[] objHolder = new object[argCount]; - Span copyOfParameters = new(objHolder, 0, argCount); - - // We don't check a max stack size since we are invoking a method which - // naturally requires a stack size that is dependent on the arg count\size. - IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; - NativeMemory.Clear(pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - - ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; - Span shouldCopyBackParameters = new(copyBackActions, argCount); - - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - - object? retValue; - try - { - RegisterForGCReporting(®); - ci.CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ci.ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = ci.Invoker.InlinedInvoke(obj: null, pByRefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) + switch (argCount) { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } + case 0: + return Invoker.InvokeWithNoArgs(obj: null, invokeAttr)!; + case 1: + return Invoker.InvokeWithOneArg(obj: null, invokeAttr, binder, parameters!, culture)!; + case 2: + case 3: + case 4: + return Invoker.InvokeWithFewArgs(obj: null, invokeAttr, binder, parameters!, culture)!; + default: + return Invoker.InvokeWithManyArgs(obj: null, invokeAttr, binder, parameters!, culture)!; } - - return retValue; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index 62e62b1550c9ad..d22bf589cf3e0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -5,24 +5,21 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection { internal sealed partial class RuntimeMethodInfo : MethodInfo { [MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path - private static InvocationFlags ComputeAndUpdateInvocationFlags(MethodInfo methodInfo, ref InvocationFlags flagsToUpdate) + internal InvocationFlags ComputeAndUpdateInvocationFlags() { InvocationFlags invocationFlags = InvocationFlags.Unknown; - Type? declaringType = methodInfo.DeclaringType; + Type? declaringType = DeclaringType; - if (methodInfo.ContainsGenericParameters // Method has unbound generics - || IsDisallowedByRefType(methodInfo.ReturnType) // Return type is an invalid by-ref (i.e., by-ref-like or void*) - || (methodInfo.CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs + if (ContainsGenericParameters // Method has unbound generics + || IsDisallowedByRefType(ReturnType) // Return type is an invalid by-ref (i.e., by-ref-like or void*) + || (CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs ) { invocationFlags = InvocationFlags.NoInvoke; @@ -41,14 +38,13 @@ private static InvocationFlags ComputeAndUpdateInvocationFlags(MethodInfo method } } - if (methodInfo.ReturnType.IsByRefLike) // Check for byref-like types for return + if (ReturnType.IsByRefLike) // Check for byref-like types for return { invocationFlags |= InvocationFlags.ContainsStackPointers; } } invocationFlags |= InvocationFlags.Initialized; - flagsToUpdate = invocationFlags; // accesses are guaranteed atomic return invocationFlags; static bool IsDisallowedByRefType(Type type) @@ -62,7 +58,7 @@ static bool IsDisallowedByRefType(Type type) } [DoesNotReturn] - private void ThrowNoInvokeException() + internal void ThrowNoInvokeException() { // method is on a class that contains stack pointers if ((InvocationFlags & InvocationFlags.ContainsStackPointers) != 0) @@ -113,7 +109,10 @@ private void ThrowNoInvokeException() ThrowNoInvokeException(); } - ValidateInvokeTarget(obj); + if (!IsStatic) + { + MethodInvokerCommon.ValidateInvokeTarget(obj, this); + } // Correct number of arguments supplied int argCount = (parameters is null) ? 0 : parameters.Length; @@ -122,136 +121,19 @@ private void ThrowNoInvokeException() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - object? retValue; - - unsafe + switch (argCount) { - if (argCount == 0) - { - retValue = Invoker.InlinedInvoke(obj, args: default, invokeAttr); - } - else if (argCount > MaxStackAllocArgCount) - { - Debug.Assert(parameters != null); - retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); - } - else - { - Debug.Assert(parameters != null); - StackAllocedArguments argStorage = default; - Span copyOfParameters = argStorage._args.AsSpan(argCount); - Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); - - StackAllocatedByRefs byrefStorage = default; -#pragma warning disable 8500 - IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; -#pragma warning restore 8500 - - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - } + case 0: + return Invoker.InvokeWithNoArgs(obj, invokeAttr); + case 1: + return Invoker.InvokeWithOneArg(obj, invokeAttr, binder, parameters!, culture); + case 2: + case 3: + case 4: + return Invoker.InvokeWithFewArgs(obj, invokeAttr, binder, parameters!, culture); + default: + return Invoker.InvokeWithManyArgs(obj, invokeAttr, binder, parameters!, culture); } - - return retValue; - } - - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - // This is a separate method to support better performance for the faster paths. - [DebuggerStepThrough] - [DebuggerHidden] - private static unsafe object? InvokeWithManyArguments( - RuntimeMethodInfo mi, - int argCount, - object? obj, - BindingFlags invokeAttr, - Binder? binder, - object?[] parameters, - CultureInfo? culture) - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new(objHolder, 0, argCount); - - // We don't check a max stack size since we are invoking a method which - // naturally requires a stack size that is dependent on the arg count\size. - IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; - NativeMemory.Clear(pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - - ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; - Span shouldCopyBackParameters = new(copyBackActions, argCount); - - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - - object? retValue; - try - { - RegisterForGCReporting(®); - mi.CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - mi.ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = mi.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) - { - ParameterCopyBackAction action = shouldCopyBackParameters[i]; - if (action != ParameterCopyBackAction.None) - { - if (action == ParameterCopyBackAction.Copy) - { - parameters[i] = copyOfParameters[i]; - } - else - { - Debug.Assert(action == ParameterCopyBackAction.CopyNullable); - Debug.Assert(copyOfParameters[i] != null); - Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); - parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); - } - } - } - - return retValue; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.cs new file mode 100644 index 00000000000000..28e2e8db76a945 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.cs @@ -0,0 +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.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GCFrameRegistration + { + private nuint _reserved1; + private nuint _reserved2; + private void** _pObjRefs; + private uint _numObjRefs; + private int _maybeInterior; + + public GCFrameRegistration(void** allocation, uint elemCount, bool areByRefs = true) + { + _reserved1 = 0; + _reserved2 = 0; + _pObjRefs = allocation; + _numObjRefs = elemCount; + _maybeInterior = areByRefs ? 1 : 0; + } + +#if CORECLR + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration); +#endif + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index b7da6009495c24..c44e69d03a03d5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -761,6 +761,9 @@ internal static CorElementType GetUnderlyingType(RuntimeType type) return RuntimeTypeHandle.GetCorElementType(type); } + + // AggressiveInlining used since on hot path for reflection. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryGetByRefElementType(RuntimeType type, [NotNullWhen(true)] out RuntimeType? elementType) { CorElementType corElemType = RuntimeTypeHandle.GetCorElementType(type); @@ -784,19 +787,53 @@ private enum CheckValueStatus /// /// Verify and optionally convert the value for special cases. /// - /// True if is a value type, False otherwise + /// True if the value requires a copy-back to the original object[]. + internal bool CheckValue(ref object? value) + { + Debug.Assert(!IsGenericParameter); + + // Fast path to whether a value can be assigned without conversion. + if (IsInstanceOfType(value)) + { + if (IsNullableOfT) + { + // Pass as a true boxed Nullable, not as a T or null. + value = RuntimeMethodHandle.ReboxToNullable(value, this); + return true; + } + + return false; + } + + bool copyBack = false; + CheckValueStatus result = TryChangeType(ref value, ref copyBack); + if (result == CheckValueStatus.Success) + { + return copyBack; + } + + switch (result) + { + case CheckValueStatus.ArgumentException: + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this)); + case CheckValueStatus.NotSupported_ByRefLike: + throw new NotSupportedException(SR.NotSupported_ByRefLike); + } + + Debug.Fail("Error result not expected"); + return false; + } + + /// + /// Verify and optionally convert the value for special cases. + /// + /// True if the value requires a copy-back to the original object[] or Span{object}. internal bool CheckValue( ref object? value, - ref ParameterCopyBackAction copyBack, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) { - // Already fast-pathed by the caller. - Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - - // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here - // because it is faster than IsValueType Debug.Assert(!IsGenericParameter); // Fast path to whether a value can be assigned without conversion. @@ -809,17 +846,14 @@ internal bool CheckValue( return true; } - // Other value types won't get here since Type equality was previous checked. - Debug.Assert(!RuntimeTypeHandle.IsValueType(this)); - return false; } - bool isValueType; - CheckValueStatus result = TryChangeType(ref value, ref copyBack, out isValueType); + bool copyBack = false; + CheckValueStatus result = TryChangeType(ref value, ref copyBack); if (result == CheckValueStatus.Success) { - return isValueType; + return copyBack; } if (result == CheckValueStatus.ArgumentException && (invokeAttr & BindingFlags.ExactBinding) == 0) @@ -836,20 +870,15 @@ internal bool CheckValue( { // Pass as a true boxed Nullable, not as a T or null. value = RuntimeMethodHandle.ReboxToNullable(value, this); - copyBack = ParameterCopyBackAction.CopyNullable; - } - else - { - copyBack = ParameterCopyBackAction.Copy; } - return IsValueType; // Note the call to IsValueType, not the variable. + return true; } - result = TryChangeType(ref value, ref copyBack, out isValueType); + result = TryChangeType(ref value, ref copyBack); if (result == CheckValueStatus.Success) { - return isValueType; + return copyBack; } } } @@ -866,27 +895,22 @@ internal bool CheckValue( return false; } - private CheckValueStatus TryChangeType( - ref object? value, - ref ParameterCopyBackAction copyBack, - out bool isValueType) + private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack) { RuntimeType? sigElementType; if (TryGetByRefElementType(this, out sigElementType)) { - copyBack = ParameterCopyBackAction.Copy; Debug.Assert(!sigElementType.IsGenericParameter); + copyBack = true; if (sigElementType.IsInstanceOfType(value)) { - isValueType = RuntimeTypeHandle.IsValueType(sigElementType); - if (isValueType) + if (RuntimeTypeHandle.IsValueType(sigElementType)) { if (sigElementType.IsNullableOfT) { // Pass as a true boxed Nullable, not as a T or null. value = RuntimeMethodHandle.ReboxToNullable(value, sigElementType); - copyBack = ParameterCopyBackAction.CopyNullable; } else { @@ -900,10 +924,8 @@ private CheckValueStatus TryChangeType( if (value == null) { - isValueType = RuntimeTypeHandle.IsValueType(sigElementType); - if (!isValueType) + if (!RuntimeTypeHandle.IsValueType(sigElementType)) { - // Normally we don't get here since 'null' was previosuly checked, but due to binders we can. return CheckValueStatus.Success; } @@ -914,20 +936,23 @@ private CheckValueStatus TryChangeType( // Allocate default. value = AllocateValueType(sigElementType, value: null); - copyBack = sigElementType.IsNullableOfT ? ParameterCopyBackAction.CopyNullable : ParameterCopyBackAction.Copy; return CheckValueStatus.Success; } - isValueType = false; return CheckValueStatus.ArgumentException; } if (value == null) { - isValueType = RuntimeTypeHandle.IsValueType(this); - if (!isValueType) + if (IsPointer) + { + // Pass an IntPtr instead of null for pointers. + value = default(IntPtr); + return CheckValueStatus.Success; + } + + if (!RuntimeTypeHandle.IsValueType(this)) { - // Normally we don't get here since 'null' was previosuly checked, but due to binders we can. return CheckValueStatus.Success; } @@ -947,28 +972,9 @@ private CheckValueStatus TryChangeType( // - Pointer (*) types to IntPtr (if dest is IntPtr) // - System.Reflection.Pointer to appropriate pointer (*) type (if dest is pointer type) if (IsPointer || IsEnum || IsPrimitive) - return TryChangeTypeSpecial(ref value, out isValueType); + return TryChangeTypeSpecial(ref value); - isValueType = false; return CheckValueStatus.ArgumentException; } - - internal bool TryByRefFastPath(ref object arg, ref bool isValueType) - { - if (TryGetByRefElementType(this, out RuntimeType? sigElementType) && - ReferenceEquals(sigElementType, arg.GetType())) - { - isValueType = sigElementType.IsValueType; - if (isValueType) - { - // Make a copy to prevent the boxed instance from being directly modified by the method. - arg = AllocateValueType(sigElementType, arg); - } - - return true; - } - - return false; - } } } diff --git a/src/libraries/System.Reflection/tests/ConstructorInvokerTests.cs b/src/libraries/System.Reflection/tests/ConstructorInvokerTests.cs new file mode 100644 index 00000000000000..056bb592687163 --- /dev/null +++ b/src/libraries/System.Reflection/tests/ConstructorInvokerTests.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using Xunit; + +namespace System.Reflection.Tests +{ + public class ConstructorInvokerTests + { + + [Fact] + public void Args_0() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor(new Type[] { })); + Assert.Equal("0", ((TestClass)invoker.Invoke())._args); + Assert.Equal("0", ((TestClass)invoker.Invoke(new Span()))._args); + } + + [Fact] + public void Args_1() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor(new Type[] { typeof(string) })); + Assert.Equal("1", ((TestClass)invoker.Invoke("1"))._args); + Assert.Equal("1", ((TestClass)invoker.Invoke(new Span(new object[] { "1" })))._args); + } + + [Fact] + public void Args_2() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor( + new Type[] { typeof(string), typeof(string) })); + + Assert.Equal("12", ((TestClass)invoker.Invoke("1", "2"))._args); + Assert.Equal("12", ((TestClass)invoker.Invoke(new Span(new object[] { "1", "2" })))._args); + } + + [Fact] + public void Args_3() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor( + new Type[] { typeof(string), typeof(string), typeof(string) })); + + Assert.Equal("123", ((TestClass)invoker.Invoke("1", "2", "3"))._args); + Assert.Equal("123", ((TestClass)invoker.Invoke(new Span(new object[] { "1", "2", "3" })))._args); + } + + [Fact] + public void Args_4() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor( + new Type[] { typeof(string), typeof(string), typeof(string), typeof(string) })); + + Assert.Equal("1234", ((TestClass)invoker.Invoke("1", "2", "3", "4"))._args); + Assert.Equal("1234", ((TestClass)invoker.Invoke(new Span(new object[] { "1", "2", "3", "4" })))._args); + } + + [Fact] + public void Args_5() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClass).GetConstructor( + new Type[] { typeof(string), typeof(string), typeof(string), typeof(string), typeof(string) })); + + Assert.Equal("12345", ((TestClass)invoker.Invoke(new Span(new object[] { "1", "2", "3", "4", "5" })))._args); + } + + [Fact] + public void ThrowsNonWrappedException_0() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClassThrowsOnCreate).GetConstructor(new Type[] { })); + Assert.Throws(invoker.Invoke); + } + + [Fact] + public void ThrowsNonWrappedException_1() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClassThrowsOnCreate).GetConstructor(new Type[] { typeof(string) })); + Assert.Throws(() => invoker.Invoke("0")); + } + + [Fact] + public void ThrowsNonWrappedException_5() + { + ConstructorInvoker invoker = ConstructorInvoker.Create(typeof(TestClassThrowsOnCreate).GetConstructor( + new Type[] { typeof(string), typeof(string), typeof(string), typeof(string), typeof(string) })); + + Assert.Throws(() => invoker.Invoke(new Span(new object[] { "1", "2", "3", "4", "5" }))); + } + + [Fact] + public void ExistingInstance() + { + ConstructorInfo ci = typeof(TestClass).GetConstructor(BindingFlags.Public | BindingFlags.Instance, Type.EmptyTypes); + TestClass tc = (TestClass)RuntimeHelpers.GetUninitializedObject(typeof(TestClass)); + Assert.Null(tc._args); + + MethodInvoker invoker = MethodInvoker.Create(ci); + object? obj = invoker.Invoke(tc); + Assert.Equal("0", tc._args); + Assert.Null(obj); + } + + private class TestClass + { + public string _args; + + public TestClass() { _args = "0"; } + + public void SomeMethod() { } + + public TestClass(string arg1) + { + _args = arg1; + } + + public TestClass(string arg1, string arg2) + { + _args = arg1 + arg2; + } + + public TestClass(string arg1, string arg2, string arg3) + { + _args = arg1 + arg2 + arg3; + } + + public TestClass(string arg1, string arg2, string arg3, string arg4) + { + _args = arg1 + arg2 + arg3 + arg4; + } + + public TestClass(string arg1, string arg2, string arg3, string arg4, string arg5) + { + _args = arg1 + arg2 + arg3 + arg4 + arg5; + } + } + + private class TestClassThrowsOnCreate + { + public TestClassThrowsOnCreate() => + throw new InvalidOperationException(); + public TestClassThrowsOnCreate(string arg1) => + throw new InvalidOperationException(); + public TestClassThrowsOnCreate(string arg1, string arg2, string arg3, string arg4, string arg5) => + throw new InvalidOperationException(); + } + } +} diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index 270dccc4fbbc33..ed8499dc251a1c 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -717,7 +717,7 @@ public static void InvokeNullableEnumParameterDefaultNo() Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { YesNo.No })); Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { YesNo.Yes })); Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { Type.Missing })); - } + } [Fact] public static void InvokeNullableEnumParameterDefaultYes() @@ -1356,4 +1356,3 @@ public static unsafe bool CallFcnPtr_Void(void* fn, int value) } #pragma warning restore 0414 } - diff --git a/src/libraries/System.Reflection/tests/MethodInvokerTests.cs b/src/libraries/System.Reflection/tests/MethodInvokerTests.cs new file mode 100644 index 00000000000000..0eb20799d2cb7c --- /dev/null +++ b/src/libraries/System.Reflection/tests/MethodInvokerTests.cs @@ -0,0 +1,277 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Reflection.Tests +{ + public class MethodInvokerTests + { + [Fact] + public void NullTypeValidation() + { + Assert.Throws(() => MethodInvoker.Create(null)); + } + + [Fact] + public void Args_0() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_0))); + Assert.Equal("0", invoker.Invoke(obj: null)); + Assert.Equal("0", invoker.Invoke(obj: null, new Span())); + } + + [Fact] + public void Args_1() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_1))); + Assert.Equal("1", invoker.Invoke(obj: null, "1")); + Assert.Equal("1", invoker.Invoke(obj: null, new Span(new object[] { "1" }))); + } + + [Fact] + public void Args_2() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_2))); + Assert.Equal("12", invoker.Invoke(obj: null, "1", "2")); + Assert.Equal("12", invoker.Invoke(obj: null, new Span(new object[] { "1", "2" }))); + } + + [Fact] + public void Args_3() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_3))); + Assert.Equal("123", invoker.Invoke(obj: null, "1", "2", "3")); + Assert.Equal("123", invoker.Invoke(obj: null, new Span(new object[] { "1", "2", "3" }))); + } + + [Fact] + public void Args_4() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_4))); + Assert.Equal("1234", invoker.Invoke(obj: null, "1", "2", "3", "4")); + Assert.Equal("1234", invoker.Invoke(obj: null, new Span(new object[] { "1", "2", "3", "4" }))); + } + + [Fact] + public void Args_5() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_5))); + Assert.Equal("12345", invoker.Invoke(obj: null, new Span(new object[] { "1", "2", "3", "4", "5" }))); + } + + [Fact] + public void Args_ByRef() + { + string argValue = "Value"; + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_ByRef))); + + // Although no copy-back, verify we can call. + Assert.Equal("Hello", invoker.Invoke(obj: null, argValue)); + + // The Span version supports copy-back. + object[] args = new object[] { argValue }; + invoker.Invoke(obj: null, new Span(args)); + Assert.Equal("Hello", args[0]); + + args[0] = null; + invoker.Invoke(obj: null, new Span(args)); + Assert.Equal("Hello", args[0]); + } + + [Fact] + public unsafe void Args_Pointer() + { + int i = 7; + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_ByPointer))); + + invoker.Invoke(obj: null, (IntPtr)(void*)&i); + Assert.Equal(8, i); + + object[] args = new object[] { (IntPtr)(void*)&i }; + invoker.Invoke(obj: null, new Span(args)); + Assert.Equal(9, i); + } + + [Fact] + public unsafe void Args_SystemPointer() + { + int i = 7; + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Args_BySystemPointer))); + + object pointer = Pointer.Box(&i, typeof(int).MakePointerType()); + invoker.Invoke(obj: null, pointer); + Assert.Equal(8, i); + + object[] args = new object[] { pointer }; + invoker.Invoke(obj: null, new Span(args)); + Assert.Equal(9, i); + } + + [Theory] + [MemberData(nameof(Invoke_TestData))] + public void ArgumentConversions(Type methodDeclaringType, string methodName, object obj, object[] parameters, object result) + { + MethodInvoker invoker = MethodInvoker.Create(GetMethod(methodDeclaringType, methodName)); + + // Adapt the input since Type.Missing is not supported, and Span requires an object[] array (e.g. not string[]). + if (parameters is null) + { + Assert.Equal(result, invoker.Invoke(obj, new Span())); + Assert.Equal(result, invoker.Invoke(obj)); + } + else if (HasTypeMissing()) + { + if (parameters.GetType().GetElementType() == typeof(object)) + { + Assert.Throws(() => invoker.Invoke(obj, new Span(parameters))); + } + else + { + // Using 'string[]', for example, is not supported with Span. + Assert.Throws(() => invoker.Invoke(obj, new Span(parameters))); + } + } + else + { + if (parameters.GetType().GetElementType() == typeof(object)) + { + Assert.Equal(result, invoker.Invoke(obj, new Span(parameters))); + + // Also verify explicit length parameters. + switch (parameters.Length) + { + case 0: + Assert.Equal(result, invoker.Invoke(obj)); + break; + case 1: + Assert.Equal(result, invoker.Invoke(obj, parameters[0])); + break; + case 2: + Assert.Equal(result, invoker.Invoke(obj, parameters[0], parameters[1])); + break; + case 3: + Assert.Equal(result, invoker.Invoke(obj, parameters[0], parameters[1], parameters[2])); + break; + case 4: + Assert.Equal(result, invoker.Invoke(obj, parameters[0], parameters[1], parameters[2], parameters[3])); + break; + } + } + else + { + Assert.Throws(() => invoker.Invoke(obj, new Span(parameters))); + } + } + + bool HasTypeMissing() + { + if (parameters is not null) + { + for (int i = 0; i < parameters.Length; i++) + { + if (ReferenceEquals(parameters[i], Type.Missing)) + { + return true; + } + } + } + + return false; + } + } + + [Fact] + public void ThrowsNonWrappedException_0() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Throw_0))); + Assert.Throws(() => invoker.Invoke(obj: null)); + Assert.Throws(() => invoker.Invoke(obj: null, new Span())); + } + + [Fact] + public void ThrowsNonWrappedException_1() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Throw_1))); + Assert.Throws(() => invoker.Invoke(obj: null, "1")); + Assert.Throws(() => invoker.Invoke(obj: null, new Span(new object[] { "1" }))); + } + + [Fact] + public void ThrowsNonWrappedException_5() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.Throw_5))); + Assert.Throws(() => invoker.Invoke(obj: null, new Span(new object[] { "1", "2", "3", "4", "5" }))); + } + + [Fact] + public void VerifyThisObj_WrongType() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.VerifyThisObj))); + Assert.Throws(() => invoker.Invoke(obj: 42)); + } + + [Fact] + public void VerifyThisObj_Null() + { + MethodInvoker invoker = MethodInvoker.Create(typeof(TestClass).GetMethod(nameof(TestClass.VerifyThisObj))); + Assert.Throws(() => invoker.Invoke(obj: null)); + } + + private static MethodInfo GetMethod(Type type, string name) + { + return type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).First(method => method.Name.Equals(name)); + } + + public static IEnumerable Invoke_TestData() => MethodInfoTests.Invoke_TestData(); + + private class TestClass + { + private int _i = 42; + + public static string Args_0() => "0"; + public static string Args_1(string arg) => arg; + public static string Args_2(string arg1, string arg2) => arg1 + arg2; + public static string Args_3(string arg1, string arg2, string arg3) => arg1 + arg2 + arg3; + public static string Args_4(string arg1, string arg2, string arg3, string arg4) => arg1 + arg2 + arg3 + arg4; + public static string Args_5(string arg1, string arg2, string arg3, string arg4, string arg5) => arg1 + arg2 + arg3 + arg4 + arg5; + + public static string Args_ByRef(ref string arg) + { + arg = "Hello"; + return arg; + } + + public static unsafe void Args_ByPointer(int* arg) + { + *arg = (*arg) +1; + } + + public static unsafe void Args_BySystemPointer(Pointer arg) + { + int* p = (int*)Pointer.Unbox(arg); + *p = (*p) + 1; + } + + public static int TypeMissing(int i = 42) + { + return i; + } + + public void VerifyThisObj() + { + Assert.Equal(42, _i); + } + + public static void Throw_0() => + throw new InvalidOperationException(); + public static void Throw_1(string arg1) => + throw new InvalidOperationException(); + public static void Throw_5(string arg1, string arg2, string arg3, string arg4, string arg5) => + throw new InvalidOperationException(); + } + + } +} diff --git a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj index d6ebfb0081296b..876bbb63fd2b03 100644 --- a/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj +++ b/src/libraries/System.Reflection/tests/System.Reflection.Tests.csproj @@ -23,6 +23,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4bd5bab05788ec..9f74201a3e791d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11208,6 +11208,17 @@ protected ConstructorInfo() { } public static bool operator ==(System.Reflection.ConstructorInfo? left, System.Reflection.ConstructorInfo? right) { throw null; } public static bool operator !=(System.Reflection.ConstructorInfo? left, System.Reflection.ConstructorInfo? right) { throw null; } } + public sealed partial class ConstructorInvoker + { + internal ConstructorInvoker() { } + public object? Invoke(System.Span arguments) { throw null; } + public object? Invoke() { throw null; } + public object? Invoke(object? arg1) { throw null; } + public object? Invoke(object? arg1, object? arg2) { throw null; } + public object? Invoke(object? arg1, object? arg2, object? arg3) { throw null; } + public object? Invoke(object? arg1, object? arg2, object? arg3, object? arg4) { throw null; } + public static System.Reflection.ConstructorInvoker Create(System.Reflection.ConstructorInfo constructor) { throw null; } + } public partial class CustomAttributeData { protected CustomAttributeData() { } @@ -11676,6 +11687,17 @@ protected MethodInfo() { } public static bool operator ==(System.Reflection.MethodInfo? left, System.Reflection.MethodInfo? right) { throw null; } public static bool operator !=(System.Reflection.MethodInfo? left, System.Reflection.MethodInfo? right) { throw null; } } + public sealed partial class MethodInvoker + { + internal MethodInvoker() { } + public object? Invoke(object? obj, System.Span arguments) { throw null; } + public object? Invoke(object? obj) { throw null; } + public object? Invoke(object? obj, object? arg1) { throw null; } + public object? Invoke(object? obj, object? arg1, object? arg2) { throw null; } + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3) { throw null; } + public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3, object? arg4) { throw null; } + public static System.Reflection.MethodInvoker Create(System.Reflection.MethodBase method) { throw null; } + } public sealed partial class Missing : System.Runtime.Serialization.ISerializable { internal Missing() { } diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 2dc26dbe71fdce..a8612905085a6c 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -205,6 +205,7 @@ + @@ -248,6 +249,7 @@ + diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs index 3bde0806a5c6ee..e829600332e34d 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs @@ -1,16 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - namespace System.Reflection { - internal partial class ConstructorInvoker + public partial class ConstructorInvoker { + internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.ArgumentTypes) + { + _invokeFunc_RefArgs = InterpretedInvoke; + } + private unsafe object? InterpretedInvoke(object? obj, IntPtr *args) { - Exception exc; - object? o = _method.InternalInvoke(obj, args, out exc); + object? o = _method.InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.Mono.cs index d0f8984b2ffec9..5a97e73dcd7d34 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.Mono.cs @@ -74,9 +74,7 @@ private RuntimeILGenerator GetILGeneratorInternal(int streamSize) => try { CreateDynMethod(); - _method ??= new RuntimeMethodInfo(_mhandle); - - return _method.Invoke(obj, invokeAttr, binder, parameters, culture); + return GetRuntimeMethodInfo().Invoke(obj, invokeAttr, binder, parameters, culture); } catch (MethodAccessException mae) { @@ -84,6 +82,12 @@ private RuntimeILGenerator GetILGeneratorInternal(int streamSize) => } } + internal RuntimeMethodInfo GetRuntimeMethodInfo() + { + _method ??= new RuntimeMethodInfo(_mhandle); + return _method; + } + [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void create_dynamic_method(DynamicMethod m, string name, MethodAttributes attributes, CallingConventions callingConvention); diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs new file mode 100644 index 00000000000000..e4c4b533900042 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Mono.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection +{ + internal partial class MethodBaseInvoker + { + internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.ArgumentTypes) + { + _invocationFlags = method.ComputeAndUpdateInvocationFlags(); + _invokeFunc_RefArgs = InterpretedInvoke_Method; + } + + internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.ArgumentTypes) + { + _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + } + + private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr *args) + { + object? o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); + + if (exc != null) + throw exc; + + return o; + } + + internal unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) + { + object? o = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); + + if (exc != null) + throw exc; + + return obj == null ? o : null; + } + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs index b6a2a83b5514a3..1b20c8bdf76fd6 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs @@ -1,38 +1,48 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; +using System.Reflection.Emit; namespace System.Reflection { - internal partial class MethodInvoker + public partial class MethodInvoker { - public MethodInvoker(MethodBase method) + private unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.ArgumentTypes) { - _method = method; - - if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) - { - // Always use the native invoke; useful for testing. - _strategyDetermined = true; - } - else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) - { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. - _invoked = true; - } + _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invocationFlags = method.ComputeAndUpdateInvocationFlags(); } - private unsafe object? InterpretedInvoke(object? obj, IntPtr *args) + private unsafe MethodInvoker(DynamicMethod method) : this(method.GetRuntimeMethodInfo(), method.ArgumentTypes) { - Exception? exc; + _invokeFunc_RefArgs = InterpretedInvoke_Method; + // No _invocationFlags for DynamicMethod. + } + + private unsafe MethodInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.ArgumentTypes) + { + _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + } - object? o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out exc); + private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr *args) + { + object? o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out Exception? exc); if (exc != null) throw exc; return o; } + + private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr *args) + { + object? o = ((RuntimeConstructorInfo)_method).InternalInvoke(obj, args, out Exception? exc); + + if (exc != null) + throw exc; + + return obj == null ? o : null; + } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index 665e83d4533f56..a5b3f651ac67a4 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -241,11 +241,10 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, if (val != null) { RuntimeType fieldType = (RuntimeType)FieldType; - ParameterCopyBackAction _ = default; if (!ReferenceEquals(val.GetType(), fieldType)) { - fieldType.CheckValue(ref val, ref _, binder, culture, invokeAttr); + fieldType.CheckValue(ref val, binder, culture, invokeAttr); } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 13460b63cf49fe..20a20b2eefc3bc 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs @@ -150,29 +150,25 @@ internal sealed unsafe partial class RuntimeMethodInfo : MethodInfo #endregion private string? toString; private RuntimeType[]? parameterTypes; - private InvocationFlags invocationFlags; - private MethodInvoker? invoker; + private MethodBaseInvoker? invoker; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = invocationFlags; - if ((flags & InvocationFlags.Initialized) == 0) - { - flags = ComputeAndUpdateInvocationFlags(this, ref invocationFlags); - } + InvocationFlags flags = Invoker._invocationFlags; + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } } - private MethodInvoker Invoker + private MethodBaseInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new MethodInvoker(this); + invoker ??= new MethodBaseInvoker(this); return invoker; } } @@ -720,29 +716,25 @@ internal sealed unsafe partial class RuntimeConstructorInfo : ConstructorInfo #endregion private string? toString; private RuntimeType[]? parameterTypes; - private InvocationFlags invocationFlags; - private ConstructorInvoker? invoker; + private MethodBaseInvoker? invoker; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = invocationFlags; - if ((flags & InvocationFlags.Initialized) == 0) - { - flags = ComputeAndUpdateInvocationFlags(this, ref invocationFlags); - } + InvocationFlags flags = Invoker._invocationFlags; + Debug.Assert((flags & InvocationFlags.Initialized) == InvocationFlags.Initialized); return flags; } } - internal ConstructorInvoker Invoker + internal MethodBaseInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new ConstructorInvoker(this); + invoker ??= new MethodBaseInvoker(this); return invoker; } } diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 732ebb26d8922a..8fa80262e4903c 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -168,22 +168,6 @@ internal static bool ObjectHasReferences(object obj) return RuntimeTypeHandle.HasReferences((obj.GetType() as RuntimeType)!); } - // A conservative GC already scans the stack looking for potential object-refs or by-refs. - // Mono uses a conservative GC so there is no need for this API to be full implemented. - internal unsafe ref struct GCFrameRegistration - { -#pragma warning disable IDE0060 - public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) - { - } -#pragma warning restore IDE0060 - } - - [Conditional("unnecessary")] - internal static unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } - [Conditional("unnecessary")] - internal static unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } - public static object GetUninitializedObject( // This API doesn't call any constructors, but the type needs to be seen as constructed. // A type is seen as constructed if a constructor is kept. diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.Mono.cs new file mode 100644 index 00000000000000..dd19ffa6a8df3e --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/GCFrameRegistration.Mono.cs @@ -0,0 +1,23 @@ +// 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; + +namespace System.Runtime +{ + // A conservative GC already scans the stack looking for potential object-refs or by-refs. + // Mono uses a conservative GC so there is no need for this API to be full implemented. + internal unsafe struct GCFrameRegistration + { +#pragma warning disable IDE0060 + public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) + { + } +#pragma warning restore IDE0060 + + [Conditional("unnecessary")] + internal static unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } + [Conditional("unnecessary")] + internal static unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index e34b3788e76a8e..0322094b0e0735 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1761,21 +1761,13 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) throw new MissingMethodException(SR.Format(SR.Acc_CreateAbstEx, this)); } - unsafe - { - return ctor.Invoker.InlinedInvoke( - obj: null, - args: default, - wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); - } + return ctor.Invoker.InvokeWithNoArgs(obj: null, wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); } // FIXME Reuse with coreclr private CheckValueStatus TryChangeTypeSpecial( - ref object value, - out bool isValueType) + ref object value) { - isValueType = true; if (IsEnum) { Type? type = Enum.GetUnderlyingType(this); @@ -1814,7 +1806,6 @@ private CheckValueStatus TryChangeTypeSpecial( } } - isValueType = false; return CheckValueStatus.ArgumentException; } @@ -2080,10 +2071,7 @@ internal static object CreateInstanceForAnotherGenericParameter( if (ctor is null || !ctor.IsPublic) throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, gt!)); - unsafe - { - return ctor.Invoker.InlinedInvoke(obj: null, args: default, BindingFlags.Default)!; - } + return ctor.Invoker.InvokeWithNoArgs(obj: null, invokeAttr: default)!; } [MethodImplAttribute(MethodImplOptions.InternalCall)] diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs index 6754ba616073aa..bacc4638dd990a 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs @@ -170,6 +170,12 @@ internal static bool IsPointer(RuntimeType type) return corElemType == CorElementType.ELEMENT_TYPE_PTR; } + internal static bool IsFunctionPointer(RuntimeType type) + { + CorElementType corElemType = GetCorElementType(type); + return corElemType == CorElementType.ELEMENT_TYPE_FNPTR; + } + internal static bool IsArray(RuntimeType type) { CorElementType corElemType = GetCorElementType(type); diff --git a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs index 06faaef6ed37ab..eea7cc85859605 100644 --- a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs +++ b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs @@ -72,6 +72,7 @@ private static int Main() TestGetUninitializedObject.Run(); TestInstanceFields.Run(); TestReflectionInvoke.Run(); + TestConstructors.Run(); TestInvokeMemberParamsCornerCase.Run(); TestDefaultInterfaceInvoke.Run(); TestCovariantReturnInvoke.Run(); @@ -187,90 +188,214 @@ public static unsafe void Run() } { + object? arg = "world"; MethodInfo helloMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHello"); - string result = (string)helloMethod.Invoke(null, new object[] { "world" }); + + string result = (string)helloMethod.Invoke(null, new object[] { arg }); + if (result != "Hello world") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(null, arg); + if (result != "Hello world") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(null, new Span(ref arg)); if (result != "Hello world") throw new Exception(); } { + object? arg = 12345; MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(int)); - string result = (string)helloGenericMethod.Invoke(null, new object[] { 12345 }); + string result = (string)helloGenericMethod.Invoke(null, new object[] { arg }); + if (result != "Hello 12345") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, arg); + if (result != "Hello 12345") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, new Span(ref arg)); if (result != "Hello 12345") throw new Exception(); } { + object? arg = "buddy"; MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(string)); - string result = (string)helloGenericMethod.Invoke(null, new object[] { "buddy" }); + string result = (string)helloGenericMethod.Invoke(null, new object[] { arg }); + if (result != "Hello buddy") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, arg); + if (result != "Hello buddy") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, new Span(ref arg)); if (result != "Hello buddy") throw new Exception(); } { + object? arg = typeof(string); MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(Type)); - string result = (string)helloGenericMethod.Invoke(null, new object[] { typeof(string) }); + string result = (string)helloGenericMethod.Invoke(null, new object[] { arg }); + if (result != "Hello System.String") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, arg); + if (result != "Hello System.String") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(null, new Span(ref arg)); if (result != "Hello System.String") throw new Exception(); } { + object? arg = "world"; MethodInfo helloByRefMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloByRef"); - object[] args = new object[] { "world", null }; + object[] args = new object[] { arg, null }; + helloByRefMethod.Invoke(null, args); if ((string)args[1] != "Hello world") throw new Exception(); + + args = new object[] { arg, null }; + MethodInvoker.Create(helloByRefMethod).Invoke(null, new Span(args)); + if ((string)args[1] != "Hello world") + throw new Exception(); } { MethodInfo helloPointerMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloPointer"); + string resultNull = (string)helloPointerMethod.Invoke(null, new object[] { null }); if (resultNull != "Hello 0") throw new Exception(); - string resultVal = (string)helloPointerMethod.Invoke(null, new object[] { Pointer.Box((void*)42, typeof(char*)) }); + resultNull = (string)MethodInvoker.Create(helloPointerMethod).Invoke(null, arg1: null); + if (resultNull != "Hello 0") + throw new Exception(); + + object? arg = null; + resultNull = (string)MethodInvoker.Create(helloPointerMethod).Invoke(null, new Span(ref arg)); + if (resultNull != "Hello 0") + throw new Exception(); + + arg = Pointer.Box((void*)42, typeof(char*)); + string resultVal = (string)helloPointerMethod.Invoke(null, new object[] { arg }); + if (resultVal != "Hello 42") + throw new Exception(); + + resultNull = (string)MethodInvoker.Create(helloPointerMethod).Invoke(null, arg); + if (resultVal != "Hello 42") + throw new Exception(); + + resultNull = (string)MethodInvoker.Create(helloPointerMethod).Invoke(null, new Span(ref arg)); if (resultVal != "Hello 42") throw new Exception(); } { MethodInfo helloPointerTooMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloPointerToo"); - string result = (string)helloPointerTooMethod.Invoke(null, new object[] { Pointer.Box((void*)85, typeof(char**)) }); + object? arg = Pointer.Box((void*)85, typeof(char**)); + + string result = (string)helloPointerTooMethod.Invoke(null, new object[] { arg }); + if (result != "Hello 85") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloPointerTooMethod).Invoke(null, arg); + if (result != "Hello 85") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloPointerTooMethod).Invoke(null, new Span(ref arg)); if (result != "Hello 85") throw new Exception(); } { MethodInfo getPointerMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetPointer"); - object result = getPointerMethod.Invoke(null, new object[] { Pointer.Box((void*)2018, typeof(void*)), null }); + object? arg = Pointer.Box((void*)2018, typeof(void*)); + object[] args = new object[] { arg, null }; + + object result = getPointerMethod.Invoke(null, args); + if (Pointer.Unbox(result) != (void*)2018) + throw new Exception(); + + result = MethodInvoker.Create(getPointerMethod).Invoke(null, arg, null); + if (Pointer.Unbox(result) != (void*)2018) + throw new Exception(); + + result = MethodInvoker.Create(getPointerMethod).Invoke(null, new Span(args)); if (Pointer.Unbox(result) != (void*)2018) throw new Exception(); } { MethodInfo helloMethod = typeof(InvokeTestsGeneric).GetTypeInfo().GetDeclaredMethod("GetHello"); - string result = (string)helloMethod.Invoke(new InvokeTestsGeneric(), new object[] { "world" }); + object? arg = "world"; + + string result = (string)helloMethod.Invoke(new InvokeTestsGeneric(), new object[] { arg }); + if (result != "Hello world System.String") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(new InvokeTestsGeneric(), arg); + if (result != "Hello world System.String") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(new InvokeTestsGeneric(), new Span(ref arg)); if (result != "Hello world System.String") throw new Exception(); } { MethodInfo helloGenericMethod = typeof(InvokeTestsGeneric).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(object)); - string result = (string)helloGenericMethod.Invoke(new InvokeTestsGeneric(), new object[] { "world" }); + object? arg = "world"; + + string result = (string)helloGenericMethod.Invoke(new InvokeTestsGeneric(), new object[] { arg }); + if (result != "Hello world System.Object") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(new InvokeTestsGeneric(), arg); + if (result != "Hello world System.Object") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(new InvokeTestsGeneric(), new Span(ref arg)); if (result != "Hello world System.Object") throw new Exception(); } { MethodInfo helloMethod = typeof(InvokeTestsGeneric).GetTypeInfo().GetDeclaredMethod("GetHello"); - string result = (string)helloMethod.Invoke(new InvokeTestsGeneric(), new object[] { "world" }); + object? arg = "world"; + + string result = (string)helloMethod.Invoke(new InvokeTestsGeneric(), new object[] { arg }); + if (result != "Hello world System.Int32") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(new InvokeTestsGeneric(), arg); + if (result != "Hello world System.Int32") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloMethod).Invoke(new InvokeTestsGeneric(), new Span(ref arg)); if (result != "Hello world System.Int32") throw new Exception(); } { MethodInfo helloGenericMethod = typeof(InvokeTestsGeneric).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(double)); - string result = (string)helloGenericMethod.Invoke(new InvokeTestsGeneric(), new object[] { 1.0 }); + object? arg = 1.0; + + string result = (string)helloGenericMethod.Invoke(new InvokeTestsGeneric(), new object[] { arg }); + if (result != "Hello 1 System.Double") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(new InvokeTestsGeneric(), arg); + if (result != "Hello 1 System.Double") + throw new Exception(); + + result = (string)MethodInvoker.Create(helloGenericMethod).Invoke(new InvokeTestsGeneric(), new Span(ref arg)); if (result != "Hello 1 System.Double") throw new Exception(); } @@ -862,6 +987,33 @@ public static void Run() } } + class TestConstructors + { + public static void Run() + { + Console.WriteLine(nameof(TestConstructors)); + + ConstructorInfo ctor = typeof(ClassToConstruct).GetConstructor(new Type[] { typeof(int) }); + ClassToConstruct obj = (ClassToConstruct)ctor.Invoke(new object[] { 1 }); + if (obj._i != 1) + throw new Exception(); + + obj = (ClassToConstruct)ConstructorInvoker.Create(ctor).Invoke(1); + if (obj._i != 1) + throw new Exception(); + } + + public class ClassToConstruct + { + public int _i; + + public ClassToConstruct(int i) + { + _i = i; + } + } + } + class TestStringConstructor { public static void Run() @@ -872,6 +1024,10 @@ public static void Run() object str = ctor.Invoke(new object[] { new char[] { 'a' }, 0, 1 }); if ((string)str != "a") throw new Exception(); + + str = ConstructorInvoker.Create(ctor).Invoke(new char[] { 'a' }, 0, 1 ); + if ((string)str != "a") + throw new Exception(); } }