Skip to content

PersistedAssemblyBuilder not emitting calling convention for Calli + CallingConventions.HasThis #113626

@steveharter

Description

@steveharter

While experimenting with the Calli opcode, it appears JIT compilation is more restrictive using a physical assembly vs. a DynamicMethod compilation, and there is not a good diagnostic message to explain why.

The sample below for DynamicMethod works, while the same IL using PersistedAssemblyBuilder fails:

GenerateDynamicMethod Result:2ca1c70b-eefc-4287-96a1-653bd93dff77
GenerateMethodInPersistedAssembly Result:Unhandled exception. System.InvalidProgramException: Common Language Runtime detected an invalid program.
   at MyType.GetGuid(MyClass, IntPtr)

The sample attempts to pass an object instance and a function pointer to a generated method, which then invokes the object's method via the function pointer via calli.

The IL of the generated method from the persisted assembly:

.method public static valuetype [System.Private.CoreLib]System.Guid 
        GetGuid(class [ConsoleApp351]ConsoleApp351.MyClass A_0,
                native int A_1) cil managed
{
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  calli      valuetype [System.Private.CoreLib]System.Guid()
  IL_0007:  ret
} // end of method MyType::GetGuid
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;

namespace ConsoleApp
{
    class Program
    {
        static unsafe void Main()
        {
            IntPtr fn = typeof(MyClass).GetProperty("MYGUID")!.GetGetMethod()!.MethodHandle.GetFunctionPointer();
            MyClass obj = new MyClass();

            // using "object" instead of "MyClass" should also work for the param type, but just use "MyClass" for now.

            // With dynamic method
            {
                Console.Write($"GenerateDynamicMethod Result:");
                Func<MyClass, IntPtr, Guid> callMe = GenerateDynamicMethod();
                Guid result = callMe(obj, fn);
                Console.WriteLine(result);
            }

            // With persisted assembly
            {
                Console.Write($"GenerateMethodInPersistedAssembly Result:");
                Func<MyClass, IntPtr, Guid> callMe = GenerateMethodInPersistedAssembly();
                Guid result = callMe(obj, fn);
                Console.WriteLine(result);
            }
        }

        static unsafe Func<MyClass, IntPtr, Guid> GenerateDynamicMethod()
        {
            DynamicMethod dynamicMethod = new DynamicMethod(
                "GetGuid",
                returnType: typeof(Guid),
                parameterTypes: new Type[] { typeof(MyClass), typeof(IntPtr) },
                typeof(Program).Module,
                skipVisibility: false);

            
            ILGenerator il = dynamicMethod.GetILGenerator();
            EmitCall(il);

            return (Func<MyClass, IntPtr, Guid>)dynamicMethod.CreateDelegate(typeof(Func<MyClass, IntPtr, Guid>));
        }

        static unsafe Func<MyClass, IntPtr, Guid> GenerateMethodInPersistedAssembly()
        {
            AssemblyName assemblyName = new ("MyAssembly");
            PersistedAssemblyBuilder persistedAssemblyBuilder = new (assemblyName, typeof(object).Assembly);
            ModuleBuilder moduleBuilder = persistedAssemblyBuilder.DefineDynamicModule("MyModule");
            TypeBuilder typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                "GetGuid",
                MethodAttributes.Public | MethodAttributes.Static,
                returnType: typeof(Guid),
                parameterTypes: new Type[] { typeof(MyClass), typeof(IntPtr)});

            ILGenerator il = methodBuilder.GetILGenerator();
            EmitCall(il);

            // Create the type
            typeBuilder.CreateType();

            // Save the assembly to a file
            string filePath = "MyAssembly.dll";
            using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
            persistedAssemblyBuilder.Save(fileStream);
            fileStream.Close();

            // Load the assembly from the file
            Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(filePath));

            // Get the method
            return (Func<MyClass, IntPtr, Guid>)
                assembly.GetType("MyType")!.
                GetMethod("GetGuid")!.
                CreateDelegate(typeof(Func<MyClass, IntPtr, Guid>));
        }

        static void EmitCall(ILGenerator il)
        {
            il.Emit(OpCodes.Ldarg_0); // this
            il.Emit(OpCodes.Ldarg_1); // fn
            il.EmitCalli(OpCodes.Calli, CallingConventions.HasThis, typeof(Guid), parameterTypes: null, null);
            il.Emit(OpCodes.Ret);
        }
    }

    public class MyClass
    {
        private Guid _guid = new Guid("{2CA1C70B-EEFC-4287-96A1-653BD93DFF77}");

        public Guid MYGUID
        {
            get => _guid;
            set => _guid = value;
        }
    }
}

Metadata

Metadata

Assignees

Labels

area-System.Reflection.Emitin-prThere is an active PR which will close this issue when it is merged

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions