Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
17 changes: 14 additions & 3 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,12 @@
<PropertyGroup>
<_UseDynamicDependenciesInsteadOfMarking Condition="'$(_UseDynamicDependenciesInsteadOfMarking)' == ''">true</_UseDynamicDependenciesInsteadOfMarking>
<_UseDynamicDependenciesForProtocolPreservation Condition="'$(_UseDynamicDependenciesForProtocolPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForProtocolPreservation>
<_UseDynamicDependenciesForSmartEnumPreservation Condition="'$(_UseDynamicDependenciesForSmartEnumPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForSmartEnumPreservation>
<_UseDynamicDependenciesForBlockCodePreservation Condition="'$(_UseDynamicDependenciesForBlockCodePreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForBlockCodePreservation>
<_UseDynamicDependenciesForGeneratedCodeOptimizations Condition="'$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForGeneratedCodeOptimizations>
<_UseDynamicDependenciesForApplyPreserveAttribute Condition="'$(_UseDynamicDependenciesForApplyPreserveAttribute)' == '' And '$(_XamarinRuntime)' == 'NativeAOT'">true</_UseDynamicDependenciesForApplyPreserveAttribute>
<_UseDynamicDependenciesForApplyPreserveAttribute Condition="'$(_UseDynamicDependenciesForApplyPreserveAttribute)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForApplyPreserveAttribute>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new property _UseDynamicDependenciesForMarkStaticRegistrar is a bit inconsistent with the step/type name (MarkForStaticRegistrar*) and the other properties (...ForApplyPreserveAttribute, etc.). Consider aligning the property name (or adding a short comment) so it’s easier to grep and understand later.

Suggested change
<_UseDynamicDependenciesForApplyPreserveAttribute Condition="'$(_UseDynamicDependenciesForApplyPreserveAttribute)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForApplyPreserveAttribute>
<_UseDynamicDependenciesForApplyPreserveAttribute Condition="'$(_UseDynamicDependenciesForApplyPreserveAttribute)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForApplyPreserveAttribute>
<!-- Controls whether Xamarin.Linker.Steps.MarkForStaticRegistrarStep uses DynamicDependency attributes instead of direct marking. -->

Copilot uses AI. Check for mistakes.
<_UseDynamicDependenciesForMarkStaticRegistrar Condition="'$(_UseDynamicDependenciesForMarkStaticRegistrar)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForMarkStaticRegistrar>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -750,6 +756,11 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.CoreTypeMapStep" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForApplyPreserveAttribute)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" />
<!-- The final decision to remove/keep the dynamic registrar must be done before the linking step -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" />
<!-- TODO: these steps should probably run after mark. -->
Expand All @@ -759,13 +770,13 @@
<!--
IMarkHandlers which run during Mark
-->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' != 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" />
<!-- MarkDispatcher substeps will run for all marked assemblies. -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" />

<!--
pre-sweep custom steps
Expand Down
2 changes: 1 addition & 1 deletion src/Network/NWWebSocketResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public bool EnumerateAdditionalHeaders (Action<string?, string?> handler)

unsafe {
delegate* unmanaged<IntPtr, IntPtr, IntPtr, void> trampoline = &TrampolineEnumerateHeadersHandler;
using var block = new BlockLiteral (trampoline, handler, typeof (NWWebSocketResponseStatus), nameof (TrampolineEnumerateHeadersHandler));
using var block = new BlockLiteral (trampoline, handler, typeof (NWWebSocketResponse), nameof (TrampolineEnumerateHeadersHandler));
return nw_ws_response_enumerate_additional_headers (GetCheckedHandle (), &block) != 0;
}
}
Expand Down
42 changes: 20 additions & 22 deletions tests/monotouch-test/ObjCRuntime/StrongEnumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,57 +33,55 @@ public void GetConstant ()
if (getValue is null || getConstant.IsDefined (typeof (ObsoleteAttribute)))
continue;

#pragma warning disable IL3050 // Using member 'System.Enum.GetValues(Type)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. It might not be possible to create an array of the enum type at runtime. Use the GetValues<TEnum> overload or the GetValuesAsUnderlyingType method instead.
foreach (var enumValue in Enum.GetValues (type)) {
#pragma warning restore IL3050
foreach (var enumValue in Enum.GetValuesAsUnderlyingType (type)) {
var obj = getConstant.Invoke (null, new object [] { enumValue });

if (valuesToSkip.Remove ((Enum) enumValue))
if (valuesToSkip.Remove ((type, enumValue)))
continue;

if (obj is not null) {
var rtrip = getValue.Invoke (null, new object [] { obj });
Assert.AreEqual (enumValue, rtrip, $"{type.FullName}.{enumValue}: Round trip failed: {enumValue}.GetConstant () -> {obj} but GetValue ({obj}) -> {rtrip}");
Assert.AreEqual (Enum.ToObject (type, enumValue), rtrip, $"{type.FullName}.{enumValue}: Round trip failed: {enumValue}.GetConstant () -> {obj} but GetValue ({obj}) -> {rtrip}");
}
}
}

// Only very that all the skipped values are correct if nothing has been trimmed away.
// Only verify that all the skipped values are correct if nothing has been trimmed away.
if (!TestRuntime.IsLinkAny)
Assert.That (valuesToSkip, Is.Empty, "All values to be skipped were actually skipped");
});
}

Enum [] GetSkippedEnumValues ()
(Type EnumType, object UnderlyingValue) [] GetSkippedEnumValues ()
{
var rv = new List<Enum> () {
var rv = new List<(Type EnumType, object UnderlyingValue)> () {
#if !XAMCORE_5_0
global::AVFoundation.AVCaptureDeviceType.BuiltInDualCamera,
(typeof (global::AVFoundation.AVCaptureDeviceType), (int) global::AVFoundation.AVCaptureDeviceType.BuiltInDualCamera),
#if __MACOS__
global::AVFoundation.AVCaptureDeviceType.External,
(typeof (global::AVFoundation.AVCaptureDeviceType), (int) global::AVFoundation.AVCaptureDeviceType.External),
#endif
global::AVFoundation.AVCaptureDeviceType.Microphone,
global::Foundation.NSLinguisticTag.OtherPunctuation,
global::Foundation.NSLinguisticTag.OtherWhitespace,
global::Foundation.NSRunLoopMode.Other,
(typeof (global::AVFoundation.AVCaptureDeviceType), (int) global::AVFoundation.AVCaptureDeviceType.Microphone),
(typeof (global::Foundation.NSLinguisticTag), (int) global::Foundation.NSLinguisticTag.OtherPunctuation),
(typeof (global::Foundation.NSLinguisticTag), (int) global::Foundation.NSLinguisticTag.OtherWhitespace),
(typeof (global::Foundation.NSRunLoopMode), (int) global::Foundation.NSRunLoopMode.Other),
#if !__TVOS__
global::HealthKit.HKCategoryTypeIdentifier.EnvironmentalAudioExposureEvent,
(typeof (global::HealthKit.HKCategoryTypeIdentifier), (int) global::HealthKit.HKCategoryTypeIdentifier.EnvironmentalAudioExposureEvent),
#endif
#if __MACOS__
global::iTunesLibrary.ITLibPlaylistProperty.Primary,
global::ImageKit.IKToolMode.SelectRect,
(typeof (global::iTunesLibrary.ITLibPlaylistProperty), (int) global::iTunesLibrary.ITLibPlaylistProperty.Primary),
(typeof (global::ImageKit.IKToolMode), (int) global::ImageKit.IKToolMode.SelectRect),
#endif
global::Security.SecKeyType.ECSecPrimeRandom,
(typeof (global::Security.SecKeyType), (int) global::Security.SecKeyType.ECSecPrimeRandom),
#if !__MACOS__
global::UIKit.UIWindowSceneSessionRole.ExternalDisplayNonInteractive,
(typeof (global::UIKit.UIWindowSceneSessionRole), (int) global::UIKit.UIWindowSceneSessionRole.ExternalDisplayNonInteractive),
#endif
#endif // !XAMCORE_5_0
};

#if __TVOS__ && !XAMCORE_5_0
if (Runtime.Arch == Arch.SIMULATOR) {
rv.AddRange (Enum.GetValues<global::BrowserEngineKit.BEAccessibilityTrait> ().Cast<Enum> ()); // BrowserEngineKit isn't available in the simulator
rv.AddRange (Enum.GetValues<global::BrowserEngineKit.BEAccessibilityNotification> ().Cast<Enum> ()); // BrowserEngineKit isn't available in the simulator
rv.AddRange (Enum.GetValues<global::BrowserEngineKit.BEAccessibilityTrait> ().Select (v => (typeof (global::BrowserEngineKit.BEAccessibilityTrait), (int) v))); // BrowserEngineKit isn't available in the simulator
rv.AddRange (Enum.GetValues<global::BrowserEngineKit.BEAccessibilityNotification> ().Select (v => (typeof (global::BrowserEngineKit.BEAccessibilityNotification), (int) v))); // BrowserEngineKit isn't available in the simulator
}
#endif // __TVOS__ && !XAMCORE_5_0

Expand All @@ -92,7 +90,7 @@ Enum [] GetSkippedEnumValues ()
// NewScene and NewItem both return 'com.apple.menu.new-item' so
// Round trip failed: NewItem.GetConstant () -> com.apple.menu.new-item but GetValue (com.apple.menu.new-item) -> NewScene
// That said NewItem is the one that should be used and NewScene is deprecated in Xcode 26.0.
rv.Add (global::UIKit.UIMenuIdentifier.NewItem);
rv.Add ((typeof (global::UIKit.UIMenuIdentifier), (int) global::UIKit.UIMenuIdentifier.NewItem));
}
#endif // !__MACOS__

Expand Down
3 changes: 3 additions & 0 deletions tools/common/DerivedLinkContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public class DerivedLinkContext : LinkContext {
// so we need a second dictionary
Dictionary<TypeDefinition, LinkedAwayTypeReference> LinkedAwayTypeMap = new Dictionary<TypeDefinition, LinkedAwayTypeReference> ();

public bool DidRunApplyPreserveAttributeStep { get; set; }
public bool DidRunMarkForStaticRegistrarStep { get; set; }

public DerivedLinkContext (LinkerConfiguration configuration, Application app)
#if !LEGACY_TOOLS
: base (configuration)
Expand Down
27 changes: 27 additions & 0 deletions tools/common/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,33 @@ static bool IsBoundAssembly (Assembly s)

return false;
}

bool _set_arm64_calling_convention;
bool? _is_arm64_calling_convention;
public bool? InlineIsArm64CallingConventionForCurrentAbi {
get {
if (!_set_arm64_calling_convention) {
if (Optimizations.InlineIsARM64CallingConvention == true) {
// We can usually inline Runtime.InlineIsARM64CallingConvention if the generated code will execute on a single architecture
switch (Abi & Abi.ArchMask) {
case Abi.x86_64:
_is_arm64_calling_convention = false;
break;
case Abi.ARM64:
case Abi.ARM64e:
_is_arm64_calling_convention = true;
break;
default:
LinkContext.Exceptions.Add (Xamarin.Bundler.ErrorHelper.CreateWarning (99, Xamarin.Bundler.Errors.MX0099, $"unknown abi: {Abi}"));
break;
}
}
_set_arm64_calling_convention = true;
}
return _is_arm64_calling_convention;
}
}

#endif // !LEGACY_TOOLS
}
}
56 changes: 54 additions & 2 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,17 @@ public MethodReference Dictionary2_Add {
}
}

public MethodReference DynamicDependencyAttribute_ctor__String {
get {
return GetMethodReference (CorlibAssembly,
System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute,
".ctor",
".ctor(String)",
isStatic: false,
System_String);
}
}

public MethodReference DynamicDependencyAttribute_ctor__String_Type {
get {
return GetMethodReference (CorlibAssembly,
Expand Down Expand Up @@ -1246,6 +1257,33 @@ public void ClearCurrentAssembly ()
field_map.Clear ();
}

// We only need to add dependency attributes if the target dependency is in a trimmed assembly,
// otherwise the target dependency won't be trimmed away.
bool IsAssemblyTrimmed (IMemberDefinition member)
{
var assembly = member is TypeDefinition td ? td.Module.Assembly : member.DeclaringType.Module.Assembly;
var action = configuration.Context.Annotations.GetAction (assembly);
return action == AssemblyAction.Link;
}

public bool AddDynamicDependencyAttribute (MethodDefinition addToMethod, MethodDefinition dependsOn)
{
if (!IsAssemblyTrimmed (dependsOn))
return false;

if (addToMethod.DeclaringType == dependsOn.DeclaringType) {
var attribute = new CustomAttribute (DynamicDependencyAttribute_ctor__String);
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, DocumentationComments.GetSignature (dependsOn)));
return AddAttributeOnlyOnce (addToMethod, attribute);
} else if (addToMethod.DeclaringType.Module == dependsOn.DeclaringType.Module) {
var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn), dependsOn.DeclaringType);
return AddAttributeOnlyOnce (addToMethod, attribute);
} else {
var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn), dependsOn.DeclaringType, dependsOn.DeclaringType.Module.Assembly);
return AddAttributeOnlyOnce (addToMethod, attribute);
}
}

public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type)
{
if (type.HasGenericParameters)
Expand Down Expand Up @@ -1280,6 +1318,17 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb
return attribute;
}

/// <summary>
/// Preserve a method conditionally on another type
/// </summary>
/// <param name="onType">The type on which to add the dynamic dependency attribute.</param>
/// <param name="forMethod">The method that is the target of the dynamic dependency.</param>
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod)
{
var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly);
return AddAttributeToStaticConstructor (onType, attrib);
}

/// <summary>
/// Preserve a field conditionally on another type
/// </summary>
Expand Down Expand Up @@ -1307,6 +1356,9 @@ public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onT
/// <returns>Whether an attribute was added or not.</returns>
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, TypeDefinition forType)
{
if (!IsAssemblyTrimmed (forType))
return false;

var placeholderName = "__linker_preserve__";
FieldDefinition? placeholderMember = null;
if (forType.HasFields)
Expand All @@ -1332,7 +1384,7 @@ public bool AddAttributeToStaticConstructor (TypeDefinition onType, CustomAttrib
return modified;
}

MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool modified)
public MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool modified)
{
modified = false;

Expand All @@ -1356,7 +1408,7 @@ MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool mod
/// <param name="provider">The provider to which the attribute should be added.</param>
/// <param name="attribute">The attribute to add.</param>
/// <returns>Whether the attribute was added or not.</returns>
bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute attribute)
public bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute attribute)
{
if (provider.HasCustomAttributes) {
foreach (var ca in provider.CustomAttributes) {
Expand Down
Loading
Loading