Skip to content

Implement trimming support for the Interop Type Map #116555

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

Merged
merged 19 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0bb15a6
Add handler to mark type map custom attributes when the triggering ty…
jkoritzinsky Jun 9, 2025
680a073
Reimplement as part of MarkStep.
jkoritzinsky Jun 10, 2025
ed28bab
Don't special-case type map attributes in AddDependenciesDueToCustomA…
jkoritzinsky Jun 11, 2025
55e9d2b
Don't emit the warning for RUC on the assembly attributes from the tr…
jkoritzinsky Jun 11, 2025
63c3153
PR feedback
jkoritzinsky Jun 20, 2025
7509663
Fix typo
jkoritzinsky Jun 20, 2025
8af7a56
Adjust test
jkoritzinsky Jun 20, 2025
fc21a24
Address PR feedback
jtschuster Jun 25, 2025
353a64f
Merge branch 'main' of https://github.com/dotnet/runtime into typemap…
jtschuster Jun 25, 2025
3d8bfcf
Remove HasGenericParameterCount(), re-add MarkStep.TypeMapHandler
jtschuster Jun 25, 2025
ad2416f
Format TypeMapHandler, remove HasGenericParameterCount
jtschuster Jun 25, 2025
c6ce9c3
Add test for kept type with no type checks and make update code to pr…
jtschuster Jun 25, 2025
d266a5a
Always root when null trim target
jkoritzinsky Jul 1, 2025
cb0711c
Add a use of TTypeMapGroup to ensure it's kept.
jkoritzinsky Jul 1, 2025
ab3cc41
Update spec and add tests for all cases in the spec.
jkoritzinsky Jul 2, 2025
68f10e2
Rewrite how we suppress the warning in the linker to really tightly s…
jkoritzinsky Jul 7, 2025
347bfde
Update compat suppressions
jkoritzinsky Jul 8, 2025
348b539
update target type names
jkoritzinsky Jul 8, 2025
7dca0b7
Remove accidentally added test code
jkoritzinsky Jul 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ internal partial ParameterProxy GetParameter(ParameterIndex index)
internal partial bool HasGenericParameters() => Method.HasInstantiation;

internal partial bool HasGenericParametersCount(int genericParameterCount) => Method.Instantiation.Length == genericParameterCount;
internal partial bool HasGenericArgumentsCount(int genericArgumentCount) => Method.Instantiation.Length == genericArgumentCount;

internal partial ImmutableArray<GenericParameterProxy> GetGenericParameters()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe
{
MethodDesc constructor = module.GetMethod(attribute.Constructor);

if (TypeMapManager.LookupTypeMapType(constructor.OwningType) != TypeMapManager.TypeMapAttributeKind.None)
continue;

if (!mdManager.GeneratesAttributeMetadata(constructor.OwningType))
continue;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<linker>
<!-- Type Map attributes will be preserved by NativeAOT or the trimmer when necessary. -->
<assembly fullname="*">
<type fullname="System.Runtime.InteropServices.TypeMapAttribute`1">
<attribute internal="RemoveAttributeInstances" />
</type>
<type fullname="System.Runtime.InteropServices.TypeMapAssociationAttribute`1">
<attribute internal="RemoveAttributeInstances" />
</type>
<type fullname="System.Runtime.InteropServices.TypeMapAssemblyTargetAttribute`1">
<attribute internal="RemoveAttributeInstances" />
</type>
</assembly>
<!-- The following attributes are only necessary when debugging is supported -->
<assembly fullname="System.Private.CoreLib" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false">
<type fullname="System.Diagnostics.DebuggableAttribute">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal readonly partial struct MethodProxy

internal partial bool HasGenericParametersCount (int genericParameterCount) => Method.TypeParameters.Length == genericParameterCount;

internal partial bool HasGenericArgumentsCount (int genericArgumentCount) => Method.TypeArguments.Length == genericArgumentCount;

internal partial ImmutableArray<GenericParameterProxy> GetGenericParameters ()
{
if (Method.TypeParameters.IsEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,13 @@ public static IntrinsicId GetIntrinsicIdForMethod (MethodProxy calledMethod)
// static System.Runtime.InteropServices.TypeMapping.GetOrCreateExternalTypeMapping<T> ()
"GetOrCreateExternalTypeMapping" when calledMethod.IsDeclaredOnType ("System.Runtime.InteropServices.TypeMapping")
&& calledMethod.IsStatic ()
&& calledMethod.HasGenericParametersCount (1)
&& calledMethod.HasGenericArgumentsCount (1)
=> IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping,

// static System.Runtime.InteropServices.TypeMapping.GetOrCreateProxyTypeMapping<T> ()
"GetOrCreateProxyTypeMapping" when calledMethod.IsDeclaredOnType ("System.Runtime.InteropServices.TypeMapping")
&& calledMethod.IsStatic ()
&& calledMethod.HasGenericParametersCount (1)
&& calledMethod.HasGenericArgumentsCount (1)
=> IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping,

_ => IntrinsicId.None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal bool HasParameterOfType (ParameterIndex parameterIndex, string fullType
internal partial bool HasGenericParameters ();
internal partial bool HasGenericParametersCount (int genericParameterCount);
internal partial ImmutableArray<GenericParameterProxy> GetGenericParameters ();
internal partial bool HasGenericArgumentsCount (int genericArgumentCount);
internal partial bool IsConstructor ();
internal partial bool IsStatic ();
internal partial bool HasImplicitThis ();
Expand Down
16 changes: 16 additions & 0 deletions src/tools/illink/src/linker/Linker.Dataflow/HandleCallAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,22 @@ private partial bool TryHandleIntrinsic (
}
break;

case IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping:
case IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping: {
GenericInstanceMethod method = ((GenericInstanceMethod) calledMethod.Method);
if (method.GenericArguments[0].ContainsGenericParameter) {
_diagnosticContext.AddDiagnostic (DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined, method.GenericArguments[0].FullName);
return true;
}

if (intrinsicId == IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping) {
_markStep.TypeMapHandler.ProcessExternalTypeMapGroupSeen (_callingMethodDefinition, method.GenericArguments[0]);
} else {
_markStep.TypeMapHandler.ProcessProxyTypeMapGroupSeen (_callingMethodDefinition, method.GenericArguments[0]);
}
}
break;

// Note about Activator.CreateInstance<T>
// There are 2 interesting cases:
// - The generic argument for T is either specific type or annotated - in that case generic instantiation will handle this
Expand Down
2 changes: 2 additions & 0 deletions src/tools/illink/src/linker/Linker.Dataflow/MethodProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal partial ParameterProxyEnumerable GetParameters ()

internal partial bool HasGenericParametersCount (int genericParameterCount) => Method.GenericParameters.Count == genericParameterCount;

internal partial bool HasGenericArgumentsCount (int genericArgumentCount) => Method is GenericInstanceMethod generic && generic.GenericArguments.Count == genericArgumentCount;

internal partial ImmutableArray<GenericParameterProxy> GetGenericParameters ()
{
if (!Method.HasGenericParameters)
Expand Down
26 changes: 23 additions & 3 deletions src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ protected LinkContext Context {
// method body scanner.
readonly Dictionary<MethodBody, bool> _compilerGeneratedMethodRequiresScanner;

TypeMapHandler _typeMapHandler;

MarkStepContext? _markContext;
MarkStepContext MarkContext {
get {
Expand All @@ -90,6 +92,8 @@ internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeH
}
}

internal TypeMapHandler TypeMapHandler => _typeMapHandler;

#if DEBUG
static readonly DependencyKind[] _entireTypeReasons = new DependencyKind[] {
DependencyKind.AccessedViaReflection,
Expand Down Expand Up @@ -221,6 +225,7 @@ public MarkStep ()
_pending_isinst_instr = new List<(TypeDefinition, MethodBody, Instruction)> ();
_entireTypesMarked = new HashSet<TypeDefinition> ();
_compilerGeneratedMethodRequiresScanner = new Dictionary<MethodBody, bool> ();
_typeMapHandler = new TypeMapHandler ();
}

public AnnotationStore Annotations => Context.Annotations;
Expand All @@ -244,6 +249,12 @@ protected virtual void Initialize ()
InitializeCorelibAttributeXml ();
Context.Pipeline.InitializeMarkHandlers (Context, MarkContext);

if (Annotations.GetEntryPointAssembly() is AssemblyDefinition entryPoint) {
_typeMapHandler = new TypeMapHandler (entryPoint);
}

_typeMapHandler.Initialize (Context, this);

ProcessMarkedPending ();
}

Expand Down Expand Up @@ -1059,7 +1070,7 @@ void LazyMarkCustomAttributes (ICustomAttributeProvider provider)
}
}

protected virtual void MarkCustomAttribute (CustomAttribute ca, in DependencyInfo reason, MessageOrigin origin)
protected internal virtual void MarkCustomAttribute (CustomAttribute ca, in DependencyInfo reason, MessageOrigin origin)
{
Annotations.Mark (ca, reason);
MarkMethod (ca.Constructor, new DependencyInfo (DependencyKind.AttributeConstructor, ca), origin);
Expand Down Expand Up @@ -1918,6 +1929,8 @@ internal void MarkStaticConstructorVisibleToReflection (TypeDefinition type, in
break;
}

_typeMapHandler.ProcessType (type);

// Treat cctors triggered by a called method specially and mark this case up-front.
if (type.HasMethods && ShouldMarkTypeStaticConstructor (type) && reason.Kind == DependencyKind.DeclaringTypeOfCalledMethod)
MarkStaticConstructor (type, new DependencyInfo (DependencyKind.TriggersCctorForCalledMethod, reason.Source), origin);
Expand All @@ -1929,8 +1942,11 @@ internal void MarkStaticConstructorVisibleToReflection (TypeDefinition type, in
// or because of a copy assembly with a reference and so on) then we should not spam the warnings due to the type itself.
// Also don't warn when the type is marked due to an assembly being rooted.
if (!(reason.Source is IMemberDefinition sourceMemberDefinition && sourceMemberDefinition.DeclaringType == type) &&
reason.Kind is not DependencyKind.TypeInAssembly)
Context.LogWarning (origin, DiagnosticId.AttributeIsReferencedButTrimmerRemoveAllInstances, type.GetDisplayName ());
reason.Kind is not DependencyKind.TypeInAssembly) {
// Don't warn for type map attribute types. They're marked as "remove attributes" but we explicitly keep the ones needed.
if (type is not { Namespace: "System.Runtime.InteropServices", Name: "TypeMapAttribute`1" or "TypeMapAssociationAttribute`1" or "TypeMapAssemblyTargetAttribute`1"})
Context.LogWarning (origin, DiagnosticId.AttributeIsReferencedButTrimmerRemoveAllInstances, type.GetDisplayName ());
}
}

if (CheckProcessed (type))
Expand Down Expand Up @@ -3233,6 +3249,8 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type

MarkImplicitlyUsedFields (type, typeOrigin);

_typeMapHandler.ProcessInstantiated (type);

DoAdditionalInstantiatedTypeProcessing (type);
}

Expand Down Expand Up @@ -3668,6 +3686,8 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
if (type.IsInterface)
break;

_typeMapHandler.ProcessType (type);

if (!Annotations.IsInstantiated (type)) {
_pending_isinst_instr.Add ((type, method.Body, instruction));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected override void Process ()

Annotations.Mark (ep.DeclaringType, di, origin);
Annotations.AddPreservedMethod (ep.DeclaringType, ep);
Annotations.SetEntryPointAssembly (assembly);
break;
case AssemblyRootMode.VisibleMembers:
var preserve_visible = TypePreserveMembers.Visible;
Expand Down
12 changes: 12 additions & 0 deletions src/tools/illink/src/linker/Linker/Annotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public partial class AnnotationStore
protected readonly HashSet<MethodDefinition> indirectly_called = new HashSet<MethodDefinition> ();
protected readonly HashSet<TypeDefinition> types_relevant_to_variant_casting = new HashSet<TypeDefinition> ();
readonly HashSet<IMemberDefinition> reflection_used = new ();
AssemblyDefinition? entry_assembly;

public AnnotationStore (LinkContext context)
{
Expand Down Expand Up @@ -587,6 +588,17 @@ public bool TryGetLinkerAttribute<T> (IMemberDefinition member, [NotNullWhen (re
return attribute != null;
}

public AssemblyDefinition? GetEntryPointAssembly()
{
return entry_assembly;
}

public void SetEntryPointAssembly(AssemblyDefinition asmDef)
{
Debug.Assert (entry_assembly is null);
entry_assembly = asmDef;
}

/// <summary>
/// Determines if method is within a declared RUC scope - this typically means that trim analysis
/// warnings should be suppressed in such a method.
Expand Down
2 changes: 2 additions & 0 deletions src/tools/illink/src/linker/Linker/DependencyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public enum DependencyKind
DynamicallyAccessedMemberOnType = 88, // type with DynamicallyAccessedMembers annotations (including those inherited from base types and interfaces)

UnsafeAccessorTarget = 89, // the member is referenced via UnsafeAccessor attribute

TypeMapEntry = 90, // external or proxy type map entry
}

public readonly struct DependencyInfo : IEquatable<DependencyInfo>
Expand Down
5 changes: 5 additions & 0 deletions src/tools/illink/src/linker/Linker/LinkContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,11 @@ public bool IsWarningSuppressed (int warningCode, string subcategory, MessageOri
if (Suppressions == null)
return false;

// Don't warn for RUC on assembly attributes.
// There's no way to suppress it.
if (warningCode == 2026 && origin.Provider is AssemblyDefinition)
return true;

return Suppressions.IsSuppressed (warningCode, origin, out _);
}

Expand Down
Loading
Loading