Skip to content

Commit e6cfae9

Browse files
committed
[dotnet-linker] Use [DynamicDependency] attributes instead of manual marking when preserving block code.
This makes it easier to move this code out of a custom linker step in the future. Contributes towards #17693.
1 parent 91d9b4b commit e6cfae9

File tree

4 files changed

+123
-54
lines changed

4 files changed

+123
-54
lines changed

dotnet/targets/Xamarin.Shared.Sdk.targets

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@
549549
<_UseDynamicDependenciesInsteadOfMarking Condition="'$(_UseDynamicDependenciesInsteadOfMarking)' == ''">true</_UseDynamicDependenciesInsteadOfMarking>
550550
<_UseDynamicDependenciesForProtocolPreservation Condition="'$(_UseDynamicDependenciesForProtocolPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForProtocolPreservation>
551551
<_UseDynamicDependenciesForSmartEnumPreservation Condition="'$(_UseDynamicDependenciesForSmartEnumPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForSmartEnumPreservation>
552+
<_UseDynamicDependenciesForBlockCodePreservation Condition="'$(_UseDynamicDependenciesForBlockCodePreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForBlockCodePreservation>
552553
</PropertyGroup>
553554

554555
<PropertyGroup>
@@ -752,6 +753,7 @@
752753
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" />
753754
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" />
754755
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" />
756+
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" />
755757
<!-- The final decision to remove/keep the dynamic registrar must be done before the linking step -->
756758
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" />
757759
<!-- TODO: these steps should probably run after mark. -->
@@ -761,7 +763,7 @@
761763
<!--
762764
IMarkHandlers which run during Mark
763765
-->
764-
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
766+
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
765767
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" />
766768
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" />
767769
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" />

tools/dotnet-linker/AppBundleRewriter.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,17 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb
13181318
return attribute;
13191319
}
13201320

1321+
/// <summary>
1322+
/// Preserve a method conditionally on another type
1323+
/// </summary>
1324+
/// <param name="onType">The type on which to add the dynamic dependency attribute.</param>
1325+
/// <param name="forMethod">The method that is the target of the dynamic dependency.</param>
1326+
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod)
1327+
{
1328+
var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly);
1329+
return AddAttributeToStaticConstructor (onType, attrib);
1330+
}
1331+
13211332
/// <summary>
13221333
/// Preserve a field conditionally on another type
13231334
/// </summary>

tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,62 +26,11 @@ public override void Initialize (LinkContext context, MarkContext markContext)
2626

2727
protected override void Process (TypeDefinition type)
2828
{
29-
/* For the following class:
30-
31-
static internal class SDInnerBlock {
32-
// this field is not preserved by other means, but it must not be linked away
33-
static internal readonly DInnerBlock Handler = Invoke;
34-
35-
[MonoPInvokeCallback (typeof (DInnerBlock))]
36-
static internal void Invoke (IntPtr block, int magic_number)
37-
{
38-
}
39-
}
40-
41-
We need to make sure the linker doesn't remove the Handler field
42-
and the Invoke method.
43-
*/
44-
45-
// First make sure we got the right class
46-
// The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field.
47-
if (!type.HasFields || !type.IsAbstract || !type.IsSealed || !type.IsNested)
48-
return;
49-
if (type.Fields.Count != 1)
50-
return;
51-
52-
// The type is also nested inside ObjCRuntime.Trampolines class)
53-
var nestingType = type.DeclaringType;
54-
if (!nestingType.Is ("ObjCRuntime", "Trampolines"))
55-
return;
56-
57-
// The class has a readonly field named 'Handler'
58-
var field = type.Fields [0];
59-
if (!field.IsInitOnly)
60-
return;
61-
if (field.Name != "Handler")
62-
return;
63-
64-
// The class has a parameterless 'Invoke' method with a 'MonoPInvokeCallback' attribute
65-
if (!type.HasMethods)
29+
if (!PreserveBlockCodeStep.GetMembersToPreserve (type, out var field, out var method))
6630
return;
67-
var method = type.Methods.SingleOrDefault (v => {
68-
if (v.Name != "Invoke")
69-
return false;
70-
if (!v.HasParameters)
71-
return false;
72-
if (!v.HasCustomAttributes)
73-
return false;
74-
if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute"))
75-
return false;
76-
return true;
77-
});
7831

79-
if (method is null)
80-
return;
81-
82-
// The type was used, so preserve the method and field
83-
Context.Annotations.Mark (method);
8432
Context.Annotations.Mark (field);
33+
Context.Annotations.Mark (method);
8534
}
8635
}
8736
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Linq;
3+
4+
using Mono.Cecil;
5+
6+
using Mono.Linker;
7+
using Mono.Linker.Steps;
8+
using Mono.Tuner;
9+
10+
using Xamarin.Bundler;
11+
12+
#nullable enable
13+
14+
namespace Xamarin.Linker.Steps {
15+
16+
public class PreserveBlockCodeStep : AssemblyModifierStep {
17+
18+
protected override string Name { get; } = "Preserve Block Code";
19+
protected override int ErrorCode { get; } = 2240;
20+
21+
protected override bool IsActiveFor (AssemblyDefinition assembly)
22+
{
23+
// We only care about assemblies that are being linked.
24+
if (Annotations.GetAction (assembly) != AssemblyAction.Link)
25+
return false;
26+
27+
// Unless an assembly is or references our platform assembly, then it won't have anything we need to preserve
28+
if (!Configuration.Profile.IsOrReferencesProductAssembly (assembly))
29+
return false;
30+
31+
return true;
32+
}
33+
34+
protected override bool ProcessType (TypeDefinition type)
35+
{
36+
if (!GetMembersToPreserve (type, out var field, out var method))
37+
return false;
38+
39+
var modified = false;
40+
modified |= abr.AddDynamicDependencyAttributeToStaticConstructor (type, field);
41+
modified |= abr.AddDynamicDependencyAttributeToStaticConstructor (type, method);
42+
return modified;
43+
}
44+
45+
public static bool GetMembersToPreserve (TypeDefinition type, [NotNullWhen (true)] out FieldDefinition? field, [NotNullWhen (true)] out MethodDefinition? method)
46+
{
47+
field = null;
48+
method = null;
49+
50+
/* For the following class:
51+
52+
static internal class SDInnerBlock {
53+
// this field is not preserved by other means, but it must not be linked away
54+
static internal readonly DInnerBlock Handler = Invoke;
55+
56+
[MonoPInvokeCallback (typeof (DInnerBlock))]
57+
static internal void Invoke (IntPtr block, int magic_number)
58+
{
59+
}
60+
}
61+
62+
* We need to make sure the linker doesn't remove the Handler field
63+
* and the Invoke method.
64+
*/
65+
66+
// First make sure we got the right class
67+
// The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field.
68+
if (!type.HasFields || !type.IsAbstract || !type.IsSealed || !type.IsNested)
69+
return false;
70+
if (type.Fields.Count != 1)
71+
return false;
72+
73+
// The type is also nested inside ObjCRuntime.Trampolines class)
74+
var nestingType = type.DeclaringType;
75+
if (!nestingType.Is ("ObjCRuntime", "Trampolines"))
76+
return false;
77+
78+
// The class has a readonly field named 'Handler'
79+
field = type.Fields [0];
80+
if (!field.IsInitOnly)
81+
return false;
82+
if (field.Name != "Handler")
83+
return false;
84+
85+
// The class has a parameterless 'Invoke' method with a 'MonoPInvokeCallback' attribute
86+
if (!type.HasMethods)
87+
return false;
88+
method = type.Methods.SingleOrDefault (v => {
89+
if (v.Name != "Invoke")
90+
return false;
91+
if (!v.HasParameters)
92+
return false;
93+
if (!v.HasCustomAttributes)
94+
return false;
95+
if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute"))
96+
return false;
97+
return true;
98+
});
99+
100+
if (method is null)
101+
return false;
102+
103+
// The type was used, so preserve the method and field
104+
return true;
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)