diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs index 4c02d9abbb8056..ee3f6a3e46fb05 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs @@ -193,17 +193,23 @@ public override void EmitCalli(OpCode opcode, // If there is a non-void return type, push one. if (returnType != typeof(void)) stackchange++; + // Pop off arguments if any. if (parameterTypes != null) stackchange -= parameterTypes.Length; + // Pop off vararg arguments. if (optionalParameterTypes != null) stackchange -= optionalParameterTypes.Length; - // Pop the this parameter if the method has a this parameter. - if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) + + // Pop the this parameter if the method has an implicit this parameter. + if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis && + (callingConvention & CallingConventions.ExplicitThis) == 0) stackchange--; + // Pop the native function pointer. stackchange--; + UpdateStackSize(OpCodes.Calli, stackchange); int token = GetTokenForSig(sig.GetSignature(true)); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs index 8976805080d145..b2f0814ca64db3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs @@ -536,17 +536,23 @@ public override void EmitCalli(OpCode opcode, CallingConventions callingConventi // If there is a non-void return type, push one. if (returnType != typeof(void)) stackchange++; + // Pop off arguments if any. if (parameterTypes != null) stackchange -= parameterTypes.Length; + // Pop off vararg arguments. if (optionalParameterTypes != null) stackchange -= optionalParameterTypes.Length; - // Pop the this parameter if the method has a this parameter. - if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) + + // Pop the this parameter if the method has an implicit this parameter. + if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis && + (callingConvention & CallingConventions.ExplicitThis) == 0) stackchange--; + // Pop the native function pointer. stackchange--; + UpdateStackSize(OpCodes.Calli, stackchange); RecordTokenFixup(); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs index c5bb88cf52503d..a117d29a3857c3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs @@ -64,11 +64,12 @@ internal static SignatureHelper GetMethodSigHelper( intCall |= MdSigCallingConvention.Generic; } - if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) - intCall |= MdSigCallingConvention.HasThis; + const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); + intCall = (MdSigCallingConvention)((byte)intCall | (unchecked((byte)callingConvention) & Mask)); sigHelp = new SignatureHelper(scope, intCall, cGenericParam, returnType, - requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); + requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); + sigHelp.AddArguments(parameterTypes, requiredParameterTypeCustomModifiers, optionalParameterTypeCustomModifiers); return sigHelp; @@ -151,11 +152,12 @@ public static SignatureHelper GetPropertySigHelper(Module? mod, CallingConventio MdSigCallingConvention intCall = MdSigCallingConvention.Property; - if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) - intCall |= MdSigCallingConvention.HasThis; + const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); + intCall = (MdSigCallingConvention)((byte)intCall | (unchecked((byte)callingConvention) & Mask)); sigHelp = new SignatureHelper(mod, intCall, returnType, requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); + sigHelp.AddArguments(parameterTypes, requiredParameterTypeCustomModifiers, optionalParameterTypeCustomModifiers); return sigHelp; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 23ceed3cde357d..f9aef1cd0064cd 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -6256,6 +6256,11 @@ void Compiler::impPopCallArgs(CORINFO_SIG_INFO* sig, GenTreeCall* call) else { arg = NewCallArg::Primitive(argNode, jitSigType); + + if (i == 1 && (sig->callConv & CORINFO_CALLCONV_EXPLICITTHIS)) + { + arg = arg.WellKnown(WellKnownArg::ThisPointer); + } } call->gtArgs.PushFront(this, arg); diff --git a/src/libraries/System.Reflection.Emit.Lightweight/tests/DynamicILInfoTests.cs b/src/libraries/System.Reflection.Emit.Lightweight/tests/DynamicILInfoTests.cs index 4ad96dac7ed091..ee20b3ee5a802c 100644 --- a/src/libraries/System.Reflection.Emit.Lightweight/tests/DynamicILInfoTests.cs +++ b/src/libraries/System.Reflection.Emit.Lightweight/tests/DynamicILInfoTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Runtime.InteropServices; using Xunit; namespace System.Reflection.Emit.Tests @@ -695,6 +694,64 @@ public void GetDynamicILInfo_NotSameNotNull(bool skipVisibility) Assert.Equal(method, dynamicILInfo.DynamicMethod); } + [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/113789", TestRuntimes.Mono)] + [InlineData(true)] + [InlineData(false)] + public unsafe void InstanceBasedFunctionPointer(bool useExplicitThis) + { + Func generatedMethodToCall = GenerateDynamicMethod(useExplicitThis); + + // Call the property getter through the dynamic method. + IntPtr fn = typeof(MyClassWithGuidProperty).GetProperty(nameof(MyClassWithGuidProperty.MyGuid))!.GetGetMethod().MethodHandle.GetFunctionPointer(); + Guid guid = Guid.NewGuid(); + MyClassWithGuidProperty obj = new(guid); + Assert.Equal(guid, generatedMethodToCall(obj, fn)); + + static Func GenerateDynamicMethod(bool useExplicitThis) + { + DynamicMethod dynamicMethod = new DynamicMethod( + "GetGuid", + returnType: typeof(Guid), + + // In this test, we use typeof(object) for the "this" pointer to ensure the IL could be re-used for other + // reference types, but normally this would be the appropriate type such as typeof(MyClassWithGuidProperty). + parameterTypes: [typeof(object), typeof(IntPtr)], + + typeof(object).Module, + skipVisibility: false); + + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); // this + il.Emit(OpCodes.Ldarg_1); // fn + + if (useExplicitThis) + { + il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis | CallingConventions.ExplicitThis, + returnType: typeof(Guid), parameterTypes: [typeof(object)], null); + } + else + { + il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, + returnType: typeof(Guid), parameterTypes: null, null); + } + + il.Emit(OpCodes.Ret); + + return dynamicMethod.CreateDelegate>(); + } + } + + private class MyClassWithGuidProperty + { + public MyClassWithGuidProperty(Guid guid) + { + MyGuid = guid; + } + + public Guid MyGuid { get; init; } + } + private DynamicMethod GetDynamicMethod(bool skipVisibility) { return new DynamicMethod(nameof(DynamicMethod), typeof(void), diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index 82416e72599516..d8f3445eb2434f 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -699,8 +699,10 @@ public override void EmitCalli(OpCode opcode, CallingConventions callingConventi { stackChange -= optionalParameterTypes.Length; } + // Pop the this parameter if the method has a this parameter. - if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis) + if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis && + (callingConvention & CallingConventions.ExplicitThis) == 0) { stackChange--; } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index 02663bfa182d8d..26d43dbaa1083e 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -800,15 +800,19 @@ private BlobBuilder GetMethodArrayMethodSignature(ArrayMethod method) => Metadat private static bool IsInstance(CallingConventions callingConvention) => callingConvention.HasFlag(CallingConventions.HasThis) || callingConvention.HasFlag(CallingConventions.ExplicitThis) ? true : false; - internal static SignatureCallingConvention GetSignatureConvention(CallingConventions callingConvention) + internal static SignatureCallingConvention GetSignatureConvention(CallingConventions callingConventions) { SignatureCallingConvention convention = SignatureCallingConvention.Default; - if ((callingConvention & CallingConventions.VarArgs) != 0) + if ((callingConventions & CallingConventions.VarArgs) != 0) { convention = SignatureCallingConvention.VarArgs; } + // CallingConventions.HasThis (0x20) and ExplicitThis (0x40) can use a bitwise OR with SignatureCallingConvention. + const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); + convention = (SignatureCallingConvention)((byte)convention | (unchecked((byte)callingConventions) & Mask)); + return convention; } diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveAssemblyBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveAssemblyBuilderTests.cs index 45f926b1a67bd7..256039267a529c 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveAssemblyBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveAssemblyBuilderTests.cs @@ -303,6 +303,90 @@ public void AssemblyWithDifferentTypes() } } + [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/113789", TestRuntimes.Mono)] + [InlineData(true)] + [InlineData(false)] + public unsafe void AssemblyWithInstanceBasedFunctionPointer(bool useExplicitThis) + { + byte[] assemblyData = GenerateMethodInPersistedAssembly(useExplicitThis); + + using MemoryStream stream = new MemoryStream(assemblyData); + TestAssemblyLoadContext testAssemblyLoadContext = new(); + + // Verify the assembly is properly generated and the runtime supports the IL. + try + { + Assembly assembly = testAssemblyLoadContext.LoadFromStream(stream); + Type generatedType = assembly.GetType("MyGeneratedType")!; + Assert.NotNull(generatedType); + MethodInfo generatedMethod = generatedType.GetMethod("GetGuid")!; + Assert.NotNull(generatedMethod); + var generatedMethodToCall = generatedMethod.CreateDelegate>(); + + // Call the property getter through the generated method. + IntPtr fn = typeof(MyClassWithGuidProperty).GetProperty(nameof(MyClassWithGuidProperty.MyGuid))!.GetGetMethod().MethodHandle.GetFunctionPointer(); + Guid guid = Guid.NewGuid(); + MyClassWithGuidProperty obj = new (guid); + Assert.Equal(guid, generatedMethodToCall(obj, fn)); + } + finally + { + testAssemblyLoadContext.Unload(); + } + + static unsafe byte[] GenerateMethodInPersistedAssembly(bool useExplicitThis) + { + AssemblyName assemblyName = new("MyAssembly"); + PersistedAssemblyBuilder ab = new(assemblyName, typeof(object).Assembly); + ModuleBuilder moduleBuilder = ab.DefineDynamicModule("MyModule"); + + // Define a type with a method that calls the instance-based function pointer through calli. + TypeBuilder typeBuilder = moduleBuilder.DefineType("MyGeneratedType", TypeAttributes.Public | TypeAttributes.Class); + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + "GetGuid", + MethodAttributes.Public | MethodAttributes.Static, + returnType: typeof(Guid), + + // In this test, we use typeof(object) for the "this" pointer to ensure the IL could be re-used for other + // reference types, but normally this would be the appropriate type such as typeof(MyClassWithGuidProperty). + parameterTypes: [typeof(object), typeof(IntPtr)]); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); // this + il.Emit(OpCodes.Ldarg_1); // fn + + if (useExplicitThis) + { + il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis | CallingConventions.ExplicitThis, + returnType: typeof(Guid), parameterTypes: [typeof(object)], null); + } + else + { + il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, + returnType: typeof(Guid), parameterTypes: null, null); + } + + il.Emit(OpCodes.Ret); + typeBuilder.CreateType(); + MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder _); + ManagedPEBuilder peBuilder = new ManagedPEBuilder(PEHeaderBuilder.CreateLibraryHeader(), new MetadataRootBuilder(metadataBuilder), ilStream); + BlobBuilder blob = new BlobBuilder(); + peBuilder.Serialize(blob); + return blob.ToArray(); + } + } + + private class MyClassWithGuidProperty + { + public MyClassWithGuidProperty(Guid guid) + { + MyGuid = guid; + } + + public Guid MyGuid { get; init; } + } + void CheckCattr(IList attributes) { CustomAttributeData cattr = attributes.First(a => a.AttributeType.Name == nameof(AttributeUsageAttribute)); diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 598d0516beb0f4..0552a0ae8f6473 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent) true true + true diff --git a/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderDefineConstructor.cs b/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderDefineConstructor.cs index e58047416b3c75..5adeb7f44fb3fb 100644 --- a/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderDefineConstructor.cs +++ b/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderDefineConstructor.cs @@ -27,7 +27,6 @@ public static IEnumerable TestData() // Ignores any CallingConventions, sets to CallingConventions.Standard yield return new object[] { MethodAttributes.Public, new Type[0], CallingConventions.Any }; - yield return new object[] { MethodAttributes.Public, new Type[0], CallingConventions.ExplicitThis }; yield return new object[] { MethodAttributes.Public, new Type[0], CallingConventions.HasThis }; yield return new object[] { MethodAttributes.Public, new Type[0], CallingConventions.Standard }; yield return new object[] { MethodAttributes.Public, new Type[0], CallingConventions.VarArgs }; @@ -137,6 +136,22 @@ public void DefineConstructor_HasThisCallingConventionsForStaticMethod_ThrowsTyp Assert.Throws(() => type.CreateTypeInfo()); } + [Fact] + public void DefineConstructor_ExplicitThisCallingConventionsForInstanceMethod_ThrowsTypeLoadExceptionOnCreation() + { + TypeBuilder type = Helpers.DynamicType(TypeAttributes.Public); + + ConstructorBuilder constructor = type.DefineConstructor( + MethodAttributes.Public, + CallingConventions.HasThis | CallingConventions.ExplicitThis, + new Type[0]); + + constructor.GetILGenerator().Emit(OpCodes.Ret); + + // ExplicitThis is not valid on definitions; it is only used when calling. + Assert.Throws(() => type.CreateTypeInfo()); + } + [Fact] public void GetConstructor_TypeNotCreated_ThrowsNotSupportedException() {