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)" /> + +