diff --git a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj
index 3757ed34b68..92c92c0ee94 100644
--- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj
+++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj
@@ -14,7 +14,6 @@
-
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs
new file mode 100644
index 00000000000..c051fe30971
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Linq;
+using Java.Interop.Tools.Cecil;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Xamarin.Android.Tasks;
+
+namespace MonoDroid.Tuner
+{
+ static class AddKeepAlivesHelper
+ {
+ internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func getCorlibAssembly, Action logMessage)
+ {
+ if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object"))
+ return false;
+
+ // Anything that was built against .NET for Android will have
+ // keep-alives already compiled in.
+ if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly))
+ return false;
+
+ MethodDefinition? methodKeepAlive = null;
+ bool changed = false;
+ foreach (TypeDefinition type in assembly.MainModule.Types)
+ changed |= ProcessType (type, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage);
+
+ return changed;
+ }
+
+ static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage)
+ {
+ bool changed = false;
+ if (MightNeedFix (type, resolver))
+ changed |= AddKeepAlives (type, ref methodKeepAlive, getCorlibAssembly, logMessage);
+
+ if (type.HasNestedTypes) {
+ foreach (var t in type.NestedTypes) {
+ changed |= ProcessType (t, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage);
+ }
+ }
+
+ return changed;
+ }
+
+ static bool MightNeedFix (TypeDefinition type, IMetadataResolver resolver)
+ {
+ return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", resolver);
+ }
+
+ static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage)
+ {
+ bool changed = false;
+ foreach (MethodDefinition method in type.Methods) {
+ if (method.Parameters.Count == 0)
+ continue;
+
+ if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"))
+ continue;
+
+ var instructions = method.Body.Instructions;
+
+ var found = false;
+ for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) {
+ var current = instructions [off];
+ if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ continue;
+
+ var processor = method.Body.GetILProcessor ();
+ var module = method.DeclaringType.Module;
+ var end = instructions.Last ();
+ if (end.Previous.OpCode == OpCodes.Endfinally)
+ end = end.Previous;
+
+ for (int i = 0; i < method.Parameters.Count; i++) {
+ if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String")
+ continue;
+
+ if (methodKeepAlive == null)
+ methodKeepAlive = GetKeepAliveMethod (getCorlibAssembly, logMessage);
+
+ if (methodKeepAlive == null) {
+ logMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method.");
+ break;
+ }
+
+ processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i]));
+ processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive)));
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ static MethodDefinition? GetKeepAliveMethod (Func getCorlibAssembly, Action logMessage)
+ {
+ var corlibAssembly = getCorlibAssembly ();
+ if (corlibAssembly == null)
+ return null;
+
+ var gcType = Extensions.GetType (corlibAssembly, "System.GC");
+ if (gcType == null)
+ return null;
+
+ return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" });
+ }
+
+ // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs
+ static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter)
+ {
+ switch (argNum) {
+ case 0: return Instruction.Create (OpCodes.Ldarg_0);
+ case 1: return Instruction.Create (OpCodes.Ldarg_1);
+ case 2: return Instruction.Create (OpCodes.Ldarg_2);
+ case 3: return Instruction.Create (OpCodes.Ldarg_3);
+ default: return Instruction.Create (OpCodes.Ldarg, parameter);
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
index 0ca2fa7f597..32f69e5e70f 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
@@ -1,169 +1,23 @@
-using System;
-using System.Linq;
-using Java.Interop.Tools.Cecil;
using Mono.Cecil;
-using Mono.Cecil.Cil;
-using Mono.Linker;
using Mono.Linker.Steps;
using Xamarin.Android.Tasks;
namespace MonoDroid.Tuner
{
- public class AddKeepAlivesStep : BaseStep
-#if !ILLINK
- , IAssemblyModifierPipelineStep
-#endif // !ILLINK
+ public class AddKeepAlivesStep : BaseStep, IAssemblyModifierPipelineStep
{
- protected override void ProcessAssembly (AssemblyDefinition assembly)
- {
- var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
- if (action == AssemblyAction.Delete)
- return;
-
- if (AddKeepAlives (assembly)) {
- if (action == AssemblyAction.Skip || action == AssemblyAction.Copy)
- Annotations.SetAction (assembly, AssemblyAction.Save);
- }
- }
-
-#if !ILLINK
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
// Only run this step on user Android assemblies
if (!context.IsAndroidUserAssembly)
return;
- context.IsAssemblyModified |= AddKeepAlives (assembly);
- }
-#endif // !ILLINK
-
- internal bool AddKeepAlives (AssemblyDefinition assembly)
- {
- if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object"))
- return false;
-
- // Anything that was built against .NET for Android will have
- // keep-alives already compiled in.
- if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly))
- return false;
-
- bool changed = false;
- foreach (TypeDefinition type in assembly.MainModule.Types)
- changed |= ProcessType (type);
-
- return changed;
- }
-
- bool ProcessType (TypeDefinition type)
- {
- bool changed = false;
- if (MightNeedFix (type))
- changed |= AddKeepAlives (type);
-
- if (type.HasNestedTypes) {
- foreach (var t in type.NestedTypes) {
- changed |= ProcessType (t);
- }
- }
-
- return changed;
- }
-
- bool MightNeedFix (TypeDefinition type)
- {
- return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", Context);
- }
-
- MethodDefinition? methodKeepAlive = null;
-
- bool AddKeepAlives (TypeDefinition type)
- {
- bool changed = false;
- foreach (MethodDefinition method in type.Methods) {
- if (method.Parameters.Count == 0)
- continue;
-
- if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"))
- continue;
-
- var instructions = method.Body.Instructions;
-
- var found = false;
- for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) {
- var current = instructions [off];
- if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) {
- found = true;
- break;
- }
- }
-
- if (found)
- continue;
-
- var processor = method.Body.GetILProcessor ();
- var module = method.DeclaringType.Module;
- var end = instructions.Last ();
- if (end.Previous.OpCode == OpCodes.Endfinally)
- end = end.Previous;
-
- for (int i = 0; i < method.Parameters.Count; i++) {
- if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String")
- continue;
-
- if (methodKeepAlive == null)
- methodKeepAlive = GetKeepAliveMethod ();
-
- if (methodKeepAlive == null) {
- LogMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method.");
- break;
- }
-
- processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i]));
- processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive)));
- changed = true;
- }
- }
- return changed;
- }
-
- protected virtual AssemblyDefinition GetCorlibAssembly ()
- {
- return Context.GetAssembly ("System.Private.CoreLib");
- }
-
- MethodDefinition? GetKeepAliveMethod ()
- {
- var corlibAssembly = GetCorlibAssembly ();
- if (corlibAssembly == null)
- return null;
-
- var gcType = Extensions.GetType (corlibAssembly, "System.GC");
- if (gcType == null)
- return null;
-
- return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" });
- }
-
- public
-#if !ILLINK
- override
-#endif
- void LogMessage (string message)
- {
- Context.LogMessage (message);
- }
-
- // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs
- static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter)
- {
- switch (argNum) {
- case 0: return Instruction.Create (OpCodes.Ldarg_0);
- case 1: return Instruction.Create (OpCodes.Ldarg_1);
- case 2: return Instruction.Create (OpCodes.Ldarg_2);
- case 3: return Instruction.Create (OpCodes.Ldarg_3);
- default: return Instruction.Create (OpCodes.Ldarg, parameter);
- }
+ context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives (
+ assembly,
+ Context,
+ () => Context.GetAssembly ("System.Private.CoreLib"),
+ (msg) => LogMessage (msg));
}
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
index 4be55630c91..8a67744e311 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
@@ -8,6 +8,7 @@
+
<_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag
@@ -197,12 +198,6 @@
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" />
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" />
- <_TrimmerCustomSteps
- Condition=" '$(AndroidAddKeepAlives)' == 'true' "
- Include="$(_AndroidLinkerCustomStepAssembly)"
- AfterStep="CleanStep"
- Type="MonoDroid.Tuner.AddKeepAlivesStep"
- />
<_TrimmerCustomSteps
Condition=" '$(AndroidLinkResources)' == 'true' "
@@ -266,6 +261,22 @@
Deterministic="$(Deterministic)" />
+
+
+
+ <_AddKeepAlivesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " />
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs
new file mode 100644
index 00000000000..3674cff0892
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs
@@ -0,0 +1,91 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Java.Interop.Tools.Cecil;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Mono.Cecil;
+using MonoDroid.Tuner;
+
+namespace Xamarin.Android.Tasks;
+
+///
+/// An MSBuild task that injects GC.KeepAlive() calls into binding methods of trimmed assemblies.
+///
+/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation,
+/// so that R2R images are generated from the already-modified assemblies.
+///
+public class AddKeepAlives : AndroidTask
+{
+ public override string TaskPrefix => "AKA";
+
+ [Required]
+ public ITaskItem [] Assemblies { get; set; } = [];
+
+ public bool Deterministic { get; set; }
+
+ public override bool RunTask ()
+ {
+ var resolver = new DefaultAssemblyResolver ();
+ var cache = new TypeDefinitionCache ();
+ var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase);
+
+ foreach (var assembly in Assemblies) {
+ var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? "");
+ if (searchDirectories.Add (dir)) {
+ resolver.AddSearchDirectory (dir);
+ }
+ }
+
+ try {
+ foreach (var assembly in Assemblies) {
+ if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) {
+ continue;
+ }
+
+ ProcessAssembly (assembly.ItemSpec, resolver, cache);
+ }
+ } finally {
+ resolver.Dispose ();
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ void ProcessAssembly (string assemblyPath, IAssemblyResolver resolver, IMetadataResolver cache)
+ {
+ string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb");
+ bool havePdb = File.Exists (pdbPath);
+
+ var readerParams = new ReaderParameters {
+ ReadSymbols = havePdb,
+ ReadWrite = true,
+ AssemblyResolver = resolver,
+ };
+
+ using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) {
+ bool modified = AddKeepAlivesHelper.AddKeepAlives (
+ assembly,
+ cache,
+ () => GetCorlibAssembly (resolver),
+ (msg) => Log.LogDebugMessage (msg));
+
+ if (!modified) {
+ return;
+ }
+
+ Log.LogDebugMessage ($" Writing modified assembly: {assemblyPath}");
+ assembly.Write (new WriterParameters {
+ WriteSymbols = havePdb,
+ DeterministicMvid = Deterministic,
+ });
+ }
+ }
+
+ static AssemblyDefinition GetCorlibAssembly (IAssemblyResolver resolver)
+ {
+ return resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib"));
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index 79f6e450ae3..e1cedf463b9 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -49,6 +49,7 @@
+