diff --git a/Configuration.props b/Configuration.props
index 3f7434a0ae0..deb5974bf17 100644
--- a/Configuration.props
+++ b/Configuration.props
@@ -228,6 +228,14 @@
AndroidRuntime="CoreCLR" />
+
+
+ <_MonoRuntimeFlavorDirName>mono
+ <_CLRRuntimeFlavorDirName>clr
+ <_RuntimeRedistDirName>redist
+
+
<_RuntimeRedistDirName>redist
diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj
index 603124f0466..53db6c52f87 100644
--- a/build-tools/create-packs/Microsoft.Android.Runtime.proj
+++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj
@@ -45,13 +45,42 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+.
DependsOnTargets="_GetLicense;_GetDefaultPackageVersion"
BeforeTargets="GetFilesToPackage" >
- <_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'CoreCLR' ">clr
- <_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'Mono' Or '$(AndroidRuntime)' == '' ">mono
+ <_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'CoreCLR' ">$(_CLRRuntimeFlavorDirName)
+ <_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'Mono' Or '$(AndroidRuntime)' == '' ">$(_MonoRuntimeFlavorDirName)
+ <_ClangArch Condition=" '$(AndroidRID)' == 'android-arm64' ">aarch64
+ <_ClangArch Condition=" '$(AndroidRID)' == 'android-arm' ">arm
+ <_ClangArch Condition=" '$(AndroidRID)' == 'android-x64' ">x86_64
+ <_ClangArch Condition=" '$(AndroidRID)' == 'android-x86' ">i686
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -59,7 +88,8 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+.
-
+
+
diff --git a/build-tools/scripts/generate-pinvoke-tables.sh b/build-tools/scripts/generate-pinvoke-tables.sh
index dbf70afd352..ca359228ce3 100755
--- a/build-tools/scripts/generate-pinvoke-tables.sh
+++ b/build-tools/scripts/generate-pinvoke-tables.sh
@@ -5,7 +5,7 @@ HOST="$(uname | tr A-Z a-z)"
NATIVE_DIR="${MY_DIR}/../../src/native"
MONODROID_SOURCE_DIR="${NATIVE_DIR}/mono/pinvoke-override"
MONODROID_INCLUDE_DIR="${NATIVE_DIR}/mono/shared"
-CLR_SOURCE_DIR="${NATIVE_DIR}/clr/host"
+CLR_SOURCE_DIR="${NATIVE_DIR}/clr/pinvoke-override"
CLR_INCLUDE_DIR="${NATIVE_DIR}/clr/include/shared"
GENERATOR_SOURCE="generate-pinvoke-tables.cc"
GENERATOR_BINARY="generate-pinvoke-tables"
diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs
index 609e68cf16a..9d881e4f552 100644
--- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs
+++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs
@@ -18,6 +18,7 @@ static class KnownProperties
public const string AndroidToolchainDirectory = "AndroidToolchainDirectory";
public const string AutoProvision = "AutoProvision";
public const string AutoProvisionUsesSudo = "AutoProvisionUsesSudo";
+ public const string CLRRuntimeFlavorDirName = "_CLRRuntimeFlavorDirName";
public const string CMakePath = "CmakePath";
public const string Configuration = "Configuration";
public const string CommandLineToolsVersion = nameof (CommandLineToolsVersion);
@@ -46,6 +47,7 @@ static class KnownProperties
public const string MonoDarwinPackageUrl = "MonoDarwinPackageUrl";
public const string MonoRequiredMinimumVersion = "MonoRequiredMinimumVersion";
public const string MonoRequiredMaximumVersion = "MonoRequiredMaximumVersion";
+ public const string MonoRuntimeFlavorDirName = "_MonoRuntimeFlavorDirName";
public const string MonoSourceFullPath = "MonoSourceFullPath";
public const string NativeRuntimeOutputRootDir = "NativeRuntimeOutputRootDir";
public const string NinjaPath = "NinjaPath";
diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in
index bd7330e78fd..6d63358fe13 100644
--- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in
+++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in
@@ -22,6 +22,7 @@ namespace Xamarin.Android.Prepare
properties.Add (KnownProperties.AndroidToolchainDirectory, StripQuotes (@"@AndroidToolchainDirectory@"));
properties.Add (KnownProperties.AutoProvision, StripQuotes ("@AutoProvision@"));
properties.Add (KnownProperties.AutoProvisionUsesSudo, StripQuotes ("@AutoProvisionUsesSudo@"));
+ properties.Add (KnownProperties.CLRRuntimeFlavorDirName, StripQuotes ("@_CLRRuntimeFlavorDirName@"));
properties.Add (KnownProperties.CMakePath, StripQuotes (@"@CmakePath@"));
properties.Add (KnownProperties.Configuration, StripQuotes ("@Configuration@"));
properties.Add (KnownProperties.CommandLineToolsFolder, StripQuotes ("@CommandLineToolsFolder@"));
@@ -48,6 +49,7 @@ namespace Xamarin.Android.Prepare
properties.Add (KnownProperties.MicrosoftAndroidSdkOutDir, StripQuotes (@"@MicrosoftAndroidSdkOutDir@"));
properties.Add (KnownProperties.MonoCecilVersion, StripQuotes ("@MonoCecilVersion@"));
properties.Add (KnownProperties.MonoDarwinPackageUrl, StripQuotes ("@MonoDarwinPackageUrl@"));
+ properties.Add (KnownProperties.MonoRuntimeFlavorDirName, StripQuotes ("@_MonoRuntimeFlavorDirName@"));
properties.Add (KnownProperties.MonoRequiredMinimumVersion, StripQuotes ("@MonoRequiredMinimumVersion@"));
properties.Add (KnownProperties.MonoRequiredMaximumVersion, StripQuotes ("@MonoRequiredMaximumVersion@"));
properties.Add (KnownProperties.MonoSourceFullPath, StripQuotes (@"@MonoSourceFullPath@"));
diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs
index 9d5a91b0cb5..d610d2d5c1c 100644
--- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs
+++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs
@@ -154,6 +154,13 @@ public static partial class Defaults
{ "x86_64", "android-x64" },
};
+ public static readonly Dictionary AbiToClangArch = new (StringComparer.Ordinal) {
+ { "armeabi-v7a", "arm" },
+ { "arm64-v8a", "aarch64" },
+ { "x86", "i686" },
+ { "x86_64", "x86_64" },
+ };
+
public static readonly List NDKTools = new List {
// Tools prefixed with architecture triple
new NDKTool (name: "as", prefixed: true),
@@ -233,6 +240,7 @@ public static partial class Paths
// Other
public static string AndroidNdkDirectory => ctx.Properties.GetRequiredValue (KnownProperties.AndroidNdkDirectory);
public static string AndroidToolchainRootDirectory => GetCachedPath (ref androidToolchainRootDirectory, () => Path.Combine (AndroidNdkDirectory, "toolchains", "llvm", "prebuilt", NdkToolchainOSTag));
+ public static string AndroidClangRootDirectory => GetCachedPath (ref androidClangRootDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "lib", "clang"));
public static string AndroidToolchainBinDirectory => GetCachedPath (ref androidToolchainBinDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "bin"));
public static string AndroidToolchainSysrootLibDirectory => GetCachedPath (ref androidToolchainSysrootLibDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "sysroot", "usr", "lib"));
public static string WindowsBinutilsInstallDir => GetCachedPath (ref windowsBinutilsInstallDir, () => Path.Combine (InstallMSBuildDir, "binutils"));
@@ -288,6 +296,7 @@ static string GetCachedPath (ref string? variable, Func creator)
static string? buildBinDir;
static string? binDir;
static string? androidToolchainRootDirectory;
+ static string? androidClangRootDirectory;
static string? androidToolchainBinDirectory;
static string? androidToolchainSysrootLibDirectory;
static string? installMSBuildDir;
diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs
index 2624a0deb69..956744edb87 100644
--- a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs
+++ b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs
@@ -24,6 +24,8 @@ sealed class AndroidPackage
#nullable enable
static readonly string[] CRTFiles = {
+ "crtbegin_so.o",
+ "crtend_so.o",
"libc.so",
"libdl.so",
"liblog.so",
@@ -31,6 +33,15 @@ sealed class AndroidPackage
"libz.so",
};
+ static readonly string[] CPPAbiFiles = {
+ "libc++_static.a",
+ "libc++abi.a",
+ };
+
+ static readonly string[] ClangArchFiles = {
+ "libunwind.a",
+ };
+
bool RefreshSdk = false;
bool RefreshNdk = false;
AndroidToolchainComponentType DependencyTypeToInstall = AndroidToolchainComponentType.All;
@@ -189,14 +200,38 @@ bool CopyRedistributableFiles (Context context)
throw new InvalidOperationException ($"Unknown LLVM version format for '{lines[0]}'");
}
+ string clangLibPath = Path.Combine (
+ Configurables.Paths.AndroidClangRootDirectory,
+ llvmVersion[0],
+ "lib",
+ "linux"
+ );
+
foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) {
string abi = kvp.Key;
string abiDir = Path.Combine (Configurables.Paths.AndroidToolchainSysrootLibDirectory, kvp.Value);
string crtFilesPath = Path.Combine (abiDir, BuildAndroidPlatforms.NdkMinimumAPI.ToString (CultureInfo.InvariantCulture));
+ string clangArch = Configurables.Defaults.AbiToClangArch[abi];
foreach (string file in CRTFiles) {
CopyFile (abi, crtFilesPath, file);
}
+
+ foreach (string file in CPPAbiFiles) {
+ CopyFile (abi, abiDir, file);
+ }
+
+ CopyFile (abi, clangLibPath, $"libclang_rt.builtins-{clangArch}-android.a");
+
+ // Yay, consistency
+ if (String.Compare (clangArch, "i686", StringComparison.Ordinal) == 0) {
+ clangArch = "i386";
+ }
+ string clangArchLibPath = Path.Combine (clangLibPath, clangArch);
+
+ foreach (string file in ClangArchFiles) {
+ CopyFile (abi, clangArchLibPath, file);
+ }
}
return true;
diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets
index 8ba19fe310b..5e6f96ac64e 100644
--- a/build-tools/xaprepare/xaprepare/xaprepare.targets
+++ b/build-tools/xaprepare/xaprepare/xaprepare.targets
@@ -39,6 +39,8 @@
Replacements="@XA_SOURCE_ROOT@=$([System.IO.Path]::GetFullPath ('$(MSBuildThisFileDirectory)..\..\..'));@XA_PREPARE_SOURCE@=$([System.IO.Path]::GetFullPath ('$(MSBuildThisFileDirectory)\.'));@XA_PRODUCT_VERSION@=$(ProductVersion)" />
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
index 8a64f834b80..9cb93fb2e06 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
@@ -24,6 +24,10 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets.
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
index 6c3a48b3131..b5313528959 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
@@ -258,6 +258,7 @@ _ResolveAssemblies MSBuild target.
+
+
+
+
+
+
+ <_UnifiedNativeRuntime Include="$(_AndroidApplicationSharedLibraryPath)%(_BuildTargetAbis.Identity)\libnetdroid-unified.so">
+ %(_BuildTargetAbis.Identity)
+ libmonodroid.so
+
+
+
+
+ <_ResolvedNativeArchive Include="@(ResolvedFileToPublish)" Condition=" '%(ResolvedFileToPublish.Extension)' == '.a' " />
+ <_ResolvedNativeObjectFile Include="@(ResolvedFileToPublish)" Condition=" '%(ResolvedFileToPublish.Extension)' == '.o' " />
+ <_ApplicationSharedLibrary Include="@(_UnifiedNativeRuntime)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectNativeFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectNativeFilesForArchive.cs
index 71e29ee02aa..25f2e506e74 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CollectNativeFilesForArchive.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectNativeFilesForArchive.cs
@@ -110,7 +110,11 @@ void AddRuntimeLibraries (PackageFileListBuilder apk, string [] supportedAbis)
foreach (ITaskItem item in ApplicationSharedLibraries) {
if (string.Compare (abi, item.GetMetadata ("abi"), StringComparison.Ordinal) != 0)
continue;
- AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec), item);
+ string? inArchiveFileName = item.GetMetadata ("ArchiveFileName");
+ if (String.IsNullOrEmpty (inArchiveFileName)) {
+ inArchiveFileName = Path.GetFileName (item.ItemSpec);
+ }
+ AddNativeLibraryToArchive (apk, abi, item.ItemSpec, inArchiveFileName, item);
}
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 31818e8cafc..0b3e6d86757 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -58,6 +58,8 @@ public class GenerateJavaStubs : AndroidTask
public string CodeGenerationTarget { get; set; } = "";
+ public bool EnableNativeRuntimeLinking { get; set; }
+
// These two properties are temporary and are used to ensure we still generate the
// same files as before using the new _LinkAssembliesNoShrink JLO scanning. They will be removed in the future.
public bool RunCheckedBuild { get; set; }
@@ -156,6 +158,7 @@ void Run (bool useMarshalMethods)
// Now that "never" never happened, we can proceed knowing that at least the assembly sets are the same for each architecture
var nativeCodeGenStates = new ConcurrentDictionary ();
NativeCodeGenState? templateCodeGenState = null;
+ PinvokeScanner? pinvokeScanner = EnableNativeRuntimeLinking ? new PinvokeScanner (Log) : null;
var firstArch = allAssembliesPerArch.First ().Key;
var generateSucceeded = true;
@@ -181,6 +184,15 @@ void Run (bool useMarshalMethods)
}
nativeCodeGenStates.TryAdd (arch, state);
+
+ if (pinvokeScanner != null && state != null) {
+ (success, List pinfos) = ScanForUsedPinvokes (pinvokeScanner, arch, state.Resolver);
+ if (!success) {
+ return;
+ }
+ state.PinvokeInfos = pinfos;
+ Log.LogDebugMessage ($"Number of unique p/invokes for architecture '{arch}': {pinfos.Count}");
+ }
});
// If we hit an error generating the Java code, we should bail out now
@@ -200,6 +212,31 @@ void Run (bool useMarshalMethods)
BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
}
+ (bool success, List? pinfos) ScanForUsedPinvokes (PinvokeScanner scanner, AndroidTargetArch arch, XAAssemblyResolver resolver)
+ {
+ if (!EnableNativeRuntimeLinking) {
+ return (true, null);
+ }
+
+ var frameworkAssemblies = new List ();
+
+ foreach (ITaskItem asm in ResolvedAssemblies) {
+ string? metadata = asm.GetMetadata ("FrameworkAssembly");
+ if (String.IsNullOrEmpty (metadata)) {
+ continue;
+ }
+
+ if (!Boolean.TryParse (metadata, out bool isFrameworkAssembly) || !isFrameworkAssembly) {
+ continue;
+ }
+
+ frameworkAssemblies.Add (asm);
+ }
+
+ var pinfos = scanner.Scan (arch, resolver, frameworkAssemblies);
+ return (true, pinfos);
+ }
+
internal static Dictionary MaybeGetArchAssemblies (Dictionary> dict, AndroidTargetArch arch)
{
if (!dict.TryGetValue (arch, out Dictionary archDict)) {
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
index 547af3de6a3..2df016f3eb0 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
@@ -25,6 +25,7 @@ public class GenerateMainAndroidManifest : AndroidTask
public string? CheckedBuild { get; set; }
public bool Debug { get; set; }
public bool EmbedAssemblies { get; set; }
+ public bool EnableNativeRuntimeLinking { get; set; }
[Required]
public string IntermediateOutputDirectory { get; set; } = "";
public string []? ManifestPlaceholders { get; set; }
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
index 32868b4579a..b9749280bf0 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
@@ -20,6 +20,10 @@ public class GenerateNativeMarshalMethodSources : AndroidTask
public bool EnableMarshalMethods { get; set; }
+ public bool EnableNativeRuntimeLinking { get; set; }
+
+ public ITaskItem[] MonoComponents { get; set; } = [];
+
[Required]
public string EnvironmentOutputDirectory { get; set; } = "";
@@ -38,7 +42,7 @@ public override bool RunTask ()
{
NativeCodeGenStateCollection? nativeCodeGenStates = null;
- if (EnableMarshalMethods) {
+ if (EnableMarshalMethods || EnableNativeRuntimeLinking) {
// Retrieve the stored NativeCodeGenStateCollection (and remove it from the cache)
nativeCodeGenStates = BuildEngine4.UnregisterTaskObjectAssemblyLocal (
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
@@ -57,7 +61,9 @@ void Generate (NativeCodeGenStateCollection? nativeCodeGenStates, string abi)
var targetAbi = abi.ToLowerInvariant ();
var targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
var marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
+ var pinvokePreserveBaseAsmFilePath = EnableNativeRuntimeLinking ? Path.Combine (EnvironmentOutputDirectory, $"pinvoke_preserve.{targetAbi}") : null;
var marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
+ var pinvokePreserveLlFilePath = pinvokePreserveBaseAsmFilePath != null ? $"{pinvokePreserveBaseAsmFilePath}.ll" : null;
var (assemblyCount, uniqueAssemblyNames) = GetAssemblyCountAndUniqueNames ();
MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen;
@@ -79,6 +85,20 @@ void Generate (NativeCodeGenStateCollection? nativeCodeGenStates, string abi)
);
}
+ if (EnableNativeRuntimeLinking) {
+ var pinvokePreserveGen = new PreservePinvokesNativeAssemblyGenerator (Log, EnsureCodeGenState (nativeCodeGenStates, targetArch), MonoComponents);
+ LLVMIR.LlvmIrModule pinvokePreserveModule = pinvokePreserveGen.Construct ();
+ using var pinvokePreserveWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
+ try {
+ pinvokePreserveGen.Generate (pinvokePreserveModule, targetArch, pinvokePreserveWriter, pinvokePreserveLlFilePath!);
+ } catch {
+ throw;
+ } finally {
+ pinvokePreserveWriter.Flush ();
+ Files.CopyIfStreamChanged (pinvokePreserveWriter.BaseStream, pinvokePreserveLlFilePath!);
+ }
+ }
+
var marshalMethodsModule = marshalMethodsAsmGen.Construct ();
using var marshalMethodsWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetNativeRuntimeComponents.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetNativeRuntimeComponents.cs
new file mode 100644
index 00000000000..d5b5e6f917c
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetNativeRuntimeComponents.cs
@@ -0,0 +1,232 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+
+namespace Xamarin.Android.Tasks;
+
+public class GetNativeRuntimeComponents : AndroidTask
+{
+ public override string TaskPrefix => "GNRC";
+
+ public ITaskItem[]? MonoComponents { get; set; }
+
+ [Required]
+ public ITaskItem[] ResolvedNativeArchives { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] ResolvedNativeObjectFiles { get; set; } = null!;
+
+ [Required]
+ public string HackLocalClrRepoPath { get; set; } = "";
+
+ [Output]
+ public ITaskItem[] NativeArchives { get; set; } = null!;
+
+ [Output]
+ public ITaskItem[] RequiredLibraries { get; set; } = null!;
+
+ [Output]
+ public ITaskItem[] LinkStartFiles { get; set; } = null!;
+
+ [Output]
+ public ITaskItem[] LinkEndFiles { get; set; } = null!;
+
+ // TODO: more research, for now it seems `--export-dynamic-symbol=name` options generated from
+ // this array don't work as expected.
+ [Output]
+ public ITaskItem[] NativeSymbolsToExport { get; set; } = [];
+
+ public override bool RunTask ()
+ {
+ var components = new NativeRuntimeComponents (MonoComponents);
+ var uniqueAbis = new HashSet (StringComparer.OrdinalIgnoreCase);
+ var archives = new List ();
+ var symbolsToExport = new List ();
+
+ Log.LogDebugMessage ($"Generating list of native files for linking");
+ foreach (NativeRuntimeComponents.Archive archiveItem in components.KnownArchives) {
+ Log.LogDebugMessage ($" archive '{archiveItem.Name}'");
+ if (!archiveItem.Include) {
+ Log.LogDebugMessage (" will not be included");
+ continue;
+ }
+ MakeArchiveItem (archiveItem, archives, uniqueAbis);
+ if (archiveItem.SymbolsToPreserve == null || archiveItem.SymbolsToPreserve.Count == 0) {
+ continue;
+ }
+
+ foreach (string symbolName in archiveItem.SymbolsToPreserve) {
+ MakeLibItem (symbolName, symbolsToExport, uniqueAbis);
+ }
+ }
+
+ // HACK! START: until CoreCLR runtime pack has the necessary .a archives
+ var discoveredItemNames = new HashSet (StringComparer.OrdinalIgnoreCase);
+ foreach (ITaskItem item in archives) {
+ discoveredItemNames.Add (Path.GetFileName (item.ItemSpec));
+ }
+
+ Log.LogWarning ("[HACK] Looking for native archives which require CoreCLR hack");
+ foreach (NativeRuntimeComponents.Archive archiveItem in components.KnownArchives) {
+ if (!archiveItem.Include || !archiveItem.NeedsClrHack) {
+ continue;
+ }
+
+ Log.LogDebugMessage ($" [HACK] archive {archiveItem.Name}");
+ if (discoveredItemNames.Contains (archiveItem.Name)) {
+ Log.LogDebugMessage (" [HACK] already found elsewhere");
+ continue;
+ }
+ HackMakeArchiveItem (archiveItem, archives, uniqueAbis);
+ }
+ // HACK! END
+
+ NativeArchives = archives.ToArray ();
+ NativeSymbolsToExport = symbolsToExport.ToArray ();
+
+ var items = new List ();
+ foreach (string lib in components.NativeLibraries) {
+ MakeLibItem (lib, items, uniqueAbis);
+ }
+ RequiredLibraries = items.ToArray ();
+
+ items = new List ();
+ foreach (string startFile in components.LinkStartFiles) {
+ MakeFileItem ("_NativeLinkStartFiles", startFile, ResolvedNativeObjectFiles, items, uniqueAbis);
+ }
+ LinkStartFiles = items.ToArray ();
+
+ items = new List ();
+ foreach (string endFile in components.LinkEndFiles) {
+ MakeFileItem ("_NativeLinkEndFiles", endFile, ResolvedNativeObjectFiles, items, uniqueAbis);
+ }
+ LinkEndFiles = items.ToArray ();
+
+ return !Log.HasLoggedErrors;
+ }
+
+ void MakeLibItem (string libName, List libraries, HashSet uniqueAbis)
+ {
+ foreach (string abi in uniqueAbis) {
+ var item = new TaskItem (libName);
+ item.SetMetadata (KnownMetadata.Abi, abi);
+ item.SetMetadata (KnownMetadata.NativeSharedLibrary, "true");
+ libraries.Add (item);
+ }
+ }
+
+ void MakeFileItem (string msbuildItemName, string fileName, ITaskItem[] inputItems, List outputItems, HashSet uniqueAbis)
+ {
+ foreach (ITaskItem item in inputItems) {
+ string name = Path.GetFileName (item.ItemSpec);
+ if (String.Compare (name, fileName, StringComparison.OrdinalIgnoreCase) == 0) {
+ outputItems.Add (DoMakeItem (msbuildItemName, item, uniqueAbis));
+ }
+ }
+ }
+
+ void MakeArchiveItem (NativeRuntimeComponents.Archive archive, List archives, HashSet uniqueAbis)
+ {
+ foreach (ITaskItem resolvedArchive in ResolvedNativeArchives) {
+ string fileName = Path.GetFileName (resolvedArchive.ItemSpec);
+ if (String.Compare (fileName, archive.Name, StringComparison.OrdinalIgnoreCase) != 0) {
+ continue;
+ }
+
+ Log.LogDebugMessage ($" creating msbuild item for archive '{archive.Name}'");
+ ITaskItem newItem = DoMakeItem ("_ResolvedNativeArchive", resolvedArchive, uniqueAbis);
+ newItem.SetMetadata (KnownMetadata.NativeLinkWholeArchive, archive.WholeArchive.ToString ());
+ newItem.SetMetadata (KnownMetadata.NativeLinkItemSet, archive.SetName);
+ if (archive.DontExportSymbols) {
+ newItem.SetMetadata (KnownMetadata.NativeDontExportSymbols, "true");
+ }
+ archives.Add (newItem);
+ }
+ }
+
+ ITaskItem DoMakeItem (string msbuildItemName, ITaskItem sourceItem, HashSet uniqueAbis)
+ {
+ var ret = new TaskItem (sourceItem.ItemSpec);
+ string rid = sourceItem.GetRequiredMetadata (msbuildItemName, KnownMetadata.RuntimeIdentifier, Log) ?? String.Empty;
+ string abi = MonoAndroidHelper.RidToAbi (rid);
+ uniqueAbis.Add (abi);
+ ret.SetMetadata (KnownMetadata.Abi, abi);
+ ret.SetMetadata (KnownMetadata.RuntimeIdentifier, rid);
+
+ return ret;
+ }
+
+ void HackMakeArchiveItem (NativeRuntimeComponents.Archive archive, List archives, HashSet uniqueAbis)
+ {
+ var relativeArtifactPaths = new List<(string path, string abi)> ();
+ string archiveName = Path.GetFileName (archive.Name);
+ string commonClrObjDir = Path.Combine ("artifacts", "obj", "coreclr");
+ const string config = "Release";
+
+ if (IsArchive ("libcoreclr_static.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine ("artifacts", "bin", "coreclr", $"android.{clrArch}.{config}"));
+ } else if (IsArchive ("libcoreclr.a")) {
+ archiveName = "libcoreclr_static.a";
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "dlls", "mscoree", "coreclr"));
+ } else if (IsArchive ("libcoreclrpal.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "pal", "src"));
+ } else if (IsArchive ("libminipal.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "shared_minipal"));
+ } else if (IsArchive ("libcoreclrminipal.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "minipal", "Unix"));
+ } else if (IsArchive ("libgc_pal.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "gc", "unix"));
+ } else if (IsArchive ("libeventprovider.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "pal", "src", "eventprovider", "dummyprovider"));
+ } else if (IsArchive ("libnativeresourcestring.a")) {
+ MakeRelativeArtifactPaths ((string clrArch) => Path.Combine (commonClrObjDir, $"android.{clrArch}.{config}", "nativeresources"));
+ } else {
+ foreach (string abi in uniqueAbis) {
+ string clrArch = GetClrArch (abi);
+ relativeArtifactPaths.Add ((Path.Combine ("artifacts", "bin", $"microsoft.netcore.app.runtime.android-{clrArch}", config, "runtimes", $"android-{clrArch}", "native"), abi));
+ }
+ }
+
+ foreach ((string relPath, string abi) in relativeArtifactPaths) {
+ string filePath = Path.Combine (HackLocalClrRepoPath, relPath, archiveName);
+ if (!File.Exists (filePath)) {
+ Log.LogError ($" [HACK] file {filePath} not found");
+ continue;
+ }
+ Log.LogWarning ($" [HACK] adding runtime component '{filePath}'");
+ var tempItem = new TaskItem (filePath);
+ tempItem.SetMetadata (KnownMetadata.Abi, abi);
+ tempItem.SetMetadata (KnownMetadata.RuntimeIdentifier, MonoAndroidHelper.AbiToRid (abi));
+ ITaskItem newItem = DoMakeItem ("_ResolvedNativeArchive", tempItem, uniqueAbis);
+ newItem.SetMetadata (KnownMetadata.NativeLinkWholeArchive, archive.WholeArchive.ToString ());
+ newItem.SetMetadata (KnownMetadata.NativeLinkItemSet, archive.SetName);
+ if (archive.DontExportSymbols) {
+ newItem.SetMetadata (KnownMetadata.NativeDontExportSymbols, "true");
+ }
+ archives.Add (newItem);
+ }
+
+ string GetClrArch (string abi)
+ {
+ return abi switch {
+ "arm64-v8a" => "arm64",
+ "x86_64" => "x64",
+ _ => throw new NotSupportedException ($"ABI {abi} is not supported for CoreCLR")
+ };
+ }
+
+ void MakeRelativeArtifactPaths (Func create)
+ {
+ foreach (string abi in uniqueAbis) {
+ string clrArch = GetClrArch (abi);
+ relativeArtifactPaths.Add ((create (clrArch), abi));
+ }
+ }
+
+ bool IsArchive (string name) => String.Compare (name, archiveName, StringComparison.OrdinalIgnoreCase) == 0;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
index f77f5a37581..e1f0f81f0cd 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
@@ -73,6 +73,7 @@ IEnumerable GetLinkerConfigs ()
RuntimePackLibraryDirectories,
CancellationToken
) {
+ AllowUndefinedSymbols = true,
StripDebugSymbols = !DebugBuild,
SaveDebugSymbols = !DebugBuild,
ZipAlignmentPages = ZipAlignmentPages,
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeRuntime.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeRuntime.cs
new file mode 100644
index 00000000000..324c21473b1
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeRuntime.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+using Microsoft.Android.Build.Tasks;
+
+namespace Xamarin.Android.Tasks;
+
+public class LinkNativeRuntime : AsyncTask
+{
+ public override string TaskPrefix => "LNR";
+
+ public ITaskItem[]? MonoComponents { get; set; }
+
+ [Required]
+ public string AndroidBinUtilsDirectory { get; set; } = "";
+
+ public string? AndroidNdkDirectory { get; set; }
+ public string? AndroidApiLevel { get; set; }
+
+ [Required]
+ public string IntermediateOutputPath { get; set; } = "";
+
+ [Required]
+ public ITaskItem[] LinkLibraries { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] NativeArchives { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] NativeObjectFiles { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] NativeLinkStartFiles { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] NativeLinkEndFiles { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] NativeSymbolsToExport { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] OutputRuntimes { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] SupportedAbis { get; set; } = null!;
+
+ [Required]
+ public ITaskItem[] RuntimePackLibraryDirectories { get; set; } = null!;
+
+ public bool SaveDebugSymbols { get; set; } = true;
+ public bool StripDebugSymbols { get; set; } = true;
+
+ public override System.Threading.Tasks.Task RunTaskAsync ()
+ {
+ return this.WhenAll (SupportedAbis, LinkRuntime);
+ }
+
+ void LinkRuntime (ITaskItem abiItem)
+ {
+ string abi = abiItem.ItemSpec;
+ Log.LogDebugMessage ($"LinkRuntime ({abi})");
+ ITaskItem outputRuntime = GetFirstAbiItem (OutputRuntimes, "_UnifiedNativeRuntime", abi);
+ string soname = Path.GetFileNameWithoutExtension (outputRuntime.ItemSpec);
+ if (soname.StartsWith ("lib", StringComparison.OrdinalIgnoreCase)) {
+ soname = soname.Substring (3);
+ }
+
+ var linker = new NativeLinker (Log, abi, soname, AndroidBinUtilsDirectory, IntermediateOutputPath, RuntimePackLibraryDirectories, CancellationToken, Cancel) {
+ StripDebugSymbols = StripDebugSymbols,
+ SaveDebugSymbols = SaveDebugSymbols,
+ UseNdkLibraries = true,
+ UseSymbolic = true,
+ NdkRootPath = AndroidNdkDirectory,
+ NdkApiLevel = AndroidApiLevel,
+ };
+
+ List items = OrganizeCommandLineItemsCLR (abi);
+ bool success = linker.Link (
+ outputRuntime,
+ items,
+ GetAbiItems (NativeLinkStartFiles, "_NativeLinkStartFiles", abi),
+ GetAbiItems (NativeLinkEndFiles, "_NativeLinkEndFiles", abi),
+ GetAbiItems (NativeSymbolsToExport, "_NativeSymbolsToExport", abi)
+ );
+
+ if (!success) {
+ Log.LogError ($"Failed to link native runtime {outputRuntime}");
+ }
+ }
+
+ // Puts object files, static archives in the correct order. This is a bit clumsy, but unfortunately necessary
+ List OrganizeCommandLineItemsCLR (string abi)
+ {
+ // Code farther down the method does NOT check whether a set is present, it assumes that. This is on purpose, to
+ // let the exception be thrown should a required (and assumed to be present) set be missing.
+ var sets = new Dictionary> (StringComparer.Ordinal);
+ foreach (ITaskItem item in GetAbiItems (NativeArchives, "_SelectedNativeArchive", abi)) {
+ string setName = item.GetRequiredMetadata ("_SelectedNativeArchive", KnownMetadata.NativeLinkItemSet, Log) ?? String.Empty;
+ if (!sets.TryGetValue (setName, out List? items)) {
+ items = new List ();
+ sets.Add (setName, items);
+ }
+
+ items.Add (item);
+ }
+
+ var ret = new List ();
+
+ // First go our own object files...
+ ret.AddRange (GetAbiItems (NativeObjectFiles, "_NativeAssemblyTarget", abi));
+
+ // ...then go our runtime archives...
+ ret.AddRange (sets[NativeRuntimeComponents.KnownSets.XamarinAndroidRuntime]);
+
+ // ...followed by CoreCLR components...
+ ret.AddRange (sets[NativeRuntimeComponents.KnownSets.CoreClrRuntime]);
+
+ // ...and after them the BCL PAL libraries...
+ ret.AddRange (sets[NativeRuntimeComponents.KnownSets.BCL]);
+
+ // ...and then the C/C++ runtime libraries
+ var systemLibs = new Dictionary (StringComparer.Ordinal);
+ foreach (ITaskItem item in GetAbiItems (LinkLibraries, "_RequiredLinkLibraries", abi)) {
+ systemLibs.Add (Path.GetFileName (item.ItemSpec), item);
+ }
+
+ ret.Add (systemLibs["log"]);
+ ret.AddRange (sets[NativeRuntimeComponents.KnownSets.CplusPlusRuntime]);
+ ret.Add (systemLibs["z"]);
+ ret.Add (systemLibs["m"]);
+ ret.Add (systemLibs["dl"]);
+ ret.Add (systemLibs["c"]);
+
+ return ret;
+ }
+
+ List GetAbiItems (ITaskItem[] source, string itemName, string abi)
+ {
+ var ret = new List ();
+
+ foreach (ITaskItem item in source) {
+ if (AbiMatches (abi, item, itemName)) {
+ ret.Add (item);
+ }
+ }
+
+ return ret;
+ }
+
+ ITaskItem GetFirstAbiItem (ITaskItem[] source, string itemName, string abi)
+ {
+ foreach (ITaskItem item in source) {
+ if (AbiMatches (abi, item, itemName)) {
+ return item;
+ }
+ }
+
+ throw new InvalidOperationException ($"Internal error: item '{itemName}' for ABI '{abi}' not found");
+ }
+
+ bool AbiMatches (string abi, ITaskItem item, string itemName)
+ {
+ return String.Compare (abi, item.GetRequiredMetadata (itemName, "Abi", Log), StringComparison.OrdinalIgnoreCase) == 0;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
index cad5439df21..52d87df5565 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
@@ -16,6 +16,7 @@ public class PrepareAbiItems : AndroidTask
const string CompressedAssembliesBase = "compressed_assemblies";
const string JniRemappingBase = "jni_remap";
const string MarshalMethodsBase = "marshal_methods";
+ const string PinvokePreserveBase = "pinvoke_preserve";
public override string TaskPrefix => "PAI";
@@ -53,6 +54,8 @@ public override bool RunTask ()
baseName = JniRemappingBase;
} else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
baseName = MarshalMethodsBase;
+ } else if (String.Compare ("runtime_linking", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
+ baseName = PinvokePreserveBase;
} else {
Log.LogError ($"Unknown mode: {Mode}");
return false;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs
index 8267d0dde17..b752096d9fa 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs
@@ -22,6 +22,16 @@ public class ProcessNativeLibraries : AndroidTask
"libxamarin-debug-app-helper",
};
+ // Please keep the list sorted. Any new runtime libraries that are added upstream need to be mentioned here.
+ static readonly HashSet KnownRuntimeNativeLibrariesCLR = new (StringComparer.OrdinalIgnoreCase) {
+ "libSystem.Globalization.Native.so",
+ "libSystem.IO.Compression.Native.so",
+ "libSystem.Native.so",
+ "libSystem.Security.Cryptography.Native.Android.so",
+ "libclrjit.so",
+ "libcorclr.so",
+ };
+
///
/// Assumed to be .so files only
///
@@ -30,6 +40,7 @@ public class ProcessNativeLibraries : AndroidTask
public string []? ExcludedLibraries { get; set; }
public bool IncludeDebugSymbols { get; set; }
+ public bool NativeRuntimeLinking { get; set; }
[Output]
public ITaskItem []? OutputLibraries { get; set; }
@@ -69,6 +80,11 @@ public override bool RunTask ()
}
if (fileName.StartsWith ("libmono-android", StringComparison.Ordinal) || fileName.StartsWith ("libnet-android", StringComparison.Ordinal)) {
+ if (NativeRuntimeLinking) {
+ // We don't need the precompiled runtime, it will be linked during application build
+ continue;
+ }
+
if (fileName.EndsWith (".debug", StringComparison.Ordinal)) {
if (!IncludeDebugSymbols)
continue;
@@ -90,12 +106,32 @@ public override bool RunTask ()
}
}
- output.Add (library);
+ if (!IgnoreLibraryWhenLinkingRuntime (library)) {
+ output.Add (library);
+ }
}
OutputLibraries = output.ToArray ();
return !Log.HasLoggedErrors;
}
+
+ bool IgnoreLibraryWhenLinkingRuntime (ITaskItem libItem)
+ {
+ if (!NativeRuntimeLinking) {
+ return false;
+ }
+
+ // We ignore all the shared libraries coming from the runtime packages, as they are all linked into our runtime and
+ // need not be packaged.
+ string packageId = libItem.GetMetadata ("NuGetPackageId");
+ if (packageId.StartsWith ("Microsoft.NETCore.App.Runtime.Mono.android-", StringComparison.OrdinalIgnoreCase) ||
+ packageId.StartsWith ("Microsoft.NETCore.App.Runtime.CoreCLR.android-", StringComparison.OrdinalIgnoreCase)) {
+ return true;
+ }
+
+ // Should `NuGetPackageId` be empty, we check the libs by name, as the last resort.
+ return KnownRuntimeNativeLibrariesCLR.Contains (Path.GetFileName (libItem.ItemSpec));
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
index 1fdb3ce1bf2..03440ccf3f1 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
@@ -28,6 +28,8 @@ class MarshalMethodCecilAdapter
var arch = kvp.Key;
var state = kvp.Value;
var obj = CreateNativeCodeGenState (arch, state);
+ obj.PinvokeInfos = state.PinvokeInfos;
+ obj.TargetArch = state.TargetArch;
collection.States.Add (arch, obj);
}
@@ -156,6 +158,8 @@ class NativeCodeGenStateCollection
class NativeCodeGenStateObject
{
public Dictionary> MarshalMethods { get; } = [];
+ public List? PinvokeInfos { get; set; }
+ public AndroidTargetArch TargetArch { get; set; } = AndroidTargetArch.None;
public List<(string JniName, string AssemblyQualifiedName)> ApplicationsAndInstrumentationsToRegister { get; } = [];
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
index 6a0785dfd94..4314d17f812 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
@@ -30,6 +30,13 @@ class NativeCodeGenState
///
public List AllJavaTypes { get; }
+ ///
+ /// Contains information about p/invokes used by the managed assemblies included in the
+ /// application. Will be **null** unless native runtime linking at application build time
+ /// is enabled.
+ ///
+ public List? PinvokeInfos { get; set; }
+
public List JavaTypesForJCW { get; }
public XAAssemblyResolver Resolver { get; }
public TypeDefinitionCache TypeCache { get; }
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs
index 8fed3e87554..8c353aeb1d5 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs
@@ -47,6 +47,7 @@ class NativeLinker
public bool AllowUndefinedSymbols { get; set; } = false;
public bool UseNdkLibraries { get; set; } = false;
public bool TargetsCLR { get; set; }
+ public bool UseSymbolic { get; set; }
public string? NdkRootPath { get; set; }
public string? NdkApiLevel { get; set; }
public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit;
@@ -157,6 +158,10 @@ public bool Link (ITaskItem outputLibraryPath, List linkItems, List shouldInclude (this);
+ public readonly bool WholeArchive;
+ public bool DontExportSymbols { get; set; }
+ public HashSet? SymbolsToPreserve { get; set; }
+ public string SetName { get; }
+
+ public readonly bool NeedsClrHack;
+
+ Func shouldInclude;
+
+ public Archive (string name, string setName, Func? include = null, bool wholeArchive = false, string? jniOnLoadName = null, bool needsClrHack = false)
+ {
+ Name = name;
+ SetName = setName;
+ shouldInclude = include == null ? ((Archive arch) => true) : include;
+ WholeArchive = wholeArchive;
+ JniOnLoadName = jniOnLoadName;
+ NeedsClrHack = needsClrHack;
+ }
+ }
+
+ sealed class ClangBuiltinsArchive : Archive
+ {
+ public ClangBuiltinsArchive (string clangAbi)
+ : base ($"libclang_rt.builtins-{clangAbi}-android.a", KnownSets.CplusPlusRuntime)
+ {}
+ }
+
+ class AndroidArchive : Archive
+ {
+ public AndroidArchive (string name, bool wholeArchive = false)
+ : base (name, KnownSets.XamarinAndroidRuntime, wholeArchive: wholeArchive)
+ {}
+ }
+
+ sealed class BclArchive : Archive
+ {
+ public BclArchive (string name, bool wholeArchive = false, string? jniOnLoadName = null)
+ : base (name, KnownSets.BCL, wholeArchive: wholeArchive, jniOnLoadName: jniOnLoadName, needsClrHack: true)
+ {
+ DontExportSymbols = true;
+ }
+ }
+
+ sealed class ClrArchive : Archive
+ {
+ public ClrArchive (string name, bool wholeArchive = false)
+ : base (name, KnownSets.CoreClrRuntime, wholeArchive: wholeArchive, needsClrHack: true)
+ {
+ DontExportSymbols = true;
+ }
+ }
+
+ sealed class CplusPlusArchive : Archive
+ {
+ public CplusPlusArchive (string name)
+ : base (name, KnownSets.CplusPlusRuntime)
+ {
+ DontExportSymbols = true;
+ }
+ }
+
+ readonly ITaskItem[]? monoComponents;
+
+ public readonly List KnownArchives;
+ public readonly List NativeLibraries;
+ public readonly List LinkStartFiles;
+ public readonly List LinkEndFiles;
+
+ public NativeRuntimeComponents (ITaskItem[]? monoComponents)
+ {
+ this.monoComponents = monoComponents;
+ KnownArchives = new () {
+ // CoreCLR runtime + BCL
+ new ClrArchive ("libcoreclr_static.a"),
+ // new ClrArchive ("libcoreclrminipal.a"),
+ // new ClrArchive ("libgc_pal.a"),
+
+ // new ClrArchive ("libcoreclrpal.a", wholeArchive: true),
+ // new ClrArchive ("libeventprovider.a"),
+ // new ClrArchive ("libnativeresourcestring.a"),
+ // new ClrArchive ("libminipal.a")
+ new ClrArchive ("libbrotlienc.a"),
+ new ClrArchive ("libbrotlidec.a"),
+ new ClrArchive ("libbrotlicommon.a"),
+
+ new BclArchive ("libSystem.Globalization.Native.a"),
+ new BclArchive ("libSystem.IO.Compression.Native.a"),
+ new BclArchive ("libSystem.IO.Ports.Native.a"),
+ new BclArchive ("libSystem.Native.a"),
+ new BclArchive ("libSystem.Security.Cryptography.Native.Android.a", jniOnLoadName: "AndroidCryptoNative_InitLibraryOnLoad") {
+ SymbolsToPreserve = new (StringComparer.Ordinal) {
+ // This isn't referenced directly by any code in libSystem.Security.Cryptography.Native.Android. It is instead
+ // referenced by the Java code shipped with the component (`DotnetProxyTrustManager`), as a native Java method:
+ //
+ // static native boolean verifyRemoteCertificate(long sslStreamProxyHandle);
+ //
+ // Therefore we must reference it explicitly
+ "Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate"
+ },
+
+ // For now, we have to export all the symbols from this archive because we need the above `Java_net*` symbol to be
+ // externally visible, and the linker's `--exclude-libs` flag works on the archive (.a) level.
+ //
+ // TODO: use `llvm-ar` to extract the relevant object file and link it separately?
+ DontExportSymbols = false,
+ },
+
+ // .NET for Android
+ new AndroidArchive ("libnet-android.release-static-release.a", wholeArchive: true),
+ new AndroidArchive ("libpinvoke-override-dynamic-release.a", wholeArchive: true),
+ new AndroidArchive ("libruntime-base-common-release.a"),
+ new AndroidArchive ("libruntime-base-release.a"),
+ new AndroidArchive ("libxa-java-interop-release.a"),
+ new AndroidArchive ("libxa-lz4-release.a"),
+ new AndroidArchive ("libxa-shared-bits-release.a"),
+ new AndroidArchive ("libxamarin-startup-release.a"),
+
+ // C++ standard library
+ new CplusPlusArchive ("libc++_static.a"),
+ new CplusPlusArchive ("libc++abi.a"),
+
+ // LLVM clang built-ins archives
+ new ClangBuiltinsArchive ("aarch64"),
+ new ClangBuiltinsArchive ("arm"),
+ new ClangBuiltinsArchive ("i686"),
+ new ClangBuiltinsArchive ("x86_64"),
+
+ new CplusPlusArchive ("libunwind.a"), // techically it's from clang
+ };
+
+ // Just the base names of libraries to link into the unified runtime. Must have all the dependencies of all the static archives we
+ // link into the final library.
+ NativeLibraries = new () {
+ "c",
+ "dl",
+ "log",
+ "m",
+ "z",
+ };
+
+ // Files that will be linked before any other object/archive/library files
+ LinkStartFiles = new () {
+ "crtbegin_so.o",
+ };
+
+ // Files that will be linked after any other object/archive/library files
+ LinkEndFiles = new () {
+ "crtend_so.o",
+ };
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PinvokeScanner.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PinvokeScanner.cs
new file mode 100644
index 00000000000..bbce0e6acdb
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/PinvokeScanner.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+
+using Mono.Cecil;
+
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class PinvokeScanner
+{
+ public sealed class PinvokeEntryInfo
+ {
+ public readonly string LibraryName;
+ public readonly string EntryName;
+
+ public PinvokeEntryInfo (MethodDefinition method)
+ {
+ LibraryName = method.PInvokeInfo.Module.Name;
+ EntryName = method.PInvokeInfo.EntryPoint;
+ }
+ }
+
+ readonly TaskLoggingHelper log;
+
+ public PinvokeScanner (TaskLoggingHelper log)
+ {
+ this.log = log;
+ }
+
+ public List Scan (AndroidTargetArch targetArch, XAAssemblyResolver resolver, ICollection frameworkAssemblies)
+ {
+ var pinvokes = new List ();
+ var pinvokeCache = new HashSet (StringComparer.Ordinal);
+
+ foreach (ITaskItem fasm in frameworkAssemblies) {
+ string asmName = Path.GetFileNameWithoutExtension (fasm.ItemSpec);
+ AssemblyDefinition? asmdef = resolver.Resolve (asmName);
+ if (asmdef == null) {
+ log.LogWarning ($"Failed to resolve assembly '{fasm.ItemSpec}' for target architecture {targetArch}");
+ continue;
+ }
+ Scan (targetArch, asmdef, pinvokeCache, pinvokes);
+ }
+
+ return pinvokes;
+ }
+
+ void Scan (AndroidTargetArch targetArch, AssemblyDefinition assembly, HashSet pinvokeCache, List pinvokes)
+ {
+ log.LogDebugMessage ($"[p/invoke][{targetArch}] Scanning assembly {assembly}");
+ foreach (ModuleDefinition module in assembly.Modules) {
+ if (!module.HasTypes) {
+ continue;
+ }
+
+ foreach (TypeDefinition type in module.Types) {
+ Scan (targetArch, type, pinvokeCache, pinvokes);
+ }
+ }
+ }
+
+ void Scan (AndroidTargetArch targetArch, TypeDefinition type, HashSet pinvokeCache, List pinvokes)
+ {
+ if (type.HasNestedTypes) {
+ foreach (TypeDefinition nestedType in type.NestedTypes) {
+ Scan (targetArch, nestedType, pinvokeCache, pinvokes);
+ }
+ }
+
+ if (!type.HasMethods) {
+ return;
+ }
+
+ log.LogDebugMessage ($"[p/invoke][{targetArch}] Scanning type '{type}'");
+ foreach (MethodDefinition method in type.Methods) {
+ if (!method.HasPInvokeInfo) {
+ continue;
+ }
+
+ var pinfo = new PinvokeEntryInfo (method);
+ string key = $"{pinfo.LibraryName}/{pinfo.EntryName}";
+ if (pinvokeCache.Contains (key)) {
+ continue;
+ }
+
+ log.LogDebugMessage ($" [{targetArch}] p/invoke method: {pinfo.LibraryName}/{pinfo.EntryName}");
+ pinvokeCache.Add (key);
+ pinvokes.Add (pinfo);
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs
new file mode 100644
index 00000000000..81eaa8bbd57
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs
@@ -0,0 +1,406 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+using Xamarin.Android.Tasks.LLVMIR;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class PreservePinvokesNativeAssemblyGenerator : LlvmIrComposer
+{
+ sealed class PInvoke
+ {
+ public readonly LlvmIrFunction NativeFunction;
+ public readonly PinvokeScanner.PinvokeEntryInfo Info;
+ public readonly ulong Hash;
+
+ public PInvoke (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo, bool is64Bit)
+ {
+ Info = pinfo;
+ Hash = MonoAndroidHelper.GetXxHash (pinfo.EntryName, is64Bit);
+
+ // All the p/invoke functions use the same dummy signature. The only thing we care about is
+ // a way to reference to the symbol at build time so that we can return pointer to it. For
+ // that all we need is a known name, signature doesn't matter to us.
+ var funcSig = new LlvmIrFunctionSignature (name: pinfo.EntryName, returnType: typeof(void));
+ NativeFunction = module.DeclareExternalFunction (funcSig);
+ }
+ }
+
+ sealed class Component
+ {
+ public readonly string Name;
+ public readonly ulong NameHash;
+ public readonly List PInvokes;
+ public bool Is64Bit;
+
+ public Component (string name, bool is64Bit)
+ {
+ Name = name;
+ NameHash = MonoAndroidHelper.GetXxHash (name, is64Bit);
+ PInvokes = new ();
+ Is64Bit = is64Bit;
+ }
+
+ public void Add (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo)
+ {
+ PInvokes.Add (new PInvoke (module, pinfo, Is64Bit));
+ }
+
+ public void Sort ()
+ {
+ PInvokes.Sort ((PInvoke a, PInvoke b) => a.Hash.CompareTo (b.Hash));
+ }
+ }
+
+ sealed class ConstructionState
+ {
+ public LlvmIrFunction Func = null!;
+ public LlvmIrFunctionLabelItem ReturnLabel = null!;
+ public LlvmIrFunctionParameter EntryPointHashParam = null!;
+ public LlvmIrInstructions.Phi Phi = null!;
+ public bool Is64Bit;
+ }
+
+ // Maps a component name after ridding it of the `lib` prefix and the extension to a "canonical"
+ // name of a library, as used in `[DllImport]` attributes.
+ readonly Dictionary libraryNameMap = new (StringComparer.Ordinal) {
+ { "xa-java-interop", "java-interop" },
+ { "mono-android.release-static", String.Empty },
+ { "mono-android.release", String.Empty },
+ };
+
+ readonly NativeCodeGenStateObject state;
+ readonly ITaskItem[] monoComponents;
+
+ public PreservePinvokesNativeAssemblyGenerator (TaskLoggingHelper log, NativeCodeGenStateObject codeGenState, ITaskItem[] monoComponents)
+ : base (log)
+ {
+ if (codeGenState.PinvokeInfos == null) {
+ throw new InvalidOperationException ($"Internal error: {nameof (codeGenState)} `{nameof (codeGenState.PinvokeInfos)}` property is `null`");
+ }
+
+ this.state = codeGenState;
+ this.monoComponents = monoComponents;
+ }
+
+ protected override void Construct (LlvmIrModule module)
+ {
+ Log.LogDebugMessage ($"[{state.TargetArch}] Constructing p/invoke preserve code");
+ List pinvokeInfos = state.PinvokeInfos!;
+ if (pinvokeInfos.Count == 0) {
+ // This is a very unlikely scenario, but we will work just fine. The module that this generator produces will merely result
+ // in an empty (but valid) .ll file and an "empty" object file to link into the shared library.
+ return;
+ }
+
+ Log.LogDebugMessage (" Looking for enabled native components");
+ var componentNames = new HashSet (StringComparer.Ordinal);
+ var componentLoadHandlers = new Dictionary (StringComparer.Ordinal);
+ var componentPreservedSymbols = new Dictionary> (StringComparer.Ordinal);
+ var nativeComponents = new NativeRuntimeComponents (monoComponents);
+ foreach (NativeRuntimeComponents.Archive archiveItem in nativeComponents.KnownArchives) {
+ if (!archiveItem.Include) {
+ continue;
+ }
+
+ Log.LogDebugMessage ($" {archiveItem.Name}");
+ componentNames.Add (archiveItem.Name);
+ if (!archiveItem.JniOnLoadName.IsNullOrEmpty ()) {
+ componentLoadHandlers.Add (archiveItem.Name, archiveItem.JniOnLoadName!);
+ }
+
+ if (archiveItem.SymbolsToPreserve == null || archiveItem.SymbolsToPreserve.Count == 0) {
+ continue;
+ }
+
+ var preservedSymbols = new HashSet ();
+ foreach (string symbolName in archiveItem.SymbolsToPreserve) {
+ preservedSymbols.Add (new LlvmIrGlobalVariableReference (symbolName));
+ }
+ componentPreservedSymbols.Add (archiveItem.Name, preservedSymbols);
+ }
+
+ if (componentNames.Count == 0) {
+ Log.LogDebugMessage ("No native framework components are included in the build, not scanning for p/invoke usage");
+ return;
+ }
+
+ bool is64Bit = state.TargetArch switch {
+ AndroidTargetArch.Arm64 => true,
+ AndroidTargetArch.X86_64 => true,
+ AndroidTargetArch.Arm => false,
+ AndroidTargetArch.X86 => false,
+ _ => throw new NotSupportedException ($"Architecture {state.TargetArch} is not supported here")
+ };
+
+ Log.LogDebugMessage (" Checking discovered p/invokes against the list of components");
+ var preservedPerComponent = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ var processedCache = new HashSet (StringComparer.OrdinalIgnoreCase);
+ var jniOnLoadNames = new HashSet (StringComparer.Ordinal);
+ bool haveLoadHandlers = componentLoadHandlers.Count > 0;
+ bool havePreservedSymbols = componentPreservedSymbols.Count > 0;
+
+ var symbolsToExplicitlyPreserve = new HashSet ();
+
+ foreach (PinvokeScanner.PinvokeEntryInfo pinfo in pinvokeInfos) {
+ Log.LogDebugMessage ($" p/invoke: {pinfo.EntryName} in {pinfo.LibraryName}");
+ string key = $"{pinfo.LibraryName}/${pinfo.EntryName}";
+ if (processedCache.Contains (key)) {
+ Log.LogDebugMessage ($" already processed");
+ continue;
+ }
+
+ processedCache.Add (key);
+ (bool preserve, string? componentName) = MustPreserve (pinfo, componentNames);
+ if (!preserve) {
+ Log.LogDebugMessage (" no need to preserve");
+ continue;
+ }
+ Log.LogDebugMessage (" must be preserved");
+
+ if (!componentName.IsNullOrEmpty ()) {
+ if (haveLoadHandlers && componentLoadHandlers.TryGetValue (componentName!, out string jniOnLoadName)) {
+ if (jniOnLoadNames.Add (jniOnLoadName)) {
+ Log.LogDebugMessage ($" component '{componentName}' registers a load handler '{jniOnLoadName}'");
+ }
+ }
+
+ if (havePreservedSymbols && componentPreservedSymbols.TryGetValue (componentName!, out HashSet preservedSymbols)) {
+ foreach (LlvmIrGlobalVariableReference vref in preservedSymbols) {
+ DeclareDummyFunction (module, vref);
+ symbolsToExplicitlyPreserve.Add (vref);
+ }
+ componentPreservedSymbols.Remove (componentName!);
+ }
+ }
+
+ if (!preservedPerComponent.TryGetValue (pinfo.LibraryName, out Component? component)) {
+ component = new Component (pinfo.LibraryName, is64Bit);
+ preservedPerComponent.Add (component.Name, component);
+ }
+ component.Add (module, pinfo);
+ }
+
+ module.AddGlobalVariable ("__jni_on_load_handler_count", (uint)jniOnLoadNames.Count, LlvmIrVariableOptions.GlobalConstant);
+ var jniOnLoadPointers = new List ();
+ foreach (string name in jniOnLoadNames) {
+ var symref = new LlvmIrGlobalVariableReference (name);
+ jniOnLoadPointers.Add (symref);
+ DeclareDummyFunction (module, symref);
+ }
+ module.AddGlobalVariable ("__jni_on_load_handlers", jniOnLoadPointers, LlvmIrVariableOptions.GlobalConstant);
+ module.AddGlobalVariable ("__jni_on_load_handler_names", jniOnLoadNames, LlvmIrVariableOptions.GlobalConstant);
+ module.AddGlobalVariable ("__explicitly_preserved_symbols", symbolsToExplicitlyPreserve, LlvmIrVariableOptions.GlobalConstant);
+
+ var components = new List (preservedPerComponent.Values);
+ if (is64Bit) {
+ AddFindPinvoke (module, components, is64Bit);
+ } else {
+ AddFindPinvoke (module, components, is64Bit);
+ }
+ }
+
+ void AddFindPinvoke (LlvmIrModule module, List components, bool is64Bit) where T: struct
+ {
+ var hashType = is64Bit ? typeof (ulong) : typeof (uint);
+ var parameters = new List {
+ new LlvmIrFunctionParameter (hashType, "library_name_hash") {
+ NoUndef = true,
+ },
+
+ new LlvmIrFunctionParameter (hashType, "entrypoint_hash") {
+ NoUndef = true,
+ },
+
+ new LlvmIrFunctionParameter (typeof(IntPtr), "known_library") {
+ Align = 1, // it's a reference to C++ `bool`
+ Dereferenceable = 1,
+ IsCplusPlusReference = true,
+ NoCapture = true,
+ NonNull = true,
+ NoUndef = true,
+ WriteOnly = true,
+ },
+ };
+
+ var sig = new LlvmIrFunctionSignature (
+ name: "find_pinvoke",
+ returnType: typeof(IntPtr),
+ parameters: parameters,
+ new LlvmIrFunctionSignature.ReturnTypeAttributes {
+ NoUndef = true,
+ }
+ );
+
+ var func = new LlvmIrFunction (sig, MakeFindPinvokeAttributeSet (module)) {
+ CallingConvention = LlvmIrCallingConvention.Fastcc,
+ Visibility = LlvmIrVisibility.Hidden,
+ };
+ LlvmIrLocalVariable retval = func.CreateLocalVariable (typeof(IntPtr), "retval");
+ var state = new ConstructionState {
+ Func = func,
+ ReturnLabel = new LlvmIrFunctionLabelItem ("return"),
+ EntryPointHashParam = parameters[1],
+ Phi = new LlvmIrInstructions.Phi (retval),
+ Is64Bit = is64Bit,
+ };
+ module.Add (state.Func);
+ state.Func.Body.Add (new LlvmIrFunctionLabelItem ("entry"));
+
+ var libraryNameSwitchEpilog = new LlvmIrFunctionLabelItem ("libNameSW.epilog");
+ var componentSwitch = new LlvmIrInstructions.Switch (parameters[0], libraryNameSwitchEpilog, "sw.libname");
+
+ state.Func.Body.Add (componentSwitch);
+ state.Phi.AddNode (libraryNameSwitchEpilog, null);
+
+ components.Sort ((Component a, Component b) => a.NameHash.CompareTo (b.NameHash));
+ Log.LogDebugMessage (" Components to be preserved:");
+ uint componentID = 1;
+
+ foreach (Component component in components) {
+ Log.LogDebugMessage ($" {component.Name} (hash: 0x{component.NameHash:x}; {component.PInvokes.Count} p/invoke(s))");
+
+ string comment = $" {component.Name} (p/invoke count: {component.PInvokes.Count})";
+ LlvmIrFunctionLabelItem componentLabel = AddSwitchItem (componentSwitch, component.NameHash, is64Bit, comment, null);
+
+ func.Body.Add (componentLabel, comment);
+ AddPInvokeSwitch (state, componentLabel, component, componentID++);
+ }
+
+ func.Body.Add (libraryNameSwitchEpilog);
+
+ var setKnownLib = new LlvmIrInstructions.Store (false, parameters[2]);
+ func.Body.Add (setKnownLib);
+ AddReturnBranch (func, state.ReturnLabel);
+
+ func.Body.Add (state.ReturnLabel);
+ func.Body.Add (state.Phi);
+ func.Body.Add (new LlvmIrInstructions.Ret (typeof (IntPtr), retval));
+ }
+
+ void AddPInvokeSwitch (ConstructionState state, LlvmIrFunctionLabelItem componentLabel, Component component, uint id) where T: struct
+ {
+ var pinvokeSwitchEpilog = new LlvmIrFunctionLabelItem ($"pinvokeSW.epilog.{id}");
+ state.Phi.AddNode (pinvokeSwitchEpilog, null);
+
+ var pinvokeSwitch = new LlvmIrInstructions.Switch (state.EntryPointHashParam, pinvokeSwitchEpilog, $"sw.pinvoke.{id}");
+ state.Func.Body.Add (pinvokeSwitch);
+
+ component.Sort ();
+ bool first = true;
+ foreach (PInvoke pi in component.PInvokes) {
+ string pinvokeName = pi.NativeFunction.Signature.Name;
+ string comment = $" {pinvokeName}";
+ LlvmIrFunctionLabelItem pinvokeLabel = AddSwitchItem (pinvokeSwitch, pi.Hash, state.Is64Bit, comment, first ? state.ReturnLabel : null);
+
+ // First item of every component switch block "reuses" the block's label
+ if (first) {
+ first = false;
+ } else {
+ state.Func.Body.Add (pinvokeLabel, comment);
+ AddReturnBranch (state.Func, state.ReturnLabel);
+ }
+
+ state.Phi.AddNode (pinvokeLabel == state.ReturnLabel ? componentLabel : pinvokeLabel, new LlvmIrGlobalVariableReference (pinvokeName));
+ }
+
+ state.Func.Body.Add (pinvokeSwitchEpilog);
+ AddReturnBranch (state.Func, state.ReturnLabel);
+ }
+
+ void AddReturnBranch (LlvmIrFunction func, LlvmIrFunctionLabelItem returnLabel)
+ {
+ var branch = new LlvmIrInstructions.Br (returnLabel);
+ func.Body.Add (branch);
+ }
+
+ LlvmIrFunctionLabelItem AddSwitchItem (LlvmIrInstructions.Switch sw, ulong hash, bool is64Bit, string? comment, LlvmIrFunctionLabelItem? label) where T: struct
+ {
+ if (is64Bit) {
+ return sw.Add ((T)(object)hash, dest: label, comment: comment);
+ }
+ return sw.Add ((T)(object)(uint)hash, dest: label, comment: comment);
+ }
+
+ LlvmIrFunctionAttributeSet MakeFindPinvokeAttributeSet (LlvmIrModule module)
+ {
+ var attrSet = new LlvmIrFunctionAttributeSet {
+ new MustprogressFunctionAttribute (),
+ new NofreeFunctionAttribute (),
+ new NorecurseFunctionAttribute (),
+ new NosyncFunctionAttribute (),
+ new NounwindFunctionAttribute (),
+ new WillreturnFunctionAttribute (),
+ new MemoryFunctionAttribute {
+ Default = MemoryAttributeAccessKind.Write,
+ Argmem = MemoryAttributeAccessKind.None,
+ InaccessibleMem = MemoryAttributeAccessKind.None,
+ },
+ new UwtableFunctionAttribute (),
+ new NoTrappingMathFunctionAttribute (true),
+ };
+
+ return module.AddAttributeSet (attrSet);
+ }
+
+ // Returns `true` for all p/invokes that we know are part of our set of components, otherwise returns `false`.
+ // Returning `false` merely means that the p/invoke isn't in any of BCL or our code and therefore we shouldn't
+ // care. It doesn't mean the p/invoke will be removed in any way.
+ (bool preserve, string? componentName) MustPreserve (PinvokeScanner.PinvokeEntryInfo pinfo, ICollection components)
+ {
+ if (String.Compare ("xa-internal-api", pinfo.LibraryName, StringComparison.Ordinal) == 0) {
+ return (true, null);
+ }
+
+ foreach (string component in components) {
+ // The most common pattern for the BCL - file name without extension
+ string componentName = Path.GetFileNameWithoutExtension (component);
+ if (Matches (pinfo.LibraryName, componentName)) {
+ return (true, componentName);
+ }
+
+ // If it starts with `lib`, drop the prefix
+ if (componentName.StartsWith ("lib", StringComparison.Ordinal)) {
+ if (Matches (pinfo.LibraryName, componentName.Substring (3))) {
+ return (true, componentName);
+ }
+ }
+
+ // Might require mapping of component name to a canonical one
+ if (libraryNameMap.TryGetValue (componentName, out string? mappedComponentName) && !String.IsNullOrEmpty (mappedComponentName)) {
+ if (Matches (pinfo.LibraryName, mappedComponentName)) {
+ return (true, componentName);
+ }
+ }
+
+ // Try full file name, as the last resort
+ if (Matches (pinfo.LibraryName, Path.GetFileName (component))) {
+ return (true, componentName);
+ }
+ }
+
+ return (false, null);
+
+ bool Matches (string libraryName, string componentName)
+ {
+ return String.Compare (libraryName, componentName, StringComparison.Ordinal) == 0;
+ }
+ }
+
+ static void DeclareDummyFunction (LlvmIrModule module, LlvmIrGlobalVariableReference symref)
+ {
+ if (symref.Name.IsNullOrEmpty ()) {
+ throw new InvalidOperationException ("Internal error: variable reference must have a name");
+ }
+
+ // Just a dummy declaration, we don't care about the arguments
+ var funcSig = new LlvmIrFunctionSignature (symref.Name!, returnType: typeof(void));
+ var _ = module.DeclareExternalFunction (funcSig);
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index 6421833e60e..dc8c5520a2d 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -365,6 +365,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' and '$(_AndroidUseMarshalMethods)' == 'True' and '$(_AndroidRuntime)' != 'MonoVM' ">True
<_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' ">False
+
+
+ <_AndroidEnableNativeRuntimeLinking Condition=" '$(_AndroidRuntime)' != 'CoreCLR' ">false
+ <_AndroidEnableNativeRuntimeLinking Condition=" '$(_AndroidEnableNativeRuntimeLinking)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' And '$(_AndroidRuntime)' == 'CoreCLR' ">true
@@ -1613,7 +1617,8 @@ because xbuild doesn't support framework reference assemblies.
FrameworkDirectories="$(_XATargetFrameworkDirectories);$(_XATargetFrameworkDirectories)Facades"
SupportedAbis="@(_BuildTargetAbis)"
EnableMarshalMethods="$(_AndroidUseMarshalMethods)"
- IntermediateOutputDirectory="$(IntermediateOutputPath)">
+ IntermediateOutputDirectory="$(IntermediateOutputPath)"
+ EnableNativeRuntimeLinking="$(_AndroidEnableNativeRuntimeLinking)">
+
+
+
@@ -1911,12 +1925,15 @@ because xbuild doesn't support framework reference assemblies.
EnableManagedMarshalMethodsLookup="$(_AndroidUseManagedMarshalMethodsLookup)"
CustomBundleConfigFile="$(AndroidBundleConfigurationFile)"
TargetsCLR="$(_AndroidUseCLR)"
- ProjectRuntimeConfigFilePath="$(ProjectRuntimeConfigFilePath)">
+ ProjectRuntimeConfigFilePath="$(ProjectRuntimeConfigFilePath)"
+ >
+
+
@@ -2040,6 +2059,7 @@ because xbuild doesn't support framework reference assemblies.
<_CompileToDalvikDependsOnTargets>
_CompileJava;
_CreateApplicationSharedLibraries;
+ _LinkNativeRuntime;
_GetMonoPlatformJarPath;
_GetLibraryImports;
_SetProguardMappingFileProperty;
@@ -2151,6 +2171,9 @@ because xbuild doesn't support framework reference assemblies.
<_NativeAssemblyTarget Include="@(_AndroidRemapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
%(_AndroidRemapAssemblySource.abi)
+ <_NativeAssemblyTarget Include="@(_RuntimeLinkingAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_RuntimeLinkingAssemblySource.abi)
+
@@ -2170,10 +2193,10 @@ because xbuild doesn't support framework reference assemblies.
+ Outputs="@(_ApplicationSharedLibrary)"
+ Condition=" '$(_AndroidEnableNativeRuntimeLinking)' != 'true' ">
+#include
+
+#include "pinvoke-override.hh"
+#include "../runtime-base/logger.hh"
+#include "../runtime-base/monodroid-dl.hh"
+#include "../runtime-base/startup-aware-lock.hh"
+
+namespace xamarin::android {
+ PINVOKE_OVERRIDE_INLINE
+ auto PinvokeOverride::load_library_symbol (const char *library_name, const char *symbol_name, void **dso_handle) noexcept -> void*
+ {
+ void *lib_handle = dso_handle == nullptr ? nullptr : *dso_handle;
+
+ if (lib_handle == nullptr) {
+ // We're being called as part of the p/invoke mechanism, we don't need to look in the AOT cache
+ constexpr bool PREFER_AOT_CACHE = false;
+ lib_handle = MonodroidDl::monodroid_dlopen (library_name, microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY, PREFER_AOT_CACHE);
+ if (lib_handle == nullptr) {
+ log_warn (LOG_ASSEMBLY, "Shared library '{}' not loaded, p/invoke '{}' may fail", optional_string (library_name), optional_string (symbol_name));
+ return nullptr;
+ }
+
+ if (dso_handle != nullptr) {
+ void *expected_null = nullptr;
+ if (!__atomic_compare_exchange (dso_handle, &expected_null, &lib_handle, false /* weak */, __ATOMIC_ACQUIRE /* success_memorder */, __ATOMIC_RELAXED /* xxxfailure_memorder */)) {
+ log_debug (LOG_ASSEMBLY, "Library '{}' handle already cached by another thread", optional_string (library_name));
+ }
+ }
+ }
+
+ void *entry_handle = MonodroidDl::monodroid_dlsym (lib_handle, symbol_name);
+ if (entry_handle == nullptr) {
+ log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", optional_string (library_name), optional_string (symbol_name));
+ return nullptr;
+ }
+
+ return entry_handle;
+ }
+
+ PINVOKE_OVERRIDE_INLINE
+ auto PinvokeOverride::load_library_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map) noexcept -> void*
+ {
+ // Make sure some other thread hasn't just added the entry
+ auto iter = api_map->find (entrypoint_name);
+ if (iter != api_map->end () && iter->second != nullptr) {
+ return iter->second;
+ }
+
+ void *entry_handle = load_library_symbol (library_name.c_str (), entrypoint_name.c_str ());
+ if (entry_handle == nullptr) {
+ // error already logged
+ return nullptr;
+ }
+
+ log_debug (LOG_ASSEMBLY, "Caching p/invoke entry {} @ {}", library_name, entrypoint_name);
+ (*api_map)[entrypoint_name] = entry_handle;
+ return entry_handle;
+ }
+
+ PINVOKE_OVERRIDE_INLINE
+ void PinvokeOverride::load_library_entry (const char *library_name, const char *entrypoint_name, PinvokeEntry &entry, void **dso_handle) noexcept
+ {
+ void *entry_handle = load_library_symbol (library_name, entrypoint_name, dso_handle);
+ void *expected_null = nullptr;
+
+ bool already_loaded = !__atomic_compare_exchange (
+ /* ptr */ &entry.func,
+ /* expected */ &expected_null,
+ /* desired */ &entry_handle,
+ /* weak */ false,
+ /* success_memorder */ __ATOMIC_ACQUIRE,
+ /* failure_memorder */ __ATOMIC_RELAXED
+ );
+
+ if (already_loaded) {
+ log_debug (LOG_ASSEMBLY, "Entry '{}' from library '{}' already loaded by another thread", entrypoint_name, library_name);
+ }
+ }
+
+ PINVOKE_OVERRIDE_INLINE
+ auto PinvokeOverride::fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, hash_t entrypoint_name_hash, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*
+ {
+ auto iter = api_map->find (entrypoint_name, entrypoint_name_hash);
+ if (iter != api_map->end () && iter->second != nullptr) {
+ return iter->second;
+ }
+
+ if (!need_lock) {
+ return load_library_entry (library_name, entrypoint_name, api_map);
+ }
+
+ StartupAwareLock lock (pinvoke_map_write_lock);
+ return load_library_entry (library_name, entrypoint_name, api_map);
+ }
+
+ PINVOKE_OVERRIDE_INLINE
+ auto PinvokeOverride::find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*
+ {
+ while (entry_count > 0uz) {
+ const size_t mid = entry_count / 2uz;
+ const PinvokeEntry *const ret = entries + mid;
+ const std::strong_ordering result = hash <=> ret->hash;
+
+ if (result < 0) {
+ entry_count = mid;
+ } else if (result > 0) {
+ entries = ret + 1;
+ entry_count -= mid + 1uz;
+ } else {
+ return const_cast(ret);
+ }
+ }
+
+ return nullptr;
+ }
+
+ PINVOKE_OVERRIDE_INLINE
+ auto PinvokeOverride::handle_other_pinvoke_request (const char *library_name, hash_t library_name_hash, const char *entrypoint_name, hash_t entrypoint_name_hash) noexcept -> void*
+ {
+ std::string lib_name {library_name};
+ std::string entry_name {entrypoint_name};
+
+ auto iter = other_pinvoke_map.find (lib_name, library_name_hash);
+ void *handle = nullptr;
+ if (iter == other_pinvoke_map.end ()) {
+ StartupAwareLock lock (pinvoke_map_write_lock);
+
+ pinvoke_api_map_ptr lib_map;
+ // Make sure some other thread hasn't just added the map
+ iter = other_pinvoke_map.find (lib_name, library_name_hash);
+ if (iter == other_pinvoke_map.end () || iter->second == nullptr) {
+ lib_map = new pinvoke_api_map (1);
+ other_pinvoke_map[lib_name] = lib_map;
+ } else {
+ lib_map = iter->second;
+ }
+
+ handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, lib_map, /* need_lock */ false);
+ } else {
+ if (iter->second == nullptr) [[unlikely]] {
+ log_warn (LOG_ASSEMBLY, "Internal error: null entry in p/invoke map for key '{}'", optional_string (library_name));
+ return nullptr; // fall back to `monodroid_dlopen`
+ }
+
+ handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, iter->second, /* need_lock */ true);
+ }
+
+ return handle;
+ }
+}
diff --git a/src/native/clr/include/host/pinvoke-override.hh b/src/native/clr/include/host/pinvoke-override.hh
index e1b7afebd95..de89dce1c5f 100644
--- a/src/native/clr/include/host/pinvoke-override.hh
+++ b/src/native/clr/include/host/pinvoke-override.hh
@@ -3,6 +3,8 @@
#include
#include
+#include
+
// NDEBUG causes robin_map.h not to include which, in turn, prevents indirect inclusion of .
// conflicts with our std::mutex definition in cppcompat.hh
#if !defined (NDEBUG)
@@ -71,153 +73,25 @@ namespace xamarin::android {
static inline constexpr pinvoke_library_map::size_type LIBRARY_MAP_INITIAL_BUCKET_COUNT = 1uz;
public:
- [[gnu::always_inline]]
- static auto load_library_symbol (const char *library_name, const char *symbol_name, void **dso_handle = nullptr) noexcept -> void*
- {
- void *lib_handle = dso_handle == nullptr ? nullptr : *dso_handle;
-
- if (lib_handle == nullptr) {
- // We're being called as part of the p/invoke mechanism, we don't need to look in the AOT cache
- constexpr bool PREFER_AOT_CACHE = false;
- lib_handle = MonodroidDl::monodroid_dlopen (library_name, microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY, PREFER_AOT_CACHE);
- if (lib_handle == nullptr) {
- log_warn (LOG_ASSEMBLY, "Shared library '{}' not loaded, p/invoke '{}' may fail", optional_string (library_name), optional_string (symbol_name));
- return nullptr;
- }
-
- if (dso_handle != nullptr) {
- void *expected_null = nullptr;
- if (!__atomic_compare_exchange (dso_handle, &expected_null, &lib_handle, false /* weak */, __ATOMIC_ACQUIRE /* success_memorder */, __ATOMIC_RELAXED /* xxxfailure_memorder */)) {
- log_debug (LOG_ASSEMBLY, "Library '{}' handle already cached by another thread", optional_string (library_name));
- }
- }
- }
-
- void *entry_handle = MonodroidDl::monodroid_dlsym (lib_handle, symbol_name);
- if (entry_handle == nullptr) {
- log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", optional_string (library_name), optional_string (symbol_name));
- return nullptr;
- }
-
- return entry_handle;
- }
-
- static auto load_library_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map) noexcept -> void*
- {
- // Make sure some other thread hasn't just added the entry
- auto iter = api_map->find (entrypoint_name);
- if (iter != api_map->end () && iter->second != nullptr) {
- return iter->second;
- }
-
- void *entry_handle = load_library_symbol (library_name.c_str (), entrypoint_name.c_str ());
- if (entry_handle == nullptr) {
- // error already logged
- return nullptr;
- }
-
- log_debug (LOG_ASSEMBLY, "Caching p/invoke entry {} @ {}", library_name, entrypoint_name);
- (*api_map)[entrypoint_name] = entry_handle;
- return entry_handle;
- }
-
- static void load_library_entry (const char *library_name, const char *entrypoint_name, PinvokeEntry &entry, void **dso_handle) noexcept
- {
- void *entry_handle = load_library_symbol (library_name, entrypoint_name, dso_handle);
- void *expected_null = nullptr;
-
- bool already_loaded = !__atomic_compare_exchange (
- /* ptr */ &entry.func,
- /* expected */ &expected_null,
- /* desired */ &entry_handle,
- /* weak */ false,
- /* success_memorder */ __ATOMIC_ACQUIRE,
- /* failure_memorder */ __ATOMIC_RELAXED
- );
-
- if (already_loaded) {
- log_debug (LOG_ASSEMBLY, "Entry '{}' from library '{}' already loaded by another thread", entrypoint_name, library_name);
- }
- }
-
- static auto fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, hash_t entrypoint_name_hash, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*
- {
- auto iter = api_map->find (entrypoint_name, entrypoint_name_hash);
- if (iter != api_map->end () && iter->second != nullptr) {
- return iter->second;
- }
-
- if (!need_lock) {
- return load_library_entry (library_name, entrypoint_name, api_map);
- }
-
- StartupAwareLock lock (pinvoke_map_write_lock);
- return load_library_entry (library_name, entrypoint_name, api_map);
- }
-
- [[gnu::always_inline]]
- static auto find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*
- {
- while (entry_count > 0uz) {
- const size_t mid = entry_count / 2uz;
- const PinvokeEntry *const ret = entries + mid;
- const std::strong_ordering result = hash <=> ret->hash;
-
- if (result < 0) {
- entry_count = mid;
- } else if (result > 0) {
- entries = ret + 1;
- entry_count -= mid + 1uz;
- } else {
- return const_cast(ret);
- }
- }
-
- return nullptr;
- }
-
- [[gnu::always_inline, gnu::flatten]]
- static auto handle_other_pinvoke_request (const char *library_name, hash_t library_name_hash, const char *entrypoint_name, hash_t entrypoint_name_hash) noexcept -> void*
- {
- std::string lib_name {library_name};
- std::string entry_name {entrypoint_name};
-
- auto iter = other_pinvoke_map.find (lib_name, library_name_hash);
- void *handle = nullptr;
- if (iter == other_pinvoke_map.end ()) {
- StartupAwareLock lock (pinvoke_map_write_lock);
-
- pinvoke_api_map_ptr lib_map;
- // Make sure some other thread hasn't just added the map
- iter = other_pinvoke_map.find (lib_name, library_name_hash);
- if (iter == other_pinvoke_map.end () || iter->second == nullptr) {
- lib_map = new pinvoke_api_map (1);
- other_pinvoke_map[lib_name] = lib_map;
- } else {
- lib_map = iter->second;
- }
-
- handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, lib_map, /* need_lock */ false);
- } else {
- if (iter->second == nullptr) [[unlikely]] {
- log_warn (LOG_ASSEMBLY, "Internal error: null entry in p/invoke map for key '{}'", optional_string (library_name));
- return nullptr; // fall back to `monodroid_dlopen`
- }
-
- handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, iter->second, /* need_lock */ true);
- }
-
- return handle;
- }
-
+ static auto load_library_symbol (const char *library_name, const char *symbol_name, void **dso_handle = nullptr) noexcept -> void*;
+ static auto load_library_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map) noexcept -> void*;
+ static void load_library_entry (const char *library_name, const char *entrypoint_name, PinvokeEntry &entry, void **dso_handle) noexcept;
+ static auto fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, hash_t entrypoint_name_hash, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*;
+ static auto find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*;
+ static auto handle_other_pinvoke_request (const char *library_name, hash_t library_name_hash, const char *entrypoint_name, hash_t entrypoint_name_hash) noexcept -> void*;
+
+ static void handle_jni_on_load (JavaVM *vm, void *reserved) noexcept;
static auto monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name) noexcept -> void*;
private:
static inline std::mutex pinvoke_map_write_lock{};
- static inline pinvoke_library_map other_pinvoke_map{};
+ static inline pinvoke_library_map other_pinvoke_map { PinvokeOverride::LIBRARY_MAP_INITIAL_BUCKET_COUNT };
+
+#if defined(PRECOMPILED)
static inline void *system_native_library_handle = nullptr;
static inline void *system_security_cryptography_native_android_library_handle = nullptr;
static inline void *system_io_compression_native_library_handle = nullptr;
static inline void *system_globalization_native_library_handle = nullptr;
+#endif
};
}
diff --git a/src/native/clr/pinvoke-override/CMakeLists.txt b/src/native/clr/pinvoke-override/CMakeLists.txt
new file mode 100644
index 00000000000..b1f35a16e2f
--- /dev/null
+++ b/src/native/clr/pinvoke-override/CMakeLists.txt
@@ -0,0 +1,92 @@
+set(LIB_NAME_PRECOMPILED pinvoke-override-precompiled)
+set(LIB_ALIAS_PRECOMPILED xa::pinvoke-override-precompiled)
+
+set(LIB_NAME_DYNAMIC pinvoke-override-dynamic)
+set(LIB_ALIAS_DYNAMIC xa::pinvoke-override-dynamic)
+
+set(XA_PINVOKE_OVERRIDE_PRECOMPILED_SOURCES
+ precompiled.cc
+)
+add_clang_check_sources("${XA_PINVOKE_OVERRIDE_PRECOMPILED_SOURCES}")
+
+set(XA_PINVOKE_OVERRIDE_DYNAMIC_SOURCES
+ dynamic.cc
+)
+add_clang_check_sources("${XA_PINVOKE_OVERRIDE_DYNAMIC_SOURCES}")
+
+list(APPEND POTENTIAL_LOCAL_COMPILER_ARGS
+ -ffunction-sections
+ -fdata-sections
+)
+
+xa_check_c_args(PINVOKE_OVERRIDE_CXX_ARGS "${POTENTIAL_LOCAL_COMPILER_ARGS}")
+
+macro(create_library _libname _alias _sources)
+ add_library(
+ ${_libname}
+ STATIC
+ ${_sources}
+ )
+
+ add_library(${_alias} ALIAS ${_libname})
+
+ set_static_library_suffix(${_libname})
+
+ target_compile_definitions(
+ ${_libname}
+ PRIVATE
+ TSL_NO_EXCEPTIONS
+ )
+
+ target_compile_options(
+ ${_libname}
+ PRIVATE
+ ${XA_COMMON_CXX_ARGS}
+ ${PINVOKE_OVERRIDE_CXX_ARGS}
+ )
+
+ target_include_directories(
+ ${_libname}
+ PRIVATE
+ ${ROBIN_MAP_DIR}/include
+ )
+
+ target_include_directories(
+ ${_libname}
+ PUBLIC
+ "$"
+ )
+
+ target_include_directories(
+ ${_libname}
+ SYSTEM PRIVATE
+ ${SYSROOT_CXX_INCLUDE_DIR}
+ ${RUNTIME_INCLUDE_DIR}
+ )
+
+ target_link_libraries(
+ ${_libname}
+ PRIVATE
+ ${SHARED_LIB_NAME}
+ xa::xamarin-app
+ xa::runtime-base
+ )
+
+ xa_add_compile_definitions(${_libname})
+ xa_add_include_directories(${_libname})
+endmacro()
+
+create_library(${LIB_NAME_PRECOMPILED} ${LIB_ALIAS_PRECOMPILED} ${XA_PINVOKE_OVERRIDE_PRECOMPILED_SOURCES})
+create_library(${LIB_NAME_DYNAMIC} ${LIB_ALIAS_DYNAMIC} ${XA_PINVOKE_OVERRIDE_DYNAMIC_SOURCES})
+
+target_compile_definitions(
+ ${LIB_NAME_PRECOMPILED}
+ PRIVATE
+ PRECOMPILED
+)
+
+set_target_properties(
+ ${LIB_NAME_PRECOMPILED}
+ PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+)
diff --git a/src/native/clr/pinvoke-override/dynamic.cc b/src/native/clr/pinvoke-override/dynamic.cc
new file mode 100644
index 00000000000..6c860aa440c
--- /dev/null
+++ b/src/native/clr/pinvoke-override/dynamic.cc
@@ -0,0 +1,104 @@
+#include
+
+#include
+
+#define PINVOKE_OVERRIDE_INLINE [[gnu::noinline]]
+#include
+
+using namespace xamarin::android;
+
+using JniOnLoadHandler = jint (*) (JavaVM *vm, void *reserved);
+
+//
+// These external functions are generated during application build (see obj/${CONFIGURATION}/${RID}/android/pinvoke_preserve.*.ll)
+//
+extern "C" {
+ void* find_pinvoke (hash_t library_name_hash, hash_t entrypoint_hash, bool &known_library);
+
+ extern const uint32_t __jni_on_load_handler_count;
+ extern const JniOnLoadHandler __jni_on_load_handlers[];
+ extern const char* __jni_on_load_handler_names[];
+ extern const void* __explicitly_preserved_symbols[];
+}
+
+[[gnu::flatten]]
+auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name) noexcept -> void*
+{
+ log_debug (LOG_ASSEMBLY, __PRETTY_FUNCTION__);
+ log_debug (LOG_ASSEMBLY, "library_name == '{}'; entrypoint_name == '{}'"sv, optional_string (library_name), optional_string (entrypoint_name));
+
+ if (library_name == nullptr || entrypoint_name == nullptr) [[unlikely]] {
+ Helpers::abort_application (
+ LOG_ASSEMBLY,
+ std::format (
+ "Both library name ('{}') and entry point name ('{}') must be specified"sv,
+ optional_string (library_name),
+ optional_string (entrypoint_name)
+ )
+ );
+ }
+
+ hash_t library_name_hash = xxhash::hash (library_name, strlen (library_name));
+ hash_t entrypoint_hash = xxhash::hash (entrypoint_name, strlen (entrypoint_name));
+ log_debug (LOG_ASSEMBLY, "library_name_hash == 0x{:x}; entrypoint_hash == 0x{:x}"sv, library_name_hash, entrypoint_hash);
+
+ bool known_library = true;
+ void *pinvoke_ptr = find_pinvoke (library_name_hash, entrypoint_hash, known_library);
+ if (pinvoke_ptr != nullptr) [[likely]] {
+ log_debug (LOG_ASSEMBLY, "pinvoke_ptr == {:p}"sv, pinvoke_ptr);
+ return pinvoke_ptr;
+ }
+
+ if (known_library) [[unlikely]] {
+ log_debug (LOG_ASSEMBLY, "Lookup in a known library == internal"sv);
+ // Should "never" happen. It seems we have a known library hash (of one that's linked into the dynamically
+ // built DSO) but an unknown symbol hash. The symbol **probably** doesn't exist (was most likely linked out if
+ // the find* functions didn't know its hash), but we cannot be sure of that so we'll try to load it.
+ pinvoke_ptr = dlsym (RTLD_DEFAULT, entrypoint_name);
+ if (pinvoke_ptr == nullptr) {
+ Helpers::abort_application (
+ LOG_ASSEMBLY,
+ std::format (
+ "Unable to load p/invoke entry '{}/{}' from the unified runtime DSO"sv,
+ optional_string (library_name),
+ optional_string (entrypoint_name)
+ )
+ );
+ }
+
+ return pinvoke_ptr;
+ }
+
+ log_debug (LOG_ASSEMBLY, "p/invoke not from a known library, slow path taken."sv);
+ pinvoke_ptr = handle_other_pinvoke_request (library_name, library_name_hash, entrypoint_name, entrypoint_hash);;
+ log_debug (LOG_ASSEMBLY, "foreign library pinvoke_ptr == {:p}"sv, pinvoke_ptr);
+ return pinvoke_ptr;
+}
+
+void PinvokeOverride::handle_jni_on_load (JavaVM *vm, void *reserved) noexcept
+{
+ if (__jni_on_load_handler_count == 0) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < __jni_on_load_handler_count; i++) {
+ __jni_on_load_handlers[i] (vm, reserved);
+ }
+
+ // This is just to reference the generated array, all we need from it is to be there
+ // TODO: see if there's an attribute we can use to make the linker keep the symbol instead.
+ // void *first_ptr = __explicitly_preserved_symbols;
+ // if (first_ptr == nullptr) {
+ // // This will never actually be logged, since by the time this function is called we haven't initialized
+ // // logging categories yet. It's here just to have some code in the if statement body.
+ // log_debug (LOG_ASSEMBLY, "No explicitly preserved symbols");
+ // }
+}
+
+const void* Host::clr_pinvoke_override (const char *library_name, const char *entry_point_name) noexcept
+{
+ log_debug (LOG_ASSEMBLY, "[dynamic] clr_pinvoke_override (\"{}\", \"{}\")"sv, optional_string (library_name), optional_string (entry_point_name));
+ void *ret = PinvokeOverride::monodroid_pinvoke_override (library_name, entry_point_name);
+ log_debug (LOG_DEFAULT, "[dynamic] p/invoke {}found", ret == nullptr ? "not"sv : ""sv);
+ return ret;
+}
diff --git a/src/native/clr/host/generate-pinvoke-tables.cc b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc
similarity index 100%
rename from src/native/clr/host/generate-pinvoke-tables.cc
rename to src/native/clr/pinvoke-override/generate-pinvoke-tables.cc
diff --git a/src/native/clr/host/pinvoke-tables.include b/src/native/clr/pinvoke-override/pinvoke-tables.include
similarity index 100%
rename from src/native/clr/host/pinvoke-tables.include
rename to src/native/clr/pinvoke-override/pinvoke-tables.include
diff --git a/src/native/clr/host/pinvoke-override.cc b/src/native/clr/pinvoke-override/precompiled.cc
similarity index 93%
rename from src/native/clr/host/pinvoke-override.cc
rename to src/native/clr/pinvoke-override/precompiled.cc
index d1467e4eda1..a6e3b037852 100644
--- a/src/native/clr/host/pinvoke-override.cc
+++ b/src/native/clr/pinvoke-override/precompiled.cc
@@ -1,5 +1,7 @@
+#define PINVOKE_OVERRIDE_INLINE [[gnu::always_inline]]
+
#include
-#include
+#include
#include
using namespace xamarin::android;
@@ -98,8 +100,9 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons
const void* Host::clr_pinvoke_override (const char *library_name, const char *entry_point_name) noexcept
{
- log_debug (LOG_ASSEMBLY, "clr_pinvoke_override (\"{}\", \"{}\")", library_name, entry_point_name);
+ log_debug (LOG_ASSEMBLY, "[precompiled] clr_pinvoke_override (\"{}\", \"{}\")", library_name, entry_point_name);
void *ret = PinvokeOverride::monodroid_pinvoke_override (library_name, entry_point_name);
- log_debug (LOG_DEFAULT, "p/invoke {}found", ret == nullptr ? "not"sv : ""sv);
+ log_debug (LOG_DEFAULT, "[precompiled] p/invoke {}found", ret == nullptr ? "not"sv : ""sv);
return ret;
}
+
diff --git a/src/native/clr/runtime-base/CMakeLists.txt b/src/native/clr/runtime-base/CMakeLists.txt
index 97930c69e14..ad37baf9bd9 100644
--- a/src/native/clr/runtime-base/CMakeLists.txt
+++ b/src/native/clr/runtime-base/CMakeLists.txt
@@ -52,6 +52,7 @@ set(LIB_ALIAS xa::runtime-base)
set(XA_RUNTIME_BASE_SOURCES
android-system.cc
cpu-arch-detect.cc
+ jni-remapping.cc
logger.cc
util.cc
)
diff --git a/src/native/clr/host/jni-remapping.cc b/src/native/clr/runtime-base/jni-remapping.cc
similarity index 100%
rename from src/native/clr/host/jni-remapping.cc
rename to src/native/clr/runtime-base/jni-remapping.cc
diff --git a/src/native/common/libunwind/CMakeLists.txt b/src/native/common/libunwind/CMakeLists.txt
index 3de53d23d34..440730faef0 100644
--- a/src/native/common/libunwind/CMakeLists.txt
+++ b/src/native/common/libunwind/CMakeLists.txt
@@ -322,6 +322,7 @@ add_library(
STATIC
${LIBUNWIND_XAMARIN_SOURCES}
)
+set_static_library_suffix(${LIB_NAME})
add_library(${LIB_ALIAS} ALIAS ${LIB_NAME})
diff --git a/src/native/common/lz4/CMakeLists.txt b/src/native/common/lz4/CMakeLists.txt
index 75552bd5828..57c8a717d01 100644
--- a/src/native/common/lz4/CMakeLists.txt
+++ b/src/native/common/lz4/CMakeLists.txt
@@ -13,6 +13,7 @@ add_library(
STATIC
${LZ4_SOURCES}
)
+set_static_library_suffix(${LIB_NAME})
add_library(${LIB_ALIAS} ALIAS ${LIB_NAME})
diff --git a/src/native/native.targets b/src/native/native.targets
index df70d1cab5f..0a8d197adc7 100644
--- a/src/native/native.targets
+++ b/src/native/native.targets
@@ -309,6 +309,17 @@
+
+ <_RuntimePackFiles Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(AndroidSupportedTargetJitAbi.AndroidRID)\*.o"
+ AndroidRID="%(AndroidSupportedTargetJitAbi.AndroidRID)"
+ AndroidRuntime="$(CMakeRuntimeFlavor)"
+ RuntimePackName="$(_RuntimePackName)" />
+ <_RuntimePackFiles Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(AndroidSupportedTargetJitAbi.AndroidRID)\*.a"
+ AndroidRID="%(AndroidSupportedTargetJitAbi.AndroidRID)"
+ AndroidRuntime="$(CMakeRuntimeFlavor)"
+ RuntimePackName="$(_RuntimePackName)" />
+
+