Skip to content

Commit 7def640

Browse files
committed
[wip] p/invoke preservation source code generator
1 parent c025a42 commit 7def640

File tree

7 files changed

+144
-20
lines changed

7 files changed

+144
-20
lines changed

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ void Run (bool useMarshalMethods)
189189
// Now that "never" never happened, we can proceed knowing that at least the assembly sets are the same for each architecture
190190
var nativeCodeGenStates = new ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState> ();
191191
NativeCodeGenState? templateCodeGenState = null;
192-
var scanner = new PinvokeScanner (Log);
192+
PinvokeScanner? pinvokeScanner = EnableNativeRuntimeLinking ? new PinvokeScanner (Log) : null;
193193

194194
var firstArch = allAssembliesPerArch.First ().Key;
195195
var generateSucceeded = true;
@@ -209,12 +209,16 @@ void Run (bool useMarshalMethods)
209209
generateSucceeded = false;
210210
}
211211

212-
if (EnableNativeRuntimeLinking) {
213-
(success, List<PinvokeScanner.PinvokeEntryInfo> pinfos) = ScanForUsedPinvokes (scanner, arch, state.Resolver);
212+
if (!success || state == null) {
213+
return;
214+
}
215+
216+
if (pinvokeScanner != null) {
217+
(success, List<PinvokeScanner.PinvokeEntryInfo> pinfos) = ScanForUsedPinvokes (pinvokeScanner, arch, state.Resolver);
214218
if (!success) {
215219
return;
216220
}
217-
BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (PinvokeScanner.PinvokesInfoRegisterTaskKey), pinfos, RegisteredTaskObjectLifetime.Build);
221+
state.PinvokeInfos = pinfos;
218222
}
219223

220224
// If this is the first architecture, we need to store the state for later use

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ void AddEnvironment ()
316316
}
317317

318318
ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>? nativeCodeGenStates = null;
319-
if (enableMarshalMethods) {
319+
if (enableMarshalMethods || EnableNativeRuntimeLinking) {
320320
nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
321321
ProjectSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey),
322322
RegisteredTaskObjectLifetime.Build
@@ -357,8 +357,10 @@ void AddEnvironment ()
357357
string targetAbi = abi.ToLowerInvariant ();
358358
string environmentBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{targetAbi}");
359359
string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
360+
string? pinvokePreserveBaseAsmFilePath = EnableNativeRuntimeLinking ? Path.Combine (EnvironmentOutputDirectory, $"pinvoke_preserve.{targetAbi}") : null;
360361
string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll";
361362
string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
363+
string? pinvokePreserveLlFilePath = pinvokePreserveBaseAsmFilePath != null ? $"{pinvokePreserveBaseAsmFilePath}.ll" : null;
362364
AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi);
363365

364366
using var appConfigWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
@@ -389,10 +391,17 @@ void AddEnvironment ()
389391
}
390392

391393
if (EnableNativeRuntimeLinking) {
392-
// var pinfoGen = new PreservePinvokesNativeAssemblyGenerator (
393-
// Log,
394-
// targetArch,
395-
394+
var pinvokePreserveGen = new PreservePinvokesNativeAssemblyGenerator (Log, EnsureCodeGenState (targetArch), MonoComponents);
395+
LLVMIR.LlvmIrModule pinvokePreserveModule = pinvokePreserveGen.Construct ();
396+
using var pinvokePreserveWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
397+
try {
398+
pinvokePreserveGen.Generate (pinvokePreserveModule, targetArch, pinvokePreserveWriter, pinvokePreserveLlFilePath);
399+
} catch {
400+
throw;
401+
} finally {
402+
pinvokePreserveWriter.Flush ();
403+
Files.CopyIfStreamChanged (pinvokePreserveWriter.BaseStream, pinvokePreserveLlFilePath);
404+
}
396405
}
397406

398407
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
@@ -1606,6 +1606,15 @@ because xbuild doesn't support framework reference assemblies.
16061606
Mode="marshal_methods">
16071607
<Output TaskParameter="AssemblySources" ItemName="_MarshalMethodsAssemblySource" />
16081608
</PrepareAbiItems>
1609+
<PrepareAbiItems
1610+
Condition=" '$(_AndroidEnableNativeRuntimeLinking)' == 'true' "
1611+
BuildTargetAbis="@(_BuildTargetAbis)"
1612+
NativeSourcesDir="$(_NativeAssemblySourceDir)"
1613+
InstantRunEnabled="$(_InstantRunEnabled)"
1614+
Debug="$(AndroidIncludeDebugSymbols)"
1615+
Mode="runtime_linking">
1616+
<Output TaskParameter="AssemblySources" ItemName="_RuntimeLinkingAssemblySource" />
1617+
</PrepareAbiItems>
16091618
</Target>
16101619

16111620
<Target Name="_GenerateEnvironmentFiles" DependsOnTargets="_ReadAndroidManifest">
@@ -1739,6 +1748,8 @@ because xbuild doesn't support framework reference assemblies.
17391748
<Touch Files="$(_AndroidStampDirectory)_GeneratePackageManagerJava.stamp" AlwaysCreate="True" />
17401749
<ItemGroup>
17411750
<FileWrites Include="@(_EnvironmentAssemblySource)" />
1751+
<FileWrites Include="@(_MarshalMethodsAssemblySource)" />
1752+
<FileWrites Condition=" '$(_AndroidEnableNativeRuntimeLinking)' == 'true' " Include="@(_RuntimeLinkingAssemblySource)" />
17421753
</ItemGroup>
17431754
</Target>
17441755

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

@@ -1982,10 +1996,10 @@ because xbuild doesn't support framework reference assemblies.
19821996

19831997
<Target Name="_CompileNativeAssemblySources"
19841998
DependsOnTargets="_PrepareNativeAssemblyItems;_GenerateCompressedAssembliesNativeSourceFiles"
1985-
Inputs="@(_TypeMapAssemblySource);@(_TypeMapAssemblyInclude);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource)"
1999+
Inputs="@(_TypeMapAssemblySource);@(_TypeMapAssemblyInclude);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource);@(_RuntimeLinkingAssemblySource)"
19862000
Outputs="@(_NativeAssemblyTarget)">
19872001
<CompileNativeAssembly
1988-
Sources="@(_TypeMapAssemblySource);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource)"
2002+
Sources="@(_TypeMapAssemblySource);@(_EnvironmentAssemblySource);@(_CompressedAssembliesAssemblySource);@(_MarshalMethodsAssemblySource);@(_AndroidRemapAssemblySource);@(_RuntimeLinkingAssemblySource)"
19892003
DebugBuild="$(AndroidIncludeDebugSymbols)"
19902004
WorkingDirectory="$(_NativeAssemblySourceDir)"
19912005
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"

0 commit comments

Comments
 (0)