Skip to content

Update '[GeneratedComInterface]' to emit ILC-friendly vtables for RVA folding #114468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Tracked by #114179
Sergio0694 opened this issue Apr 10, 2025 · 5 comments
Open
Tracked by #114179

Comments

@Sergio0694
Copy link
Contributor

Now that:

We could optimize the codegen for [GeneratedComInterface] to preinitialize the CCW vtable:

file unsafe partial interface InterfaceImplementation
{
    internal static void** CreateManagedVirtualFunctionTable()
    {
        void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::WindowsRuntime.IWeakReference), sizeof(void*) * 4);
        {
            nint v0, v1, v2;
            global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out v0, out v1, out v2);
            vtable[0] = (void*)v0;
            vtable[1] = (void*)v1;
            vtable[2] = (void*)v2;
        }

        {
            vtable[3] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, global::System.Guid*, void**, int> )&ABI_Resolve;
        }

        return vtable;
    }
}

This could instead be something like this:

file unsafe struct InterfaceImplementationVtable
{
    public delegate* unmanaged[MemberFunction]<void*, Guid*, void**, int> QueryInterface;
    public delegate* unmanaged[MemberFunction]<void*, uint> AddRef;
    public delegate* unmanaged[MemberFunction]<void*, uint> Release;
    public delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, global::System.Guid*, void**, int> Resolve;
}

file static class InterfaceImplementationVtableImpl
{
    [FixedAddressValueType]
    public static readonly InterfaceImplementationVtable Vtable;

    static InterfaceImplementationVtableImpl()
    {
        ComWrappers.GetIUnknownImpl(
            fpQueryInterface: out *(nint*)&((InterfaceImplementationVtable*)Unsafe.AsPointer(ref Vtbl))->QueryInterface,
            fpAddRef: out *(nint*)&((InterfaceImplementationVtable*)Unsafe.AsPointer(ref Vtbl))->AddRef,
            fpRelease: out *(nint*)&((InterfaceImplementationVtable*)Unsafe.AsPointer(ref Vtbl))->Release);

        Vtable.Resolve = &InterfaceImplementation.ABI_Resolve;
    }
}

file unsafe partial interface InterfaceImplementation
{
    internal static void** CreateManagedVirtualFunctionTable()
    {
        return (void**)Unsafe.AsPointer(in InterfaceImplementationVtableImpl.Vtable);
    }
}

This would be fully foldable on Native AOT, so that it gets:

  • No overhead to initialize it
  • No allocation at runtime
  • On Windows, it goes into a readonly PE section (pending)

cc. @jkoritzinsky

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Apr 10, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented Apr 10, 2025

Can InterfaceImplementationVtable and InterfaceImplementationVtableImpl be a single type?

This would be fully foldable on Native AOT, so that it gets:

This is better for native AOT and worse for JIT-based runtimes (more type loading). I think it is a fine tradeoff to take, just making sure that it is understood.

@Sergio0694
Copy link
Contributor Author

"Can InterfaceImplementationVtable and InterfaceImplementationVtableImpl be a single type?"

I wasn't sure whether that interface would have other static field initializers potentially interfering with ILC (I think it has one for Iid?), so I put it in a separate type, but it was only meant as an example. I'll defer to you, Jeremy, and other interop folks to come up with whatever design is preferred there. As long as we add a test in the NAOT outerloop preinitialization tests to make sure things get preinitialized correctly, I think it wouldn't matter anyway 🙂

@AaronRobinsonMSFT AaronRobinsonMSFT added this to the Future milestone Apr 10, 2025
@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Apr 10, 2025
@Sergio0694
Copy link
Contributor Author

Double checked, I actually mixed things up with InterfaceInformation:

Image

This has the cctor, but InterfaceImplementation doesn't. So yeah we could just put the vtable there and save 1 type.

Also with the new codegen we can get rid of the lazy-init of the vtable in InterfaceInformation, as it's no longer needed 🙂

@dongle-the-gadget
Copy link

Could we move a bit further and remove the CreateManagedVirtualFunctionTable method itself? This is considering that since the method is an implementation detail that no one other than the source generator depends on it, and since the source generator would need to be updated anyways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests

4 participants