Skip to content

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

Closed
@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

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions