Skip to content

Commit 6b94e21

Browse files
sbomerCopilotCopilot
authored
[xabt] Move AddKeepAlives trimmer step to standalone MSBuild task (#10952)
Migrate AddKeepAlivesStep out of the ILLink custom step pipeline into a standalone MSBuild task that runs AfterTargets="ILLink", following the same pattern established by StripEmbeddedLibraries in #10894. Core IL-rewriting logic is extracted into AddKeepAlivesHelper, shared by both the new task (trimmed builds) and the existing pipeline step (non-trimmed builds via LinkAssembliesNoShrink). Replace DefaultAssemblyResolver with DirectoryAssemblyResolver (ReadWrite=true) to avoid file handle conflicts on Windows. The directory resolver caches all assemblies (both explicit and dependency-resolved), preventing duplicate file opens that caused IOException when the same assembly was opened as both a dependency and a primary target. ## [xabt] Combine post-trimming steps into single PostTrimmingPipeline task Replace standalone AddKeepAlives and StripEmbeddedLibraries MSBuild tasks with a single PostTrimmingPipeline task that opens assemblies once (via DirectoryAssemblyResolver with ReadWrite) and runs both modifications in a single pass. Extract StripEmbeddedLibrariesStep as an IAssemblyModifierPipelineStep for reuse. ## [xabt] Refactor PostTrimmingPipeline to use IAssemblyModifierPipelineStep pattern Use List<IAssemblyModifierPipelineStep> with StripEmbeddedLibrariesStep and PostTrimmingAddKeepAlivesStep instead of calling helpers directly. Move the IsFrameworkAssembly check into StripEmbeddedLibrariesStep.ProcessAssembly and remove all outer-loop filtering so each step handles its own guards internally. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8792645 commit 6b94e21

9 files changed

Lines changed: 327 additions & 283 deletions

File tree

src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
<!--Include shared linker sources-->
1616
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\External\Linker\BaseMarkHandler.cs" Link="External\BaseMarkHandler.cs" />
17-
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\AddKeepAlivesStep.cs" Link="MonoDroid.Tuner\AddKeepAlivesStep.cs" />
1817
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\AndroidLinkConfiguration.cs" Link="MonoDroid.Tuner\AndroidLinkConfiguration.cs" />
1918
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\Extensions.cs" Link="MonoDroid.Tuner\Extensions.cs" />
2019
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\FixAbstractMethodsStep.cs" Link="MonoDroid.Tuner\FixAbstractMethodsStep.cs" />
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Linq;
3+
using Java.Interop.Tools.Cecil;
4+
using Mono.Cecil;
5+
using Mono.Cecil.Cil;
6+
using Xamarin.Android.Tasks;
7+
8+
namespace MonoDroid.Tuner
9+
{
10+
static class AddKeepAlivesHelper
11+
{
12+
internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func<AssemblyDefinition?> getCorlibAssembly, Action<string> logMessage)
13+
{
14+
if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object"))
15+
return false;
16+
17+
// Anything that was built against .NET for Android will have
18+
// keep-alives already compiled in.
19+
if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly))
20+
return false;
21+
22+
MethodDefinition? methodKeepAlive = null;
23+
bool changed = false;
24+
foreach (TypeDefinition type in assembly.MainModule.Types)
25+
changed |= ProcessType (type, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage);
26+
27+
return changed;
28+
}
29+
30+
static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func<AssemblyDefinition?> getCorlibAssembly, Action<string> logMessage)
31+
{
32+
bool changed = false;
33+
if (MightNeedFix (type, resolver))
34+
changed |= AddKeepAlives (type, ref methodKeepAlive, getCorlibAssembly, logMessage);
35+
36+
if (type.HasNestedTypes) {
37+
foreach (var t in type.NestedTypes) {
38+
changed |= ProcessType (t, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage);
39+
}
40+
}
41+
42+
return changed;
43+
}
44+
45+
static bool MightNeedFix (TypeDefinition type, IMetadataResolver resolver)
46+
{
47+
return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", resolver);
48+
}
49+
50+
static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func<AssemblyDefinition?> getCorlibAssembly, Action<string> logMessage)
51+
{
52+
bool changed = false;
53+
foreach (MethodDefinition method in type.Methods) {
54+
if (method.Parameters.Count == 0)
55+
continue;
56+
57+
if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"))
58+
continue;
59+
60+
var instructions = method.Body.Instructions;
61+
62+
var found = false;
63+
for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) {
64+
var current = instructions [off];
65+
if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) {
66+
found = true;
67+
break;
68+
}
69+
}
70+
71+
if (found)
72+
continue;
73+
74+
var processor = method.Body.GetILProcessor ();
75+
var module = method.DeclaringType.Module;
76+
var end = instructions.Last ();
77+
if (end.Previous.OpCode == OpCodes.Endfinally)
78+
end = end.Previous;
79+
80+
for (int i = 0; i < method.Parameters.Count; i++) {
81+
if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String")
82+
continue;
83+
84+
if (methodKeepAlive == null)
85+
methodKeepAlive = GetKeepAliveMethod (getCorlibAssembly, logMessage);
86+
87+
if (methodKeepAlive == null) {
88+
logMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method.");
89+
break;
90+
}
91+
92+
processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i]));
93+
processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive)));
94+
changed = true;
95+
}
96+
}
97+
return changed;
98+
}
99+
100+
static MethodDefinition? GetKeepAliveMethod (Func<AssemblyDefinition?> getCorlibAssembly, Action<string> logMessage)
101+
{
102+
var corlibAssembly = getCorlibAssembly ();
103+
if (corlibAssembly == null)
104+
return null;
105+
106+
var gcType = Extensions.GetType (corlibAssembly, "System.GC");
107+
if (gcType == null)
108+
return null;
109+
110+
return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" });
111+
}
112+
113+
// Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs
114+
static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter)
115+
{
116+
switch (argNum) {
117+
case 0: return Instruction.Create (OpCodes.Ldarg_0);
118+
case 1: return Instruction.Create (OpCodes.Ldarg_1);
119+
case 2: return Instruction.Create (OpCodes.Ldarg_2);
120+
case 3: return Instruction.Create (OpCodes.Ldarg_3);
121+
default: return Instruction.Create (OpCodes.Ldarg, parameter);
122+
}
123+
}
124+
}
125+
}
Lines changed: 6 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,23 @@
1-
using System;
2-
using System.Linq;
3-
using Java.Interop.Tools.Cecil;
41
using Mono.Cecil;
5-
using Mono.Cecil.Cil;
6-
using Mono.Linker;
72
using Mono.Linker.Steps;
83
using Xamarin.Android.Tasks;
94

105
namespace MonoDroid.Tuner
116
{
12-
public class AddKeepAlivesStep : BaseStep
13-
#if !ILLINK
14-
, IAssemblyModifierPipelineStep
15-
#endif // !ILLINK
7+
public class AddKeepAlivesStep : BaseStep, IAssemblyModifierPipelineStep
168
{
179

18-
protected override void ProcessAssembly (AssemblyDefinition assembly)
19-
{
20-
var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
21-
if (action == AssemblyAction.Delete)
22-
return;
23-
24-
if (AddKeepAlives (assembly)) {
25-
if (action == AssemblyAction.Skip || action == AssemblyAction.Copy)
26-
Annotations.SetAction (assembly, AssemblyAction.Save);
27-
}
28-
}
29-
30-
#if !ILLINK
3110
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
3211
{
3312
// Only run this step on user Android assemblies
3413
if (!context.IsAndroidUserAssembly)
3514
return;
3615

37-
context.IsAssemblyModified |= AddKeepAlives (assembly);
38-
}
39-
#endif // !ILLINK
40-
41-
internal bool AddKeepAlives (AssemblyDefinition assembly)
42-
{
43-
if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object"))
44-
return false;
45-
46-
// Anything that was built against .NET for Android will have
47-
// keep-alives already compiled in.
48-
if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly))
49-
return false;
50-
51-
bool changed = false;
52-
foreach (TypeDefinition type in assembly.MainModule.Types)
53-
changed |= ProcessType (type);
54-
55-
return changed;
56-
}
57-
58-
bool ProcessType (TypeDefinition type)
59-
{
60-
bool changed = false;
61-
if (MightNeedFix (type))
62-
changed |= AddKeepAlives (type);
63-
64-
if (type.HasNestedTypes) {
65-
foreach (var t in type.NestedTypes) {
66-
changed |= ProcessType (t);
67-
}
68-
}
69-
70-
return changed;
71-
}
72-
73-
bool MightNeedFix (TypeDefinition type)
74-
{
75-
return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", Context);
76-
}
77-
78-
MethodDefinition? methodKeepAlive = null;
79-
80-
bool AddKeepAlives (TypeDefinition type)
81-
{
82-
bool changed = false;
83-
foreach (MethodDefinition method in type.Methods) {
84-
if (method.Parameters.Count == 0)
85-
continue;
86-
87-
if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"))
88-
continue;
89-
90-
var instructions = method.Body.Instructions;
91-
92-
var found = false;
93-
for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) {
94-
var current = instructions [off];
95-
if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) {
96-
found = true;
97-
break;
98-
}
99-
}
100-
101-
if (found)
102-
continue;
103-
104-
var processor = method.Body.GetILProcessor ();
105-
var module = method.DeclaringType.Module;
106-
var end = instructions.Last ();
107-
if (end.Previous.OpCode == OpCodes.Endfinally)
108-
end = end.Previous;
109-
110-
for (int i = 0; i < method.Parameters.Count; i++) {
111-
if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String")
112-
continue;
113-
114-
if (methodKeepAlive == null)
115-
methodKeepAlive = GetKeepAliveMethod ();
116-
117-
if (methodKeepAlive == null) {
118-
LogMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method.");
119-
break;
120-
}
121-
122-
processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i]));
123-
processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive)));
124-
changed = true;
125-
}
126-
}
127-
return changed;
128-
}
129-
130-
protected virtual AssemblyDefinition GetCorlibAssembly ()
131-
{
132-
return Context.GetAssembly ("System.Private.CoreLib");
133-
}
134-
135-
MethodDefinition? GetKeepAliveMethod ()
136-
{
137-
var corlibAssembly = GetCorlibAssembly ();
138-
if (corlibAssembly == null)
139-
return null;
140-
141-
var gcType = Extensions.GetType (corlibAssembly, "System.GC");
142-
if (gcType == null)
143-
return null;
144-
145-
return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" });
146-
}
147-
148-
public
149-
#if !ILLINK
150-
override
151-
#endif
152-
void LogMessage (string message)
153-
{
154-
Context.LogMessage (message);
155-
}
156-
157-
// Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs
158-
static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter)
159-
{
160-
switch (argNum) {
161-
case 0: return Instruction.Create (OpCodes.Ldarg_0);
162-
case 1: return Instruction.Create (OpCodes.Ldarg_1);
163-
case 2: return Instruction.Create (OpCodes.Ldarg_2);
164-
case 3: return Instruction.Create (OpCodes.Ldarg_3);
165-
default: return Instruction.Create (OpCodes.Ldarg, parameter);
166-
}
16+
context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives (
17+
assembly,
18+
Context,
19+
() => Context.GetAssembly ("System.Private.CoreLib"),
20+
(msg) => LogMessage (msg));
16721
}
16822
}
16923
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using Java.Interop.Tools.Cecil;
3+
using Mono.Cecil;
4+
using Xamarin.Android.Tasks;
5+
6+
namespace MonoDroid.Tuner;
7+
8+
/// <summary>
9+
/// Post-trimming version of AddKeepAlives that calls AddKeepAlivesHelper directly,
10+
/// matching the original ILLink behavior (no IsAndroidUserAssembly pre-filter).
11+
/// The helper has its own assembly-level guards (HasTypeReference, IsDotNetAndroidAssembly).
12+
/// </summary>
13+
class PostTrimmingAddKeepAlivesStep : IAssemblyModifierPipelineStep
14+
{
15+
readonly IMetadataResolver cache;
16+
readonly Func<AssemblyDefinition?> getCorlibAssembly;
17+
readonly Action<string> logMessage;
18+
19+
public PostTrimmingAddKeepAlivesStep (IMetadataResolver cache, Func<AssemblyDefinition?> getCorlibAssembly, Action<string> logMessage)
20+
{
21+
this.cache = cache;
22+
this.getCorlibAssembly = getCorlibAssembly;
23+
this.logMessage = logMessage;
24+
}
25+
26+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
27+
{
28+
context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives (assembly, cache, getCorlibAssembly, logMessage);
29+
}
30+
}

0 commit comments

Comments
 (0)