Skip to content

Expand ILLink/ILC support for 'eliminate dead branches around typeof comparisons' to work on internal types too #110300

@Sergio0694

Description

@Sergio0694

Overview

This is a follow up to #102248 which primarily affects CsWinRT. In order to support marshalling types we don't own (meaning we cannot attach a vtable to them via an attribute), the AOT generators in CsWinRT also generate a global lookup table with all necessary vtables for types it has seen as possibly used across the ABI boundary, in a given project. This looks something like this:

internal static class GlobalVtableLookup
{
    [ModuleInitializer]
    internal static void InitializeGlobalVtableLookup()
    {
    	ComWrappersSupport.RegisterTypeComInterfaceEntriesLookup(LookupVtableEntries);
    	ComWrappersSupport.RegisterTypeRuntimeClassNameLookup(LookupRuntimeClassName);
    }
    
    private static ComWrappers.ComInterfaceEntry[] LookupVtableEntries(System.Type type)
    {
    	switch (type.ToString())
    	{
    	case "System.Collections.Generic.Dictionary`2[System.String,System.String]":
    	case "System.Collections.ObjectModel.ReadOnlyDictionary`2[System.String,System.String]":
                // Bunch of initialization...
                return TheVTableForThisType();
    	case "System.ComponentModel.DataAnnotations.ValidationResult[]":
                // Bunch of initialization...
                return TheVTableForThisType();
    	case "ABI.System.Collections.Generic.ToAbiEnumeratorAdapter`1[System.Collections.IList]":
                // Bunch of initialization...
                return TheVTableForThisType();
    	// ...
    	default:
               return null;
    	}
    }
    
    // 'LookupRuntimeClassName' here, which is kinda similar but returns type names instead
}

This works just fine, but we noticed the linker isn't able to remove all branches for types that are not constructed.

Repro steps

  1. Create a blank .NET 9 project like this:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <InvariantGlobalization>true</InvariantGlobalization>
    <CsWinRTAotWarningLevel>2</CsWinRTAotWarningLevel>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <IlcGenerateMstatFile>true</IlcGenerateMstatFile>
    <IlcGenerateDgmlFile>true</IlcGenerateDgmlFile>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0-preview3" />
  </ItemGroup>
</Project>
  1. Paste this code:
using CommunityToolkit.Mvvm.ComponentModel;

Console.WriteLine(new MyViewModel());

public sealed partial class MyViewModel : ObservableObject
{
}
  1. Publish with dotnet publish -r win-x64 .\ConsoleApp13.csproj

Here's what we see in sizoscope:

Image

That is, sizoscope isn't able to trim branches for types not constructed when checked via the type name.

Note

We cannot do GetType() == typeof() checks, because that would not work with internal types (which we also need to handle).

Proposal

We need a generalized way for this to work in such a way that we can also leverage this for internal types. For instance, either:

  • Making ILLink/ILC also trim branches just comparing the fully qualified type name
  • Making ILLink/ILC support this via some magic intrinsic (eg. GetType() == Type.GetType("The.Type.Name")
  • Something else..?

To clarify, the issue is internal types from other assemblies, ie. such that we can't just do typeof(TheType) in code.

cc. @MichalStrehovsky

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions