Skip to content

Commit 57ab585

Browse files
committed
[wip] p/invoke preservation source code generator
1 parent 1869a69 commit 57ab585

File tree

7 files changed

+141
-21
lines changed

7 files changed

+141
-21
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,23 +188,23 @@ void Run (bool useMarshalMethods)
188188
var nativeCodeGenStates = new Dictionary<AndroidTargetArch, NativeCodeGenState> ();
189189
bool generateJavaCode = true;
190190
NativeCodeGenState? templateCodeGenState = null;
191-
var scanner = new PinvokeScanner (Log);
191+
PinvokeScanner? pinvokeScanner = EnableNativeRuntimeLinking ? new PinvokeScanner (Log) : null;
192192

193193
foreach (var kvp in allAssembliesPerArch) {
194194
AndroidTargetArch arch = kvp.Key;
195195
Dictionary<string, ITaskItem> archAssemblies = kvp.Value;
196196
(bool success, NativeCodeGenState? state) = GenerateJavaSourcesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods, generateJavaCode);
197197

198-
if (!success) {
198+
if (!success || state == null) {
199199
return;
200200
}
201201

202-
if (EnableNativeRuntimeLinking) {
203-
(success, List<PinvokeScanner.PinvokeEntryInfo> pinfos) = ScanForUsedPinvokes (scanner, arch, state.Resolver);
202+
if (pinvokeScanner != null) {
203+
(success, List<PinvokeScanner.PinvokeEntryInfo> pinfos) = ScanForUsedPinvokes (pinvokeScanner, arch, state.Resolver);
204204
if (!success) {
205205
return;
206206
}
207-
BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (PinvokeScanner.PinvokesInfoRegisterTaskKey), pinfos, RegisteredTaskObjectLifetime.Build);
207+
state.PinvokeInfos = pinfos;
208208
}
209209

210210
if (generateJavaCode) {

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ void AddEnvironment ()
327327
}
328328

329329
Dictionary<AndroidTargetArch, NativeCodeGenState>? nativeCodeGenStates = null;
330-
if (enableMarshalMethods) {
330+
if (enableMarshalMethods || EnableNativeRuntimeLinking) {
331331
nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<Dictionary<AndroidTargetArch, NativeCodeGenState>> (
332332
ProjectSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey),
333333
RegisteredTaskObjectLifetime.Build
@@ -370,8 +370,10 @@ void AddEnvironment ()
370370
string targetAbi = abi.ToLowerInvariant ();
371371
string environmentBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{targetAbi}");
372372
string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
373+
string? pinvokePreserveBaseAsmFilePath = EnableNativeRuntimeLinking ? Path.Combine (EnvironmentOutputDirectory, $"pinvoke_preserve.{targetAbi}") : null;
373374
string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll";
374375
string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
376+
string? pinvokePreserveLlFilePath = pinvokePreserveBaseAsmFilePath != null ? $"{pinvokePreserveBaseAsmFilePath}.ll" : null;
375377
AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi);
376378

377379
using var appConfigWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
@@ -402,10 +404,17 @@ void AddEnvironment ()
402404
}
403405

404406
if (EnableNativeRuntimeLinking) {
405-
// var pinfoGen = new PreservePinvokesNativeAssemblyGenerator (
406-
// Log,
407-
// targetArch,
408-
407+
var pinvokePreserveGen = new PreservePinvokesNativeAssemblyGenerator (Log, EnsureCodeGenState (targetArch), MonoComponents);
408+
LLVMIR.LlvmIrModule pinvokePreserveModule = pinvokePreserveGen.Construct ();
409+
using var pinvokePreserveWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
410+
try {
411+
pinvokePreserveGen.Generate (pinvokePreserveModule, targetArch, pinvokePreserveWriter, pinvokePreserveLlFilePath);
412+
} catch {
413+
throw;
414+
} finally {
415+
pinvokePreserveWriter.Flush ();
416+
Files.CopyIfStreamChanged (pinvokePreserveWriter.BaseStream, pinvokePreserveLlFilePath);
417+
}
409418
}
410419

411420
LLVMIR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGen.Construct ();

src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class PrepareAbiItems : AndroidTask
1616
const string CompressedAssembliesBase = "compressed_assemblies";
1717
const string JniRemappingBase = "jni_remap";
1818
const string MarshalMethodsBase = "marshal_methods";
19+
const string PinvokePreserveBase = "pinvoke_preserve";
1920

2021
public override string TaskPrefix => "PAI";
2122

@@ -56,6 +57,8 @@ public override bool RunTask ()
5657
baseName = JniRemappingBase;
5758
} else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
5859
baseName = MarshalMethodsBase;
60+
} else if (String.Compare ("runtime_linking", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
61+
baseName = PinvokePreserveBase;
5962
} else {
6063
Log.LogError ($"Unknown mode: {Mode}");
6164
return false;

src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class NativeCodeGenState
3030
/// </summary>
3131
public List<TypeDefinition> AllJavaTypes { get; }
3232

33+
/// <summary>
34+
/// Contains information about p/invokes used by the managed assemblies included in the
35+
/// application. Will be **null** unless native runtime linking at application build time
36+
/// is enabled.
37+
/// </summary>
38+
public List<PinvokeScanner.PinvokeEntryInfo>? PinvokeInfos { get; set; }
39+
3340
public List<TypeDefinition> JavaTypesForJCW { get; }
3441
public XAAssemblyResolver Resolver { get; }
3542
public TypeDefinitionCache TypeCache { get; }

src/Xamarin.Android.Build.Tasks/Utilities/PinvokeScanner.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ namespace Xamarin.Android.Tasks;
1414

1515
class PinvokeScanner
1616
{
17-
public const string PinvokesInfoRegisterTaskKey = ".:!PreservePinvokesTaskKey!:.";
18-
1917
public sealed class PinvokeEntryInfo
2018
{
2119
public readonly string LibraryName;

src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text;
66

77
using Microsoft.Android.Build.Tasks;
8+
using Microsoft.Build.Framework;
89
using Microsoft.Build.Utilities;
910

1011
using Xamarin.Android.Tasks.LLVMIR;
@@ -17,19 +18,107 @@ namespace Xamarin.Android.Tasks;
1718

1819
class PreservePinvokesNativeAssemblyGenerator : LlvmIrComposer
1920
{
20-
readonly TaskLoggingHelper log;
21-
readonly AndroidTargetArch targetArch;
22-
readonly ICollection<PinvokeScanner.PinvokeEntryInfo> pinfos;
21+
// Maps a component name after ridding it of the `lib` prefix and the extension to a "canonical"
22+
// name of a library, as used in `[DllImport]` attributes.
23+
readonly Dictionary<string, string> libraryNameMap = new (StringComparer.Ordinal) {
24+
{ "xa-java-interop", "java-interop" },
25+
{ "mono-android.release-static", String.Empty },
26+
{ "mono-android.release", String.Empty },
27+
};
2328

24-
public PreservePinvokesNativeAssemblyGenerator (TaskLoggingHelper log, AndroidTargetArch targetArch, ICollection<PinvokeScanner.PinvokeEntryInfo> pinfos)
29+
readonly NativeCodeGenState state;
30+
readonly ITaskItem[] monoComponents;
31+
32+
public PreservePinvokesNativeAssemblyGenerator (TaskLoggingHelper log, NativeCodeGenState codeGenState, ITaskItem[] monoComponents)
2533
: base (log)
2634
{
27-
this.log = log;
28-
this.targetArch = targetArch;
29-
this.pinfos = pinfos;
35+
if (codeGenState.PinvokeInfos == null) {
36+
throw new InvalidOperationException ($"Internal error: {nameof (codeGenState)} `{nameof (codeGenState.PinvokeInfos)}` property is `null`");
37+
}
38+
39+
this.state = codeGenState;
40+
this.monoComponents = monoComponents;
3041
}
3142

3243
protected override void Construct (LlvmIrModule module)
3344
{
45+
Log.LogDebugMessage ("Constructing p/invoke preserve code");
46+
List<PinvokeScanner.PinvokeEntryInfo> pinvokeInfos = state.PinvokeInfos!;
47+
if (pinvokeInfos.Count == 0) {
48+
// This is a very unlikely scenario, but we will work just fine. The module that this generator produces will merely result
49+
// in an empty (but valid) .ll file and an "empty" object file to link into the shared library.
50+
return;
51+
}
52+
53+
Log.LogDebugMessage (" Looking for enabled native components");
54+
var componentNames = new List<string> ();
55+
var nativeComponents = new NativeRuntimeComponents (monoComponents);
56+
foreach (NativeRuntimeComponents.Archive archiveItem in nativeComponents.KnownArchives) {
57+
if (!archiveItem.Include) {
58+
continue;
59+
}
60+
61+
Log.LogDebugMessage ($" {archiveItem.Name}");
62+
componentNames.Add (archiveItem.Name);
63+
}
64+
65+
if (componentNames.Count == 0) {
66+
Log.LogDebugMessage ("No native framework components are included in the build, not scanning for p/invoke usage");
67+
return;
68+
}
69+
70+
Log.LogDebugMessage (" Checking discovered p/invokes against the list of components");
71+
foreach (PinvokeScanner.PinvokeEntryInfo pinfo in pinvokeInfos) {
72+
Log.LogDebugMessage ($" p/invoke: {pinfo.EntryName} in {pinfo.LibraryName}");
73+
if (MustPreserve (pinfo, componentNames)) {
74+
Log.LogDebugMessage (" must be preserved");
75+
} else {
76+
Log.LogDebugMessage (" no need to preserve");
77+
}
78+
}
79+
}
80+
81+
// Returns `true` for all p/invokes that we know are part of our set of components, otherwise returns `false`.
82+
// Returning `false` merely means that the p/invoke isn't in any of BCL or our code and therefore we shouldn't
83+
// care. It doesn't mean the p/invoke will be removed in any way.
84+
bool MustPreserve (PinvokeScanner.PinvokeEntryInfo pinfo, List<string> components)
85+
{
86+
if (String.Compare ("xa-internal-api", pinfo.LibraryName, StringComparison.Ordinal) == 0) {
87+
return true;
88+
}
89+
90+
foreach (string component in components) {
91+
// The most common pattern for the BCL - file name without extension
92+
string componentName = Path.GetFileNameWithoutExtension (component);
93+
if (Matches (pinfo.LibraryName, componentName)) {
94+
return true;
95+
}
96+
97+
// If it starts with `lib`, drop the prefix
98+
if (componentName.StartsWith ("lib", StringComparison.Ordinal)) {
99+
if (Matches (pinfo.LibraryName, componentName.Substring (3))) {
100+
return true;
101+
}
102+
}
103+
104+
// Might require mapping of component name to a canonical one
105+
if (libraryNameMap.TryGetValue (componentName, out string? mappedComponentName) && !String.IsNullOrEmpty (mappedComponentName)) {
106+
if (Matches (pinfo.LibraryName, mappedComponentName)) {
107+
return true;
108+
}
109+
}
110+
111+
// Try full file name, as the last resort
112+
if (Matches (pinfo.LibraryName, Path.GetFileName (component))) {
113+
return true;
114+
}
115+
}
116+
117+
return false;
118+
119+
bool Matches (string libraryName, string componentName)
120+
{
121+
return String.Compare (libraryName, componentName, StringComparison.Ordinal) == 0;
122+
}
34123
}
35124
}

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,15 @@ because xbuild doesn't support framework reference assemblies.
15981598
Mode="marshal_methods">
15991599
<Output TaskParameter="AssemblySources" ItemName="_MarshalMethodsAssemblySource" />
16001600
</PrepareAbiItems>
1601+
<PrepareAbiItems
1602+
Condition=" '$(_AndroidEnableNativeRuntimeLinking)' == 'true' "
1603+
BuildTargetAbis="@(_BuildTargetAbis)"
1604+
NativeSourcesDir="$(_NativeAssemblySourceDir)"
1605+
InstantRunEnabled="$(_InstantRunEnabled)"
1606+
Debug="$(AndroidIncludeDebugSymbols)"
1607+
Mode="runtime_linking">
1608+
<Output TaskParameter="AssemblySources" ItemName="_RuntimeLinkingAssemblySource" />
1609+
</PrepareAbiItems>
16011610
</Target>
16021611

16031612
<Target Name="_GenerateEnvironmentFiles" DependsOnTargets="_ReadAndroidManifest">
@@ -1740,6 +1749,8 @@ because xbuild doesn't support framework reference assemblies.
17401749
<ItemGroup>
17411750
<FileWrites Include="$(_AndroidBuildIdFile)" />
17421751
<FileWrites Include="@(_EnvironmentAssemblySource)" />
1752+
<FileWrites Include="@(_MarshalMethodsAssemblySource)" />
1753+
<FileWrites Condition=" '$(_AndroidEnableNativeRuntimeLinking)' == 'true' " Include="@(_RuntimeLinkingAssemblySource)" />
17431754
</ItemGroup>
17441755
</Target>
17451756

@@ -1964,6 +1975,9 @@ because xbuild doesn't support framework reference assemblies.
19641975
<_NativeAssemblyTarget Include="@(_AndroidRemapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
19651976
<abi>%(_AndroidRemapAssemblySource.abi)</abi>
19661977
</_NativeAssemblyTarget>
1978+
<_NativeAssemblyTarget Include="@(_RuntimeLinkingAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
1979+
<abi>%(_RuntimeLinkingAssemblySource.abi)</abi>
1980+
</_NativeAssemblyTarget>
19671981
</ItemGroup>
19681982
</Target>
19691983

@@ -1983,10 +1997,10 @@ because xbuild doesn't support framework reference assemblies.
19831997

19841998
<Target Name="_CompileNativeAssemblySources"
19851999
DependsOnTargets="_PrepareNativeAssemblyItems;_GenerateCompressedAssembliesNativeSourceFiles"
1986-
Inputs="@(_TypeMapAssemblySource);@(_TypeMapAssemblyInclude);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource)"
2000+
Inputs="@(_TypeMapAssemblySource);@(_TypeMapAssemblyInclude);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource);@(_RuntimeLinkingAssemblySource)"
19872001
Outputs="@(_NativeAssemblyTarget)">
19882002
<CompileNativeAssembly
1989-
Sources="@(_TypeMapAssemblySource);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource)"
2003+
Sources="@(_TypeMapAssemblySource);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource);@(_RuntimeLinkingAssemblySource)"
19902004
DebugBuild="$(AndroidIncludeDebugSymbols)"
19912005
WorkingDirectory="$(_NativeAssemblySourceDir)"
19922006
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"

0 commit comments

Comments
 (0)