Skip to content

[XABT] Move marshal method generation to a "linker step". #10027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
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 @@ -11,6 +11,9 @@
namespace MonoDroid.Tuner
{
public class AddKeepAlivesStep : BaseStep
#if !ILLINK
, IAssemblyModifierPipelineStep
#endif // !ILLINK
{

protected override void ProcessAssembly (AssemblyDefinition assembly)
Expand All @@ -25,6 +28,17 @@ protected override void ProcessAssembly (AssemblyDefinition assembly)
}
}

#if !ILLINK
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
// Only run this step on user Android assemblies
if (context.IsFrameworkAssembly || !context.IsAndroidAssembly)
return false;

return AddKeepAlives (assembly);
}
#endif // !ILLINK

internal bool AddKeepAlives (AssemblyDefinition assembly)
{
if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace MonoDroid.Tuner;
/// <summary>
/// Scans an assembly for JLOs that need JCWs generated and writes them to an XML file.
/// </summary>
public class FindJavaObjectsStep : BaseStep
public class FindJavaObjectsStep : BaseStep, IAssemblyModifierPipelineStep
{
public string ApplicationJavaClass { get; set; } = "";

Expand All @@ -29,8 +29,26 @@ public class FindJavaObjectsStep : BaseStep

public FindJavaObjectsStep (TaskLoggingHelper log) => Log = log;

public bool ProcessAssembly (AssemblyDefinition assembly, string destinationJLOXml)
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (context.Destination.ItemSpec);
var scanned = ScanAssembly (assembly, context, destinationJLOXml);

if (!scanned) {
// We didn't scan for Java objects, so write an empty .xml file for later steps
JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log);
return false;
}

// This step does not change the assembly
return false;
}

public bool ScanAssembly (AssemblyDefinition assembly, StepContext context, string destinationJLOXml)
{
if (!ShouldScan (context))
return false;

var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;

if (action == AssemblyAction.Delete)
Expand All @@ -56,6 +74,25 @@ public bool ProcessAssembly (AssemblyDefinition assembly, string destinationJLOX
return true;
}

bool ShouldScan (StepContext context)
{
if (!context.IsAndroidAssembly)
return false;

// When marshal methods or non-JavaPeerStyle.XAJavaInterop1 are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
// application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
// build and stored in a jar file.
var useMarshalMethods = !context.IsDebug && context.EnableMarshalMethods;
var shouldSkipNonUserAssemblies = !useMarshalMethods && context.CodeGenerationTarget == JavaPeerStyle.XAJavaInterop1;

if (shouldSkipNonUserAssemblies && !context.IsUserAssembly) {
Log.LogDebugMessage ($"Skipping assembly '{context.Source.ItemSpec}' because it is not a user assembly and we don't need JLOs from non-user assemblies");
return false;
}

return true;
}

List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly)
{
var types = new List<TypeDefinition> ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ namespace MonoDroid.Tuner
/// NOTE: this step is subclassed so it can be called directly from Xamarin.Android.Build.Tasks
/// </summary>
public class FixAbstractMethodsStep : BaseMarkHandler
#if !ILLINK
, IAssemblyModifierPipelineStep
#endif // !ILLINK
{
public override void Initialize (LinkContext context, MarkContext markContext)
{
Expand Down Expand Up @@ -79,6 +82,17 @@ internal bool FixAbstractMethods (AssemblyDefinition assembly)
return changed;
}

#if !ILLINK
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
// Only run this step on non-main user Android assemblies
if (context.IsMainAssembly || context.IsFrameworkAssembly || !context.IsAndroidAssembly)
return false;

return FixAbstractMethods (assembly);
}
#endif // !ILLINK

readonly HashSet<string> warnedAssemblies = new (StringComparer.Ordinal);

internal void CheckAppDomainUsage (AssemblyDefinition assembly, Action<string> warn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
namespace MonoDroid.Tuner
{
public class FixLegacyResourceDesignerStep : LinkDesignerBase
#if !ILLINK
, Xamarin.Android.Tasks.IAssemblyModifierPipelineStep
#endif // !ILLINK
{
internal const string DesignerAssemblyName = "_Microsoft.Android.Resource.Designer";
internal const string DesignerAssemblyNamespace = "_Microsoft.Android.Resource.Designer";
Expand Down Expand Up @@ -67,6 +70,17 @@ protected override void LoadDesigner ()
}
}

#if !ILLINK
public bool ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
{
// Only run this step on non-main user Android assemblies
if (context.IsMainAssembly || context.IsFrameworkAssembly || !context.IsAndroidAssembly)
return false;

return ProcessAssemblyDesigner (assembly);
}
#endif // !ILLINK

internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly)
{
if (!FindResourceDesigner (assembly, mainApplication: false, out TypeDefinition designer, out CustomAttribute designerAttribute)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Java.Interop.Tools.JavaCallableWrappers;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using Mono.Linker;
using Mono.Linker.Steps;
using Xamarin.Android.Tasks;
using Xamarin.Android.Tools;

namespace MonoDroid.Tuner;

/// <summary>
/// Scans an assembly for JLOs that need JCWs generated and writes them to an XML file.
/// </summary>
public class RewriteMarshalMethodsStep : BaseStep, IAssemblyModifierPipelineStep
{
public TaskLoggingHelper Log { get; set; }

bool? brokenExceptionTransitionsEnabled;

public RewriteMarshalMethodsStep (TaskLoggingHelper log)
{
Log = log;
}

public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
if (!context.IsAndroidAssembly)
return false;

var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;

if (action == AssemblyAction.Delete)
return false;

// We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed
// in order to properly generate wrapper methods in the marshal methods assembly rewriter.
// We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for.
if (!brokenExceptionTransitionsEnabled.HasValue) {
var environmentParser = new EnvironmentFilesParser ();
brokenExceptionTransitionsEnabled = environmentParser.AreBrokenExceptionTransitionsEnabled (context.Environments);
}

var allJavaTypes = ScanForJavaTypes (assembly, context.Architecture);
var javaTypesForJCW = allJavaTypes.Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t, Context)).ToList ();

var jcwContext = new JCWGeneratorContext (context.Architecture, Context.Resolver, context.ResolvedAssemblies, javaTypesForJCW, Context, true);
var jcwGenerator = new JCWGenerator (Log, jcwContext) {
CodeGenerationTarget = context.CodeGenerationTarget,
};

jcwGenerator.Classify (context.AndroidSdkPlatform);

var state = new NativeCodeGenState (context.Architecture, Context, Context.Resolver, allJavaTypes, javaTypesForJCW, jcwGenerator.Classifier);
Run (state, context);

// TODO: Only return true if we actually modified the assembly
return true;
}

List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly, AndroidTargetArch arch)
{
var types = new List<TypeDefinition> ();

var scanner = new XAJavaTypeScanner (arch, Log, Context);

foreach (ModuleDefinition md in assembly.Modules) {
foreach (TypeDefinition td in md.Types) {
scanner.AddJavaType (td, types);
}
}

return types;
}

void Run (NativeCodeGenState state, StepContext context)
{
if (state.Classifier is null) {
Log.LogError ("state.Classifier cannot be null if marshal methods are enabled");
return;
}

if (!context.EnableManagedMarshalMethodsLookup) {
RewriteMethods (state, brokenExceptionTransitionsEnabled.GetValueOrDefault ());
state.Classifier.AddSpecialCaseMethods ();
} else {
// We need to run `AddSpecialCaseMethods` before `RewriteMarshalMethods` so that we can see the special case
// methods (such as TypeManager.n_Activate_mm) when generating the managed lookup tables.
state.Classifier.AddSpecialCaseMethods ();
state.ManagedMarshalMethodsLookupInfo = new ManagedMarshalMethodsLookupInfo (Log);
RewriteMethods (state, brokenExceptionTransitionsEnabled.GetValueOrDefault ());
}

Log.LogDebugMessage ($"[{state.TargetArch}] Number of generated marshal methods: {state.Classifier.MarshalMethods.Count}");
if (state.Classifier.RejectedMethodCount > 0) {
Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.RejectedMethodCount}");
}

if (state.Classifier.WrappedMethodCount > 0) {
// TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {state.Classifier.WrappedMethodCount}");
}
}

void RewriteMethods (NativeCodeGenState state, bool brokenExceptionTransitionsEnabled)
{
if (state.Classifier == null) {
return;
}

var rewriter = new MarshalMethodsAssemblyRewriter (Log, state.TargetArch, state.Classifier, state.Resolver, state.ManagedMarshalMethodsLookupInfo);
rewriter.Rewrite (brokenExceptionTransitionsEnabled);
}
}
Loading
Loading