File: src\libraries\System.Private.CoreLib\src\System\Reflection\InvokerEmitUtil.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
 
namespace System.Reflection
{
    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_RefArgs(object? obj, IntPtr* refArguments);
        internal delegate object? InvokeFunc_ObjSpanArgs(object? obj, Span<object?> arguments);
        internal delegate object? InvokeFunc_Obj4Args(object? obj, object? arg1, object? arg2, object? arg3, object? arg4);
 
        public static InvokeFunc_Obj4Args CreateInvokeDelegate_Obj4Args(MethodBase method, bool backwardsCompat)
        {
            Debug.Assert(!method.ContainsGenericParameters);
 
            bool emitNew = method is RuntimeConstructorInfo;
            bool hasThis = !emitNew && !method.IsStatic;
 
            Type[] delegateParameters = [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.
            ReadOnlySpan<ParameterInfo> parameters = method.GetParametersAsSpan();
            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 || parameterType.IsFunctionPointer)
                {
                    Unbox(il, typeof(IntPtr));
                }
                else if (parameterType.IsValueType)
                {
                    Unbox(il, 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 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 = [typeof(object), typeof(Span<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.
            ReadOnlySpan<ParameterInfo> parameters = method.GetParametersAsSpan();
            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 || parameterType.IsFunctionPointer)
                {
                    Unbox(il, typeof(IntPtr));
                }
                else if (parameterType.IsValueType)
                {
                    Unbox(il, 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 InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(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 = [typeof(object), typeof(object), typeof(IntPtr*)];
 
            string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty;
            var dm = new DynamicMethod(
                InvokeStubPrefix + declaringTypeName + method.Name,
                returnType: typeof(object),
                delegateParameters,
                typeof(object).Module, // Use system module to identify our DynamicMethods.
                skipVisibility: true);
 
            ILGenerator il = dm.GetILGenerator();
 
            // Handle instance methods.
            if (hasThis)
            {
                il.Emit(OpCodes.Ldarg_1);
                if (method.DeclaringType!.IsValueType)
                {
                    il.Emit(OpCodes.Unbox, method.DeclaringType);
                }
            }
 
            // Push the arguments.
            ReadOnlySpan<ParameterInfo> parameters = method.GetParametersAsSpan();
            for (int i = 0; i < parameters.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_2);
                if (i != 0)
                {
                    il.Emit(OpCodes.Ldc_I4, i * IntPtr.Size);
                    il.Emit(OpCodes.Add);
                }
 
                il.Emit(OpCodes.Ldfld, Methods.ByReferenceOfByte_Value());
 
                RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType;
                if (!parameterType.IsByRef)
                {
                    il.Emit(OpCodes.Ldobj, parameterType.IsPointer || parameterType.IsFunctionPointer ? typeof(IntPtr) : parameterType);
                }
            }
 
            EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat);
 
            // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true.
            return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null);
        }
 
        private static void Unbox(ILGenerator il, Type parameterType)
        {
            // Unbox without using OpCodes.Unbox\UnboxAny to avoid a type check since that was already done by reflection.
            // Also required for unboxing true nullables created by reflection since that is not necessarily a valid CLI operation.
            Debug.Assert(parameterType.IsValueType);
            il.Emit(OpCodes.Call, Methods.Object_GetRawData());
            il.Emit(OpCodes.Ldobj, parameterType);
        }
 
        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 (backwardsCompat && RuntimeFeature.IsDynamicCodeCompiled)
            {
#if MONO
                il.Emit(OpCodes.Call, Methods.DisableInline());
#else
                il.Emit(OpCodes.Call, Methods.NextCallReturnAddress());
                il.Emit(OpCodes.Pop);
#endif
            }
 
            // Invoke the method.
            if (emitNew)
            {
                il.Emit(OpCodes.Newobj, (ConstructorInfo)method);
            }
            else if (method.IsStatic || method.DeclaringType!.IsValueType)
            {
                il.Emit(OpCodes.Call, (MethodInfo)method);
            }
            else
            {
                il.Emit(OpCodes.Callvirt, (MethodInfo)method);
            }
 
            // Handle the return.
            if (emitNew)
            {
                Type returnType = method.DeclaringType!;
                if (returnType.IsValueType)
                {
                    il.Emit(OpCodes.Box, returnType);
                }
            }
            else
            {
                RuntimeType returnType;
                if (method is RuntimeMethodInfo rmi)
                {
                    returnType = (RuntimeType)rmi.ReturnType;
                }
                else
                {
                    Debug.Assert(method is DynamicMethod);
                    returnType = (RuntimeType)((DynamicMethod)method).ReturnType;
                }
 
                if (returnType == typeof(void))
                {
                    il.Emit(OpCodes.Ldnull);
                }
                else if (returnType.IsValueType)
                {
                    il.Emit(OpCodes.Box, returnType);
                }
                else if (returnType.IsPointer)
                {
                    il.Emit(OpCodes.Ldtoken, returnType);
                    il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle());
                    il.Emit(OpCodes.Call, Methods.Pointer_Box());
                }
                else if (returnType.IsFunctionPointer)
                {
                    il.Emit(OpCodes.Box, typeof(IntPtr));
                }
                else if (returnType.IsByRef)
                {
                    // Check for null ref return.
                    RuntimeType elementType = (RuntimeType)returnType.GetElementType()!;
                    Label retValueOk = il.DefineLabel();
                    il.Emit(OpCodes.Dup);
                    il.Emit(OpCodes.Brtrue_S, retValueOk);
                    il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_NullReference_InvokeNullRefReturned());
                    il.MarkLabel(retValueOk);
 
                    // Handle per-type differences.
                    if (elementType.IsValueType)
                    {
                        il.Emit(OpCodes.Ldobj, elementType);
                        il.Emit(OpCodes.Box, elementType);
                    }
                    else if (elementType.IsPointer)
                    {
                        il.Emit(OpCodes.Ldind_Ref);
                        il.Emit(OpCodes.Conv_U);
                        il.Emit(OpCodes.Ldtoken, elementType);
                        il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle());
                        il.Emit(OpCodes.Call, Methods.Pointer_Box());
                    }
                    else if (elementType.IsFunctionPointer)
                    {
                        il.Emit(OpCodes.Box, typeof(IntPtr));
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldobj, elementType);
                    }
                }
            }
 
            il.Emit(OpCodes.Ret);
        }
 
        private static class ThrowHelper
        {
            public static void Throw_NullReference_InvokeNullRefReturned()
            {
                throw new NullReferenceException(SR.NullReference_InvokeNullRefReturned);
            }
        }
 
        private static class Methods
        {
            private static FieldInfo? s_ByReferenceOfByte_Value;
            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<object>).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))!;
 
            private static MethodInfo? s_Object_GetRawData;
            public static MethodInfo Object_GetRawData() =>
                s_Object_GetRawData ??= typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetRawData), BindingFlags.NonPublic | BindingFlags.Static)!;
 
            private static MethodInfo? s_Pointer_Box;
            public static MethodInfo Pointer_Box() =>
                s_Pointer_Box ??= typeof(Pointer).GetMethod(nameof(Pointer.Box), [typeof(void*), typeof(Type)])!;
 
            private static MethodInfo? s_Type_GetTypeFromHandle;
            public static MethodInfo Type_GetTypeFromHandle() =>
                s_Type_GetTypeFromHandle ??= typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), [typeof(RuntimeTypeHandle)])!;
 
#if MONO
            private static MethodInfo? s_DisableInline;
            public static MethodInfo DisableInline() =>
                s_DisableInline ??= typeof(System.Runtime.CompilerServices.JitHelpers).GetMethod(nameof(System.Runtime.CompilerServices.JitHelpers.DisableInline), BindingFlags.NonPublic | BindingFlags.Static)!;
#else
            private static MethodInfo? s_NextCallReturnAddress;
            public static MethodInfo NextCallReturnAddress() =>
                s_NextCallReturnAddress ??= typeof(StubHelpers.StubHelpers).GetMethod(nameof(StubHelpers.StubHelpers.NextCallReturnAddress), BindingFlags.NonPublic | BindingFlags.Static)!;
#endif
        }
    }
}