Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -14,7 +14,6 @@

<!--Include shared linker sources-->
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\External\Linker\BaseMarkHandler.cs" Link="External\BaseMarkHandler.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\AddKeepAlivesStep.cs" Link="MonoDroid.Tuner\AddKeepAlivesStep.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\AndroidLinkConfiguration.cs" Link="MonoDroid.Tuner\AndroidLinkConfiguration.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\Extensions.cs" Link="MonoDroid.Tuner\Extensions.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\FixAbstractMethodsStep.cs" Link="MonoDroid.Tuner\FixAbstractMethodsStep.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AssemblyDefinition> getCorlibAssembly, Action<string> 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<AssemblyDefinition> getCorlibAssembly, Action<string> 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<AssemblyDefinition> getCorlibAssembly, Action<string> 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<AssemblyDefinition> getCorlibAssembly, Action<string> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<UsingTask TaskName="Xamarin.Android.Tasks.RemoveRegisterAttribute" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.StripEmbeddedLibraries" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.AddKeepAlives" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<PropertyGroup>
<_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag</_RemoveRegisterFlag>
Expand Down Expand Up @@ -197,12 +198,6 @@
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" />
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" />
<!-- Custom steps that run after MarkStep -->
<_TrimmerCustomSteps
Condition=" '$(AndroidAddKeepAlives)' == 'true' "
Include="$(_AndroidLinkerCustomStepAssembly)"
AfterStep="CleanStep"
Type="MonoDroid.Tuner.AddKeepAlivesStep"
/>
<!-- Custom steps that run after CleanStep -->
<_TrimmerCustomSteps
Condition=" '$(AndroidLinkResources)' == 'true' "
Expand Down Expand Up @@ -266,6 +261,22 @@
Deterministic="$(Deterministic)" />
</Target>

<!--
Inject GC.KeepAlive() calls into binding methods of trimmed assemblies.
Runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation,
so R2R images are generated from the already-modified assemblies.
-->
<Target Name="_AddKeepAlives"
AfterTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' and '$(AndroidAddKeepAlives)' == 'true' ">
<ItemGroup>
<_AddKeepAlivesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " />
</ItemGroup>
<AddKeepAlives
Assemblies="@(_AddKeepAlivesAssembly)"
Deterministic="$(Deterministic)" />
</Target>

<!-- Inject _TypeMapKind into the property cache -->
<Target Name="_SetTypemapProperties"
BeforeTargets="_CreatePropertiesCache">
Expand Down
Loading