Skip to content

Support precompiling CCW vtables in ILC cctor interpreter (Native AOT) #114024

@Sergio0694

Description

@Sergio0694

Related to #79204

As we're migrating the Microsoft Store to Native AOT, and also adopting Native AOT in Windows components, we're investigating all opportunities to improve performance and reduce size, as well as regressions from things that were handled better on .NET Native. One area that seems to be quite impactful due to the way the WinRT projections and interop stack work, is the codegen around CCW vtables. Currently, ILC is not able to fold these into constant blobs, meaning that we pay the initialization cost + the static constructor (and the cctor checks on every access) for:

  • Every single IDIC implementation (ie. for every single projected interface)
  • Every single statically visible generic instantiation (for async tasks, delegate types, collection types)

These add up to thousands of types, so there's a pretty good opportunity for improvements. .NET Native handled this via some special logic to initialize constant blobs for vtables, which Native AOT doesn't have. However, my thinking is that we could simply extend the support in ILC to interpret static constructor by making a couple of required APIs intrinsics, and making sure it can recognize common patterns.

For APIs that would need to be handled as intrinsics by ILC:

Next to this, it should also be able to handle common patterns around:

  • Copying constant vtables (from other pre-initialized vtable pointers) to memory locations
  • Assigning [UnmanagedCallersOnly] method addresses to vtable slots

Ideally, ILC should be able to fold all of these vtables into a constant blob (like an RVA span field), and trim the cctor entirely.

Our expectation is that this should be doable because Native AOT can rely on the fact that:

  • Types aren't unloaded (so it doesn't matter where each vtable is allocated, it can be any memory)
  • The address of all [UnmanagedCallersOnly] method (or, any method in general) should be a constant
  • The address of each entry from ComWrappers.GetIUnknownImpl should also be a constant that can be hardcoded by ILC

Common patterns

I've prepared snippet with the common patterns we'd need handling:

  • IUnknown vtable
  • Vtables built by directly assigning to vtable offsets
  • Vtables built by assigning through a vtable type
CCW patterns (click to expand)
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#pragma warning disable

// 1) Base IUnknown vtable
internal static unsafe class IUnknownImpl
{
    public static nint AbiToProjectionVftablePtr { get; } = GetAbiToProjectionVftablePtr();

    private static nint GetAbiToProjectionVftablePtr()
    {
        IUnknownVftbl* vftbl = (IUnknownVftbl*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IUnknownImpl), sizeof(IUnknownVftbl));

        ComWrappers.GetIUnknownImpl(
            fpQueryInterface: out *(nint*)&vftbl->QueryInterface,
            fpAddRef: out *(nint*)&vftbl->AddRef,
            fpRelease: out *(nint*)&vftbl->Release);

        return (nint)vftbl;
    }
}

// 2) Example vtable with direct assignments
internal static unsafe class IInspectableImpl1
{
    public static nint AbiToProjectionVftablePtr { get; } = GetAbiToProjectionVftablePtr();

    private static nint GetAbiToProjectionVftablePtr()
    {
        void** vftbl = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IInspectableImpl1), sizeof(void*) * 6);

        *(IUnknownVftbl*)vftbl = *(IUnknownVftbl*)IUnknownImpl.AbiToProjectionVftablePtr;

        vftbl[3] = (delegate* unmanaged[MemberFunction]<void*, uint*, Guid**, int>)&GetIids;
        vftbl[4] = (delegate* unmanaged[MemberFunction]<void*, nint*, int>)&GetRuntimeClassName;
        vftbl[5] = (delegate* unmanaged[MemberFunction]<void*, int*, int>)&GetTrustLevel;

        return (nint)vftbl;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetIids(void* thisPtr, uint* iidCount, Guid** iids)
    {
        *iidCount = 0;
        *iids = null;

        return 0;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetRuntimeClassName(void* thisPtr, nint* className)
    {
        *className = default;

        return 0;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetTrustLevel(void* thisPtr, int* trustLevel)
    {
        *trustLevel = 0;

        return 0;
    }
}

// 3) Example vtable with vtable assignments
internal static unsafe class IInspectableImpl2
{
    public static nint AbiToProjectionVftablePtr { get; } = GetAbiToProjectionVftablePtr();

    private static nint GetAbiToProjectionVftablePtr()
    {
        IInspectableVftbl* vftbl = (IInspectableVftbl*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IInspectableImpl2), sizeof(IInspectableVftbl));

        *(IUnknownVftbl*)vftbl = *(IUnknownVftbl*)IUnknownImpl.AbiToProjectionVftablePtr;

        vftbl->GetIids = &GetIids;
        vftbl->GetRuntimeClassName = &GetRuntimeClassName;
        vftbl->GetTrustLevel = &GetTrustLevel;

        return (nint)vftbl;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetIids(void* thisPtr, uint* iidCount, Guid** iids)
    {
        *iidCount = 0;
        *iids = null;

        return 0;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetRuntimeClassName(void* thisPtr, nint* className)
    {
        *className = default;

        return 0;
    }

    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
    private static int GetTrustLevel(void* thisPtr, int* trustLevel)
    {
        *trustLevel = 0;

        return 0;
    }
}

public unsafe struct IUnknownVftbl
{
    public delegate* unmanaged[MemberFunction]<void*, Guid*, void**, int> QueryInterface;
    public delegate* unmanaged[MemberFunction]<void*, uint> AddRef;
    public delegate* unmanaged[MemberFunction]<void*, uint> Release;
}

internal unsafe struct IInspectableVftbl
{
    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]<void*, uint*, Guid**, int> GetIids;
    public delegate* unmanaged[MemberFunction]<void*, nint*, int> GetRuntimeClassName;
    public delegate* unmanaged[MemberFunction]<void*, int*, int> GetTrustLevel;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions