未验证 提交 880903b2 编写于 作者: L Levi Broderick 提交者: GitHub

Avoid intermediate allocations in MethodInfo/ConstructorInfo.Invoke (#50814)

* Avoid temporary array allocations in reflection

* Add nullability annotations

* Move some lazy property getters out of the hot path

* More factoring out common fast paths

* Comment cleanup - no functional changes

* Moved some function blocks around for clarity - no functional changes

* Pass span into native stack byref

* Knock max stackalloced ctor parameters back to 4

* Move all CheckConsistency logic inline
上级 ada0d61b
......@@ -458,19 +458,15 @@ internal RuntimeMethodHandle GetMethodDescriptor()
// if we are here we passed all the previous checks. Time to look at the arguments
bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
object retValue;
if (actualCount > 0)
{
object[] arguments = CheckArguments(parameters!, binder, invokeAttr, culture, sig);
retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions);
// copy out. This should be made only if ByRef are present.
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
}
else
{
retValue = RuntimeMethodHandle.InvokeMethod(null, null, sig, false, wrapExceptions);
}
StackAllocedArguments stackArgs = default;
Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
object? retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions);
// copy out. This should be made only if ByRef are present.
// n.b. cannot use Span<T>.CopyTo, as parameters.GetType() might not actually be typeof(object[])
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
GC.KeepAlive(this);
return retValue;
......
// 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.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using Internal.Runtime.CompilerServices;
namespace System.Reflection
{
......@@ -62,30 +65,61 @@ internal virtual Type[] GetParameterTypes()
return parameterTypes;
}
internal object[] CheckArguments(object[] parameters, Binder? binder,
private protected Span<object?> CheckArguments(ref StackAllocedArguments stackArgs, object?[]? parameters, Binder? binder,
BindingFlags invokeAttr, CultureInfo? culture, Signature sig)
{
// copy the arguments in a different array so we detach from any user changes
object[] copyOfParameters = new object[parameters.Length];
Debug.Assert(Unsafe.SizeOf<StackAllocedArguments>() == StackAllocedArguments.MaxStackAllocArgCount * Unsafe.SizeOf<object>(),
"MaxStackAllocArgCount not properly defined.");
ParameterInfo[]? p = null;
for (int i = 0; i < parameters.Length; i++)
{
object arg = parameters[i];
RuntimeType argRT = sig.Arguments[i];
Span<object?> copyOfParameters = default;
if (arg == Type.Missing)
if (parameters is not null)
{
// 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.
copyOfParameters = (parameters.Length <= StackAllocedArguments.MaxStackAllocArgCount)
? MemoryMarshal.CreateSpan(ref stackArgs._arg0, parameters.Length)
: new Span<object?>(new object?[parameters.Length]);
ParameterInfo[]? p = null;
for (int i = 0; i < parameters.Length; i++)
{
p ??= GetParametersNoCopy();
if (p[i].DefaultValue == System.DBNull.Value)
throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters));
arg = p[i].DefaultValue!;
object? arg = parameters[i];
RuntimeType argRT = sig.Arguments[i];
if (arg == Type.Missing)
{
p ??= GetParametersNoCopy();
if (p[i].DefaultValue == System.DBNull.Value)
throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters));
arg = p[i].DefaultValue!;
}
copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr);
}
copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr)!;
}
return copyOfParameters;
}
// Helper struct to avoid intermediate object[] allocation in calls to the native reflection stack.
// Typical usage is to define a local of type default(StackAllocedArguments), then pass 'ref theLocal'
// as the first parameter to CheckArguments. CheckArguments will try to utilize storage within this
// struct instance if there's sufficient space; otherwise CheckArguments will allocate a temp array.
private protected struct StackAllocedArguments
{
internal const int MaxStackAllocArgCount = 4;
internal object? _arg0;
#pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic
private object? _arg1;
private object? _arg2;
private object? _arg3;
#pragma warning restore CA1823, CS0169, IDE0051
}
#endregion
}
}
......@@ -7,6 +7,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache;
namespace System.Reflection
......@@ -26,14 +27,16 @@ internal sealed class RuntimeConstructorInfo : ConstructorInfo, IRuntimeMethodIn
private IntPtr m_handle;
private MethodAttributes m_methodAttributes;
private BindingFlags m_bindingFlags;
private volatile Signature? m_signature;
private Signature? m_signature;
private INVOCATION_FLAGS m_invocationFlags;
internal INVOCATION_FLAGS InvocationFlags
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if ((m_invocationFlags & INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED) == 0)
[MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path
INVOCATION_FLAGS LazyCreateInvocationFlags()
{
INVOCATION_FLAGS invocationFlags = INVOCATION_FLAGS.INVOCATION_FLAGS_IS_CTOR; // this is a given
......@@ -68,10 +71,17 @@ internal INVOCATION_FLAGS InvocationFlags
invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_IS_DELEGATE_CTOR;
}
m_invocationFlags = invocationFlags | INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED;
invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED;
m_invocationFlags = invocationFlags; // accesses are guaranteed atomic
return invocationFlags;
}
return m_invocationFlags;
INVOCATION_FLAGS flags = m_invocationFlags;
if ((flags & INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED) == 0)
{
flags = LazyCreateInvocationFlags();
}
return flags;
}
}
#endregion
......@@ -95,7 +105,22 @@ internal INVOCATION_FLAGS InvocationFlags
internal override bool CacheEquals(object? o) =>
o is RuntimeConstructorInfo m && m.m_handle == m_handle;
private Signature Signature => m_signature ??= new Signature(this, m_declaringType);
private Signature Signature
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
[MethodImpl(MethodImplOptions.NoInlining)] // move lazy sig generation out of the hot path
Signature LazyCreateSignature()
{
Signature newSig = new Signature(this, m_declaringType);
Volatile.Write(ref m_signature, newSig);
return newSig;
}
return m_signature ?? LazyCreateSignature();
}
}
private RuntimeType ReflectedTypeInternal => m_reflectedTypeCache.GetRuntimeType();
......@@ -313,16 +338,17 @@ internal void ThrowNoInvokeException()
// if we are here we passed all the previous checks. Time to look at the arguments
bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
if (actualCount > 0)
{
object[] arguments = CheckArguments(parameters!, binder, invokeAttr, culture, sig);
object retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, sig, false, wrapExceptions);
// copy out. This should be made only if ByRef are present.
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
return retValue;
}
return RuntimeMethodHandle.InvokeMethod(obj, null, sig, false, wrapExceptions);
StackAllocedArguments stackArgs = default;
Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
object? retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, sig, false, wrapExceptions);
// copy out. This should be made only if ByRef are present.
// n.b. cannot use Span<T>.CopyTo, as parameters.GetType() might not actually be typeof(object[])
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
return retValue;
}
[RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")]
......@@ -366,16 +392,17 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[]
// if we are here we passed all the previous checks. Time to look at the arguments
bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
if (actualCount > 0)
{
object[] arguments = CheckArguments(parameters!, binder, invokeAttr, culture, sig);
object retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, true, wrapExceptions);
// copy out. This should be made only if ByRef are present.
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
return retValue;
}
return RuntimeMethodHandle.InvokeMethod(null, null, sig, true, wrapExceptions);
StackAllocedArguments stackArgs = default;
Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
object retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, true, wrapExceptions)!; // ctor must return non-null
// copy out. This should be made only if ByRef are present.
// n.b. cannot use Span<T>.CopyTo, as parameters.GetType() might not actually be typeof(object[])
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
return retValue;
}
#endregion
}
......
......@@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading;
......@@ -30,9 +31,11 @@ internal sealed class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo
internal INVOCATION_FLAGS InvocationFlags
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if ((m_invocationFlags & INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED) == 0)
[MethodImpl(MethodImplOptions.NoInlining)] // move lazy invocation flags population out of the hot path
INVOCATION_FLAGS LazyCreateInvocationFlags()
{
INVOCATION_FLAGS invocationFlags = INVOCATION_FLAGS.INVOCATION_FLAGS_UNKNOWN;
......@@ -55,10 +58,17 @@ internal INVOCATION_FLAGS InvocationFlags
invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS;
}
m_invocationFlags = invocationFlags | INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED;
invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED;
m_invocationFlags = invocationFlags; // accesses are guaranteed atomic
return invocationFlags;
}
return m_invocationFlags;
INVOCATION_FLAGS flags = m_invocationFlags;
if ((flags & INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED) == 0)
{
flags = LazyCreateInvocationFlags();
}
return flags;
}
}
......@@ -105,7 +115,22 @@ private static bool IsDisallowedByRefType(Type type)
internal override bool CacheEquals(object? o) =>
o is RuntimeMethodInfo m && m.m_handle == m_handle;
internal Signature Signature => m_signature ??= new Signature(this, m_declaringType);
internal Signature Signature
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
[MethodImpl(MethodImplOptions.NoInlining)] // move lazy sig generation out of the hot path
Signature LazyCreateSignature()
{
Signature newSig = new Signature(this, m_declaringType);
Volatile.Write(ref m_signature, newSig);
return newSig;
}
return m_signature ?? LazyCreateSignature();
}
}
internal BindingFlags BindingFlags => m_bindingFlags;
......@@ -330,10 +355,11 @@ public override MethodImplAttributes GetMethodImplementationFlags()
#endregion
#region Invocation Logic(On MemberBase)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckConsistency(object? target)
{
// only test instance methods
if ((m_methodAttributes & MethodAttributes.Static) != MethodAttributes.Static)
if ((m_methodAttributes & MethodAttributes.Static) == 0)
{
if (!m_declaringType.IsInstanceOfType(target))
{
......@@ -384,26 +410,23 @@ private void ThrowNoInvokeException()
[Diagnostics.DebuggerHidden]
public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
{
object[]? arguments = InvokeArgumentsCheck(obj, invokeAttr, binder, parameters, culture);
StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible
Span<object?> arguments = InvokeArgumentsCheck(ref stackArgs, obj, invokeAttr, binder, parameters, culture);
bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
if (arguments == null || arguments.Length == 0)
return RuntimeMethodHandle.InvokeMethod(obj, null, Signature, false, wrapExceptions);
else
{
object retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false, wrapExceptions);
object? retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false, wrapExceptions);
// copy out. This should be made only if ByRef are present.
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
// copy out. This should be made only if ByRef are present.
// n.b. cannot use Span<T>.CopyTo, as parameters.GetType() might not actually be typeof(object[])
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
return retValue;
}
return retValue;
}
[DebuggerStepThroughAttribute]
[Diagnostics.DebuggerHidden]
private object[]? InvokeArgumentsCheck(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
private Span<object?> InvokeArgumentsCheck(ref StackAllocedArguments stackArgs, object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
{
Signature sig = Signature;
......@@ -425,10 +448,12 @@ private void ThrowNoInvokeException()
if (formalCount != actualCount)
throw new TargetParameterCountException(SR.Arg_ParmCnt);
Span<object?> retVal = default;
if (actualCount != 0)
return CheckArguments(parameters!, binder, invokeAttr, culture, sig);
else
return null;
{
retVal = CheckArguments(ref stackArgs, parameters!, binder, invokeAttr, culture, sig);
}
return retVal;
}
#endregion
......
......@@ -9,6 +9,7 @@
using System.Runtime.Loader;
using System.Runtime.Serialization;
using System.Threading;
using Internal.Runtime.CompilerServices;
namespace System
{
......@@ -963,7 +964,7 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method)
[DebuggerStepThroughAttribute]
[Diagnostics.DebuggerHidden]
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object InvokeMethod(object? target, object[]? arguments, Signature sig, bool constructor, bool wrapExceptions);
internal static extern object? InvokeMethod(object? target, in Span<object?> arguments, Signature sig, bool constructor, bool wrapExceptions);
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray);
......
......@@ -739,6 +739,39 @@ public:
#define OFFSETOF__PtrArray__m_Array_ ARRAYBASE_SIZE
/* Corresponds to the managed Span<T> and ReadOnlySpan<T> types.
This should only ever be passed from the managed to the unmanaged world byref,
as any copies of this struct made within the unmanaged world will not observe
potential GC relocations of the source data. */
template < class KIND >
class Span
{
private:
/* Keep fields below in sync with managed Span / ReadOnlySpan layout. */
KIND* _pointer;
unsigned int _length;
public:
// !! CAUTION !!
// Caller must take care not to reassign returned reference if this span corresponds
// to a managed ReadOnlySpan<T>. If KIND is a reference type, caller must use a
// helper like SetObjectReference instead of assigning values directly to the
// reference location.
KIND& GetAt(SIZE_T index)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(index < GetLength());
return _pointer[index];
}
// Gets the length (in elements) of this span.
__inline SIZE_T GetLength() const
{
return _length;
}
};
/* a TypedByRef is a structure that is used to implement VB's BYREF variants.
it is basically a tuple of an address of some data along with a TypeHandle
that indicates the type of the address */
......
......@@ -478,7 +478,7 @@ void CallDescrWorkerReflectionWrapper(CallDescrData * pCallDescrData, Frame * pF
PAL_ENDTRY
} // CallDescrWorkerReflectionWrapper
OBJECTREF InvokeArrayConstructor(TypeHandle th, MethodDesc* pMeth, PTRARRAYREF* objs, int argCnt)
OBJECTREF InvokeArrayConstructor(TypeHandle th, MethodDesc* pMeth, Span<OBJECTREF>* objs, int argCnt)
{
CONTRACTL {
THROWS;
......@@ -501,16 +501,16 @@ OBJECTREF InvokeArrayConstructor(TypeHandle th, MethodDesc* pMeth, PTRARRAYREF*
for (DWORD i=0; i<(DWORD)argCnt; i++)
{
if (!(*objs)->m_Array[i])
if (!objs->GetAt(i))
COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex"));
MethodTable* pMT = ((*objs)->m_Array[i])->GetMethodTable();
MethodTable* pMT = objs->GetAt(i)->GetMethodTable();
CorElementType oType = TypeHandle(pMT).GetVerifierCorElementType();
if (!InvokeUtil::IsPrimitiveType(oType) || !InvokeUtil::CanPrimitiveWiden(ELEMENT_TYPE_I4,oType))
COMPlusThrow(kArgumentException,W("Arg_PrimWiden"));
memcpy(&indexes[i],(*objs)->m_Array[i]->UnBox(),pMT->GetNumInstanceFieldBytes());
memcpy(&indexes[i], objs->GetAt(i)->UnBox(),pMT->GetNumInstanceFieldBytes());
}
return AllocateArrayEx(th, indexes, argCnt);
......@@ -749,20 +749,18 @@ void DECLSPEC_NORETURN ThrowInvokeMethodException(MethodDesc * pMethod, OBJECTRE
}
FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
Object *target, PTRArray *objs, SignatureNative* pSigUNSAFE,
Object *target, Span<OBJECTREF>* objs, SignatureNative* pSigUNSAFE,
CLR_BOOL fConstructor, CLR_BOOL fWrapExceptions)
{
FCALL_CONTRACT;
struct {
OBJECTREF target;
PTRARRAYREF args;
SIGNATURENATIVEREF pSig;
OBJECTREF retVal;
} gc;
gc.target = ObjectToOBJECTREF(target);
gc.args = (PTRARRAYREF)objs;
gc.pSig = (SIGNATURENATIVEREF)pSigUNSAFE;
gc.retVal = NULL;
......@@ -793,7 +791,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
if (ownerType.IsArray()) {
gc.retVal = InvokeArrayConstructor(ownerType,
pMeth,
&gc.args,
objs,
gc.pSig->NumFixedArgs());
goto Done;
}
......@@ -1044,7 +1042,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
argDest = ArgDestination(pStackCopy, 0, NULL);
}
InvokeUtil::CopyArg(th, &(gc.args->m_Array[i]), &argDest);
InvokeUtil::CopyArg(th, &objs->GetAt(i), &argDest);
}
ENDFORBIDGC();
......@@ -1159,7 +1157,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
while (byRefToNullables != NULL) {
OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable());
SetObjectReference(&gc.args->m_Array[byRefToNullables->argNum], obj);
SetObjectReference(&objs->GetAt(byRefToNullables->argNum), obj);
byRefToNullables = byRefToNullables->next;
}
......
......@@ -270,7 +270,7 @@ class RuntimeMethodHandle {
public:
static FCDECL1(ReflectMethodObject*, GetCurrentMethod, StackCrawlMark* stackMark);
static FCDECL5(Object*, InvokeMethod, Object *target, PTRArray *objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL fWrapExceptions);
static FCDECL5(Object*, InvokeMethod, Object *target, Span<OBJECTREF>* objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL fWrapExceptions);
struct StreamingContextData {
Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册