From 7f9228e5d562b0b5e30f4c2130af6bc981acb79c Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 3 Mar 2023 16:14:58 -0600 Subject: [PATCH 1/4] Respect IsDynamicCodeSupported in more places in Linq.Expressions * CanEmitObjectArrayDelegate * CanCreateArbitraryDelegates These properties are all set to `false` when running on NativeAOT, so have them respect RuntimeFeature.IsDynamicCodeSupported. Fix #81803 --- .../src/System/Dynamic/Utils/DelegateHelpers.cs | 3 ++- .../Expressions/Interpreter/CallInstruction.cs | 2 +- .../System.Linq.Expressions/tests/CompilerTests.cs | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs index b46fa67682891..9b505b2687cbf 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -12,7 +13,7 @@ namespace System.Dynamic.Utils internal static class DelegateHelpers { // This can be flipped to true using feature switches at publishing time - internal static bool CanEmitObjectArrayDelegate => true; + internal static bool CanEmitObjectArrayDelegate => RuntimeFeature.IsDynamicCodeSupported; // Separate class so that the it can be trimmed away and doesn't get conflated // with the Reflection.Emit statics below. diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs index 951f44a6c50f5..5ab0cea57217b 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs @@ -16,7 +16,7 @@ internal abstract partial class CallInstruction : Instruction /// public abstract int ArgumentCount { get; } - private static bool CanCreateArbitraryDelegates => true; + private static bool CanCreateArbitraryDelegates => RuntimeFeature.IsDynamicCodeSupported; #region Construction diff --git a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs index 3c2f4bc6b5698..2e23a1dab375a 100644 --- a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs @@ -451,8 +451,22 @@ public static void CompileWorksWhenDynamicCodeNotSupported() Delegate del = Expression.Lambda(Expression.Add(param, Expression.Constant(5)), param).Compile(); Assert.Equal(305, del.DynamicInvoke(300)); + + // testing more than 2 parameters is important because because it follows a different code path in Compile. + Expression> fiveParameterExpression = (a, b, c, d, e) => a + b + c + d + e; + Func fiveParameterFunc = fiveParameterExpression.Compile(); + Assert.Equal(7, fiveParameterFunc(2, 2, 1, 1, 0)); + + Expression> callExpression = (a, b) => Add(a, b); + Func callFunc = callExpression.Compile(); + Assert.Equal(29, callFunc(20, 9)); }, options); } + + private static int Add(int a, int b) + { + return a + b; + } } public enum ConstantsEnum From 4643eae393e4ec1e28d2b150efb0353ab5ad750c Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 7 Jul 2023 15:29:00 -0500 Subject: [PATCH 2/4] Work around DynamicDelegateAugments.CreateObjectArrayDelegate not existing in CoreClr's System.Private.CoreLib. Allow System.Linq.Expressions to create an object[] delegate using Ref.Emit even though RuntimeFeature.IsDynamicCodeSupported is set to false (ex. using a feature switch). To enable this, add an internal method in CoreLib that temporarily allows the current thread to skip the RuntimeFeature check and allows DynamicMethod instances to be created. When System.Linq.Expressions needs to generate one of these delegates, it calls the internal method through Reflection and continues to use Ref.Emit to generate the delegate. --- .../System/Dynamic/Utils/DelegateHelpers.cs | 202 ++++++++++-------- .../tests/CompilerTests.cs | 2 +- .../ILLink.Descriptors.LibraryBuild.xml | 4 + .../System/Reflection/Emit/AssemblyBuilder.cs | 33 ++- 4 files changed, 152 insertions(+), 89 deletions(-) diff --git a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs index 9b505b2687cbf..d285e0aebdf38 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs @@ -12,8 +12,8 @@ namespace System.Dynamic.Utils { internal static class DelegateHelpers { - // This can be flipped to true using feature switches at publishing time - internal static bool CanEmitObjectArrayDelegate => RuntimeFeature.IsDynamicCodeSupported; + // This can be flipped to false using feature switches at publishing time + internal static bool CanEmitObjectArrayDelegate => true; // Separate class so that the it can be trimmed away and doesn't get conflated // with the Reflection.Emit statics below. @@ -22,14 +22,23 @@ private static class DynamicDelegateLightup public static Func, Delegate> CreateObjectArrayDelegate { get; } = CreateObjectArrayDelegateInternal(); - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", - Justification = "Works around https://github.com/dotnet/linker/issues/2392")] private static Func, Delegate> CreateObjectArrayDelegateInternal() => Type.GetType("Internal.Runtime.Augments.DynamicDelegateAugments")! .GetMethod("CreateObjectArrayDelegate")! .CreateDelegate, Delegate>>(); } + private static class ForceAllowDynamicCodeLightup + { + public static Func? ForceAllowDynamicCodeDelegate { get; } + = ForceAllowDynamicCodeDelegateInternal(); + + private static Func? ForceAllowDynamicCodeDelegateInternal() + => typeof(AssemblyBuilder) + .GetMethod("ForceAllowDynamicCode", BindingFlags.NonPublic | BindingFlags.Static) + ?.CreateDelegate>(); + } + internal static Delegate CreateObjectArrayDelegate(Type delegateType, Func handler) { if (CanEmitObjectArrayDelegate) @@ -187,113 +196,132 @@ private static Delegate CreateObjectArrayDelegateRefEmit(Type delegateType, Func if (thunkMethod == null) { - int thunkIndex = Interlocked.Increment(ref s_ThunksCreated); - Type[] paramTypes = new Type[parameters.Length + 1]; - paramTypes[0] = typeof(Func); - - StringBuilder thunkName = new StringBuilder(); - thunkName.Append("Thunk"); - thunkName.Append(thunkIndex); - if (hasReturnValue) + IDisposable? forceAllowDynamicCodeScope = null; + if (!RuntimeFeature.IsDynamicCodeSupported) { - thunkName.Append("ret_"); - thunkName.Append(returnType.Name); + // Force 'new DynamicMethod' to not throw even though RuntimeFeature.IsDynamicCodeSupported is false. + // If we are running on a runtime that supports dynamic code, even though the feature switch is off, + // for example when running on CoreClr with PublishAot=true, this will allow IL to be emitted. + // If we are running on a runtime that really doesn't support dynamic code, like NativeAOT, + // CanEmitObjectArrayDelegate will be flipped to 'false', and this method won't be invoked. + forceAllowDynamicCodeScope = ForceAllowDynamicCodeLightup.ForceAllowDynamicCodeDelegate?.Invoke(); } - for (int i = 0; i < parameters.Length; i++) + try { - thunkName.Append('_'); - thunkName.Append(parameters[i].ParameterType.Name); - paramTypes[i + 1] = parameters[i].ParameterType; - } - DynamicMethod dynamicThunkMethod = new DynamicMethod(thunkName.ToString(), returnType, paramTypes); - thunkMethod = dynamicThunkMethod; - ILGenerator ilgen = dynamicThunkMethod.GetILGenerator(); + int thunkIndex = Interlocked.Increment(ref s_ThunksCreated); + Type[] paramTypes = new Type[parameters.Length + 1]; + paramTypes[0] = typeof(Func); - LocalBuilder argArray = ilgen.DeclareLocal(typeof(object[])); - LocalBuilder retValue = ilgen.DeclareLocal(typeof(object)); + StringBuilder thunkName = new StringBuilder(); + thunkName.Append("Thunk"); + thunkName.Append(thunkIndex); + if (hasReturnValue) + { + thunkName.Append("ret_"); + thunkName.Append(returnType.Name); + } - // create the argument array - if (parameters.Length == 0) - { - ilgen.Emit(OpCodes.Call, s_ArrayEmpty); - } - else - { - ilgen.Emit(OpCodes.Ldc_I4, parameters.Length); - ilgen.Emit(OpCodes.Newarr, typeof(object)); - } - ilgen.Emit(OpCodes.Stloc, argArray); + for (int i = 0; i < parameters.Length; i++) + { + thunkName.Append('_'); + thunkName.Append(parameters[i].ParameterType.Name); + paramTypes[i + 1] = parameters[i].ParameterType; + } - // populate object array - bool hasRefArgs = false; - for (int i = 0; i < parameters.Length; i++) - { - bool paramIsByReference = parameters[i].ParameterType.IsByRef; - Type paramType = parameters[i].ParameterType; - if (paramIsByReference) - paramType = paramType.GetElementType()!; + DynamicMethod dynamicThunkMethod = new DynamicMethod(thunkName.ToString(), returnType, paramTypes); + thunkMethod = dynamicThunkMethod; + ILGenerator ilgen = dynamicThunkMethod.GetILGenerator(); - hasRefArgs = hasRefArgs || paramIsByReference; + LocalBuilder argArray = ilgen.DeclareLocal(typeof(object[])); + LocalBuilder retValue = ilgen.DeclareLocal(typeof(object)); - ilgen.Emit(OpCodes.Ldloc, argArray); - ilgen.Emit(OpCodes.Ldc_I4, i); - ilgen.Emit(OpCodes.Ldarg, i + 1); + // create the argument array + if (parameters.Length == 0) + { + ilgen.Emit(OpCodes.Call, s_ArrayEmpty); + } + else + { + ilgen.Emit(OpCodes.Ldc_I4, parameters.Length); + ilgen.Emit(OpCodes.Newarr, typeof(object)); + } + ilgen.Emit(OpCodes.Stloc, argArray); - if (paramIsByReference) + // populate object array + bool hasRefArgs = false; + for (int i = 0; i < parameters.Length; i++) { - ilgen.Emit(OpCodes.Ldobj, paramType); + bool paramIsByReference = parameters[i].ParameterType.IsByRef; + Type paramType = parameters[i].ParameterType; + if (paramIsByReference) + paramType = paramType.GetElementType()!; + + hasRefArgs = hasRefArgs || paramIsByReference; + + ilgen.Emit(OpCodes.Ldloc, argArray); + ilgen.Emit(OpCodes.Ldc_I4, i); + ilgen.Emit(OpCodes.Ldarg, i + 1); + + if (paramIsByReference) + { + ilgen.Emit(OpCodes.Ldobj, paramType); + } + Type boxType = ConvertToBoxableType(paramType); + ilgen.Emit(OpCodes.Box, boxType); + ilgen.Emit(OpCodes.Stelem_Ref); } - Type boxType = ConvertToBoxableType(paramType); - ilgen.Emit(OpCodes.Box, boxType); - ilgen.Emit(OpCodes.Stelem_Ref); - } - if (hasRefArgs) - { - ilgen.BeginExceptionBlock(); - } + if (hasRefArgs) + { + ilgen.BeginExceptionBlock(); + } - // load delegate - ilgen.Emit(OpCodes.Ldarg_0); + // load delegate + ilgen.Emit(OpCodes.Ldarg_0); - // load array - ilgen.Emit(OpCodes.Ldloc, argArray); + // load array + ilgen.Emit(OpCodes.Ldloc, argArray); - // invoke Invoke - ilgen.Emit(OpCodes.Callvirt, s_FuncInvoke); - ilgen.Emit(OpCodes.Stloc, retValue); + // invoke Invoke + ilgen.Emit(OpCodes.Callvirt, s_FuncInvoke); + ilgen.Emit(OpCodes.Stloc, retValue); - if (hasRefArgs) - { - // copy back ref/out args - ilgen.BeginFinallyBlock(); - for (int i = 0; i < parameters.Length; i++) + if (hasRefArgs) { - if (parameters[i].ParameterType.IsByRef) - { - Type byrefToType = parameters[i].ParameterType.GetElementType()!; - - // update parameter - ilgen.Emit(OpCodes.Ldarg, i + 1); - ilgen.Emit(OpCodes.Ldloc, argArray); - ilgen.Emit(OpCodes.Ldc_I4, i); - ilgen.Emit(OpCodes.Ldelem_Ref); - ilgen.Emit(OpCodes.Unbox_Any, byrefToType); - ilgen.Emit(OpCodes.Stobj, byrefToType); + // copy back ref/out args + ilgen.BeginFinallyBlock(); + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType.IsByRef) + { + Type byrefToType = parameters[i].ParameterType.GetElementType()!; + + // update parameter + ilgen.Emit(OpCodes.Ldarg, i + 1); + ilgen.Emit(OpCodes.Ldloc, argArray); + ilgen.Emit(OpCodes.Ldc_I4, i); + ilgen.Emit(OpCodes.Ldelem_Ref); + ilgen.Emit(OpCodes.Unbox_Any, byrefToType); + ilgen.Emit(OpCodes.Stobj, byrefToType); + } } + ilgen.EndExceptionBlock(); } - ilgen.EndExceptionBlock(); - } - if (hasReturnValue) + if (hasReturnValue) + { + ilgen.Emit(OpCodes.Ldloc, retValue); + ilgen.Emit(OpCodes.Unbox_Any, ConvertToBoxableType(returnType)); + } + + ilgen.Emit(OpCodes.Ret); + } + finally { - ilgen.Emit(OpCodes.Ldloc, retValue); - ilgen.Emit(OpCodes.Unbox_Any, ConvertToBoxableType(returnType)); + forceAllowDynamicCodeScope?.Dispose(); } - - ilgen.Emit(OpCodes.Ret); } s_thunks[delegateType] = thunkMethod; diff --git a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs index 2e23a1dab375a..c765fefdb1a23 100644 --- a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs @@ -455,7 +455,7 @@ public static void CompileWorksWhenDynamicCodeNotSupported() // testing more than 2 parameters is important because because it follows a different code path in Compile. Expression> fiveParameterExpression = (a, b, c, d, e) => a + b + c + d + e; Func fiveParameterFunc = fiveParameterExpression.Compile(); - Assert.Equal(7, fiveParameterFunc(2, 2, 1, 1, 0)); + Assert.Equal(6, fiveParameterFunc(2, 2, 1, 1, 0)); Expression> callExpression = (a, b) => Add(a, b); Func callFunc = callExpression.Compile(); diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.LibraryBuild.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.LibraryBuild.xml index 2cafb97358f18..9ea6ada68e8cf 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.LibraryBuild.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.LibraryBuild.xml @@ -8,6 +8,10 @@ + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/AssemblyBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/AssemblyBuilder.cs index 4989a83cc4912..d7ee53669cc91 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/AssemblyBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/AssemblyBuilder.cs @@ -9,6 +9,9 @@ namespace System.Reflection.Emit { public abstract partial class AssemblyBuilder : Assembly { + [ThreadStatic] + private static bool t_allowDynamicCode; + protected AssemblyBuilder() { } @@ -81,12 +84,40 @@ public override string[] GetManifestResourceNames() => internal static void EnsureDynamicCodeSupported() { - if (!RuntimeFeature.IsDynamicCodeSupported) + if (!RuntimeFeature.IsDynamicCodeSupported && !t_allowDynamicCode) { ThrowDynamicCodeNotSupported(); } } + /// + /// Allows dynamic code even though RuntimeFeature.IsDynamicCodeSupported is false. + /// + /// An object that, when disposed, will revert allowing dynamic code back to its initial state. + /// + /// This is useful for cases where RuntimeFeature.IsDynamicCodeSupported returns false, but + /// the runtime is still capable of emitting dynamic code. For example, when generating delegates + /// in System.Linq.Expressions while PublishAot=true is set in the project. At debug time, the app + /// uses the non-AOT runtime with the IsDynamicCodeSupported feature switch set to false. + /// + internal static IDisposable ForceAllowDynamicCode() => new ForceAllowDynamicCodeScope(); + + private sealed class ForceAllowDynamicCodeScope : IDisposable + { + private readonly bool _previous; + + public ForceAllowDynamicCodeScope() + { + _previous = t_allowDynamicCode; + t_allowDynamicCode = true; + } + + public void Dispose() + { + t_allowDynamicCode = _previous; + } + } + private static void ThrowDynamicCodeNotSupported() => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionEmit); } From b57a8979bb239533b103078f09b33083099468c2 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 7 Jul 2023 16:03:47 -0500 Subject: [PATCH 3/4] Adjust CompileWorksWhenDynamicCodeNotSupported to test in more scenarios. --- .../tests/CompilerTests.cs | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs index c765fefdb1a23..3521789b3eefa 100644 --- a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Microsoft.DotNet.RemoteExecutor; @@ -442,31 +443,52 @@ public static void CompileWorksWhenDynamicCodeNotSupported() using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(static () => { - ParameterExpression param = Expression.Parameter(typeof(int)); + CompileWorksWhenDynamicCodeNotSupportedInner(); + }, options); + } - Func typedDel = - Expression.Lambda>(Expression.Add(param, Expression.Constant(4)), param).Compile(); - Assert.Equal(304, typedDel(300)); + // run the same test code as the above CompileWorksWhenDynamicCodeNotSupported test + // to ensure this test works correctly on all platforms - even if RemoteExecutor is not supported + [Fact] + public static void CompileWorksWhenDynamicCodeNotSupportedInner() + { + ParameterExpression param = Expression.Parameter(typeof(int)); - Delegate del = - Expression.Lambda(Expression.Add(param, Expression.Constant(5)), param).Compile(); - Assert.Equal(305, del.DynamicInvoke(300)); + Func typedDel = + Expression.Lambda>(Expression.Add(param, Expression.Constant(4)), param).Compile(); + Assert.Equal(304, typedDel(300)); - // testing more than 2 parameters is important because because it follows a different code path in Compile. - Expression> fiveParameterExpression = (a, b, c, d, e) => a + b + c + d + e; - Func fiveParameterFunc = fiveParameterExpression.Compile(); - Assert.Equal(6, fiveParameterFunc(2, 2, 1, 1, 0)); + Delegate del = + Expression.Lambda(Expression.Add(param, Expression.Constant(5)), param).Compile(); + Assert.Equal(305, del.DynamicInvoke(300)); - Expression> callExpression = (a, b) => Add(a, b); - Func callFunc = callExpression.Compile(); - Assert.Equal(29, callFunc(20, 9)); - }, options); + // testing more than 2 parameters is important because because it follows a different code path in Compile. + Expression> fiveParameterExpression = (a, b, c, d, e) => a + b + c + d + e; + Func fiveParameterFunc = fiveParameterExpression.Compile(); + Assert.Equal(6, fiveParameterFunc(2, 2, 1, 1, 0)); + + Expression> callExpression = (a, b) => Add(a, b); + Func callFunc = callExpression.Compile(); + Assert.Equal(29, callFunc(20, 9)); + + MethodCallExpression methodCallExpression = Expression.Call( + instance: null, + typeof(CompilerTests).GetMethod("Add4", BindingFlags.NonPublic | BindingFlags.Static), + Expression.Constant(5), Expression.Constant(6), Expression.Constant(7), Expression.Constant(8)); + + Func methodCallDelegate = Expression.Lambda>(methodCallExpression).Compile(); + Assert.Equal(26, methodCallDelegate()); } private static int Add(int a, int b) { return a + b; } + + private static int Add4(int a, int b, int c, int d) + { + return a + b + c + d; + } } public enum ConstantsEnum From 514bd95c0bb131846bf2c1da88a497eba4e47a21 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 11 Jul 2023 08:46:03 -0500 Subject: [PATCH 4/4] Address PR feedback --- .../System/Dynamic/Utils/DelegateHelpers.cs | 198 +++++++++--------- 1 file changed, 98 insertions(+), 100 deletions(-) diff --git a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs index d285e0aebdf38..a56b5f813adec 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Dynamic/Utils/DelegateHelpers.cs @@ -196,132 +196,130 @@ private static Delegate CreateObjectArrayDelegateRefEmit(Type delegateType, Func if (thunkMethod == null) { - IDisposable? forceAllowDynamicCodeScope = null; - if (!RuntimeFeature.IsDynamicCodeSupported) + static IDisposable? CreateForceAllowDynamicCodeScope() { - // Force 'new DynamicMethod' to not throw even though RuntimeFeature.IsDynamicCodeSupported is false. - // If we are running on a runtime that supports dynamic code, even though the feature switch is off, - // for example when running on CoreClr with PublishAot=true, this will allow IL to be emitted. - // If we are running on a runtime that really doesn't support dynamic code, like NativeAOT, - // CanEmitObjectArrayDelegate will be flipped to 'false', and this method won't be invoked. - forceAllowDynamicCodeScope = ForceAllowDynamicCodeLightup.ForceAllowDynamicCodeDelegate?.Invoke(); + if (!RuntimeFeature.IsDynamicCodeSupported) + { + // Force 'new DynamicMethod' to not throw even though RuntimeFeature.IsDynamicCodeSupported is false. + // If we are running on a runtime that supports dynamic code, even though the feature switch is off, + // for example when running on CoreClr with PublishAot=true, this will allow IL to be emitted. + // If we are running on a runtime that really doesn't support dynamic code, like NativeAOT, + // CanEmitObjectArrayDelegate will be flipped to 'false', and this method won't be invoked. + return ForceAllowDynamicCodeLightup.ForceAllowDynamicCodeDelegate?.Invoke(); + } + + return null; } - try - { + using IDisposable? forceAllowDynamicCodeScope = CreateForceAllowDynamicCodeScope(); - int thunkIndex = Interlocked.Increment(ref s_ThunksCreated); - Type[] paramTypes = new Type[parameters.Length + 1]; - paramTypes[0] = typeof(Func); + int thunkIndex = Interlocked.Increment(ref s_ThunksCreated); + Type[] paramTypes = new Type[parameters.Length + 1]; + paramTypes[0] = typeof(Func); - StringBuilder thunkName = new StringBuilder(); - thunkName.Append("Thunk"); - thunkName.Append(thunkIndex); - if (hasReturnValue) - { - thunkName.Append("ret_"); - thunkName.Append(returnType.Name); - } - - for (int i = 0; i < parameters.Length; i++) - { - thunkName.Append('_'); - thunkName.Append(parameters[i].ParameterType.Name); - paramTypes[i + 1] = parameters[i].ParameterType; - } + StringBuilder thunkName = new StringBuilder(); + thunkName.Append("Thunk"); + thunkName.Append(thunkIndex); + if (hasReturnValue) + { + thunkName.Append("ret_"); + thunkName.Append(returnType.Name); + } - DynamicMethod dynamicThunkMethod = new DynamicMethod(thunkName.ToString(), returnType, paramTypes); - thunkMethod = dynamicThunkMethod; - ILGenerator ilgen = dynamicThunkMethod.GetILGenerator(); + for (int i = 0; i < parameters.Length; i++) + { + thunkName.Append('_'); + thunkName.Append(parameters[i].ParameterType.Name); + paramTypes[i + 1] = parameters[i].ParameterType; + } - LocalBuilder argArray = ilgen.DeclareLocal(typeof(object[])); - LocalBuilder retValue = ilgen.DeclareLocal(typeof(object)); + DynamicMethod dynamicThunkMethod = new DynamicMethod(thunkName.ToString(), returnType, paramTypes); + thunkMethod = dynamicThunkMethod; + ILGenerator ilgen = dynamicThunkMethod.GetILGenerator(); - // create the argument array - if (parameters.Length == 0) - { - ilgen.Emit(OpCodes.Call, s_ArrayEmpty); - } - else - { - ilgen.Emit(OpCodes.Ldc_I4, parameters.Length); - ilgen.Emit(OpCodes.Newarr, typeof(object)); - } - ilgen.Emit(OpCodes.Stloc, argArray); + LocalBuilder argArray = ilgen.DeclareLocal(typeof(object[])); + LocalBuilder retValue = ilgen.DeclareLocal(typeof(object)); - // populate object array - bool hasRefArgs = false; - for (int i = 0; i < parameters.Length; i++) - { - bool paramIsByReference = parameters[i].ParameterType.IsByRef; - Type paramType = parameters[i].ParameterType; - if (paramIsByReference) - paramType = paramType.GetElementType()!; + // create the argument array + if (parameters.Length == 0) + { + ilgen.Emit(OpCodes.Call, s_ArrayEmpty); + } + else + { + ilgen.Emit(OpCodes.Ldc_I4, parameters.Length); + ilgen.Emit(OpCodes.Newarr, typeof(object)); + } + ilgen.Emit(OpCodes.Stloc, argArray); - hasRefArgs = hasRefArgs || paramIsByReference; + // populate object array + bool hasRefArgs = false; + for (int i = 0; i < parameters.Length; i++) + { + bool paramIsByReference = parameters[i].ParameterType.IsByRef; + Type paramType = parameters[i].ParameterType; + if (paramIsByReference) + paramType = paramType.GetElementType()!; - ilgen.Emit(OpCodes.Ldloc, argArray); - ilgen.Emit(OpCodes.Ldc_I4, i); - ilgen.Emit(OpCodes.Ldarg, i + 1); + hasRefArgs = hasRefArgs || paramIsByReference; - if (paramIsByReference) - { - ilgen.Emit(OpCodes.Ldobj, paramType); - } - Type boxType = ConvertToBoxableType(paramType); - ilgen.Emit(OpCodes.Box, boxType); - ilgen.Emit(OpCodes.Stelem_Ref); - } + ilgen.Emit(OpCodes.Ldloc, argArray); + ilgen.Emit(OpCodes.Ldc_I4, i); + ilgen.Emit(OpCodes.Ldarg, i + 1); - if (hasRefArgs) + if (paramIsByReference) { - ilgen.BeginExceptionBlock(); + ilgen.Emit(OpCodes.Ldobj, paramType); } + Type boxType = ConvertToBoxableType(paramType); + ilgen.Emit(OpCodes.Box, boxType); + ilgen.Emit(OpCodes.Stelem_Ref); + } - // load delegate - ilgen.Emit(OpCodes.Ldarg_0); + if (hasRefArgs) + { + ilgen.BeginExceptionBlock(); + } - // load array - ilgen.Emit(OpCodes.Ldloc, argArray); + // load delegate + ilgen.Emit(OpCodes.Ldarg_0); + + // load array + ilgen.Emit(OpCodes.Ldloc, argArray); - // invoke Invoke - ilgen.Emit(OpCodes.Callvirt, s_FuncInvoke); - ilgen.Emit(OpCodes.Stloc, retValue); + // invoke Invoke + ilgen.Emit(OpCodes.Callvirt, s_FuncInvoke); + ilgen.Emit(OpCodes.Stloc, retValue); - if (hasRefArgs) + if (hasRefArgs) + { + // copy back ref/out args + ilgen.BeginFinallyBlock(); + for (int i = 0; i < parameters.Length; i++) { - // copy back ref/out args - ilgen.BeginFinallyBlock(); - for (int i = 0; i < parameters.Length; i++) + if (parameters[i].ParameterType.IsByRef) { - if (parameters[i].ParameterType.IsByRef) - { - Type byrefToType = parameters[i].ParameterType.GetElementType()!; - - // update parameter - ilgen.Emit(OpCodes.Ldarg, i + 1); - ilgen.Emit(OpCodes.Ldloc, argArray); - ilgen.Emit(OpCodes.Ldc_I4, i); - ilgen.Emit(OpCodes.Ldelem_Ref); - ilgen.Emit(OpCodes.Unbox_Any, byrefToType); - ilgen.Emit(OpCodes.Stobj, byrefToType); - } + Type byrefToType = parameters[i].ParameterType.GetElementType()!; + + // update parameter + ilgen.Emit(OpCodes.Ldarg, i + 1); + ilgen.Emit(OpCodes.Ldloc, argArray); + ilgen.Emit(OpCodes.Ldc_I4, i); + ilgen.Emit(OpCodes.Ldelem_Ref); + ilgen.Emit(OpCodes.Unbox_Any, byrefToType); + ilgen.Emit(OpCodes.Stobj, byrefToType); } - ilgen.EndExceptionBlock(); - } - - if (hasReturnValue) - { - ilgen.Emit(OpCodes.Ldloc, retValue); - ilgen.Emit(OpCodes.Unbox_Any, ConvertToBoxableType(returnType)); } - - ilgen.Emit(OpCodes.Ret); + ilgen.EndExceptionBlock(); } - finally + + if (hasReturnValue) { - forceAllowDynamicCodeScope?.Dispose(); + ilgen.Emit(OpCodes.Ldloc, retValue); + ilgen.Emit(OpCodes.Unbox_Any, ConvertToBoxableType(returnType)); } + + ilgen.Emit(OpCodes.Ret); } s_thunks[delegateType] = thunkMethod;