From fc944eee080bd34da94790aab9e6ad64a52cee1c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 17 Jul 2023 13:52:22 +0000 Subject: [PATCH] LLVM IR code generator refactoring and updates (#8140) Context: https://llvm.org/docs/OpaquePointers.html Context: 903ba37ce70d2840983774e1d6fb55f8002561e2 903ba37c added a new native code generator using the LLVM IR language, to let LLVM code generator handle all the CPU and platform specific details. This commit improves the original generator by simplifying the code generator model so that tasks generating native code need to know **less** about LLVM IR structure. The most important changes: * Migrate to opaque pointers (LLVM 15+) which will be the only ones supported from LLVM 16 onwards. This will allow us to update our toolchain to LLVM 16 or newer at some point. It also simplifies both the code generator **and** the generated code. * Change code generation model. Now the "client" classes don't need to concern themselves with **how** the LLVM IR code looks and how it is formatted. Instead, they build a model of the code they want to output and let the generator do the rest. * Handle many more tasks automatically: * LLVM IR string management and registration * Buffer management for structures which have pointers to buffers * References to local variables and strings * Local temporary counting (unnamed labels and function parameters). LLVM requires that all unnamed local temporaries (that is labels and function parameters) are named using a shared counter, with all of thus generated names being numbers in sequence. In the previous version of the generator it was problematic, in this version it's easy and seamless. --- ...teCompressedAssembliesNativeSourceFiles.cs | 14 +- .../Tasks/GenerateJniRemappingNativeCode.cs | 8 +- .../Tasks/GeneratePackageManagerJava.cs | 30 +- .../Tasks/GetAotArguments.cs | 30 +- .../Tasks/LinkApplicationSharedLibraries.cs | 31 +- .../Utilities/EnvironmentHelper.cs | 2 +- .../Utilities/NativeAssemblyParser.cs | 16 +- ...pplicationConfigNativeAssemblyGenerator.cs | 168 +- ...ressedAssembliesNativeAssemblyGenerator.cs | 81 +- .../JniRemappingAssemblyGenerator.cs | 161 +- .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 43 - .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 47 - .../LlvmIrGenerator/FunctionAttributes.cs | 599 ++++- .../LlvmIrGenerator/IStructureInfo.cs | 15 - .../LlvmFunctionAttributeSet.cs | 50 - .../LlvmIrGenerator/LlvmIrBufferManager.cs | 67 + .../LlvmIrGenerator/LlvmIrComposer.cs | 79 +- .../LlvmIrGenerator/LlvmIrDataLayout.cs | 353 +++ .../LlvmIrGenerator/LlvmIrFunction.cs | 557 ++++- .../LlvmIrFunctionAttributeSet.cs | 107 + .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 312 +++ .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 552 ----- .../LlvmIrGenerator.Constants.cs | 50 + .../LlvmIrGenerator/LlvmIrGenerator.cs | 2109 ++++++++--------- .../LlvmIrGenerator/LlvmIrInstructions.cs | 627 +++++ .../LlvmIrGenerator/LlvmIrKnownMetadata.cs | 7 + .../LlvmIrGenerator/LlvmIrMetadataManager.cs | 57 +- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 681 ++++++ .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 52 + .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 63 + .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 73 + .../LlvmIrGenerator/LlvmIrModuleX64.cs | 76 + .../LlvmIrGenerator/LlvmIrModuleX86.cs | 68 + .../LlvmIrGenerator/LlvmIrStringGroup.cs | 16 + .../LlvmIrGenerator/LlvmIrStringManager.cs | 71 + .../LlvmIrGenerator/LlvmIrVariable.cs | 234 +- .../LlvmIrGenerator/LlvmIrVariableOptions.cs | 4 +- .../LlvmIrVariableReference.cs | 50 - .../LlvmNativeFunctionSignature.cs | 34 - .../LlvmIrGenerator/MemberInfoUtilities.cs | 2 +- .../LlvmIrGenerator/StructureInfo.cs | 75 +- .../LlvmIrGenerator/StructureInstance.cs | 67 +- .../LlvmIrGenerator/StructureMemberInfo.cs | 23 +- .../LlvmIrGenerator/StructureStringData.cs | 14 - .../LlvmIrGenerator/TypeUtilities.cs | 114 +- .../LlvmIrGenerator/X64LlvmIrGenerator.cs | 52 - .../LlvmIrGenerator/X86LlvmIrGenerator.cs | 45 - .../MarshalMethodsNativeAssemblyGenerator.cs | 720 ++++-- .../Utilities/MonoAndroidHelper.cs | 61 + .../Utilities/NativeTypeMappingData.cs | 2 +- .../Utilities/TypeMapGenerator.cs | 72 +- ...TypeMappingDebugNativeAssemblyGenerator.cs | 36 +- ...peMappingReleaseNativeAssemblyGenerator.cs | 396 ++-- src/monodroid/CMakeLists.txt | 74 +- src/monodroid/jni/application_dso_stub.cc | 2 +- src/monodroid/jni/monodroid-glue-internal.hh | 2 +- src/monodroid/jni/monodroid-glue.cc | 12 +- src/monodroid/jni/xamarin-app.hh | 2 +- src/monodroid/libstub/stub.cc | 14 + 59 files changed, 6150 insertions(+), 3229 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs index 8367c21b744..48f849596e0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs @@ -74,16 +74,22 @@ void GenerateCompressedAssemblySources () void Generate (IDictionary dict) { - var llvmAsmgen = new CompressedAssembliesNativeAssemblyGenerator (dict); - llvmAsmgen.Init (); + var composer = new CompressedAssembliesNativeAssemblyGenerator (dict); + LLVMIR.LlvmIrModule compressedAssemblies = composer.Construct (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}"); string llvmIrFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - llvmAsmgen.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath); - sw.Flush (); + try { + composer.Generate (compressedAssemblies, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath); + } catch { + throw; + } finally { + sw.Flush (); + } + if (Files.CopyIfStreamChanged (sw.BaseStream, llvmIrFilePath)) { Log.LogDebugMessage ($"File {llvmIrFilePath} was regenerated"); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs index 7bd5824012a..b89ce87d26d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs @@ -77,16 +77,16 @@ void Generate () Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count); } - void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeReplacementsCount) + void Generate (JniRemappingAssemblyGenerator jniRemappingComposer, int typeReplacementsCount) { - jniRemappingGenerator.Init (); + LLVMIR.LlvmIrModule module = jniRemappingComposer.Construct (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}"); string llFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - jniRemappingGenerator.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath); + jniRemappingComposer.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath); sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, llFilePath); } @@ -94,7 +94,7 @@ void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeRepl BuildEngine4.RegisterTaskObjectAssemblyLocal ( ProjectSpecificTaskObjectKey (JniRemappingNativeCodeInfoKey), - new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingGenerator.ReplacementMethodIndexEntryCount), + new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingComposer.ReplacementMethodIndexEntryCount), RegisteredTaskObjectLifetime.Build ); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 623b20976de..670fcfe566e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -390,7 +390,7 @@ void AddEnvironment () // and up to 4 other for arch-specific assemblies. Only **one** arch-specific store is ever loaded on the app // runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names // and in the same order. - MonoComponents = monoComponents, + MonoComponents = (MonoComponent)monoComponents, NativeLibraries = uniqueNativeLibraries, HaveAssemblyStore = UseAssemblyStore, AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, @@ -400,7 +400,7 @@ void AddEnvironment () JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, MarshalMethodsEnabled = EnableMarshalMethods, }; - appConfigAsmGen.Init (); + LLVMIR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct (); var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build); MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; @@ -415,7 +415,7 @@ void AddEnvironment () } else { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); } - marshalMethodsAsmGen.Init (); + LLVMIR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGen.Construct (); foreach (string abi in SupportedAbis) { string targetAbi = abi.ToLowerInvariant (); @@ -423,18 +423,28 @@ void AddEnvironment () string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}"); string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll"; string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll"; - AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi); + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - appConfigAsmGen.Write (targetArch, sw, environmentLlFilePath); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); + try { + appConfigAsmGen.Generate (appConfigModule, targetArch, sw, environmentLlFilePath); + } catch { + throw; + } finally { + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); + } } using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath); + try { + marshalMethodsAsmGen.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePath); + } catch { + throw; + } finally { + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs index 3037f957c7d..b839175b2ed 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs @@ -293,14 +293,8 @@ string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolP libs.Add (Path.Combine (androidLibPath, "libc.so")); libs.Add (Path.Combine (androidLibPath, "libm.so")); } else if (!UseAndroidNdk && EnableLLVM) { - // We need to link against libc and libm, but since NDK is not in use, the linker won't be able to find the actual Android libraries. - // Therefore, we will use their stubs to satisfy the linker. At runtime they will, of course, use the actual Android libraries. - string relPath = Path.Combine ("..", ".."); - if (!OS.IsWindows) { - // the `binutils` directory is one level down (${OS}/binutils) than the Windows one - relPath = Path.Combine (relPath, ".."); - } - string libstubsPath = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, relPath, "libstubs", ArchToRid (arch))); + string libstubsPath = MonoAndroidHelper.GetLibstubsArchDirectoryPath (AndroidBinUtilsDirectory, arch); + libs.Add (Path.Combine (libstubsPath, "libc.so")); libs.Add (Path.Combine (libstubsPath, "libm.so")); } @@ -332,26 +326,6 @@ string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolP } return ldFlags.ToString (); - - string ArchToRid (AndroidTargetArch arch) - { - switch (arch) { - case AndroidTargetArch.Arm64: - return "android-arm64"; - - case AndroidTargetArch.Arm: - return "android-arm"; - - case AndroidTargetArch.X86: - return "android-x86"; - - case AndroidTargetArch.X86_64: - return "android-x64"; - - default: - throw new InvalidOperationException ($"Internal error: unsupported ABI '{arch}'"); - } - } } static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, string archDir = null) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index bbe49073e8c..5f4b09ececa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -29,6 +29,7 @@ sealed class InputFiles { public List ObjectFiles; public string OutputSharedLibrary; + public List ExtraLibraries; } [Required] @@ -112,22 +113,23 @@ void RunLinker (Config config) IEnumerable GetLinkerConfigs () { + string runtimeNativeLibsDir = MonoAndroidHelper.GetNativeLibsRootDirectoryPath (AndroidBinUtilsDirectory); + string runtimeNativeLibStubsDir = MonoAndroidHelper.GetLibstubsRootDirectoryPath (AndroidBinUtilsDirectory); var abis = new Dictionary (StringComparer.Ordinal); ITaskItem[] dsos = ApplicationSharedLibraries; foreach (ITaskItem item in dsos) { string abi = item.GetMetadata ("abi"); - abis [abi] = GatherFilesForABI(item.ItemSpec, abi, ObjectFiles); + abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir); } const string commonLinkerArgs = - "--unresolved-symbols=ignore-in-shared-libs " + + "--shared " + + "--allow-shlib-undefined " + "--export-dynamic " + "-soname libxamarin-app.so " + "-z relro " + "-z noexecstack " + "--enable-new-dtags " + - "--eh-frame-hdr " + - "-shared " + "--build-id " + "--warn-shared-textrel " + "--fatal-warnings"; @@ -177,6 +179,12 @@ IEnumerable GetLinkerConfigs () targetLinkerArgs.Add ("-o"); targetLinkerArgs.Add (QuoteFileName (inputs.OutputSharedLibrary)); + if (inputs.ExtraLibraries != null) { + foreach (string lib in inputs.ExtraLibraries) { + targetLinkerArgs.Add (lib); + } + } + string targetArgs = String.Join (" ", targetLinkerArgs); yield return new Config { LinkerPath = ld, @@ -186,11 +194,24 @@ IEnumerable GetLinkerConfigs () } } - InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles) + InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir) { + List extraLibraries = null; + string RID = MonoAndroidHelper.AbiToRid (abi); + AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); + string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); + string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID); + + extraLibraries = new List { + $"-L \"{runtimeLibsDir}\"", + $"-L \"{libStubsPath}\"", + "-lc", + }; + return new InputFiles { OutputSharedLibrary = runtimeSharedLibrary, ObjectFiles = GetItemsForABI (abi, objectFiles), + ExtraLibraries = extraLibraries, }; } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 5b79d0e8309..afd2c24fbab 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -402,7 +402,7 @@ static Dictionary ReadEnvironmentVariables (EnvironmentFile envF static string[] GetField (string llvmAssemblerFile, string nativeAssemblerFile, string line, ulong lineNumber) { string[] ret = line?.Trim ()?.Split ('\t'); - Assert.IsTrue (ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'"); + Assert.IsTrue (ret != null && ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'"); return ret; } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs index bfbf5fb709c..3a2aa982f0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs @@ -192,7 +192,7 @@ public SymbolMetadata (SymbolMetadataKind kind, string value = null) static readonly char[] splitOnWhitespace = new char[] { ' ', '\t' }; static readonly char[] splitOnComma = new char[] { ',' }; - static readonly Regex assemblerLabelRegex = new Regex ("^[_.a-zA-Z0-9]+:", RegexOptions.Compiled); + static readonly Regex assemblerLabelRegex = new Regex ("^[_.$a-zA-Z0-9]+:", RegexOptions.Compiled); Dictionary symbols = new Dictionary (StringComparer.Ordinal); Dictionary> symbolMetadata = new Dictionary> (StringComparer.Ordinal); @@ -238,8 +238,10 @@ void Load (string sourceFilePath) AssemblerSection currentSection = null; AssemblerSymbol currentSymbol = null; - string symbolName; + string symbolName = null; ulong lineNumber = 0; + bool addedNewSymbol = false; + foreach (string l in File.ReadLines (sourceFilePath, Encoding.UTF8)) { lineNumber++; @@ -253,6 +255,15 @@ void Load (string sourceFilePath) continue; } + if (addedNewSymbol) { + addedNewSymbol = false; + // Some forms of LLVM IR can generate two labels for a single symbol, depending on symbol visibility, attributes and llc parameters. + // The exported symbol name 'symbol:' may be followed by another one '.Lsymbol$local:', we need to detect this and ignore the new symbol. + if (assemblerLabelRegex.IsMatch (line) && String.Compare (line.Trim (), $".L{symbolName}$local:", StringComparison.Ordinal) == 0) { + continue; + } + } + if (StartsNewSection (parts, ref currentSection)) { currentSymbol = null; // Symbols cannot cross sections continue; @@ -265,6 +276,7 @@ void Load (string sourceFilePath) if (assemblerLabelRegex.IsMatch (line)) { symbolName = GetSymbolName (line); currentSymbol = AddNewSymbol (symbolName); + addedNewSymbol = true; continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index c240e5aefa2..2dcbac7b0e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -6,7 +6,6 @@ using Java.Interop.Tools.TypeNameMappings; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; - using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks @@ -27,17 +26,13 @@ sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextData { public override string GetComment (object data, string fieldName) { - var dso_entry = data as DSOCacheEntry; - if (dso_entry == null) { - throw new InvalidOperationException ("Invalid data type, expected an instance of DSOCacheEntry"); - } - + var dso_entry = EnsureType (data); if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { - return $"hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; + return $" hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; } if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {dso_entry.name}"; + return $" name: {dso_entry.name}"; } return String.Empty; @@ -57,6 +52,7 @@ sealed class DSOCacheEntry public ulong hash; public bool ignore; + [NativeAssembler (UsesDataProvider = true)] public string name; public IntPtr handle = IntPtr.Zero; } @@ -131,24 +127,24 @@ sealed class XamarinAndroidBundledAssembly public uint name_length; [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] - public char name; + public string name; } // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x015E6972616D58; - SortedDictionary environmentVariables; - SortedDictionary systemProperties; + SortedDictionary ? environmentVariables; + SortedDictionary ? systemProperties; TaskLoggingHelper log; - StructureInstance? application_config; + StructureInstance? application_config; List>? dsoCache; List>? xamarinAndroidBundledAssemblies; - StructureInfo? applicationConfigStructureInfo; - StructureInfo? dsoCacheEntryStructureInfo; - StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; - StructureInfo assemblyStoreSingleAssemblyRuntimeDataStructureinfo; - StructureInfo assemblyStoreRuntimeDataStructureInfo; + StructureInfo? applicationConfigStructureInfo; + StructureInfo? dsoCacheEntryStructureInfo; + StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; + StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; + StructureInfo? assemblyStoreRuntimeDataStructureInfo; public bool UsesMonoAOT { get; set; } public bool UsesMonoLLVM { get; set; } @@ -188,8 +184,23 @@ public ApplicationConfigNativeAssemblyGenerator (IDictionary env this.log = log; } - public override void Init () + protected override void Construct (LlvmIrModule module) { + MapStructures (module); + + module.AddGlobalVariable ("format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); + module.AddGlobalVariable ("mono_aot_mode_name", MonoAOTMode); + + var envVars = new LlvmIrGlobalVariable (environmentVariables, "app_environment_variables") { + Comment = " Application environment variables array, name:value", + }; + module.Add (envVars, stringGroupName: "env", stringGroupComment: " Application environment variables name:value pairs"); + + var sysProps = new LlvmIrGlobalVariable (systemProperties, "app_system_properties") { + Comment = " System properties defined by the application", + }; + module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); + dsoCache = InitDSOCache (); var app_cfg = new ApplicationConfig { uses_mono_llvm = UsesMonoLLVM, @@ -218,7 +229,14 @@ public override void Init () mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; - application_config = new StructureInstance (app_cfg); + application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); + module.AddGlobalVariable ("application_config", application_config); + + var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { + Comment = " DSO cache entries", + BeforeWriteCallback = HashAndSortDSOCache, + }; + module.Add (dso_cache); if (!HaveAssemblyStore) { xamarinAndroidBundledAssemblies = new List> (NumberOfAssembliesInApk); @@ -229,13 +247,63 @@ public override void Init () data_size = 0, data = 0, name_length = (uint)BundledAssemblyNameWidth, - name = '\0', + name = null, }; for (int i = 0; i < NumberOfAssembliesInApk; i++) { - xamarinAndroidBundledAssemblies.Add (new StructureInstance (emptyBundledAssemblyData)); + xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData)); + } + } + + string bundledBuffersSize = xamarinAndroidBundledAssemblies == null ? "empty (unused when assembly stores are enabled)" : $"{BundledAssemblyNameWidth} bytes long"; + var bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "bundled_assemblies", LlvmIrVariableOptions.GlobalWritable) { + Value = xamarinAndroidBundledAssemblies, + Comment = $" Bundled assembly name buffers, all {bundledBuffersSize}", + }; + module.Add (bundled_assemblies); + + AddAssemblyStores (module); + } + + void AddAssemblyStores (LlvmIrModule module) + { + ulong itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); + var assembly_store_bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "assembly_store_bundled_assemblies", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_store_bundled_assemblies); + + itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); + var assembly_stores = new LlvmIrGlobalVariable (typeof(List>), "assembly_stores", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_stores); + } + + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) + { + var cache = variable.Value as List>; + if (cache == null) { + throw new InvalidOperationException ($"Internal error: DSO cache must no be empty"); + } + + bool is64Bit = target.Is64Bit; + foreach (StructureInstance instance in cache) { + if (instance.Obj == null) { + throw new InvalidOperationException ("Internal error: DSO cache must not contain null entries"); } + + var entry = instance.Obj as DSOCacheEntry; + if (entry == null) { + throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); + } + + entry.hash = GetXxHash (entry.HashedName, is64Bit); } + + cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); } List> InitDSOCache () @@ -273,7 +341,7 @@ List> InitDSOCache () name = name, }; - dsoCache.Add (new StructureInstance (entry)); + dsoCache.Add (new StructureInstance (dsoCacheEntryStructureInfo, entry)); } } @@ -300,56 +368,14 @@ void AddNameMutations (string name) } } - protected override void MapStructures (LlvmIrGenerator generator) - { - applicationConfigStructureInfo = generator.MapStructure (); - generator.MapStructure (); - assemblyStoreSingleAssemblyRuntimeDataStructureinfo = generator.MapStructure (); - assemblyStoreRuntimeDataStructureInfo = generator.MapStructure (); - xamarinAndroidBundledAssemblyStructureInfo = generator.MapStructure (); - dsoCacheEntryStructureInfo = generator.MapStructure (); - } - - protected override void Write (LlvmIrGenerator generator) - { - generator.WriteVariable ("format_tag", FORMAT_TAG); - generator.WriteString ("mono_aot_mode_name", MonoAOTMode); - - generator.WriteNameValueArray ("app_environment_variables", environmentVariables); - generator.WriteNameValueArray ("app_system_properties", systemProperties); - - generator.WriteStructure (applicationConfigStructureInfo, application_config, LlvmIrVariableOptions.GlobalConstant, "application_config"); - - WriteDSOCache (generator); - WriteBundledAssemblies (generator); - WriteAssemblyStoreAssemblies (generator); - } - - void WriteAssemblyStoreAssemblies (LlvmIrGenerator generator) - { - ulong count = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); - generator.WriteStructureArray (assemblyStoreSingleAssemblyRuntimeDataStructureinfo, count, "assembly_store_bundled_assemblies", initialComment: "Assembly store individual assembly data"); - - count = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); - generator.WriteStructureArray (assemblyStoreRuntimeDataStructureInfo, count, "assembly_stores", initialComment: "Assembly store data"); - } - - void WriteBundledAssemblies (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - generator.WriteStructureArray (xamarinAndroidBundledAssemblyStructureInfo, xamarinAndroidBundledAssemblies, "bundled_assemblies", initialComment: $"Bundled assembly name buffers, all {BundledAssemblyNameWidth} bytes long"); - } - - void WriteDSOCache (LlvmIrGenerator generator) - { - bool is64Bit = generator.Is64Bit; - - // We need to hash here, because the hash is architecture-specific - foreach (StructureInstance entry in dsoCache) { - entry.Obj.hash = HashName (entry.Obj.HashedName, is64Bit); - } - dsoCache.Sort ((StructureInstance a, StructureInstance b) => a.Obj.hash.CompareTo (b.Obj.hash)); - - generator.WriteStructureArray (dsoCacheEntryStructureInfo, dsoCache, "dso_cache"); + applicationConfigStructureInfo = module.MapStructure (); + module.MapStructure (); + assemblyStoreSingleAssemblyRuntimeDataStructureinfo = module.MapStructure (); + assemblyStoreRuntimeDataStructureInfo = module.MapStructure (); + xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure (); + dsoCacheEntryStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs index 39ce70a4d73..e0b3740a453 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs @@ -5,21 +5,21 @@ namespace Xamarin.Android.Tasks { - class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer + partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer { const string DescriptorsArraySymbolName = "compressed_assembly_descriptors"; const string CompressedAssembliesSymbolName = "compressed_assemblies"; sealed class CompressedAssemblyDescriptorContextDataProvider : NativeAssemblerStructContextDataProvider { - public override ulong GetBufferSize (object data, string fieldName) + public override string? GetPointedToSymbolName (object data, string fieldName) { if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) { - return 0; + return null; } var descriptor = EnsureType (data); - return descriptor.uncompressed_file_size; + return descriptor.BufferSymbolName; } } @@ -28,10 +28,13 @@ public override ulong GetBufferSize (object data, string fieldName) [NativeAssemblerStructContextDataProvider (typeof (CompressedAssemblyDescriptorContextDataProvider))] sealed class CompressedAssemblyDescriptor { + [NativeAssembler (Ignore = true)] + public string BufferSymbolName; + public uint uncompressed_file_size; public bool loaded; - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] public byte data; }; @@ -60,60 +63,84 @@ sealed class CompressedAssemblies }; IDictionary assemblies; - StructureInfo compressedAssemblyDescriptorStructureInfo; - StructureInfo compressedAssembliesStructureInfo; - List>? compressedAssemblyDescriptors; - StructureInstance compressedAssemblies; + StructureInfo compressedAssemblyDescriptorStructureInfo; + StructureInfo compressedAssembliesStructureInfo; public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies) { this.assemblies = assemblies; } - public override void Init () + void InitCompressedAssemblies (out List>? compressedAssemblyDescriptors, + out StructureInstance? compressedAssemblies, + out List? buffers) { if (assemblies == null || assemblies.Count == 0) { + compressedAssemblyDescriptors = null; + compressedAssemblies = null; + buffers = null; return; } + ulong counter = 0; compressedAssemblyDescriptors = new List> (assemblies.Count); + buffers = new List (assemblies.Count); foreach (var kvp in assemblies) { string assemblyName = kvp.Key; CompressedAssemblyInfo info = kvp.Value; + string bufferName = $"__compressedAssemblyData_{counter++}"; var descriptor = new CompressedAssemblyDescriptor { + BufferSymbolName = bufferName, uncompressed_file_size = info.FileSize, loaded = false, data = 0 }; - compressedAssemblyDescriptors.Add (new StructureInstance (descriptor)); + var bufferVar = new LlvmIrGlobalVariable (typeof(List), bufferName, LlvmIrVariableOptions.LocalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = descriptor.uncompressed_file_size, + }; + buffers.Add (bufferVar); + + compressedAssemblyDescriptors.Add (new StructureInstance (compressedAssemblyDescriptorStructureInfo, descriptor)); } - compressedAssemblies = new StructureInstance (new CompressedAssemblies { count = (uint)assemblies.Count }); + compressedAssemblies = new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies { count = (uint)assemblies.Count }); } - protected override void MapStructures (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - compressedAssemblyDescriptorStructureInfo = generator.MapStructure (); - compressedAssembliesStructureInfo = generator.MapStructure (); - } + MapStructures (module); + + List>? compressedAssemblyDescriptors; + StructureInstance? compressedAssemblies; + List? buffers; + + InitCompressedAssemblies (out compressedAssemblyDescriptors, out compressedAssemblies, out buffers); - protected override void Write (LlvmIrGenerator generator) - { if (compressedAssemblyDescriptors == null) { - generator.WriteStructure (compressedAssembliesStructureInfo, null, CompressedAssembliesSymbolName); + module.AddGlobalVariable ( + typeof(StructureInstance), + CompressedAssembliesSymbolName, + new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalWritable + ); return; } - generator.WriteStructureArray ( - compressedAssemblyDescriptorStructureInfo, - compressedAssemblyDescriptors, - LlvmIrVariableOptions.LocalWritable, - DescriptorsArraySymbolName, - initialComment: "Compressed assembly data storage" - ); - generator.WriteStructure (compressedAssembliesStructureInfo, compressedAssemblies, CompressedAssembliesSymbolName); + module.AddGlobalVariable (CompressedAssembliesSymbolName, compressedAssemblies, LlvmIrVariableOptions.GlobalWritable); + module.AddGlobalVariable (DescriptorsArraySymbolName, compressedAssemblyDescriptors, LlvmIrVariableOptions.LocalWritable); + + module.Add (new LlvmIrGroupDelimiterVariable ()); + module.Add (buffers); + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void MapStructures (LlvmIrModule module) + { + compressedAssemblyDescriptorStructureInfo = module.MapStructure (); + compressedAssembliesStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs index 486fd2f4c7e..93ab1cd7b31 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs @@ -44,6 +44,9 @@ public JniRemappingMethodReplacement (string sourceType, string sourceMethod, st class JniRemappingAssemblyGenerator : LlvmIrComposer { + const string TypeReplacementsVariableName = "jni_remapping_type_replacements"; + const string MethodReplacementIndexVariableName = "jni_remapping_method_replacement_index"; + sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider { public override string GetComment (object data, string fieldName) @@ -51,11 +54,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType(data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $"replacement: {entry.replacement}"; + return $" replacement: {entry.replacement}"; } return String.Empty; @@ -69,7 +72,7 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } return String.Empty; @@ -104,11 +107,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $"replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; + return $" replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; } if (String.Compare ("signature", fieldName, StringComparison.Ordinal) == 0) { @@ -179,14 +182,11 @@ sealed class JniRemappingTypeReplacementEntry List typeReplacementsInput; List methodReplacementsInput; - StructureInfo jniRemappingStringStructureInfo; - StructureInfo jniRemappingReplacementMethodStructureInfo; - StructureInfo jniRemappingIndexMethodEntryStructureInfo; - StructureInfo jniRemappingIndexTypeEntryStructureInfo; - StructureInfo jniRemappingTypeReplacementEntryStructureInfo; - - List> typeReplacements; - List> methodIndexTypes; + StructureInfo jniRemappingStringStructureInfo; + StructureInfo jniRemappingReplacementMethodStructureInfo; + StructureInfo jniRemappingIndexMethodEntryStructureInfo; + StructureInfo jniRemappingIndexTypeEntryStructureInfo; + StructureInfo jniRemappingTypeReplacementEntryStructureInfo; public int ReplacementMethodIndexEntryCount { get; private set; } = 0; @@ -199,24 +199,24 @@ public JniRemappingAssemblyGenerator (List typeRepl this.methodReplacementsInput = methodReplacements ?? throw new ArgumentNullException (nameof (methodReplacements)); } - public override void Init () + (List>? typeReplacements, List>? methodIndexTypes) Init () { if (typeReplacementsInput == null) { - return; + return (null, null); } - typeReplacements = new List> (); + var typeReplacements = new List> (); foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { var entry = new JniRemappingTypeReplacementEntry { name = MakeJniRemappingString (mtr.From), replacement = mtr.To, }; - typeReplacements.Add (new StructureInstance (entry)); + typeReplacements.Add (new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, entry)); } - typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); - methodIndexTypes = new List> (); + var methodIndexTypes = new List> (); var types = new Dictionary> (StringComparer.Ordinal); foreach (JniRemappingMethodReplacement mmr in methodReplacementsInput) { @@ -227,7 +227,7 @@ public override void Init () TypeMethods = new List> (), }; - typeEntry = new StructureInstance (entry); + typeEntry = new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, entry); methodIndexTypes.Add (typeEntry); types.Add (mmr.SourceType, typeEntry); } @@ -242,17 +242,19 @@ public override void Init () }, }; - typeEntry.Obj.TypeMethods.Add (new StructureInstance (method)); + typeEntry.Instance.TypeMethods.Add (new StructureInstance (jniRemappingIndexMethodEntryStructureInfo, method)); } foreach (var kvp in types) { - kvp.Value.Obj.method_count = (uint)kvp.Value.Obj.TypeMethods.Count; - kvp.Value.Obj.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + kvp.Value.Instance.method_count = (uint)kvp.Value.Instance.TypeMethods.Count; + kvp.Value.Instance.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); } - methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); ReplacementMethodIndexEntryCount = methodIndexTypes.Count; + return (typeReplacements, methodIndexTypes); + string MakeMethodsArrayName (string typeName) { return $"mm_{typeName.Replace ('/', '_')}"; @@ -265,101 +267,58 @@ JniRemappingString MakeJniRemappingString (string str) str = str, }; } - } - - uint GetLength (string str) - { - if (String.IsNullOrEmpty (str)) { - return 0; - } - - return (uint)Encoding.UTF8.GetBytes (str).Length; - } - - protected override void MapStructures (LlvmIrGenerator generator) - { - jniRemappingStringStructureInfo = generator.MapStructure (); - jniRemappingReplacementMethodStructureInfo = generator.MapStructure (); - jniRemappingIndexMethodEntryStructureInfo = generator.MapStructure (); - jniRemappingIndexTypeEntryStructureInfo = generator.MapStructure (); - jniRemappingTypeReplacementEntryStructureInfo = generator.MapStructure (); - } - - void WriteNestedStructure (LlvmIrGenerator generator, LlvmIrGenerator.StructureBodyWriterOptions bodyWriterOptions, Type structureType, object fieldInstance) - { - if (fieldInstance == null) { - return; - } - if (structureType == typeof (JniRemappingString)) { - generator.WriteNestedStructure (jniRemappingStringStructureInfo, new StructureInstance ((JniRemappingString)fieldInstance), bodyWriterOptions); - return; - } - - if (structureType == typeof (JniRemappingReplacementMethod)) { - generator.WriteNestedStructure (jniRemappingReplacementMethodStructureInfo, new StructureInstance ((JniRemappingReplacementMethod)fieldInstance), bodyWriterOptions); - return; - } - - if (structureType == typeof (JniRemappingIndexTypeEntry)) { - generator.WriteNestedStructure (jniRemappingIndexTypeEntryStructureInfo, new StructureInstance ((JniRemappingIndexTypeEntry)fieldInstance), bodyWriterOptions); - } + uint GetLength (string str) + { + if (String.IsNullOrEmpty (str)) { + return 0; + } - if (structureType == typeof (JniRemappingIndexMethodEntry)) { - generator.WriteNestedStructure (jniRemappingIndexMethodEntryStructureInfo, new StructureInstance ((JniRemappingIndexMethodEntry)fieldInstance), bodyWriterOptions); + return (uint)Encoding.UTF8.GetBytes (str).Length; } - - throw new InvalidOperationException ($"Unsupported nested structure type {structureType}"); } - protected override void Write (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - generator.WriteEOL (); - generator.WriteEOL ("JNI remapping data"); + MapStructures (module); + List>? typeReplacements; + List>? methodIndexTypes; + + (typeReplacements, methodIndexTypes) = Init (); if (typeReplacements == null) { - generator.WriteStructureArray ( - jniRemappingTypeReplacementEntryStructureInfo, - 0, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_type_replacements" + module.AddGlobalVariable ( + typeof(StructureInstance), + TypeReplacementsVariableName, + new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, new JniRemappingTypeReplacementEntry ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalConstant ); - generator.WriteStructureArray ( - jniRemappingIndexTypeEntryStructureInfo, - 0, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_method_replacement_index" + module.AddGlobalVariable ( + typeof(StructureInstance), + MethodReplacementIndexVariableName, + new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, new JniRemappingIndexTypeEntry ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalConstant ); - return; } - generator.WriteStructureArray ( - jniRemappingTypeReplacementEntryStructureInfo, - typeReplacements, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_type_replacements", - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (TypeReplacementsVariableName, typeReplacements, LlvmIrVariableOptions.GlobalConstant); foreach (StructureInstance entry in methodIndexTypes) { - generator.WriteStructureArray ( - jniRemappingIndexMethodEntryStructureInfo, - entry.Obj.TypeMethods, - LlvmIrVariableOptions.LocalConstant, - entry.Obj.MethodsArraySymbolName, - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (entry.Instance.MethodsArraySymbolName, entry.Instance.TypeMethods, LlvmIrVariableOptions.LocalConstant); } - generator.WriteStructureArray ( - jniRemappingIndexTypeEntryStructureInfo, - methodIndexTypes, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_method_replacement_index", - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (MethodReplacementIndexVariableName, methodIndexTypes, LlvmIrVariableOptions.GlobalConstant); + } + + void MapStructures (LlvmIrModule module) + { + jniRemappingStringStructureInfo = module.MapStructure (); + jniRemappingReplacementMethodStructureInfo = module.MapStructure (); + jniRemappingIndexMethodEntryStructureInfo = module.MapStructure (); + jniRemappingIndexTypeEntryStructureInfo = module.MapStructure (); + jniRemappingTypeReplacementEntryStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs deleted file mode 100644 index 5f60214ea33..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class Arm32LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"; - public override int PointerSize => 4; - protected override string Triple => "armv7-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { - new FramePointerFunctionAttribute ("all"), - new TargetCpuFunctionAttribute ("generic"), - new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+thumb-mode,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"), - }; - - public Arm32LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs deleted file mode 100644 index 68ca5fd19e8..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class Arm64LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"; - public override int PointerSize => 8; - protected override string Triple => "aarch64-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { - new FramePointerFunctionAttribute ("non-leaf"), - new TargetCpuFunctionAttribute ("generic"), - new TargetFeaturesFunctionAttribute ("+neon,+outline-atomics"), - }; - - public Arm64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index d0bbc9cc7cc..a64f6194655 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -6,7 +6,7 @@ namespace Xamarin.Android.Tasks.LLVMIR { // Not all attributes are currently used throughout the code, but we define them call for potential future use. // Documentation can be found here: https://llvm.org/docs/LangRef.html#function-attributes - abstract class LLVMFunctionAttribute + abstract class LlvmIrFunctionAttribute : IComparable, IComparable, IEquatable { public string Name { get; } public bool Quoted { get; } @@ -14,7 +14,7 @@ abstract class LLVMFunctionAttribute public bool ParamsAreOptional { get; } public bool HasValueAsignment { get; } - protected LLVMFunctionAttribute (string name, bool quoted, bool supportsParams, bool optionalParams, bool hasValueAssignment) + protected LlvmIrFunctionAttribute (string name, bool quoted, bool supportsParams, bool optionalParams, bool hasValueAssignment) { Name = EnsureNonEmptyParameter (nameof (name), name); @@ -89,16 +89,93 @@ protected string EnsureNonEmptyParameter (string name, string value) return value; } + + public int CompareTo (object obj) + { + var attr = obj as LlvmIrFunctionAttribute; + if (obj == null) { + return 1; + } + + return CompareTo (attr); + } + + public int CompareTo (LlvmIrFunctionAttribute other) + { + return Name.CompareTo (other?.Name); + } + + public override int GetHashCode() + { + int hc = 0; + if (Name != null) { + hc ^= Name.GetHashCode (); + } + + return + hc ^ + Quoted.GetHashCode () ^ + SupportsParams.GetHashCode () ^ + ParamsAreOptional.GetHashCode () ^ + HasValueAsignment.GetHashCode (); + } + + public override bool Equals (object obj) + { + var attr = obj as LlvmIrFunctionAttribute; + if (attr == null) { + return false; + } + + return Equals (attr); + } + + public virtual bool Equals (LlvmIrFunctionAttribute other) + { + if (other == null) { + return false; + } + + if (String.Compare (Name, other.Name, StringComparison.Ordinal) != 0) { + return false; + } + + return + Quoted == other.Quoted && + SupportsParams == other.SupportsParams && + ParamsAreOptional == other.ParamsAreOptional && + HasValueAsignment == other.HasValueAsignment; + } + + public static bool operator > (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) + { + return a.CompareTo (b) > 0; + } + + public static bool operator < (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) + { + return a.CompareTo (b) < 0; + } + + public static bool operator >= (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) + { + return a.CompareTo (b) >= 0; + } + + public static bool operator <= (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) + { + return a.CompareTo (b) <= 0; + } } - abstract class LLVMFlagFunctionAttribute : LLVMFunctionAttribute + abstract class LlvmIrFlagFunctionAttribute : LlvmIrFunctionAttribute { - protected LLVMFlagFunctionAttribute (string name, bool quoted = false) + protected LlvmIrFlagFunctionAttribute (string name, bool quoted = false) : base (name, quoted, supportsParams: false, optionalParams: false, hasValueAssignment: false) {} } - class AlignstackFunctionAttribute : LLVMFunctionAttribute + class AlignstackFunctionAttribute : LlvmIrFunctionAttribute { uint alignment; @@ -116,9 +193,28 @@ protected override void RenderParams (StringBuilder sb) { sb.Append (alignment.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AlignstackFunctionAttribute; + if (attr == null) { + return false; + } + + return alignment == attr.alignment; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ alignment.GetHashCode (); + } } - class AllocFamilyFunctionAttribute : LLVMFunctionAttribute + class AllocFamilyFunctionAttribute : LlvmIrFunctionAttribute { string family; @@ -132,9 +228,28 @@ protected override void RenderAssignedValue (StringBuilder sb) { sb.Append (family); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllocFamilyFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (family, attr.family, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (family?.GetHashCode () ?? 0); + } } - class AllockindFunctionAttribute : LLVMFunctionAttribute + class AllockindFunctionAttribute : LlvmIrFunctionAttribute { string kind; @@ -150,9 +265,28 @@ protected override void RenderParams (StringBuilder sb) sb.Append (kind); sb.Append ('"'); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllockindFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (kind, attr.kind, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (kind?.GetHashCode () ?? 0); + } } - class AllocsizeFunctionAttribute : LLVMFunctionAttribute + class AllocsizeFunctionAttribute : LlvmIrFunctionAttribute { uint elementSize; uint? numberOfElements; @@ -174,65 +308,84 @@ protected override void RenderParams (StringBuilder sb) sb.Append (", "); sb.Append (numberOfElements.Value.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllocsizeFunctionAttribute; + if (attr == null) { + return false; + } + + return elementSize == attr.elementSize && numberOfElements == attr.numberOfElements; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ elementSize.GetHashCode () ^ (numberOfElements?.GetHashCode () ?? 0); + } } - class AlwaysinlineFunctionAttribute : LLVMFlagFunctionAttribute + class AlwaysinlineFunctionAttribute : LlvmIrFlagFunctionAttribute { public AlwaysinlineFunctionAttribute () : base ("alwaysinline") {} } - class ArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + class ArgmemonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public ArgmemonlyFunctionAttribute () : base ("argmemonly") {} } - class BuiltinFunctionAttribute : LLVMFlagFunctionAttribute + class BuiltinFunctionAttribute : LlvmIrFlagFunctionAttribute { public BuiltinFunctionAttribute () : base ("builtin") {} } - class ColdFunctionAttribute : LLVMFlagFunctionAttribute + class ColdFunctionAttribute : LlvmIrFlagFunctionAttribute { public ColdFunctionAttribute () : base ("cold") {} } - class ConvergentFunctionAttribute : LLVMFlagFunctionAttribute + class ConvergentFunctionAttribute : LlvmIrFlagFunctionAttribute { public ConvergentFunctionAttribute () : base ("convergent") {} } - class DisableSanitizerInstrumentationFunctionAttribute : LLVMFlagFunctionAttribute + class DisableSanitizerInstrumentationFunctionAttribute : LlvmIrFlagFunctionAttribute { public DisableSanitizerInstrumentationFunctionAttribute () : base ("disable_sanitizer_instrumentation") {} } - class DontcallErrorFunctionAttribute : LLVMFlagFunctionAttribute + class DontcallErrorFunctionAttribute : LlvmIrFlagFunctionAttribute { public DontcallErrorFunctionAttribute () : base ("dontcall-error", quoted: true) {} } - class DontcallWarnFunctionAttribute : LLVMFlagFunctionAttribute + class DontcallWarnFunctionAttribute : LlvmIrFlagFunctionAttribute { public DontcallWarnFunctionAttribute () : base ("dontcall-warn", quoted: true) {} } - class FramePointerFunctionAttribute : LLVMFunctionAttribute + class FramePointerFunctionAttribute : LlvmIrFunctionAttribute { string fpMode; @@ -252,387 +405,494 @@ public FramePointerFunctionAttribute (string fpMode = "none") } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (fpMode); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as FramePointerFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (fpMode, attr.fpMode, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (fpMode?.GetHashCode () ?? 0); + } } - class HotFunctionAttribute : LLVMFlagFunctionAttribute + class HotFunctionAttribute : LlvmIrFlagFunctionAttribute { public HotFunctionAttribute () : base ("hot") {} } - class InaccessiblememonlyFunctionAttribute : LLVMFlagFunctionAttribute + class InaccessiblememonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public InaccessiblememonlyFunctionAttribute () : base ("inaccessiblememonly") {} } - class InaccessiblememOrArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + class InaccessiblememOrArgmemonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public InaccessiblememOrArgmemonlyFunctionAttribute () : base ("inaccessiblemem_or_argmemonly") {} } - class InlinehintFunctionAttribute : LLVMFlagFunctionAttribute + class InlinehintFunctionAttribute : LlvmIrFlagFunctionAttribute { public InlinehintFunctionAttribute () : base ("inlinehint") {} } - class JumptableFunctionAttribute : LLVMFlagFunctionAttribute + class JumptableFunctionAttribute : LlvmIrFlagFunctionAttribute { public JumptableFunctionAttribute () : base ("jumptable") {} } - class MinsizeFunctionAttribute : LLVMFlagFunctionAttribute + enum MemoryAttributeAccessKind + { + None, + Read, + Write, + ReadWrite, + } + + class MemoryFunctionAttribute : LlvmIrFunctionAttribute + { + public MemoryAttributeAccessKind? Default { get; set; } + public MemoryAttributeAccessKind? Argmem { get; set; } + public MemoryAttributeAccessKind? InaccessibleMem { get; set; } + + public MemoryFunctionAttribute () + : base ("memory", quoted: false, supportsParams: true, optionalParams: true, hasValueAssignment: false) + {} + + protected override bool HasOptionalParams () + { + // All of them are optional, but at least one of them must be specified + bool ret = Default.HasValue || Argmem.HasValue || InaccessibleMem.HasValue; + if (!ret) { + throw new InvalidOperationException ("Internal error: at least one access kind must be specified"); + } + + return ret; + } + + protected override void RenderParams (StringBuilder sb) + { + bool haveSomething = false; + + if (Default.HasValue) { + AppendParam (GetAccessKindString (Default)); + } + + if (Argmem.HasValue) { + AppendParam ($"argmem: {GetAccessKindString (Argmem)}"); + } + + if (InaccessibleMem.HasValue) { + AppendParam ($"inaccessiblemem: {GetAccessKindString (InaccessibleMem)}"); + } + + void AppendParam (string text) + { + if (haveSomething) { + sb.Append (", "); + } + sb.Append (text); + haveSomething = true; + } + } + + string GetAccessKindString (MemoryAttributeAccessKind? kind) + { + return kind.Value switch { + MemoryAttributeAccessKind.None => "none", + MemoryAttributeAccessKind.Read => "read", + MemoryAttributeAccessKind.Write => "write", + MemoryAttributeAccessKind.ReadWrite => "readwrite", + _ => throw new InvalidOperationException ($"Internal error: unsupported access kind {kind}") + }; + } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as MemoryFunctionAttribute; + if (attr == null) { + return false; + } + + return Default == attr.Default && Argmem == attr.Argmem && InaccessibleMem == attr.InaccessibleMem; + } + } + + class MinsizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public MinsizeFunctionAttribute () : base ("minsize") {} } - class NakedFunctionAttribute : LLVMFlagFunctionAttribute + class NakedFunctionAttribute : LlvmIrFlagFunctionAttribute { public NakedFunctionAttribute () : base ("naked") {} } - class NoInlineLineTablesFunctionAttribute : LLVMFlagFunctionAttribute + class NoInlineLineTablesFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoInlineLineTablesFunctionAttribute () : base ("no-inline-line-tables", quoted: true) {} } - class NoJumpTablesFunctionAttribute : LLVMFlagFunctionAttribute + class NoJumpTablesFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoJumpTablesFunctionAttribute () : base ("no-jump-tables") {} } - class NobuiltinFunctionAttribute : LLVMFlagFunctionAttribute + class NobuiltinFunctionAttribute : LlvmIrFlagFunctionAttribute { public NobuiltinFunctionAttribute () : base ("nobuiltin") {} } - class NoduplicateFunctionAttribute : LLVMFlagFunctionAttribute + class NocallbackFunctionAttribute : LlvmIrFlagFunctionAttribute + { + public NocallbackFunctionAttribute () + : base ("nocallback") + {} + } + + class NoduplicateFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoduplicateFunctionAttribute () : base ("noduplicate") {} } - class NofreeFunctionAttribute : LLVMFlagFunctionAttribute + class NofreeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NofreeFunctionAttribute () : base ("nofree") {} } - class NoimplicitfloatFunctionAttribute : LLVMFlagFunctionAttribute + class NoimplicitfloatFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoimplicitfloatFunctionAttribute () : base ("noimplicitfloat") {} } - class NoinlineFunctionAttribute : LLVMFlagFunctionAttribute + class NoinlineFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoinlineFunctionAttribute () : base ("noinline") {} } - class NomergeFunctionAttribute : LLVMFlagFunctionAttribute + class NomergeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NomergeFunctionAttribute () : base ("nomerge") {} } - class NonlazybindFunctionAttribute : LLVMFlagFunctionAttribute + class NonlazybindFunctionAttribute : LlvmIrFlagFunctionAttribute { public NonlazybindFunctionAttribute () : base ("nonlazybind") {} } - class NoprofileFunctionAttribute : LLVMFlagFunctionAttribute + class NoprofileFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoprofileFunctionAttribute () : base ("noprofile") {} } - class NoredzoneFunctionAttribute : LLVMFlagFunctionAttribute + class NoredzoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoredzoneFunctionAttribute () : base ("noredzone") {} } - class IndirectTlsSegRefsFunctionAttribute : LLVMFlagFunctionAttribute + class IndirectTlsSegRefsFunctionAttribute : LlvmIrFlagFunctionAttribute { public IndirectTlsSegRefsFunctionAttribute () : base ("indirect-tls-seg-refs") {} } - class NoreturnFunctionAttribute : LLVMFlagFunctionAttribute + class NoreturnFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoreturnFunctionAttribute () : base ("noreturn") {} } - class NorecurseFunctionAttribute : LLVMFlagFunctionAttribute + class NorecurseFunctionAttribute : LlvmIrFlagFunctionAttribute { public NorecurseFunctionAttribute () : base ("norecurse") {} } - class WillreturnFunctionAttribute : LLVMFlagFunctionAttribute + class WillreturnFunctionAttribute : LlvmIrFlagFunctionAttribute { public WillreturnFunctionAttribute () : base ("willreturn") {} } - class NosyncFunctionAttribute : LLVMFlagFunctionAttribute + class NosyncFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosyncFunctionAttribute () : base ("nosync") {} } - class NounwindFunctionAttribute : LLVMFlagFunctionAttribute + class NounwindFunctionAttribute : LlvmIrFlagFunctionAttribute { public NounwindFunctionAttribute () : base ("nounwind") {} } - class NosanitizeBoundsFunctionAttribute : LLVMFlagFunctionAttribute + class NosanitizeBoundsFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosanitizeBoundsFunctionAttribute () : base ("nosanitize_bounds") {} } - class NosanitizeCoverageFunctionAttribute : LLVMFlagFunctionAttribute + class NosanitizeCoverageFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosanitizeCoverageFunctionAttribute () : base ("nosanitize_coverage") {} } - class NullPointerIsValidFunctionAttribute : LLVMFlagFunctionAttribute + class NullPointerIsValidFunctionAttribute : LlvmIrFlagFunctionAttribute { public NullPointerIsValidFunctionAttribute () : base ("null_pointer_is_valid") {} } - class OptforfuzzingFunctionAttribute : LLVMFlagFunctionAttribute + class OptforfuzzingFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptforfuzzingFunctionAttribute () : base ("optforfuzzing") {} } - class OptnoneFunctionAttribute : LLVMFlagFunctionAttribute + class OptnoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptnoneFunctionAttribute () : base ("optnone") {} } - class OptsizeFunctionAttribute : LLVMFlagFunctionAttribute + class OptsizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptsizeFunctionAttribute () : base ("optsize") {} } - class PatchableFunctionFunctionAttribute : LLVMFlagFunctionAttribute + class PatchableFunctionFunctionAttribute : LlvmIrFlagFunctionAttribute { public PatchableFunctionFunctionAttribute () : base ("patchable-function", quoted: true) {} } - class ProbeStackFunctionAttribute : LLVMFlagFunctionAttribute + class ProbeStackFunctionAttribute : LlvmIrFlagFunctionAttribute { public ProbeStackFunctionAttribute () : base ("probe-stack") {} } - class ReadnoneFunctionAttribute : LLVMFlagFunctionAttribute + class ReadnoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReadnoneFunctionAttribute () : base ("readnone") {} } - class ReadonlyFunctionAttribute : LLVMFlagFunctionAttribute + class ReadonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReadonlyFunctionAttribute () : base ("readonly") {} } - class StackProbeSizeFunctionAttribute : LLVMFlagFunctionAttribute + class StackProbeSizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public StackProbeSizeFunctionAttribute () : base ("stack-probe-size", quoted: true) {} } - class NoStackArgProbeFunctionAttribute : LLVMFlagFunctionAttribute + class NoStackArgProbeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoStackArgProbeFunctionAttribute () : base ("no-stack-arg-probe") {} } - class WriteonlyFunctionAttribute : LLVMFlagFunctionAttribute + class WriteonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public WriteonlyFunctionAttribute () : base ("writeonly") {} } - class ReturnsTwiceFunctionAttribute : LLVMFlagFunctionAttribute + class ReturnsTwiceFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReturnsTwiceFunctionAttribute () : base ("returns_twice") {} } - class SafestackFunctionAttribute : LLVMFlagFunctionAttribute + class SafestackFunctionAttribute : LlvmIrFlagFunctionAttribute { public SafestackFunctionAttribute () : base ("safestack") {} } - class SanitizeAddressFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeAddressFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeAddressFunctionAttribute () : base ("sanitize_address") {} } - class SanitizeMemoryFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeMemoryFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeMemoryFunctionAttribute () : base ("sanitize_memory") {} } - class SanitizeThreadFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeThreadFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeThreadFunctionAttribute () : base ("sanitize_thread") {} } - class SanitizeHwaddressFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeHwaddressFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeHwaddressFunctionAttribute () : base ("sanitize_hwaddress") {} } - class SanitizeMemtagFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeMemtagFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeMemtagFunctionAttribute () : base ("sanitize_memtag") {} } - class SpeculativeLoadHardeningFunctionAttribute : LLVMFlagFunctionAttribute + class SpeculativeLoadHardeningFunctionAttribute : LlvmIrFlagFunctionAttribute { public SpeculativeLoadHardeningFunctionAttribute () : base ("speculative_load_hardening") {} } - class SpeculatableFunctionAttribute : LLVMFlagFunctionAttribute + class SpeculatableFunctionAttribute : LlvmIrFlagFunctionAttribute { public SpeculatableFunctionAttribute () : base ("speculatable") {} } - class SspFunctionAttribute : LLVMFlagFunctionAttribute + class SspFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspFunctionAttribute () : base ("ssp") {} } - class SspstrongFunctionAttribute : LLVMFlagFunctionAttribute + class SspstrongFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspstrongFunctionAttribute () : base ("sspstrong") {} } - class SspreqFunctionAttribute : LLVMFlagFunctionAttribute + class SspreqFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspreqFunctionAttribute () : base ("sspreq") {} } - class StrictfpFunctionAttribute : LLVMFlagFunctionAttribute + class StrictfpFunctionAttribute : LlvmIrFlagFunctionAttribute { public StrictfpFunctionAttribute () : base ("strictfp") {} } - class DenormalFpMathFunctionAttribute : LLVMFlagFunctionAttribute + class DenormalFpMathFunctionAttribute : LlvmIrFlagFunctionAttribute { public DenormalFpMathFunctionAttribute () : base ("denormal-fp-math", quoted: true) {} } - class DenormalFpMathF32FunctionAttribute : LLVMFlagFunctionAttribute + class DenormalFpMathF32FunctionAttribute : LlvmIrFlagFunctionAttribute { public DenormalFpMathF32FunctionAttribute () : base ("denormal-fp-math-f32", quoted: true) {} } - class ThunkFunctionAttribute : LLVMFlagFunctionAttribute + class ThunkFunctionAttribute : LlvmIrFlagFunctionAttribute { public ThunkFunctionAttribute () : base ("thunk", quoted: true) {} } - class TlsLoadHoistFunctionAttribute : LLVMFlagFunctionAttribute + class TlsLoadHoistFunctionAttribute : LlvmIrFlagFunctionAttribute { public TlsLoadHoistFunctionAttribute () : base ("tls-load-hoist") {} } - class UwtableFunctionAttribute : LLVMFunctionAttribute + class UwtableFunctionAttribute : LlvmIrFunctionAttribute { bool? isSync; @@ -652,30 +912,49 @@ protected override void RenderParams (StringBuilder sb) sb.Append (isSync.Value ? "sync" : "async"); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as UwtableFunctionAttribute; + if (attr == null) { + return false; + } + + return isSync == attr.isSync; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (isSync?.GetHashCode () ?? 0); + } } - class NocfCheckFunctionAttribute : LLVMFlagFunctionAttribute + class NocfCheckFunctionAttribute : LlvmIrFlagFunctionAttribute { public NocfCheckFunctionAttribute () : base ("nocf_check") {} } - class ShadowcallstackFunctionAttribute : LLVMFlagFunctionAttribute + class ShadowcallstackFunctionAttribute : LlvmIrFlagFunctionAttribute { public ShadowcallstackFunctionAttribute () : base ("shadowcallstack") {} } - class MustprogressFunctionAttribute : LLVMFlagFunctionAttribute + class MustprogressFunctionAttribute : LlvmIrFlagFunctionAttribute { public MustprogressFunctionAttribute () : base ("mustprogress") {} } - class WarnStackSizeFunctionAttribute : LLVMFunctionAttribute + class WarnStackSizeFunctionAttribute : LlvmIrFunctionAttribute { uint threshold; @@ -686,9 +965,28 @@ public WarnStackSizeFunctionAttribute (uint threshold) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (threshold); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as WarnStackSizeFunctionAttribute; + if (attr == null) { + return false; + } + + return threshold == attr.threshold; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ threshold.GetHashCode (); + } } - class VscaleRangeFunctionAttribute : LLVMFunctionAttribute + class VscaleRangeFunctionAttribute : LlvmIrFunctionAttribute { uint min; uint? max; @@ -710,9 +1008,28 @@ protected override void RenderParams (StringBuilder sb) sb.Append (", "); sb.Append (max.Value.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as VscaleRangeFunctionAttribute; + if (attr == null) { + return false; + } + + return min == attr.min && max == attr.max; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ min.GetHashCode () ^ (max?.GetHashCode () ?? 0); + } } - class MinLegalVectorWidthFunctionAttribute : LLVMFunctionAttribute + class MinLegalVectorWidthFunctionAttribute : LlvmIrFunctionAttribute { uint size; @@ -723,9 +1040,28 @@ public MinLegalVectorWidthFunctionAttribute (uint size) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as MinLegalVectorWidthFunctionAttribute; + if (attr == null) { + return false; + } + + return size == attr.size; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ size.GetHashCode (); + } } - class StackProtectorBufferSizeFunctionAttribute : LLVMFunctionAttribute + class StackProtectorBufferSizeFunctionAttribute : LlvmIrFunctionAttribute { uint size; @@ -736,9 +1072,28 @@ public StackProtectorBufferSizeFunctionAttribute (uint size) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as StackProtectorBufferSizeFunctionAttribute; + if (attr == null) { + return false; + } + + return size == attr.size; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ size.GetHashCode (); + } } - class TargetCpuFunctionAttribute : LLVMFunctionAttribute + class TargetCpuFunctionAttribute : LlvmIrFunctionAttribute { string cpu; @@ -749,9 +1104,28 @@ public TargetCpuFunctionAttribute (string cpu) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TargetCpuFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (cpu, attr.cpu, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (cpu?.GetHashCode () ?? 0); + } } - class TuneCpuFunctionAttribute : LLVMFunctionAttribute + class TuneCpuFunctionAttribute : LlvmIrFunctionAttribute { string cpu; @@ -762,9 +1136,28 @@ public TuneCpuFunctionAttribute (string cpu) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TuneCpuFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (cpu, attr.cpu, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (cpu?.GetHashCode () ?? 0); + } } - class TargetFeaturesFunctionAttribute : LLVMFunctionAttribute + class TargetFeaturesFunctionAttribute : LlvmIrFunctionAttribute { string features; @@ -775,9 +1168,28 @@ public TargetFeaturesFunctionAttribute (string features) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (features); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TargetFeaturesFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (features, attr.features, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (features?.GetHashCode () ?? 0); + } } - class NoTrappingMathFunctionAttribute : LLVMFunctionAttribute + class NoTrappingMathFunctionAttribute : LlvmIrFunctionAttribute { bool yesno; @@ -788,9 +1200,28 @@ public NoTrappingMathFunctionAttribute (bool yesno) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (yesno.ToString ().ToLowerInvariant ()); + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as NoTrappingMathFunctionAttribute; + if (attr == null) { + return false; + } + + return yesno == attr.yesno; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ yesno.GetHashCode (); + } } - class StackrealignFunctionAttribute : LLVMFlagFunctionAttribute + class StackrealignFunctionAttribute : LlvmIrFlagFunctionAttribute { public StackrealignFunctionAttribute () : base ("stackrealign", quoted: true) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs deleted file mode 100644 index afd17fefda5..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - interface IStructureInfo - { - Type Type { get; } - ulong Size { get; } - int MaxFieldAlignment { get; } - string Name { get; } - string NativeTypeDesignator { get; } - - void RenderDeclaration (LlvmIrGenerator generator); - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs deleted file mode 100644 index 4ba4ed9be75..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class LlvmFunctionAttributeSet : IEnumerable - { - HashSet attributes; - - public LlvmFunctionAttributeSet () - { - attributes = new HashSet (); - } - - public void Add (LLVMFunctionAttribute attr) - { - if (attr == null) { - throw new ArgumentNullException (nameof (attr)); - } - - // TODO: implement uniqueness checks - attributes.Add (attr); - } - - public void Add (LlvmFunctionAttributeSet sourceSet) - { - if (sourceSet == null) { - throw new ArgumentNullException (nameof (sourceSet)); - } - - foreach (LLVMFunctionAttribute attr in sourceSet) { - Add (attr); - } - } - - public string Render () - { - List list = attributes.ToList (); - list.Sort ((LLVMFunctionAttribute a, LLVMFunctionAttribute b) => a.Name.CompareTo (b.Name)); - - return String.Join (" ", list.Select (a => a.Render ())); - } - - public IEnumerator GetEnumerator () => attributes.GetEnumerator (); - - IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs new file mode 100644 index 00000000000..8cb8c2910e4 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVMIR; + +partial class LlvmIrModule +{ + sealed class LlvmIrBufferManager + { + Dictionary counters; + Dictionary> bufferVariableNames; + + public LlvmIrBufferManager () + { + counters = new Dictionary (StringComparer.Ordinal); + } + + public string Allocate (StructureInstance structure, StructureMemberInfo smi, ulong size) + { + string baseName = $"_{structure.Info.Name}_{smi.Info.Name}"; + + if (!counters.TryGetValue (baseName, out ulong count)) { + count = 0; + counters.Add (baseName, count); + } else { + count++; + counters[baseName] = count; + } + + return Register (structure, smi, $"{baseName}_{count:x}_{structure.IndexInArray:x}"); + } + + public string? GetBufferVariableName (StructureInstance structure, StructureMemberInfo smi) + { + if (bufferVariableNames == null || bufferVariableNames.Count == 0) { + return null; + } + + if (!bufferVariableNames.TryGetValue (structure.Obj, out Dictionary members)) { + return null; + } + + if (!members.TryGetValue (MakeUniqueMemberId (structure, smi), out string bufferVariableName)) { + return null; + } + + return bufferVariableName; + } + + string Register (StructureInstance structure, StructureMemberInfo smi, string bufferVariableName) + { + if (bufferVariableNames == null) { + bufferVariableNames = new Dictionary> (); + } + + if (!bufferVariableNames.TryGetValue (structure.Obj, out Dictionary members)) { + members = new Dictionary (StringComparer.Ordinal); + bufferVariableNames.Add (structure.Obj, members); + } + + members.Add (MakeUniqueMemberId (structure, smi), bufferVariableName); + return bufferVariableName; + } + + string MakeUniqueMemberId (StructureInstance structure, StructureMemberInfo smi) => $"{smi.Info.Name}_{structure.IndexInArray}"; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 374a432e76a..8db94269f32 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -7,74 +7,51 @@ namespace Xamarin.Android.Tasks.LLVMIR { - /// - /// Base class for all classes which "compose" LLVM IR assembly. - /// abstract class LlvmIrComposer { - protected AndroidTargetArch TargetArch { get; } + bool constructed; - protected LlvmIrComposer () - {} + protected abstract void Construct (LlvmIrModule module); - public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) + public LlvmIrModule Construct () { - LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); + var module = new LlvmIrModule (); + Construct (module); + module.AfterConstruction (); + constructed = true; - InitGenerator (generator); - MapStructures (generator); - generator.WriteFileTop (); - generator.WriteStructureDeclarations (); - Write (generator); - generator.WriteFileEnd (); + return module; } - protected static string GetAbiName (AndroidTargetArch arch) + public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter output, string fileName) { - return arch switch { - AndroidTargetArch.Arm => "armeabi-v7a", - AndroidTargetArch.Arm64 => "arm64-v8a", - AndroidTargetArch.X86 => "x86", - AndroidTargetArch.X86_64 => "x86_64", - _ => throw new InvalidOperationException ($"Unsupported Android architecture: {arch}"), - }; + if (!constructed) { + throw new InvalidOperationException ($"Internal error: module not constructed yet. Was Constrict () called?"); + } + + LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName); + generator.Generate (output, module); + output.Flush (); } - protected ulong HashName (string name, bool is64Bit) + public static ulong GetXxHash (string str, bool is64Bit) { - byte[] nameBytes = Encoding.UTF8.GetBytes (name); + byte[] stringBytes = Encoding.UTF8.GetBytes (str); if (is64Bit) { - return XxHash64.HashToUInt64 (nameBytes); + return XxHash64.HashToUInt64 (stringBytes); } - return (ulong)XxHash32.HashToUInt32 (nameBytes); + return (ulong)XxHash32.HashToUInt32 (stringBytes); } - protected virtual void InitGenerator (LlvmIrGenerator generator) - {} - - /// - /// Initialize the composer. It needs to allocate and populate all the structures that - /// are used by the composer, before they can be mapped by the generator. The code here - /// should initialize only the architecture-independent fields of structures etc to - /// write. The composer is reused between architectures, and only the Write method is - /// aware of which architecture is targetted. - /// - public abstract void Init (); - - /// - /// Maps all the structures used to internal LLVM IR representation. Every structure MUST - /// be mapped. - /// - protected abstract void MapStructures (LlvmIrGenerator generator); + protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) + { + var gv = variable as LlvmIrGlobalVariable; + if (gv == null) { + throw new InvalidOperationException ("Internal error: global variable expected"); + } - /// - /// Generate LLVM IR code from data structures initialized by . This is - /// called once per ABI, with the appropriate for the target - /// ABI. If any ABI-specific initialization must be performed on the data structures to - /// be written, it has to be done here (applies to e.g. constructs that require to know the - /// native pointer size). - /// - protected abstract void Write (LlvmIrGenerator generator); + return gv; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs new file mode 100644 index 00000000000..33da1be9a57 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR; + +abstract class LlvmIrDataLayoutField +{ + public const char Separator = ':'; + + public string Id { get; } + + protected LlvmIrDataLayoutField (string id) + { + if (String.IsNullOrEmpty (id)) { + throw new ArgumentException (nameof (id), "must not be null or empty"); + } + + Id = id; + } + + public virtual void Render (StringBuilder sb) + { + sb.Append (Id); + } + + public static string ConvertToString (uint v) + { + return v.ToString (CultureInfo.InvariantCulture); + } + + protected void Append (StringBuilder sb, uint v, bool needSeparator = true) + { + if (needSeparator) { + sb.Append (Separator); + } + + sb.Append (ConvertToString (v)); + } + + protected void Append (StringBuilder sb, uint? v, bool needSeparator = true) + { + if (!v.HasValue) { + return; + } + + Append (sb, v.Value, needSeparator); + } +} + +class LlvmIrDataLayoutPointerSize : LlvmIrDataLayoutField +{ + public uint? AddressSpace { get; set; } + public uint Abi { get; } + public uint Size { get; } + public uint? Pref { get; set; } + public uint? Idx { get; set; } + + public LlvmIrDataLayoutPointerSize (uint size, uint abi) + : base ("p") + { + Size = size; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + if (AddressSpace.HasValue && AddressSpace.Value > 0) { + Append (sb, AddressSpace.Value, needSeparator: false); + } + Append (sb, Size); + Append (sb, Abi); + Append (sb, Pref); + Append (sb, Idx); + } +} + +abstract class LlvmIrDataLayoutTypeAlignment : LlvmIrDataLayoutField +{ + public uint Size { get; } + public uint Abi { get; } + public uint? Pref { get; set; } + + protected LlvmIrDataLayoutTypeAlignment (string id, uint size, uint abi) + : base (id) + { + Size = size; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + Append (sb, Size, needSeparator: false); + Append (sb, Abi); + Append (sb, Pref); + } +} + +class LlvmIrDataLayoutIntegerAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutIntegerAlignment (uint size, uint abi, uint? pref = null) + : base ("i", size, abi) + { + if (size == 8 && abi != 8) { + throw new ArgumentOutOfRangeException (nameof (abi), "Must equal 8 for i8"); + } + + Pref = pref; + } +} + +class LlvmIrDataLayoutVectorAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutVectorAlignment (uint size, uint abi, uint? pref = null) + : base ("v", size, abi) + { + Pref = pref; + } +} + +class LlvmIrDataLayoutFloatAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutFloatAlignment (uint size, uint abi, uint? pref = null) + : base ("f", size, abi) + { + Pref = pref; + } +} + +class LlvmIrDataLayoutAggregateObjectAlignment : LlvmIrDataLayoutField +{ + public uint Abi { get; } + public uint? Pref { get; set; } + + public LlvmIrDataLayoutAggregateObjectAlignment (uint abi, uint? pref = null) + : base ("a") + { + Abi = abi; + Pref = pref; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + Append (sb, Abi); + Append (sb, Pref); + } +} + +enum LlvmIrDataLayoutFunctionPointerAlignmentType +{ + Independent, + Multiple, +} + +class LlvmIrDataLayoutFunctionPointerAlignment : LlvmIrDataLayoutField +{ + public uint Abi { get; } + public LlvmIrDataLayoutFunctionPointerAlignmentType Type { get; } + + public LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType type, uint abi) + : base ("F") + { + Type = type; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + char type = Type switch { + LlvmIrDataLayoutFunctionPointerAlignmentType.Independent => 'i', + LlvmIrDataLayoutFunctionPointerAlignmentType.Multiple => 'n', + _ => throw new InvalidOperationException ($"Unsupported function pointer alignment type '{Type}'") + }; + sb.Append (type); + Append (sb, Abi, needSeparator: false); + } +} + +enum LlvmIrDataLayoutManglingOption +{ + ELF, + GOFF, + MIPS, + MachO, + WindowsX86COFF, + WindowsCOFF, + XCOFF +} + +class LlvmIrDataLayoutMangling : LlvmIrDataLayoutField +{ + public LlvmIrDataLayoutManglingOption Option { get; } + + public LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption option) + : base ("m") + { + Option = option; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + sb.Append (Separator); + + char opt = Option switch { + LlvmIrDataLayoutManglingOption.ELF => 'e', + LlvmIrDataLayoutManglingOption.GOFF => 'l', + LlvmIrDataLayoutManglingOption.MIPS => 'm', + LlvmIrDataLayoutManglingOption.MachO => 'o', + LlvmIrDataLayoutManglingOption.WindowsX86COFF => 'x', + LlvmIrDataLayoutManglingOption.WindowsCOFF => 'w', + LlvmIrDataLayoutManglingOption.XCOFF => 'a', + _ => throw new InvalidOperationException ($"Unsupported mangling option '{Option}'") + }; + + sb.Append (opt); + } +} + +// See: https://llvm.org/docs/LangRef.html#data-layout +class LlvmIrDataLayout +{ + bool bigEndian; + bool littleEndian = true; + + public bool BigEndian { + get => bigEndian; + set { + bigEndian = value; + littleEndian = !bigEndian; + } + } + + public bool LittleEndian { + get => littleEndian; + set { + littleEndian = value; + bigEndian = !littleEndian; + } + } + + public uint? AllocaAddressSpaceId { get; set; } + public uint? GlobalsAddressSpaceId { get; set; } + public LlvmIrDataLayoutMangling? Mangling { get; set; } + public uint? ProgramAddressSpaceId { get; set; } + public uint? StackAlignment { get; set; } + + public LlvmIrDataLayoutAggregateObjectAlignment? AggregateObjectAlignment { get; set; } + public List? FloatAlignment { get; set; } + public LlvmIrDataLayoutFunctionPointerAlignment? FunctionPointerAlignment { get; set; } + public List? IntegerAlignment { get; set; } + public List? VectorAlignment { get; set; } + public List? PointerSize { get; set; } + + public List? NativeIntegerWidths { get; set; } + public List? NonIntegralPointerTypeAddressSpaces { get; set; } + + public string Render () + { + var sb = new StringBuilder (); + + sb.Append ("target datalayout = \""); + + sb.Append (LittleEndian ? 'e' : 'E'); + + if (Mangling != null) { + sb.Append ('-'); + Mangling.Render (sb); + } + + AppendFieldList (PointerSize); + + if (FunctionPointerAlignment != null) { + sb.Append ('-'); + FunctionPointerAlignment.Render (sb); + } + + AppendFieldList (IntegerAlignment); + AppendFieldList (FloatAlignment); + AppendFieldList (VectorAlignment); + + Append ('P', ProgramAddressSpaceId); + Append ('G', GlobalsAddressSpaceId); + Append ('A', AllocaAddressSpaceId); + + if (AggregateObjectAlignment != null) { + sb.Append ('-'); + AggregateObjectAlignment.Render (sb); + } + + AppendList ("n", NativeIntegerWidths); + AppendList ("ni", NonIntegralPointerTypeAddressSpaces); + Append ('S', StackAlignment); + + sb.Append ('"'); + + return sb.ToString (); + + void AppendFieldList (List? list) where T: LlvmIrDataLayoutField + { + if (list == null || list.Count == 0) { + return; + } + + foreach (LlvmIrDataLayoutField field in list) { + sb.Append ('-'); + field.Render (sb); + } + } + + void AppendList (string id, List? list) + { + if (list == null || list.Count == 0) { + return; + } + + sb.Append ('-'); + sb.Append (id); + + bool first = true; + foreach (uint v in list) { + if (first) { + first = false; + } else { + sb.Append (LlvmIrDataLayoutField.Separator); + } + + sb.Append (LlvmIrDataLayoutField.ConvertToString (v)); + } + } + + void Append (char id, uint? v) + { + if (!v.HasValue) { + return; + } + + sb.Append ('-'); + sb.Append (id); + sb.Append (LlvmIrDataLayoutField.ConvertToString (v.Value)); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index f2f9ba989ec..aa156ecaef9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -1,188 +1,523 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; namespace Xamarin.Android.Tasks.LLVMIR { - class LlvmIrFunctionLocalVariable : LlvmIrVariable + interface ILlvmIrSavedFunctionParameterState {} + + class LlvmIrFunctionParameter : LlvmIrLocalVariable { - public LlvmIrFunctionLocalVariable (Type type, string? name = null, bool isNativePointer = false) - : base (type, name, signature: null, isNativePointer: isNativePointer) - {} + sealed class SavedParameterState : ILlvmIrSavedFunctionParameterState + { + public readonly LlvmIrFunctionParameter Owner; + + public uint? Align; + public bool? AllocPtr; + public uint? Dereferenceable; + public bool? ImmArg; + public bool? NoCapture; + public bool? NonNull; + public bool? NoUndef; + public bool? ReadNone; + public bool? SignExt; + public bool? ZeroExt; + public bool? IsCplusPlusReference; + public bool IsVarArgs; + + public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? previousState = null) + { + Owner = owner; + if (previousState == null) { + return; + } + + Align = previousState.Align; + AllocPtr = previousState.AllocPtr; + Dereferenceable = previousState.Dereferenceable; + ImmArg = previousState.ImmArg; + NoCapture = previousState.NoCapture; + NonNull = previousState.NonNull; + ReadNone = previousState.ReadNone; + SignExt = previousState.SignExt; + ZeroExt = previousState.ZeroExt; + IsCplusPlusReference = previousState.IsCplusPlusReference; + IsVarArgs = previousState.IsVarArgs; + } + } + + SavedParameterState state; + + // To save on time, we declare only attributes that are actually used in our generated code. More will be added, as needed. + + /// + /// align(n) attribute, see . + /// As a special case for us, a value of 0 means use the natural target pointer alignment. + /// + public uint? Align { + get => state.Align; + set => state.Align = value; + } + + /// + /// allocptr attribute, see + /// + public bool? AllocPtr { + get => state.AllocPtr; + set => state.AllocPtr = value; + } + + /// + /// dereferenceable(n) attribute, see . + /// As a special case for us, a value of 0 means use the natural target pointer alignment. + /// + public uint? Dereferenceable { + get => state.Dereferenceable; + set => state.Dereferenceable = value; + } + + /// + /// immarg attribute, see + /// + public bool? ImmArg { + get => state.ImmArg; + set => state.ImmArg = value; + } + + /// + /// nocapture attribute, see + /// + public bool? NoCapture { + get => state.NoCapture; + set => state.NoCapture = value; + } + + /// + /// nonnull attribute, see + /// + public bool? NonNull { + get => state.NonNull; + set => state.NonNull = value; + } + + /// + /// noundef attribute, see + /// + public bool? NoUndef { + get => state.NoUndef; + set => state.NoUndef = value; + } + + /// + /// readnone attribute, see + /// + public bool? ReadNone { + get => state.ReadNone; + set => state.ReadNone = value; + } + + /// + /// signext attribute, see + /// + public bool? SignExt { + get => state.SignExt; + set => state.SignExt = value; + } + + /// + /// zeroext attribute, see + /// + public bool? ZeroExt { + get => state.ZeroExt; + set => state.ZeroExt = value; + } + + /// + /// This serves a purely documentational purpose, when generating comments about types. It describes a parameter that is a C++ reference, something we can't + /// reflect on the managed side. + /// + public bool? IsCplusPlusReference { + get => state.IsCplusPlusReference; + set => state.IsCplusPlusReference = value; + } + + /// + /// Indicates that the argument is a C variable arguments placeholder (`...`) + /// + public bool IsVarArgs { + get => state.IsVarArgs; + set => state.IsVarArgs = value; + } - public LlvmIrFunctionLocalVariable (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false) - : base (typeof(LlvmNativeFunctionSignature), name, nativeFunction, isNativePointer: isNativePointer) + public LlvmIrFunctionParameter (Type type, string? name = null) + : base (type, name) { - if (nativeFunction == null) { - throw new ArgumentNullException(nameof (nativeFunction)); + NameMatters = false; + state = new SavedParameterState (this); + + // TODO: check why it doesn't work as expected - can't see the flags set in the output + if (type == typeof(sbyte) || type == typeof (short)) { + SignExt = true; + } else if (type == typeof(byte) || type == typeof (ushort)) { + ZeroExt = true; } } - public LlvmIrFunctionLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) - : base (variable, name, isNativePointer) - {} + /// + /// Save (opaque) parameter state. This is necessary because we generate code from the same model (module) for different + /// targets. At the same time, function, signature and parameter instances are shared between the different code generation + /// sessions, so we must sure the state as set by the model is properly preserved. NOTE: it does NOT make the code thread-safe! + /// Instances are **still** shared and thus different threads would step on each other's toes should they saved and restored + /// state without synchronization. + /// + public ILlvmIrSavedFunctionParameterState SaveState () + { + SavedParameterState ret = state; + state = new SavedParameterState (this, ret); + return ret; + } + + /// + /// Restore (opaque) state. for more info + /// + public void RestoreState (ILlvmIrSavedFunctionParameterState savedState) + { + var oldState = savedState as SavedParameterState; + if (oldState == null) { + throw new InvalidOperationException ("Internal error: savedState not an instance of ParameterState"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); + } + + state = oldState; + } } - class LlvmIrFunctionParameter : LlvmIrFunctionLocalVariable + interface ILlvmIrSavedFunctionSignatureState {} + + class LlvmIrFunctionSignature : IEquatable { - public bool IsCplusPlusReference { get; } + public sealed class ReturnTypeAttributes + { + public bool? InReg; + public bool? NoUndef; + public bool? SignExt; + public bool? ZeroExt; + public bool? NonNull; + + public ReturnTypeAttributes () + {} + + public ReturnTypeAttributes (ReturnTypeAttributes other) + { + InReg = other.InReg; + NoUndef = other.NoUndef; + NonNull = other.NonNull; + SignExt = other.SignExt; + ZeroExt = other.ZeroExt; + } + } - public LlvmIrFunctionParameter (Type type, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) - : base (type, name, isNativePointer) + sealed class SavedSignatureState : ILlvmIrSavedFunctionSignatureState { - IsCplusPlusReference = isCplusPlusReference; + public readonly LlvmIrFunctionSignature Owner; + public readonly IList ParameterStates; + public readonly ReturnTypeAttributes ReturnAttributes; + + public SavedSignatureState (LlvmIrFunctionSignature owner, IList parameterStates, ReturnTypeAttributes returnAttributes) + { + Owner = owner; + ParameterStates = parameterStates; + ReturnAttributes = returnAttributes; + } } - public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) - : base (nativeFunction, name, isNativePointer) + ReturnTypeAttributes returnAttributes; + + public string Name { get; } + public Type ReturnType { get; } + public ReturnTypeAttributes ReturnAttributes => returnAttributes; + public IList Parameters { get; } + + public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null, ReturnTypeAttributes? returnAttributes = null) { - IsCplusPlusReference = isCplusPlusReference; + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Name = name; + ReturnType = returnType; + this.returnAttributes = returnAttributes ?? new ReturnTypeAttributes (); + Parameters = parameters ?? new List (); } - } - class LlvmIrFunctionArgument - { - public object Value { get; } - public Type Type { get; } + /// + /// Create new signature using data from the one, with the exception of name. + /// Useful when there are several functions with different names but identical parameters and return types. + /// + public LlvmIrFunctionSignature (string name, LlvmIrFunctionSignature templateSignature) + : this (name, templateSignature.ReturnType, templateSignature.Parameters) + {} - public LlvmIrFunctionArgument (Type type, object? value = null) + /// + /// Save (opaque) signature state. This includes states of all the parameters. + /// for more information. + /// + public ILlvmIrSavedFunctionSignatureState SaveState () { - Type = type ?? throw new ArgumentNullException (nameof (type)); + var list = new List (); - if (value != null && value.GetType () != type) { - throw new ArgumentException ($"value type '{value.GetType ()}' does not match the argument type '{type}'"); + foreach (LlvmIrFunctionParameter parameter in Parameters) { + list.Add (parameter.SaveState ()); } - Value = value; + var ret = new SavedSignatureState (this, list.AsReadOnly (), returnAttributes); + returnAttributes = new ReturnTypeAttributes (returnAttributes); + return ret; } - public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable) + /// + /// Restore (opaque) signature state. This includes states of all the parameters. + /// for more information. + /// + public void RestoreState (ILlvmIrSavedFunctionSignatureState savedState) { - Type = typeof(LlvmIrFunctionLocalVariable); - Value = variable; + var oldState = savedState as SavedSignatureState; + if (oldState == null) { + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SavedSignatureState)}"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); + } + + for (int i = 0; i < oldState.ParameterStates.Count; i++) { + ILlvmIrSavedFunctionParameterState parameterState = oldState.ParameterStates[i]; + Parameters[i].RestoreState (parameterState); + } + returnAttributes = new ReturnTypeAttributes (oldState.ReturnAttributes); + } + + public override int GetHashCode () + { + int hc = + Name.GetHashCode () ^ + Parameters.GetHashCode () ^ + ReturnType.GetHashCode (); + + foreach (LlvmIrFunctionParameter p in Parameters) { + hc ^= p.GetHashCode (); + } + + return hc; + } + + public override bool Equals (object obj) + { + var sig = obj as LlvmIrFunctionSignature; + if (sig == null) { + return false; + } + + return Equals (sig); + } + + public bool Equals (LlvmIrFunctionSignature other) + { + if (other == null) { + return false; + } + + if (Parameters.Count != other.Parameters.Count || + ReturnType != other.ReturnType || + String.Compare (Name, other.Name, StringComparison.Ordinal) != 0 + ) { + return false; + } + + for (int i = 0; i < Parameters.Count; i++) { + if (Parameters[i] != other.Parameters[i]) { + return false; + } + } + + return true; } } + interface ILlvmIrSavedFunctionState {} + /// - /// Describes a native function to be emitted and keeps code emitting state between calls to various generator + /// Describes a native function to be emitted or declared and keeps code emitting state between calls to various generator. /// methods. /// - class LlvmIrFunction + class LlvmIrFunction : IEquatable { - const string Indent1 = LlvmIrGenerator.Indent; - const string Indent2 = LlvmIrGenerator.Indent + LlvmIrGenerator.Indent; - - // Function signature - public string Name { get; } - public Type ReturnType { get; } - public int AttributeSetID { get; } - public IList? Parameters { get; } - public string ImplicitFuncTopLabel { get; } - public IList? ParameterVariables { get; } + public class FunctionState + { + // Counter shared by unnamed local variables (including function parameters) and unnamed labels. + ulong unnamedTemporaryCounter = 0; - // Function writing state - public string Indent { get; private set; } = LlvmIrGenerator.Indent; + // Implicit unnamed label at the start of the function + ulong? startingBlockNumber; - // Used for unnamed function parameters as well as unnamed local variables - uint localSlot = 0; - uint indentLevel = 1; + public ulong StartingBlockNumber { + get { + if (startingBlockNumber.HasValue) { + return startingBlockNumber.Value; + } - public LlvmIrFunction (string name, Type returnType, int attributeSetID, List? parameters = null) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); + throw new InvalidOperationException ($"Internal error: starting block number not set"); + } } - Name = name; - ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); - AttributeSetID = attributeSetID; - Parameters = parameters?.Select (p => EnsureParameterName (p))?.ToList ()?.AsReadOnly (); - ParameterVariables = Parameters?.Select (p => new LlvmIrFunctionLocalVariable (p.Type, p.Name))?.ToList ()?.AsReadOnly (); - // Unnamed local variables need to start from the value which equals [number_of_unnamed_parameters] + 1, - // since there's an implicit label created for the top of the function whose name is `[number_of_unnamed_parameters]` - ImplicitFuncTopLabel = localSlot.ToString (CultureInfo.InvariantCulture); - localSlot++; + public FunctionState () + {} - LlvmIrFunctionParameter EnsureParameterName (LlvmIrFunctionParameter parameter) + public ulong NextTemporary () { - if (parameter == null) { - throw new InvalidOperationException ("null parameters aren't allowed"); - } + ulong ret = unnamedTemporaryCounter++; + return ret; + } - if (!String.IsNullOrEmpty (parameter.Name)) { - return parameter; + public void ConfigureStartingBlockNumber () + { + if (startingBlockNumber.HasValue) { + return; } - string name = GetNextSlotName (); - if (parameter.NativeFunction != null) { - return new LlvmIrFunctionParameter (parameter.NativeFunction, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); - } - return new LlvmIrFunctionParameter (parameter.Type, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); + startingBlockNumber = unnamedTemporaryCounter++; } } - public LlvmIrFunctionLocalVariable MakeLocalVariable (Type type, string? name = null) + sealed class SavedFunctionState : ILlvmIrSavedFunctionState { - if (String.IsNullOrEmpty (name)) { - name = GetNextSlotName (); + public readonly LlvmIrFunction Owner; + public readonly ILlvmIrSavedFunctionSignatureState SignatureState; + + public SavedFunctionState (LlvmIrFunction owner, ILlvmIrSavedFunctionSignatureState signatureState) + { + Owner = owner; + SignatureState = signatureState; } + } + + FunctionState functionState; + + public LlvmIrFunctionSignature Signature { get; } + public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.LocalUnnamed; + public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } + public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; + public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; + public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; + public LlvmIrFunctionBody Body { get; } + public string? Comment { get; set; } + public bool ReturnsValue => Signature.ReturnType != typeof(void); + public bool UsesVarArgs { get; } + + public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) + { + Signature = signature; + AttributeSet = attributeSet; + + functionState = new FunctionState (); + foreach (LlvmIrFunctionParameter parameter in signature.Parameters) { + if (UsesVarArgs) { + throw new InvalidOperationException ($"Internal error: function '{signature.Name}' uses variable arguments and it has at least one argument following the varargs (...) one. This is not allowed."); + } + + if (parameter.IsVarArgs) { + UsesVarArgs = true; + continue; + } + + if (!String.IsNullOrEmpty (parameter.Name)) { + continue; + } - return new LlvmIrFunctionLocalVariable (type, name); + parameter.AssignNumber (functionState.NextTemporary ()); + } + functionState.ConfigureStartingBlockNumber (); + + Body = new LlvmIrFunctionBody (this, functionState); } - public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, string? name = null) + /// + /// Create new function using data from the signature, with the exception of name. + /// Useful when there are several functions with different names but identical parameters and return types. + /// + public LlvmIrFunction (string name, LlvmIrFunctionSignature templateSignature, LlvmIrFunctionAttributeSet? attributeSet = null) + : this (new LlvmIrFunctionSignature (name, templateSignature), attributeSet) + {} + + public LlvmIrFunction (string name, Type returnType, List? parameters = null, LlvmIrFunctionAttributeSet? attributeSet = null) + : this (new LlvmIrFunctionSignature (name, returnType, parameters), attributeSet) + {} + + /// + /// Creates a local variable which, if is null or empty, is assinged the correct + /// name based on a counter local to the function. + /// + public LlvmIrLocalVariable CreateLocalVariable (Type type, string? name = null) { + var ret = new LlvmIrLocalVariable (type, name); if (String.IsNullOrEmpty (name)) { - name = GetNextSlotName (); + ret.AssignNumber (functionState.NextTemporary ()); } - return new LlvmIrFunctionLocalVariable (variable, name); + return ret; } - public void IncreaseIndent () + /// + /// Save (opaque) function state. This includes signature state. + /// for more information. + /// + public ILlvmIrSavedFunctionState SaveState () { - indentLevel++; - Indent = MakeIndent (indentLevel); + return new SavedFunctionState (this, Signature.SaveState ()); } - public void DecreaseIndent () + /// + /// Restore (opaque) function state. This includes signature state. + /// for more information. + /// + public void RestoreState (ILlvmIrSavedFunctionState savedState) { - if (indentLevel == 0) { - return; + var oldState = savedState as SavedFunctionState; + if (oldState == null) { + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SavedFunctionState)}"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); } - indentLevel--; - Indent = MakeIndent (indentLevel); + Signature.RestoreState (oldState.SignatureState); } - string MakeIndent (uint level) + public override int GetHashCode () { - switch (level) { - case 0: - return String.Empty; - - case 1: - return Indent1; - - case 2: - return Indent2; + return Signature.GetHashCode (); + } - default: - var sb = new StringBuilder (); - for (uint i = 0; i < level; i++) { - sb.Append (LlvmIrGenerator.Indent); - } - return sb.ToString (); + public override bool Equals (object obj) + { + var func = obj as LlvmIrFunction; + if (func == null) { + return false; } + + return Equals (func); } - string GetNextSlotName () + public bool Equals (LlvmIrFunction other) { - string name = $"{localSlot.ToString (CultureInfo.InvariantCulture)}"; - localSlot++; - return name; + if (other == null) { + return false; + } + + return Signature == other.Signature; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs new file mode 100644 index 00000000000..7c890e13417 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable +{ + public uint Number { get; set; } = 0; + public bool DoNotAddTargetSpecificAttributes { get; set; } + + HashSet attributes; + Dictionary>? privateTargetSpecificAttributes; + + public LlvmIrFunctionAttributeSet () + { + attributes = new HashSet (); + } + + public LlvmIrFunctionAttributeSet (LlvmIrFunctionAttributeSet other) + { + attributes = new HashSet (other); + Number = other.Number; + } + + public IList? GetPrivateTargetAttributes (AndroidTargetArch targetArch) + { + if (privateTargetSpecificAttributes == null || !privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { + return null; + } + + return list.AsReadOnly (); + } + + public void Add (LlvmIrFunctionAttribute attr) + { + if (attr == null) { + throw new ArgumentNullException (nameof (attr)); + } + + if (!attributes.Contains (attr)) { + attributes.Add (attr); + } + } + + public void Add (IList attrList) + { + foreach (LlvmIrFunctionAttribute attr in attrList) { + Add (attr); + } + } + + public string Render () + { + List list = attributes.ToList (); + list.Sort ((LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) => a.Name.CompareTo (b.Name)); + + return String.Join (" ", list.Select (a => a.Render ())); + } + + public IEnumerator GetEnumerator () => attributes.GetEnumerator (); + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + + public bool Equals (LlvmIrFunctionAttributeSet other) + { + if (other == null) { + return false; + } + + if (attributes.Count != other.attributes.Count) { + return false; + } + + foreach (LlvmIrFunctionAttribute attr in attributes) { + if (!other.attributes.Contains (attr)) { + return false; + } + } + + return true; + } + + public override bool Equals (object obj) + { + var attrSet = obj as LlvmIrFunctionAttributeSet; + if (attrSet == null) { + return false; + } + + return Equals (attrSet); + } + + public override int GetHashCode() + { + int hc = 0; + + foreach (LlvmIrFunctionAttribute attr in attributes) { + hc ^= attr?.GetHashCode () ?? 0; + } + + return hc; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs new file mode 100644 index 00000000000..5c1b03990ae --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR; + +/// +/// Abstract class from which all of the items (labels, function parameters, +/// local variables and instructions) derive. +/// +abstract class LlvmIrFunctionBodyItem +{ + /// + /// If an item has this property set to true, it won't be written to output when + /// code is generated. This is used for implicit items that don't need to be part of + /// the generated code (e.g. the starting block label) + /// + public bool SkipInOutput { get; protected set; } + public string? Comment { get; set; } + + public void Write (GeneratorWriteContext context, LlvmIrGenerator generator) + { + DoWrite (context, generator); + if (!String.IsNullOrEmpty (Comment)) { + context.Output.Write (' '); + generator.WriteComment (context, Comment); + } + context.Output.WriteLine (); + } + + protected abstract void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator); +} + +/// +/// Base class for function labels and local variables (including parameters), which +/// obtain automatic names derived from a shared counter, unless explicitly named. +/// +abstract class LlvmIrFunctionLocalItem : LlvmIrFunctionBodyItem +{ + string? name; + + public string Name { + get { + if (String.IsNullOrEmpty (name)) { + throw new InvalidOperationException ("Internal error: name hasn't been set yet"); + } + return name; + } + + protected set { + if (String.IsNullOrEmpty (value)) { + throw new InvalidOperationException ("Internal error: value must not be null or empty"); + } + name = value; + } + } + + protected LlvmIrFunctionLocalItem (string? name) + { + if (name != null) { + Name = name; + } + } + + protected LlvmIrFunctionLocalItem (LlvmIrFunction.FunctionState state, string? name) + { + if (name != null) { + if (name.Length == 0) { + throw new ArgumentException ("must not be an empty string", nameof (name)); + } + + Name = name; + return; + } + + SetName (state.NextTemporary ()); + } + + protected void SetName (ulong num) + { + Name = num.ToString (CultureInfo.InvariantCulture); + } + + protected bool NameIsSet () => !String.IsNullOrEmpty (name); +} + +class LlvmIrFunctionLabelItem : LlvmIrFunctionLocalItem +{ + /// + /// Labels are a bit peculiar in that they must not have their name set to the automatic value (based on + /// a counter shared with function parameters) at creation time, but only when they are actually added to + /// the function body. The reason is that LLVM IR requires all the unnamed temporaries (function parameters and + /// labels) to be named sequentially, but sometimes a label must be referenced before it is added to the instruction + /// stream, e.g. in the br instruction. On the other hand, it is perfectly fine to assign label a name that + /// isn't an integer at **instantiation** time, which is why we have the parameter here. + /// + public LlvmIrFunctionLabelItem (string? name = null) + : base (name) + {} + + public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.FunctionState state) + { + if (NameIsSet ()) { + return; + } + + SetName (state.NextTemporary ()); + } + + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) + { + context.DecreaseIndent (); + + context.Output.WriteLine (); + context.Output.Write (context.CurrentIndent); + context.Output.Write (Name); + context.Output.Write (':'); + + context.IncreaseIndent (); + } +} + +class LlvmIrFunctionBodyComment : LlvmIrFunctionBodyItem +{ + public string Text { get; } + + public LlvmIrFunctionBodyComment (string comment) + { + Text = comment; + } + + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) + { + context.Output.Write (context.CurrentIndent); + generator.WriteCommentLine (context, Text); + } +} + +class LlvmIrFunctionBody +{ + sealed class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem + { + public LlvmIrFunctionImplicitStartLabel (ulong num) + { + SetName (num); + SkipInOutput = true; + } + } + + List items; + HashSet definedLabels; + LlvmIrFunction ownerFunction; + LlvmIrFunction.FunctionState functionState; + LlvmIrFunctionLabelItem implicitStartBlock; + + LlvmIrFunctionLabelItem? precedingBlock1; + LlvmIrFunctionLabelItem? precedingBlock2; + LlvmIrFunctionLabelItem? previousLabel; + + public IList Items => items.AsReadOnly (); + public LlvmIrFunctionLabelItem? PrecedingBlock1 => precedingBlock1; + public LlvmIrFunctionLabelItem? PrecedingBlock2 => precedingBlock2; + + public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) + { + ownerFunction = func; + this.functionState = functionState; + definedLabels = new HashSet (StringComparer.Ordinal); + items = new List (); + previousLabel = implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); + } + + public void Add (LlvmIrFunctionLabelItem label) + { + label.WillAddToBody (this, functionState); + if (definedLabels.Contains (label.Name)) { + throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{ownerFunction.Signature.Name}' body"); + } + items.Add (label); + definedLabels.Add (label.Name); + + // Rotate preceding blocks + if (precedingBlock2 != null) { + precedingBlock2 = null; + } + + precedingBlock2 = precedingBlock1; + precedingBlock1 = previousLabel; + previousLabel = label; + + var comment = new StringBuilder (" preds = %"); + comment.Append (precedingBlock1.Name); + if (precedingBlock2 != null) { + comment.Append (", %"); + comment.Append (precedingBlock2.Name); + } + label.Comment = comment.ToString (); + } + + public void Add (LlvmIrFunctionBodyItem item) + { + items.Add (item); + } + + public void AddComment (string text) + { + Add (new LlvmIrFunctionBodyComment (text)); + } + + public LlvmIrInstructions.Alloca Alloca (LlvmIrVariable result) + { + var ret = new LlvmIrInstructions.Alloca (result); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Br Br (LlvmIrFunctionLabelItem label) + { + var ret = new LlvmIrInstructions.Br (label); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Br Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem ifTrue, LlvmIrFunctionLabelItem ifFalse) + { + var ret = new LlvmIrInstructions.Br (cond, ifTrue, ifFalse); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Call Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null, LlvmIrVariable? funcPointer = null) + { + var ret = new LlvmIrInstructions.Call (function, result, arguments, funcPointer); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Ext Ext (LlvmIrVariable source, Type targetType, LlvmIrVariable result) + { + var ret = new LlvmIrInstructions.Ext (source, targetType, result); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Icmp Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) + { + var ret = new LlvmIrInstructions.Icmp (cond, op1, op2, result); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Load Load (LlvmIrVariable source, LlvmIrVariable result, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Load (source, result) { + TBAA = tbaa, + }; + Add (ret); + return ret; + } + + /// + /// Creates the `phi` instruction form we use the most throughout marshal methods generator - one which refers to an if/else block and where + /// **both** value:label pairs are **required**. Parameters and are nullable because, in theory, + /// it is possible that hasn't had the required blocks defined prior to adding the `phi` instruction and, thus, + /// we must check for the possibility here. + /// + public LlvmIrInstructions.Phi Phi (LlvmIrVariable result, LlvmIrVariable val1, LlvmIrFunctionLabelItem? label1, LlvmIrVariable val2, LlvmIrFunctionLabelItem? label2) + { + var ret = new LlvmIrInstructions.Phi (result, val1, label1, val2, label2); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Ret Ret (Type retvalType, object? retval = null) + { + var ret = new LlvmIrInstructions.Ret (retvalType, retval); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Store Store (LlvmIrVariable from, LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Store (from, to) { + TBAA = tbaa, + }; + + Add (ret); + return ret; + } + + /// + /// Stores `null` in the indicated variable + /// + public LlvmIrInstructions.Store Store (LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Store (to) { + TBAA = tbaa, + }; + + Add (ret); + return ret; + } + + public LlvmIrInstructions.Unreachable Unreachable () + { + var ret = new LlvmIrInstructions.Unreachable (); + + Add (ret); + return ret; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs deleted file mode 100644 index 94f90dae375..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ /dev/null @@ -1,552 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - abstract partial class LlvmIrGenerator - { - // In code generated by clang, function attributes are determined based on the compiler optimization, - // security arguments, architecture specific flags and so on. For our needs we will have but a - // handful of such sets, based on what clang generates for our native runtime. As such, there is nothing - // "smart" about how we select the attributes, they must match the compiler output for XA runtime, that's all. - // - // Sets are initialized here with the options common to all architectures, the rest is added in the architecture - // specific derived classes. - // - public const int FunctionAttributesXamarinAppInit = 0; - public const int FunctionAttributesJniMethods = 1; - public const int FunctionAttributesCall = 2; - - protected readonly Dictionary FunctionAttributes = new Dictionary (); - - bool codeOutputInitialized = false; - - /// - /// Writes the function definition up to the opening curly brace - /// - public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - LlvmFunctionAttributeSet? attributes = null; - if (function.AttributeSetID >= 0 && !FunctionAttributes.TryGetValue (function.AttributeSetID, out attributes)) { - throw new InvalidOperationException ($"Function '{function.Name}' refers to attribute set that does not exist (ID: {function.AttributeSetID})"); - } - - Output.WriteLine (); - if (!String.IsNullOrEmpty (comment)) { - foreach (string line in comment.Split ('\n')) { - WriteCommentLine (line); - } - } - - if (attributes != null) { - WriteCommentLine ($"Function attributes: {attributes.Render ()}"); - } - - Output.Write ($"define {GetKnownIRType (function.ReturnType)} @{function.Name} ("); - WriteFunctionParameters (function.Parameters, writeNames: true); - Output.Write(") local_unnamed_addr "); - if (attributes != null) { - Output.Write ($"#{function.AttributeSetID.ToString (CultureInfo.InvariantCulture)}"); - } - Output.WriteLine (); - Output.WriteLine ("{"); - } - - void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null) - { - if (variable.NativeFunction != null) { - if (builder == null) { - WriteFunctionSignature (variable.NativeFunction); - } else { - builder.Append (RenderFunctionSignature (variable.NativeFunction)); - } - return; - } - - string extraPointer = variable.IsNativePointer ? "*" : String.Empty; - string irType = $"{GetKnownIRType (variable.Type)}{extraPointer}"; - if (builder == null) { - Output.Write (irType); - } else { - builder.Append (irType); - } - } - - void WriteFunctionParameters (IList? parameters, bool writeNames) - { - string rendered = RenderFunctionParameters (parameters, writeNames); - if (String.IsNullOrEmpty (rendered)) { - return; - } - - Output.Write (rendered); - } - - public string RenderFunctionParameters (IList? parameters, bool writeNames) - { - if (parameters == null || parameters.Count == 0) { - return String.Empty; - } - - var sb = new StringBuilder (); - bool first = true; - foreach (LlvmIrFunctionParameter p in parameters) { - if (!first) { - sb.Append (", "); - } else { - first = false; - } - - CodeRenderType (p, sb); - - if (writeNames) { - sb.Append ($" %{p.Name}"); - } - } - - return sb.ToString (); - } - - public void WriteFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) - { - Output.Write (RenderFunctionSignature (sig, isPointer)); - } - - public string RenderFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) - { - if (sig == null) { - throw new ArgumentNullException (nameof (sig)); - } - - var sb = new StringBuilder (); - sb.Append (GetKnownIRType (sig.ReturnType)); - sb.Append (" ("); - sb.Append (RenderFunctionParameters (sig.Parameters, writeNames: false)); - sb.Append (")"); - if (isPointer) { - sb.Append ('*'); - } - - return sb.ToString (); - } - - /// - /// Writes the epilogue of a function, including the return statement if the function return - /// type is void. - /// - public void WriteFunctionEnd (LlvmIrFunction function) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (function.ReturnType == typeof (void)) { - EmitReturnInstruction (function); - } - - Output.WriteLine ("}"); - } - - /// - /// Emits the ret statement using as the returned value. If - /// is null, void is used as the return value. - /// - public void EmitReturnInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable? retVar = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - string ret = retVar != null ? $"{GetKnownIRType(retVar.Type)} %{retVar.Name}" : "void"; - Output.WriteLine ($"{function.Indent}ret {ret}"); - } - - /// - /// Emits the store instruction (https://llvm.org/docs/LangRef.html#store-instruction), which stores data from a local - /// variable into either local or global destination. If types of and - /// differ, is bitcast to the type of . It is responsibility of the - /// caller to make sure the two types are compatible and/or convertible to each other. - /// - public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable source, LlvmIrVariableReference destination) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - // TODO: implement bitcast, if necessary - Output.Write ($"{function.Indent}store "); - CodeRenderType (source); - Output.Write ($" %{source.Name}, "); - CodeRenderType (destination); - Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); - } - - /// - /// Emits the load instruction (https://llvm.org/docs/LangRef.html#load-instruction) - /// - public LlvmIrFunctionLocalVariable EmitLoadInstruction (LlvmIrFunction function, LlvmIrVariableReference source, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - var sb = new StringBuilder (); - CodeRenderType (source, sb); - - string variableType = sb.ToString (); - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (source, resultVariableName); - Output.WriteLine ($"{function.Indent}%{result.Name} = load {variableType}, {variableType}* @{source.Name}, align {PointerSize.ToString (CultureInfo.InvariantCulture)}"); - - return result; - } - - /// - /// Emits the icmp comparison instruction (https://llvm.org/docs/LangRef.html#icmp-instruction) - /// - public LlvmIrFunctionLocalVariable EmitIcmpInstruction (LlvmIrFunction function, LlvmIrIcmpCond cond, LlvmIrVariableReference variable, string expectedValue, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - string condOp; - switch (cond) { - case LlvmIrIcmpCond.Equal: // equal - condOp = "eq"; - break; - - case LlvmIrIcmpCond.NotEqual: // not equal - condOp = "ne"; - break; - - case LlvmIrIcmpCond.UnsignedGreaterThan: // unsigned greater than - condOp = "ugt"; - break; - - case LlvmIrIcmpCond.UnsignedGreaterOrEqual: // unsigned greater or equal - condOp = "uge"; - break; - - case LlvmIrIcmpCond.UnsignedLessThan: // unsigned less than - condOp = "ult"; - break; - - case LlvmIrIcmpCond.UnsignedLessOrEqual: // unsigned less or equal - condOp = "ule"; - break; - - case LlvmIrIcmpCond.SignedGreaterThan: // signed greater than, - condOp = "sgt"; - break; - - case LlvmIrIcmpCond.SignedGreaterOrEqual: // signed greater or equal - condOp = "sge"; - break; - - case LlvmIrIcmpCond.SignedLessThan: // signed less than - condOp = "slt"; - break; - - case LlvmIrIcmpCond.SignedLessOrEqual: // signed less or equal - condOp = "sle"; - break; - - default: - throw new InvalidOperationException ($"Unsupported `icmp` conditional '{cond}'"); - } - - var sb = new StringBuilder (); - CodeRenderType (variable, sb); - - string variableType = sb.ToString (); - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (variable.Type, resultVariableName); - - Output.WriteLine ($"{function.Indent}%{result.Name} = icmp {condOp} {variableType} {variable.Reference}, {expectedValue}"); - - return result; - } - - public void EmitBrInstruction (LlvmIrFunction function, LlvmIrVariableReference condVariable, string labelTrue, string labelFalse) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - Output.WriteLine ($"{function.Indent}br i1 {condVariable.Reference}, label %{labelTrue}, label %{labelFalse}"); - } - - public void EmitBrInstruction (LlvmIrFunction function, string label) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - Output.WriteLine ($"{function.Indent}br label %{label}"); - } - - public void EmitLabel (LlvmIrFunction function, string labelName) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - Output.WriteLine ($"{labelName}:"); - } - - public LlvmIrFunctionLocalVariable? EmitCall (LlvmIrFunction function, LlvmIrVariableReference targetRef, List? arguments = null, - string? resultVariableName = null, LlvmIrCallMarker marker = LlvmIrCallMarker.Tail, int AttributeSetID = FunctionAttributesCall) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (targetRef == null) { - throw new ArgumentNullException (nameof (targetRef)); - } - - LlvmNativeFunctionSignature targetSignature = targetRef.NativeFunction; - if (targetSignature == null) { - throw new ArgumentException ("must be reference to native function", nameof (targetRef)); - } - - if (targetSignature.Parameters.Count > 0) { - if (arguments == null) { - throw new ArgumentNullException (nameof (arguments)); - } - - if (targetSignature.Parameters.Count != arguments.Count) { - throw new ArgumentException ($"number of passed parameters ({arguments.Count}) does not match number of parameters in function signature ({targetSignature.Parameters.Count})", nameof (arguments)); - } - } - - bool returnsValue = targetSignature.ReturnType != typeof(void); - LlvmIrFunctionLocalVariable? result = null; - - Output.Write (function.Indent); - if (returnsValue) { - result = function.MakeLocalVariable (targetSignature.ReturnType, resultVariableName); - Output.Write ($"%{result.Name} = "); - } - - switch (marker) { - case LlvmIrCallMarker.Tail: - Output.Write ("tail "); - break; - - case LlvmIrCallMarker.MustTail: - Output.Write ("musttail "); - break; - - case LlvmIrCallMarker.NoTail: - Output.Write ("notail "); - break; - - case LlvmIrCallMarker.None: - break; - - default: - throw new InvalidOperationException ($"Unsupported call marker '{marker}'"); - } - - Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} {targetRef.Reference} ("); - - if (targetSignature.Parameters.Count > 0) { - for (int i = 0; i < targetSignature.Parameters.Count; i++) { - LlvmIrFunctionParameter parameter = targetSignature.Parameters[i]; - LlvmIrFunctionArgument argument = arguments[i]; - - AssertValidType (i, parameter, argument); - - if (i > 0) { - Output.Write (", "); - } - - string extra = parameter.IsNativePointer ? "*" : String.Empty; - string paramType = $"{GetKnownIRType (parameter.Type)}{extra}"; - Output.Write ($"{paramType} "); - - if (argument.Value is LlvmIrFunctionLocalVariable variable) { - Output.Write ($"%{variable.Name}"); - } else if (parameter.Type.IsNativePointer () || parameter.IsNativePointer) { - if (parameter.IsCplusPlusReference) { - Output.Write ("nonnull "); - } - - string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); - Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) "); - - if (argument.Value is LlvmIrVariableReference variableRef) { - bool needBitcast = parameter.Type != argument.Type; - - if (needBitcast) { - Output.Write ("bitcast ("); - CodeRenderType (variableRef); - Output.Write ("* "); - } - - Output.Write (variableRef.Reference); - - if (needBitcast) { - Output.Write ($" to {paramType})"); - } - } else { - throw new InvalidOperationException ($"Unexpected pointer type in argument {i}, '{argument.Type}'"); - } - } else { - Output.Write (argument.Value.ToString ()); - } - } - } - - Output.Write (")"); - - if (AttributeSetID >= 0) { - if (!FunctionAttributes.ContainsKey (AttributeSetID)) { - throw new InvalidOperationException ($"Unknown attribute set ID {AttributeSetID}"); - } - Output.Write ($" #{AttributeSetID.ToString (CultureInfo.InvariantCulture)}"); - } - Output.WriteLine (); - - return result; - - static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) - { - if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { - return; - } - - if (parameter.Type != typeof(IntPtr)) { - if (argument.Type != parameter.Type) { - ThrowException (); - } - return; - } - - if (argument.Type.IsNativePointer ()) { - return; - } - - if (typeof(LlvmIrVariable).IsAssignableFrom (argument.Type) && - argument.Value is LlvmIrVariable variable && - (variable.IsNativePointer || variable.NativeFunction != null)) { - return; - } - - ThrowException (); - - void ThrowException () - { - throw new InvalidOperationException ($"Argument {index} type '{argument.Type}' does not match the expected function parameter type '{parameter.Type}'"); - } - } - } - - /// - /// Emits the phi instruction (https://llvm.org/docs/LangRef.html#phi-instruction) for a function pointer type - /// - public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrVariableReference target, List<(LlvmIrVariableReference variableRef, string label)> pairs, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (target, resultVariableName); - Output.Write ($"{function.Indent}%{result.Name} = phi "); - CodeRenderType (target); - - bool first = true; - foreach ((LlvmIrVariableReference variableRef, string label) in pairs) { - if (first) { - first = false; - Output.Write (' '); - } else { - Output.Write (", "); - } - - Output.Write ($"[{variableRef.Reference}, %{label}]"); - } - Output.WriteLine (); - - return result; - } - - public void InitCodeOutput () - { - if (codeOutputInitialized) { - return; - } - - InitFunctionAttributes (); - InitCodeMetadata (); - codeOutputInitialized = true; - } - - protected virtual void InitCodeMetadata () - { - MetadataManager.Add ("llvm.linker.options"); - } - - protected virtual void InitFunctionAttributes () - { - FunctionAttributes[FunctionAttributesXamarinAppInit] = new LlvmFunctionAttributeSet { - new MinLegalVectorWidthFunctionAttribute (0), - new MustprogressFunctionAttribute (), - new NofreeFunctionAttribute (), - new NorecurseFunctionAttribute (), - new NosyncFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new NounwindFunctionAttribute (), - new SspstrongFunctionAttribute (), - new StackProtectorBufferSizeFunctionAttribute (8), - new UwtableFunctionAttribute (), - new WillreturnFunctionAttribute (), - new WriteonlyFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesJniMethods] = new LlvmFunctionAttributeSet { - new MinLegalVectorWidthFunctionAttribute (0), - new MustprogressFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new NounwindFunctionAttribute (), - new SspstrongFunctionAttribute (), - new StackProtectorBufferSizeFunctionAttribute (8), - new UwtableFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesCall] = new LlvmFunctionAttributeSet { - new NounwindFunctionAttribute (), - }; - } - - void WriteAttributeSets () - { - if (!codeOutputInitialized) { - return; - } - - WriteSet (FunctionAttributesXamarinAppInit, Output); - WriteSet (FunctionAttributesJniMethods, Output); - WriteSet (FunctionAttributesCall, Output); - - Output.WriteLine (); - - void WriteSet (int id, TextWriter output) - { - output.Write ($"attributes #{id.ToString (CultureInfo.InvariantCulture)} = {{ "); - foreach (LLVMFunctionAttribute attr in FunctionAttributes[id]) { - output.Write (attr.Render ()); - output.Write (' '); - } - output.WriteLine ("}"); - } - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs new file mode 100644 index 00000000000..9600dbc81e1 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + partial class LlvmIrGenerator + { + // https://llvm.org/docs/LangRef.html#linkage-types + static readonly Dictionary llvmLinkage = new Dictionary { + { LlvmIrLinkage.Default, String.Empty }, + { LlvmIrLinkage.Private, "private" }, + { LlvmIrLinkage.Internal, "internal" }, + { LlvmIrLinkage.AvailableExternally, "available_externally" }, + { LlvmIrLinkage.LinkOnce, "linkonce" }, + { LlvmIrLinkage.Weak, "weak" }, + { LlvmIrLinkage.Common, "common" }, + { LlvmIrLinkage.Appending, "appending" }, + { LlvmIrLinkage.ExternWeak, "extern_weak" }, + { LlvmIrLinkage.LinkOnceODR, "linkonce_odr" }, + { LlvmIrLinkage.External, "external" }, + }; + + // https://llvm.org/docs/LangRef.html#runtime-preemption-specifiers + static readonly Dictionary llvmRuntimePreemption = new Dictionary { + { LlvmIrRuntimePreemption.Default, String.Empty }, + { LlvmIrRuntimePreemption.DSOPreemptable, "dso_preemptable" }, + { LlvmIrRuntimePreemption.DSOLocal, "dso_local" }, + }; + + // https://llvm.org/docs/LangRef.html#visibility-styles + static readonly Dictionary llvmVisibility = new Dictionary { + { LlvmIrVisibility.Default, "default" }, + { LlvmIrVisibility.Hidden, "hidden" }, + { LlvmIrVisibility.Protected, "protected" }, + }; + + // https://llvm.org/docs/LangRef.html#global-variables + static readonly Dictionary llvmAddressSignificance = new Dictionary { + { LlvmIrAddressSignificance.Default, String.Empty }, + { LlvmIrAddressSignificance.Unnamed, "unnamed_addr" }, + { LlvmIrAddressSignificance.LocalUnnamed, "local_unnamed_addr" }, + }; + + // https://llvm.org/docs/LangRef.html#global-variables + static readonly Dictionary llvmWritability = new Dictionary { + { LlvmIrWritability.Constant, "constant" }, + { LlvmIrWritability.Writable, "global" }, + }; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 469133189d6..bb81b03db0e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -9,1565 +10,1303 @@ namespace Xamarin.Android.Tasks.LLVMIR { - /// - /// Base class for all classes which implement architecture-specific code generators. - /// - abstract partial class LlvmIrGenerator + sealed class GeneratorStructureInstance : StructureInstance { - internal sealed class StructureBodyWriterOptions - { - public readonly bool WriteFieldComment; - public readonly string FieldIndent; - public readonly string StructIndent; - public readonly TextWriter? StructureOutput; - public readonly TextWriter? StringsOutput; - public readonly TextWriter? BuffersOutput; + public GeneratorStructureInstance (StructureInfo info, object instance) + : base (info, instance) + {} + } - public StructureBodyWriterOptions (bool writeFieldComment, string fieldIndent = "", string structIndent = "", - TextWriter? structureOutput = null, TextWriter? stringsOutput = null, TextWriter? buffersOutput = null) - { - WriteFieldComment = writeFieldComment; - FieldIndent = fieldIndent; - StructIndent = structIndent; - StructureOutput = structureOutput; - StringsOutput = stringsOutput; - BuffersOutput = buffersOutput; - } + sealed class GeneratorWriteContext + { + const char IndentChar = '\t'; + + int currentIndentLevel = 0; + + public readonly TextWriter Output; + public readonly LlvmIrModule Module; + public readonly LlvmIrModuleTarget Target; + public readonly LlvmIrMetadataManager MetadataManager; + public string CurrentIndent { get; private set; } = String.Empty; + public bool InVariableGroup { get; set; } + + public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager) + { + Output = writer; + Module = module; + Target = target; + MetadataManager = metadataManager; } - sealed class PackedStructureMember + public void IncreaseIndent () { - public readonly string ValueIRType; - public readonly string? PaddingIRType; - public readonly object? Value; - public readonly bool IsPadded; - public readonly StructureMemberInfo MemberInfo; + currentIndentLevel++; + CurrentIndent = MakeIndentString (); + } - public PackedStructureMember (StructureMemberInfo memberInfo, object? value, string? valueIRType = null, string? paddingIRType = null) - { - ValueIRType = valueIRType ?? memberInfo.IRType; - Value = value; - MemberInfo = memberInfo; - PaddingIRType = paddingIRType; - IsPadded = !String.IsNullOrEmpty (paddingIRType); + public void DecreaseIndent () + { + if (currentIndentLevel > 0) { + currentIndentLevel--; } + CurrentIndent = MakeIndentString (); } - public sealed class StringSymbolInfo + string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; + } + + partial class LlvmIrGenerator + { + sealed class LlvmTypeInfo { - public readonly string SymbolName; + public readonly bool IsPointer; + public readonly bool IsAggregate; + public readonly bool IsStructure; public readonly ulong Size; + public readonly ulong MaxFieldAlignment; - public StringSymbolInfo (string symbolName, ulong size) + public LlvmTypeInfo (bool isPointer, bool isAggregate, bool isStructure, ulong size, ulong maxFieldAlignment) { - SymbolName = symbolName; + IsPointer = isPointer; + IsAggregate = isAggregate; + IsStructure = isStructure; Size = size; + MaxFieldAlignment = maxFieldAlignment; } } - static readonly Dictionary typeMap = new Dictionary { - { typeof (bool), "i8" }, - { typeof (byte), "i8" }, - { typeof (char), "i8" }, - { typeof (sbyte), "i8" }, - { typeof (short), "i16" }, - { typeof (ushort), "i16" }, - { typeof (int), "i32" }, - { typeof (uint), "i32" }, - { typeof (long), "i64" }, - { typeof (ulong), "i64" }, - { typeof (float), "float" }, - { typeof (double), "double" }, - { typeof (string), "i8*" }, - { typeof (IntPtr), "i8*" }, - { typeof (void), "void" }, - }; - - // https://llvm.org/docs/LangRef.html#single-value-types - static readonly Dictionary typeSizes = new Dictionary { - { typeof (bool), 1 }, - { typeof (byte), 1 }, - { typeof (char), 1 }, - { typeof (sbyte), 1 }, - { typeof (short), 2 }, - { typeof (ushort), 2 }, - { typeof (int), 4 }, - { typeof (uint), 4 }, - { typeof (long), 8 }, - { typeof (ulong), 8 }, - { typeof (float), 4 }, // floats are 32-bit - { typeof (double), 8 }, // doubles are 64-bit - }; - - // https://llvm.org/docs/LangRef.html#linkage-types - static readonly Dictionary llvmLinkage = new Dictionary { - { LlvmIrLinkage.Default, String.Empty }, - { LlvmIrLinkage.Private, "private" }, - { LlvmIrLinkage.Internal, "internal" }, - { LlvmIrLinkage.AvailableExternally, "available_externally" }, - { LlvmIrLinkage.LinkOnce, "linkonce" }, - { LlvmIrLinkage.Weak, "weak" }, - { LlvmIrLinkage.Common, "common" }, - { LlvmIrLinkage.Appending, "appending" }, - { LlvmIrLinkage.ExternWeak, "extern_weak" }, - { LlvmIrLinkage.LinkOnceODR, "linkonce_odr" }, - { LlvmIrLinkage.External, "external" }, - }; - - // https://llvm.org/docs/LangRef.html#runtime-preemption-specifiers - static readonly Dictionary llvmRuntimePreemption = new Dictionary { - { LlvmIrRuntimePreemption.Default, String.Empty }, - { LlvmIrRuntimePreemption.DSOPreemptable, "dso_preemptable" }, - { LlvmIrRuntimePreemption.DSOLocal, "dso_local" }, - }; - - // https://llvm.org/docs/LangRef.html#visibility-styles - static readonly Dictionary llvmVisibility = new Dictionary { - { LlvmIrVisibility.Default, "default" }, - { LlvmIrVisibility.Hidden, "hidden" }, - { LlvmIrVisibility.Protected, "protected" }, - }; - - // https://llvm.org/docs/LangRef.html#global-variables - static readonly Dictionary llvmAddressSignificance = new Dictionary { - { LlvmIrAddressSignificance.Default, String.Empty }, - { LlvmIrAddressSignificance.Unnamed, "unnamed_addr" }, - { LlvmIrAddressSignificance.LocalUnnamed, "local_unnamed_addr" }, - }; - - // https://llvm.org/docs/LangRef.html#global-variables - static readonly Dictionary llvmWritability = new Dictionary { - { LlvmIrWritability.Constant, "constant" }, - { LlvmIrWritability.Writable, "global" }, - }; + sealed class BasicType + { + public readonly string Name; + public readonly ulong Size; + public readonly bool IsNumeric; - static readonly LlvmIrVariableOptions preAllocatedBufferVariableOptions = new LlvmIrVariableOptions { - Writability = LlvmIrWritability.Writable, - Linkage = LlvmIrLinkage.Internal, + public BasicType (string name, ulong size, bool isNumeric = true) + { + Name = name; + Size = size; + IsNumeric = isNumeric; + } + } + + public const string IRPointerType = "ptr"; + + static readonly Dictionary basicTypeMap = new Dictionary { + { typeof (bool), new ("i8", 1, isNumeric: false) }, + { typeof (byte), new ("i8", 1) }, + { typeof (char), new ("i16", 2) }, + { typeof (sbyte), new ("i8", 1) }, + { typeof (short), new ("i16", 2) }, + { typeof (ushort), new ("i16", 2) }, + { typeof (int), new ("i32", 4) }, + { typeof (uint), new ("i32", 4) }, + { typeof (long), new ("i64", 8) }, + { typeof (ulong), new ("i64", 8) }, + { typeof (float), new ("float", 4) }, + { typeof (double), new ("double", 8) }, + { typeof (void), new ("void", 0, isNumeric: false) }, }; - string fileName; - ulong stringCounter = 0; - ulong structStringCounter = 0; - ulong structBufferCounter = 0; - - List structures = new List (); - Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); - LlvmIrMetadataItem llvmModuleFlags; - - public const string Indent = "\t"; + public string FilePath { get; } + public string FileName { get; } - protected abstract string DataLayout { get; } - public abstract int PointerSize { get; } - protected abstract string Triple { get; } + LlvmIrModuleTarget target; - public bool Is64Bit { get; } - public TextWriter Output { get; } - public AndroidTargetArch TargetArch { get; } - - protected LlvmIrMetadataManager MetadataManager { get; } - - protected LlvmIrGenerator (AndroidTargetArch arch, TextWriter output, string fileName) + protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) { - Output = output; - MetadataManager = new LlvmIrMetadataManager (); - TargetArch = arch; - Is64Bit = arch == AndroidTargetArch.X86_64 || arch == AndroidTargetArch.Arm64; - this.fileName = fileName; + FilePath = Path.GetFullPath (filePath); + FileName = Path.GetFileName (filePath); + this.target = target; } - /// - /// Create architecture-specific generator for the given . Contents are written - // to the stream and is used mostly for error - // reporting. - /// - public static LlvmIrGenerator Create (AndroidTargetArch arch, StreamWriter output, string fileName) + public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) { - LlvmIrGenerator ret = Instantiate (); - ret.Init (); - return ret; - - LlvmIrGenerator Instantiate () - { - return arch switch { - AndroidTargetArch.Arm => new Arm32LlvmIrGenerator (arch, output, fileName), - AndroidTargetArch.Arm64 => new Arm64LlvmIrGenerator (arch, output, fileName), - AndroidTargetArch.X86 => new X86LlvmIrGenerator (arch, output, fileName), - AndroidTargetArch.X86_64 => new X64LlvmIrGenerator (arch, output, fileName), - _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") - }; - } + return arch switch { + AndroidTargetArch.Arm => new LlvmIrGenerator (fileName, new LlvmIrModuleArmV7a ()), + AndroidTargetArch.Arm64 => new LlvmIrGenerator (fileName, new LlvmIrModuleAArch64 ()), + AndroidTargetArch.X86 => new LlvmIrGenerator (fileName, new LlvmIrModuleX86 ()), + AndroidTargetArch.X86_64 => new LlvmIrGenerator (fileName, new LlvmIrModuleX64 ()), + _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") + }; } - static string EnsureIrType (Type type) + public void Generate (TextWriter writer, LlvmIrModule module) { - if (!typeMap.TryGetValue (type, out string irType)) { - throw new InvalidOperationException ($"Unsupported managed type {type}"); - } + LlvmIrMetadataManager metadataManager = module.GetMetadataManagerCopy (); + target.AddTargetSpecificMetadata (metadataManager); - return irType; - } - - static Type GetActualType (Type type) - { - // Arrays of types are handled elsewhere, so we obtain the array base type here - if (type.IsArray) { - return type.GetElementType (); + var context = new GeneratorWriteContext (writer, module, target, metadataManager); + if (!String.IsNullOrEmpty (FilePath)) { + WriteCommentLine (context, $" ModuleID = '{FileName}'"); + context.Output.WriteLine ($"source_filename = \"{FileName}\""); } - return type; - } - - /// - /// Map managed to its LLVM IR counterpart. Only primitive types, - /// string and IntPtr are supported. - /// - public static string MapManagedTypeToIR (Type type) - { - return EnsureIrType (GetActualType (type)); - } - - /// - /// Map managed type to its LLVM IR counterpart. Only primitive types, string and - /// IntPtr are supported. Additionally, return the native type size (in bytes) in - /// - /// - public string MapManagedTypeToIR (Type type, out ulong size) - { - Type actualType = GetActualType (type); - string irType = EnsureIrType (actualType); - size = GetTypeSize (actualType); + context.Output.WriteLine (target.DataLayout.Render ()); + context.Output.WriteLine ($"target triple = \"{target.Triple}\""); + WriteStructureDeclarations (context); + WriteGlobalVariables (context); + WriteFunctions (context); - return irType; + // Bottom of the file + WriteStrings (context); + WriteExternalFunctionDeclarations (context); + WriteAttributeSets (context); + WriteMetadata (context); } - ulong GetTypeSize (Type actualType) + void WriteStrings (GeneratorWriteContext context) { - if (!typeSizes.TryGetValue (actualType, out ulong size)) { - if (actualType == typeof (string) || actualType == typeof (IntPtr) || actualType == typeof (LlvmNativeFunctionSignature)) { - size = (ulong)PointerSize; - } else { - throw new InvalidOperationException ($"Unsupported managed type {actualType}"); - } + if (context.Module.Strings == null || context.Module.Strings.Count == 0) { + return; } - return size; - } - - /// - /// Map managed type to its LLVM IR counterpart. Only primitive types, - /// string and IntPtr are supported. Additionally, return the native type size - /// (in bytes) in - /// - public string MapManagedTypeToIR (out ulong size) - { - return MapManagedTypeToIR (typeof(T), out size); - } + context.Output.WriteLine (); + WriteComment (context, " Strings"); - /// - /// Map a managed to its C++ counterpart. Only primitive types, - /// string and IntPtr are supported. - /// - public static string MapManagedTypeToNative (Type type) - { - Type baseType = GetActualType (type); + foreach (LlvmIrStringGroup group in context.Module.Strings) { + context.Output.WriteLine (); - if (baseType == typeof (bool)) return "bool"; - if (baseType == typeof (byte)) return "uint8_t"; - if (baseType == typeof (char)) return "char"; - if (baseType == typeof (sbyte)) return "int8_t"; - if (baseType == typeof (short)) return "int16_t"; - if (baseType == typeof (ushort)) return "uint16_t"; - if (baseType == typeof (int)) return "int32_t"; - if (baseType == typeof (uint)) return "uint32_t"; - if (baseType == typeof (long)) return "int64_t"; - if (baseType == typeof (ulong)) return "uint64_t"; - if (baseType == typeof (float)) return "float"; - if (baseType == typeof (double)) return "double"; - if (baseType == typeof (string)) return "char*"; - if (baseType == typeof (IntPtr)) return "void*"; + if (!String.IsNullOrEmpty (group.Comment)) { + WriteCommentLine (context, group.Comment); + } - return type.GetShortName (); - } + foreach (LlvmIrStringVariable info in group.Strings) { + string s = QuoteString ((string)info.Value, out ulong size); - public string GetIRType (out ulong size, T? value = default) - { - if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { - if (value == null) { - throw new ArgumentNullException (nameof (value)); + WriteGlobalVariableStart (context, info); + context.Output.Write ('['); + context.Output.Write (size.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x i8] c"); + context.Output.Write (s); + context.Output.Write (", align "); + context.Output.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); } - - size = (ulong)PointerSize; - return RenderFunctionSignature ((LlvmNativeFunctionSignature)(object)value); } - - return MapManagedTypeToIR (out size); } - public string GetKnownIRType (Type type) + void WriteGlobalVariables (GeneratorWriteContext context) { - if (type == null) { - throw new ArgumentNullException (nameof (type)); - } - - if (type.IsNativeClass ()) { - IStructureInfo si = GetStructureInfo (type); - return $"%{si.NativeTypeDesignator}.{si.Name}"; + if (context.Module.GlobalVariables == null || context.Module.GlobalVariables.Count == 0) { + return; } - return MapManagedTypeToIR (type); - } + foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { + if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { + if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) { + context.Output.WriteLine (); + context.Output.Write (context.CurrentIndent); + WriteComment (context, groupDelimiter.Comment); + } - public string GetValue (T value) - { - if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { - if (value == null) { - throw new ArgumentNullException (nameof (value)); + context.InVariableGroup = !context.InVariableGroup; + if (context.InVariableGroup) { + context.Output.WriteLine (); + } + continue; } - var v = (LlvmNativeFunctionSignature)(object)value; - if (v.FieldValue != null) { - return MonoAndroidHelper.CultureInvariantToString (v.FieldValue); + if (gv.BeforeWriteCallback != null) { + gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); } + WriteGlobalVariable (context, gv); + } + } - return MonoAndroidHelper.CultureInvariantToString (v); + void WriteGlobalVariableStart (GeneratorWriteContext context, LlvmIrGlobalVariable variable) + { + if (!String.IsNullOrEmpty (variable.Comment)) { + WriteCommentLine (context, variable.Comment); } + context.Output.Write ('@'); + context.Output.Write (variable.Name); + context.Output.Write (" = "); - return MonoAndroidHelper.CultureInvariantToString (value) ?? String.Empty; + LlvmIrVariableOptions options = variable.Options ?? LlvmIrGlobalVariable.DefaultOptions; + WriteLinkage (context, options.Linkage); + WritePreemptionSpecifier (context, options.RuntimePreemption); + WriteVisibility (context, options.Visibility); + WriteAddressSignificance (context, options.AddressSignificance); + WriteWritability (context, options.Writability); } - /// - /// Initialize the generator. It involves adding required LLVM IR module metadata (such as data model specification, - /// code generation flags etc) - /// - protected virtual void Init () + void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable variable) { - llvmModuleFlags = MetadataManager.Add ("llvm.module.flags"); - LlvmIrMetadataItem ident = MetadataManager.Add ("llvm.ident"); + if (!context.InVariableGroup) { + context.Output.WriteLine (); + } - var flagsFields = new List (); - AddModuleFlagsMetadata (flagsFields); + WriteGlobalVariableStart (context, variable); + WriteTypeAndValue (context, variable, out LlvmTypeInfo typeInfo); + context.Output.Write (", align "); - foreach (LlvmIrMetadataItem item in flagsFields) { - llvmModuleFlags.AddReferenceField (item.Name); + ulong alignment; + if (typeInfo.IsAggregate) { + ulong count = GetAggregateValueElementCount (variable); + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); + } else if (typeInfo.IsStructure) { + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); + } else if (typeInfo.IsPointer) { + alignment = target.NativePointerSize; + } else { + alignment = typeInfo.Size; } - LlvmIrMetadataItem identValue = MetadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); - ident.AddReferenceField (identValue.Name); + context.Output.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); } - protected void AddLlvmModuleFlag (LlvmIrMetadataItem flag) + void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - llvmModuleFlags.AddReferenceField (flag.Name); - } + WriteType (context, variable, out typeInfo); + context.Output.Write (' '); - /// - /// Since LLVM IR is strongly typed, it requires each structure to be properly declared before it is - /// used throughout the code. This method uses reflection to scan the managed type - /// and record the information for future use. The returned structure contains - /// the description. It is used later on not only to declare the structure in output code, but also to generate - /// data from instances of . This method is typically called from the - /// method. - /// - public StructureInfo MapStructure () - { - Type t = typeof(T); - if (!t.IsClass && !t.IsValueType) { - throw new InvalidOperationException ($"{t} must be a class or a struct"); + Type valueType; + if (variable.Value is LlvmIrVariable referencedVariable) { + valueType = referencedVariable.Type; + } else { + valueType = variable.Value?.GetType () ?? variable.Type; } - var ret = new StructureInfo (this); - structures.Add (ret); - - return ret; - } - - internal IStructureInfo GetStructureInfo (Type type) - { - IStructureInfo? ret = null; + if (variable.Value == null) { + // Order of checks is important here. Aggregates can contain pointer types, in which case typeInfo.IsPointer + // will be `true` and the aggregate would be incorrectly initialized with `null` instead of the correct + // `zeroinitializer` + if (typeInfo.IsAggregate) { + WriteValue (context, valueType, variable); + return; + } - foreach (IStructureInfo si in structures) { - if (si.Type != type) { - continue; + if (typeInfo.IsPointer) { + context.Output.Write ("null"); + return; } - ret = si; - break; + throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); } - if (ret == null) { - throw new InvalidOperationException ($"Unmapped structure {type}"); + if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { + throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); } - return ret; + WriteValue (context, valueType, variable); } - TextWriter EnsureOutput (TextWriter? output) - { - return output ?? Output; - } + ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable); - void WriteGlobalSymbolStart (string symbolName, LlvmIrVariableOptions options, TextWriter? output = null) + ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) { - output = EnsureOutput (output); - - output.Write ('@'); - output.Write (symbolName); - output.Write (" = "); - - string linkage = llvmLinkage [options.Linkage]; - if (!string.IsNullOrEmpty (linkage)) { - output.Write (linkage); - output.Write (' '); + if (!type.IsArray ()) { + throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); } - if (options.AddressSignificance != LlvmIrAddressSignificance.Default) { - output.Write (llvmAddressSignificance[options.AddressSignificance]); - output.Write (' '); - } - - output.Write (llvmWritability[options.Writability]); - output.Write (' '); - } - object? GetTypedMemberValue (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) - { - object? value = smi.GetValue (instance.Obj); if (value == null) { - return defaultValue; + if (globalVariable != null) { + return globalVariable.ArrayItemCount; + } + return 0; } - if (value.GetType () != expectedType) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should have a value of '{expectedType}' type, instead it had a '{value.GetType ()}'"); + // TODO: use caching here + if (type.ImplementsInterface (typeof(IDictionary))) { + return (uint)((IDictionary)value).Count * 2; } - if (expectedType == typeof(bool)) { - return (bool)value ? 1 : 0; + if (type.ImplementsInterface (typeof(ICollection))) { + return (uint)((ICollection)value).Count; } - return value; + throw new InvalidOperationException ($"Internal error: should never get here"); } - bool MaybeWriteStructureString (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + void WriteType (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - if (smi.MemberType != typeof(string)) { - return false; - } - - output = EnsureOutput (output); - string? str = (string?)GetTypedMemberValue (info, smi, instance, typeof(string), null); - if (str == null) { - instance.AddPointerData (smi, null, 0); - return false; - } - - StringSymbolInfo stringSymbol = WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter); - instance.AddPointerData (smi, stringSymbol.SymbolName, stringSymbol.Size); - - return true; + WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); } - bool MaybeWritePreAllocatedBuffer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + void WriteType (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) { - if (!smi.Info.IsNativePointerToPreallocatedBuffer (out ulong bufferSize)) { - return false; - } - - if (smi.Info.UsesDataProvider ()) { - bufferSize = info.GetBufferSizeFromProvider (smi, instance); - } - - output = EnsureOutput (output); - string irType = MapManagedTypeToIR (smi.MemberType); - string variableName = $"__{info.Name}_{smi.Info.Name}_{structBufferCounter.ToString (CultureInfo.InvariantCulture)}"; - structBufferCounter++; - - WriteGlobalSymbolStart (variableName, preAllocatedBufferVariableOptions, output); - ulong size = bufferSize * smi.BaseTypeSize; - - // WriteLine $"[{bufferSize} x {irType}] zeroinitializer, align {GetAggregateAlignment ((int)smi.BaseTypeSize, size)}" - output.Write ('['); - output.Write (bufferSize.ToString (CultureInfo.InvariantCulture)); - output.Write (" x "); - output.Write (irType); - output.Write ("] zeroinitializer, align "); - output.WriteLine (GetAggregateAlignment ((int) smi.BaseTypeSize, size).ToString (CultureInfo.InvariantCulture)); - - instance.AddPointerData (smi, variableName, size); - return true; - } + if (memberInfo.IsNativePointer) { + typeInfo = new LlvmTypeInfo ( + isPointer: true, + isAggregate: false, + isStructure: false, + size: target.NativePointerSize, + maxFieldAlignment: target.NativePointerSize + ); - bool WriteStructureArrayStart (StructureInfo info, IList>? instances, LlvmIrVariableOptions options, string? symbolName = null, string? initialComment = null, TextWriter? output = null) - { - if (options.IsGlobal && String.IsNullOrEmpty (symbolName)) { - throw new ArgumentException ("must not be null or empty for global symbols", nameof (symbolName)); + context.Output.Write (IRPointerType); + return; } - bool named = !String.IsNullOrEmpty (symbolName); - if (named || !String.IsNullOrEmpty (initialComment)) { - WriteEOL (output: output); - WriteEOL (initialComment ?? symbolName, output); + if (memberInfo.IsInlineArray) { + WriteArrayType (context, memberInfo.MemberType.GetArrayElementType (), memberInfo.ArrayElements, out typeInfo); + return; } - if (named) { - WriteGlobalSymbolStart (symbolName, options, output); + if (memberInfo.IsIRStruct ()) { + var sim = new GeneratorStructureInstance (context.Module.GetStructureInfo (memberInfo.MemberType), memberInfo.GetValue (si.Obj)); + WriteStructureType (context, sim, out typeInfo); + return; } - return named; + WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } - void WriteStructureArrayEnd (StructureInfo info, string? symbolName, ulong count, bool named, bool skipFinalComment = false, TextWriter? output = null, bool isArrayOfPointers = false) + void WriteStructureType (GeneratorWriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) { - output = EnsureOutput (output); + ulong alignment = GetStructureMaxFieldAlignment (si.Info); - int alignment = isArrayOfPointers ? PointerSize : GetAggregateAlignment (info.MaxFieldAlignment, info.Size * count); - output.Write (", align "); - output.Write (alignment.ToString (CultureInfo.InvariantCulture)); - if (named && !skipFinalComment) { - WriteEOL ($"end of '{symbolName!}' array", output); - } else { - WriteEOL (output: output); - } - } - - /// - /// Writes an array of zero-initialized entries. specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructureArray (StructureInfo info, ulong count, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, bool isArrayOfPointers = false) - { - bool named = WriteStructureArrayStart (info, null, options, symbolName, initialComment); - - // $"[{count} x %{info.NativeTypeDesignator}.{info.Name}{pointerAsterisk}] zeroinitializer" - Output.Write ('['); - Output.Write (count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x %"); - Output.Write (info.NativeTypeDesignator); - Output.Write ('.'); - Output.Write (info.Name); - if (isArrayOfPointers) - Output.Write ('*'); - Output.Write ("] zeroinitializer"); + typeInfo = new LlvmTypeInfo ( + isPointer: false, + isAggregate: false, + isStructure: true, + size: si.Info.Size, + maxFieldAlignment: alignment + ); - WriteStructureArrayEnd (info, symbolName, (ulong)count, named, skipFinalComment: true, isArrayOfPointers: isArrayOfPointers); + context.Output.Write ('%'); + context.Output.Write (si.Info.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Info.Name); } - /// - /// Writes an array of zero-initialized entries. The array will be generated as a local, writable symbol. - /// - public void WriteStructureArray (StructureInfo info, ulong count, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, bool isArrayOfPointers = false) + void WriteType (GeneratorWriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) { - WriteStructureArray (info, count, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment, isArrayOfPointers); - } + if (IsStructureInstance (type)) { + if (value == null) { + throw new ArgumentException ("must not be null for structure instances", nameof (value)); + } - /// - /// Writes an array of managed type , with data optionally specified in (if it's null, the array - /// will be zero-initialized). specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructureArray (StructureInfo info, IList>? instances, LlvmIrVariableOptions options, - string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, - Action? nestedStructureWriter = null) - { - var arrayOutput = new StringWriter (); - bool named = WriteStructureArrayStart (info, instances, options, symbolName, initialComment, arrayOutput); - int count = instances != null ? instances.Count : 0; - - // $"[{count} x %{info.NativeTypeDesignator}.{info.Name}] " - arrayOutput.Write ('['); - arrayOutput.Write (count.ToString (CultureInfo.InvariantCulture)); - arrayOutput.Write (" x %"); - arrayOutput.Write (info.NativeTypeDesignator); - arrayOutput.Write ('.'); - arrayOutput.Write (info.Name); - arrayOutput.Write ("] "); - - if (instances != null) { - var bodyWriterOptions = new StructureBodyWriterOptions ( - writeFieldComment: true, - fieldIndent: $"{Indent}{Indent}", - structIndent: Indent, - structureOutput: arrayOutput, - stringsOutput: info.HasStrings ? new StringWriter () : null, - buffersOutput: info.HasPreAllocatedBuffers ? new StringWriter () : null - ); + WriteStructureType (context, (StructureInstance)value, out typeInfo); + return; + } - arrayOutput.WriteLine ('['); - for (int i = 0; i < count; i++) { - StructureInstance instance = instances[i]; + string irType; + ulong size; + bool isPointer; - arrayOutput.Write (Indent); - arrayOutput.Write ("; "); - arrayOutput.WriteLine (i.ToString (CultureInfo.InvariantCulture)); - WriteStructureBody (info, instance, bodyWriterOptions, nestedStructureWriter); - if (i < count - 1) { - arrayOutput.Write (", "); - } - WriteEOL (output: arrayOutput); - } - arrayOutput.Write (']'); + if (type.IsArray ()) { + Type elementType = type.GetArrayElementType (); + ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable); - WriteBufferToOutput (bodyWriterOptions.StringsOutput); - WriteBufferToOutput (bodyWriterOptions.BuffersOutput); - } else { - arrayOutput.Write ("zeroinitializer"); + WriteArrayType (context, elementType, elementCount, out typeInfo); + return; } - WriteStructureArrayEnd (info, symbolName, (ulong)count, named, skipFinalComment: instances == null, output: arrayOutput); - WriteBufferToOutput (arrayOutput); - } - - /// - /// Writes an array of managed type , with data optionally specified in (if it's null, the array - /// will be zero-initialized). The array will be generated as a local, writable symbol. - /// - public void WriteStructureArray (StructureInfo info, IList>? instances, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null) - { - WriteStructureArray (info, instances, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment); + irType = GetIRType (type, out size, out isPointer); + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: false, + isStructure: false, + size: size, + maxFieldAlignment: size + ); + context.Output.Write (irType); } - public void WriteArray (IList values, string symbolName, string? initialComment = null) + void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) { - WriteEOL (); - WriteEOL (initialComment ?? symbolName); - - ulong arrayStringCounter = 0; - var strings = new List (); + string irType; + ulong size; + ulong maxFieldAlignment; + bool isPointer; - foreach (string s in values) { - StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}", s, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); - strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); - } + if (elementType.IsStructureInstance (out Type? structureType)) { + StructureInfo si = context.Module.GetStructureInfo (structureType); - if (strings.Count > 0) { - Output.WriteLine (); - } + irType = $"%{si.NativeTypeDesignator}.{si.Name}"; + size = si.Size; + maxFieldAlignment = GetStructureMaxFieldAlignment (si); + isPointer = false; + } else { + irType = GetIRType (elementType, out size, out isPointer); + maxFieldAlignment = size; + } + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: true, + isStructure: false, + size: size, + maxFieldAlignment: maxFieldAlignment + ); - WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); + context.Output.Write ('['); + context.Output.Write (elementCount.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x "); + context.Output.Write (irType); + context.Output.Write (']'); } - public void WriteArray (IList values, LlvmIrVariableOptions options, string symbolName, Func? commentProvider = null) where T: struct + ulong GetStructureMaxFieldAlignment (StructureInfo si) { - bool optimizeOutput = commentProvider == null; - - WriteGlobalSymbolStart (symbolName, options); - string elementType = MapManagedTypeToIR (typeof (T), out ulong size); + if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { + return target.NativePointerSize; + } - // WriteLine $"[{values.Count} x {elementType}] [" - Output.Write ('['); - Output.Write (values.Count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x "); - Output.Write (elementType); - Output.WriteLine ("] ["); + return si.MaxFieldAlignment; + } - Output.Write (Indent); - for (int i = 0; i < values.Count; i++) { - if (i != 0) { - if (optimizeOutput) { - Output.Write (','); - if (i % 8 == 0) { - Output.Write (" ; "); - Output.Write (i - 8); - Output.Write (".."); - Output.WriteLine (i - 1); + bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); - Output.Write (Indent); - } else { - Output.Write (' '); - } - } else { - Output.Write (Indent); - } + void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable) + { + if (variable.Type.IsArray ()) { + bool zeroInitialize = false; + if (variable is LlvmIrGlobalVariable gv) { + zeroInitialize = gv.ZeroInitializeArray || variable.Value == null; + } else { + zeroInitialize = GetAggregateValueElementCount (variable) == 0; } - Output.Write (elementType); - Output.Write (' '); - Output.Write (MonoAndroidHelper.CultureInvariantToString (values [i])); - - if (!optimizeOutput) { - bool last = i == values.Count - 1; - if (!last) { - Output.Write (','); - } - - string? comment = commentProvider (i, values[i]); - if (!String.IsNullOrEmpty (comment)) { - Output.Write (" ; "); - Output.Write (comment); - } - - if (!last) { - Output.WriteLine (); - } + if (zeroInitialize) { + context.Output.Write ("zeroinitializer"); + return; } - } - if (optimizeOutput && values.Count / 8 != 0) { - int idx = values.Count - (values.Count % 8); - Output.Write (" ; "); - Output.Write (idx); - Output.Write (".."); - Output.Write (values.Count - 1); + + WriteArrayValue (context, variable); + return; } - Output.WriteLine (); - Output.Write ("], align "); - Output.WriteLine (GetAggregateAlignment ((int) size, size * (ulong) values.Count).ToString (CultureInfo.InvariantCulture)); + WriteValue (context, valueType, variable.Value); } - void AssertArraySize (StructureInfo info, StructureMemberInfo smi, ulong length, ulong expectedLength) + void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong length, ulong expectedLength) { if (length == expectedLength) { return; } - throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{info.Name}', expected {expectedLength}, found {length}"); - } - - void RenderArray (StructureInfo info, StructureMemberInfo smi, byte[] bytes, TextWriter output, ulong? expectedArraySize = null) - { - // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte - AssertArraySize (info, smi, expectedArraySize ?? (ulong)bytes.Length, smi.ArrayElements); - output.Write ('c'); - output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}"); } - void MaybeWriteStructureStringsAndBuffers (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options) + void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) { - if (options.StringsOutput != null) { - MaybeWriteStructureString (info, smi, instance, options.StringsOutput); + if (smi.IsNativePointer) { + if (WriteNativePointerValue (context, structInstance, smi, value)) { + return; + } } - if (options.BuffersOutput != null) { - MaybeWritePreAllocatedBuffer (info, smi, instance, options.BuffersOutput); - } - } + if (smi.IsInlineArray) { + Array a = (Array)value; + ulong length = smi.ArrayElements == 0 ? (ulong)a.Length : smi.ArrayElements; - void WriteStructureField (StructureInfo info, StructureInstance instance, StructureMemberInfo smi, int fieldIndex, - StructureBodyWriterOptions options, TextWriter output, object? valueOverride = null, ulong? expectedArraySize = null, - Action? nestedStructureWriter = null) - { - object? value = null; + if (smi.MemberType == typeof(byte[])) { + var bytes = (byte[])value; - if (smi.IsIRStruct ()) { - if (nestedStructureWriter == null) { - throw new InvalidOperationException ($"Nested structure found in type {typeof(T)}, field {smi.Info.Name} but no nested structure writer provided"); - } - nestedStructureWriter (this, options, smi.MemberType, valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType)); - } else if (smi.IsNativePointer) { - output.Write (options.FieldIndent); - WritePointer (info, smi, instance, output); - } else if (smi.IsNativeArray) { - if (!smi.IsInlineArray) { - throw new InvalidOperationException ($"Out of line arrays aren't supported at this time (structure '{info.Name}', field '{smi.Info.Name}')"); + // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte + AssertArraySize (structInstance, smi, length, smi.ArrayElements); + context.Output.Write ('c'); + context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + return; } - output.Write (options.FieldIndent); - output.Write (smi.IRType); - output.Write (" "); - value = valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType); + throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point. Field {smi.Info.Name} in structure {structInstance.Info.Name}"); + } - if (smi.MemberType == typeof(byte[])) { - RenderArray (info, smi, (byte[])value, output, expectedArraySize); - } else { - throw new InvalidOperationException ($"Arrays of type '{smi.MemberType}' aren't supported at this point (structure '{info.Name}', field '{smi.Info.Name}')"); - } - } else { - value = valueOverride; - output.Write (options.FieldIndent); - WritePrimitiveField (info, smi, instance, output); + if (smi.IsIRStruct ()) { + StructureInfo si = context.Module.GetStructureInfo (smi.MemberType); + WriteValue (context, typeof(GeneratorStructureInstance), new GeneratorStructureInstance (si, value)); + return; } - FinishStructureField (info, smi, instance, options, fieldIndex, value, output); + if (smi.Info.IsNativePointerToPreallocatedBuffer (out _)) { + string bufferVariableName = context.Module.LookupRequiredBufferVariableName (structInstance, smi); + context.Output.Write ('@'); + context.Output.Write (bufferVariableName); + return; + } + + WriteValue (context, smi.MemberType, value); } - void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options, Action? nestedStructureWriter = null) + bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) { - TextWriter structureOutput = EnsureOutput (options.StructureOutput); - - // $"{options.StructIndent}%{info.NativeTypeDesignator}.{info.Name} " - structureOutput.Write (options.StructIndent); - structureOutput.Write ('%'); - structureOutput.Write (info.NativeTypeDesignator); - structureOutput.Write ('.'); - structureOutput.Write (info.Name); - structureOutput.Write (' '); + // Structure members decorated with the [NativePointer] attribute cannot have a + // value other than `null`, unless they are strings or references to symbols - if (instance != null) { - structureOutput.WriteLine ('{'); - for (int i = 0; i < info.Members.Count; i++) { - StructureMemberInfo smi = info.Members[i]; - - MaybeWriteStructureStringsAndBuffers (info, smi, instance, options); - WriteStructureField (info, instance, smi, i, options, structureOutput, nestedStructureWriter: nestedStructureWriter); + if (smi.Info.PointsToSymbol (out string? symbolName)) { + if (String.IsNullOrEmpty (symbolName) && smi.Info.UsesDataProvider ()) { + if (si.Info.DataProvider == null) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{si.Info.Name}' points to a symbol, but symbol name wasn't provided and there's no configured data context provider"); + } + symbolName = si.Info.DataProvider.GetPointedToSymbolName (si.Obj, smi.Info.Name); } - structureOutput.Write (options.StructIndent); - structureOutput.Write ('}'); - } else { - structureOutput.Write ("zeroinitializer"); - } - } - - void MaybeWriteFieldComment (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options, object? value, TextWriter output) - { - if (!options.WriteFieldComment) { - return; - } - - string? comment = info.GetCommentFromProvider (smi, instance); - if (String.IsNullOrEmpty (comment)) { - var sb = new StringBuilder (smi.Info.Name); - if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { - sb.Append (" (0x"); - sb.Append ($"{value:x}"); - sb.Append (')'); + if (String.IsNullOrEmpty (symbolName)) { + context.Output.Write ("null"); + } else { + context.Output.Write ('@'); + context.Output.Write (symbolName); } - comment = sb.ToString (); + return true; } - WriteComment (output, comment); - } - void FinishStructureField (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options, int fieldIndex, object? value, TextWriter output) - { - if (fieldIndex < info.Members.Count - 1) { - output.Write (", "); + if (smi.MemberType != typeof(string)) { + context.Output.Write ("null"); + return true; } - MaybeWriteFieldComment (info, smi, instance, options, value, output); - WriteEOL (output); - } - void WritePrimitiveField (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter output, object? overrideValue = null) - { - object? value = overrideValue ?? GetTypedMemberValue (info, smi, instance, smi.MemberType); - output.Write (smi.IRType); - output.Write (' '); - output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return false; } - void WritePointer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter output, object? overrideValue = null) + void WriteValue (GeneratorWriteContext context, Type type, object? value) { - if (info.HasStrings) { - StructurePointerData? spd = instance.GetPointerData (smi); - if (spd != null) { - WriteGetStringPointer (spd.VariableName, spd.Size, indent: false, output: output); - return; - } - } - - if (info.HasPreAllocatedBuffers) { - StructurePointerData? spd = instance.GetPointerData (smi); - if (spd != null) { - WriteGetBufferPointer (spd.VariableName, smi.IRType, spd.Size, indent: false, output: output); - return; - } + if (value is LlvmIrVariable variableRef) { + context.Output.Write (variableRef.Reference); + return; } - if (smi.Info.PointsToSymbol (out string? symbolName)) { - if (String.IsNullOrEmpty (symbolName) && smi.Info.UsesDataProvider ()) { - if (info.DataProvider == null) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' points to a symbol, but symbol name wasn't provided and there's no configured data context provider"); - } - symbolName = info.DataProvider.GetPointedToSymbolName (instance.Obj, smi.Info.Name); - } + if (IsNumeric (type)) { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } - if (String.IsNullOrEmpty (symbolName)) { - WriteNullPointer (smi, output); - return; - } + if (type == typeof(bool)) { + context.Output.Write ((bool)value ? '1' : '0'); + return; + } - ulong bufferSize = info.GetBufferSizeFromProvider (smi, instance); - WriteGetBufferPointer (symbolName, smi.IRType, bufferSize, indent: false, output: output); + if (IsStructureInstance (type)) { + WriteStructureValue (context, (StructureInstance?)value); return; } - object? value = overrideValue ?? smi.GetValue (instance.Obj); - if (value == null || ((value is IntPtr) && (IntPtr)value == IntPtr.Zero)) { - WriteNullPointer (smi, output); + if (type == typeof(IntPtr)) { + // Pointers can only be `null` or a reference to variable + context.Output.Write ("null"); return; } - if (value.GetType ().IsPrimitive) { - ulong v = Convert.ToUInt64 (value); - if (v == 0) { - WriteNullPointer (smi, output); + if (type == typeof(string)) { + if (value == null) { + context.Output.Write ("null"); return; } + + LlvmIrStringVariable sv = context.Module.LookupRequiredVariableForString ((string)value); + context.Output.Write (sv.Reference); + return; + } + + if (type.IsInlineArray ()) { + } - throw new InvalidOperationException ($"While processing field '{smi.Info.Name}' of type '{info.Name}': non-null pointers to objects of managed type '{smi.MemberType}' (IR type '{smi.IRType}') currently not supported (value: {value})"); + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } - void WriteNullPointer (StructureMemberInfo smi, TextWriter output) + void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance) { - output.Write (smi.IRType); - output.Write (" null"); - } + if (instance == null || instance.IsZeroInitialized) { + context.Output.Write ("zeroinitializer"); + return; + } - // In theory, functionality implemented here should be folded into WriteStructureArray, but in practice it would slow processing for most of the structures we - // write, thus we'll keep this one separate, even at the cost of some code duplication - // - // This code is extremely ugly, one day it should be made look nicer (right... :D) - // - public void WritePackedStructureArray (StructureInfo info, IList> instances, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null) - { - StructureBodyWriterOptions bodyWriterOptions = InitStructureWrite (info, options, symbolName, writeFieldComment, fieldIndent: $"{Indent}{Indent}"); - TextWriter structureOutput = EnsureOutput (bodyWriterOptions.StructureOutput); - var structureBodyOutput = new StringWriter (); - var structureTypeOutput = new StringWriter (); - - bool firstInstance = true; - var members = new List> (); - var instanceType = new StringBuilder (); - foreach (StructureInstance instance in instances) { - members.Clear (); - bool hasPaddedFields = false; - - if (!firstInstance) { - structureTypeOutput.WriteLine (','); - structureBodyOutput.WriteLine (','); - } else { - firstInstance = false; - } + context.Output.WriteLine ('{'); + context.IncreaseIndent (); - foreach (StructureMemberInfo smi in info.Members) { - object? value = GetTypedMemberValue (info, smi, instance, smi.MemberType); + StructureInfo info = instance.Info; + int lastMember = info.Members.Count - 1; - if (!smi.NeedsPadding) { - members.Add (new PackedStructureMember (smi, value)); - continue; - } + for (int i = 0; i < info.Members.Count; i++) { + StructureMemberInfo smi = info.Members[i]; - if (smi.MemberType != typeof(byte[])) { - throw new InvalidOperationException ($"Only byte arrays are supported currently (field '{smi.Info.Name}' of structure '{info.Name}')"); - } + context.Output.Write (context.CurrentIndent); + WriteType (context, instance, smi, out _); + context.Output.Write (' '); - var array = (byte[])value; - var arrayLength = (ulong)array.Length; + object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); + WriteValue (context, instance, smi, value); - if (arrayLength > smi.ArrayElements) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should not have more than {smi.ArrayElements} elements"); - } + if (i < lastMember) { + context.Output.Write (", "); + } - ulong padding = smi.ArrayElements - arrayLength; - if (padding == 0) { - members.Add (new PackedStructureMember (smi, value)); - continue; + string? comment = info.GetCommentFromProvider (smi, instance); + if (String.IsNullOrEmpty (comment)) { + var sb = new StringBuilder (" "); + sb.Append (MapManagedTypeToNative (smi)); + sb.Append (' '); + sb.Append (smi.Info.Name); + if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { + sb.Append ($" (0x{value:x})"); } + comment = sb.ToString (); + } + WriteCommentLine (context, comment); + } - if (padding < 8) { - var paddedValue = new byte[arrayLength + padding]; - Array.Copy (array, paddedValue, array.Length); - for (int i = (int)arrayLength; i < paddedValue.Length; i++) { - paddedValue[i] = 0; - } - members.Add (new PackedStructureMember (smi, paddedValue)); - continue; - } + context.DecreaseIndent (); + context.Output.Write (context.CurrentIndent); + context.Output.Write ('}'); + } - members.Add (new PackedStructureMember (smi, value, valueIRType: $"[{arrayLength} x i8]", paddingIRType: $"[{padding} x i8]")); - hasPaddedFields = true; + void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) + { + ICollection entries; + if (variable.Type.ImplementsInterface (typeof(IDictionary))) { + var list = new List (); + foreach (var kvp in (IDictionary)variable.Value) { + list.Add (kvp.Key); + list.Add (kvp.Value); } + entries = list; + } else { + entries = (ICollection)variable.Value; + } - bool firstField; - instanceType.Clear (); - if (!hasPaddedFields) { - instanceType.Append ("\t%"); - instanceType.Append (info.NativeTypeDesignator); - instanceType.Append ('.'); - instanceType.Append (info.Name); - } else { - instanceType.Append ("\t{ "); - - firstField = true; - foreach (PackedStructureMember psm in members) { - if (!firstField) { - instanceType.Append (", "); - } else { - firstField = false; - } - - if (!psm.IsPadded) { - instanceType.Append (psm.ValueIRType); - continue; - } - - // $"<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}>" - instanceType.Append ("<{ "); - instanceType.Append (psm.ValueIRType); - instanceType.Append (", "); - instanceType.Append (psm.PaddingIRType); - instanceType.Append (" }>"); - } + if (entries.Count == 0) { + context.Output.Write ("zeroinitializer"); + return; + } - instanceType.Append (" }"); - } - structureTypeOutput.Write (instanceType.ToString ()); + context.Output.WriteLine ('['); + context.IncreaseIndent (); - structureBodyOutput.Write (instanceType.ToString ()); - structureBodyOutput.WriteLine (" {"); + Type elementType = variable.Type.GetArrayElementType (); + bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + ulong counter = 0; + string? prevItemComment = null; + uint stride; - firstField = true; - bool previousFieldWasPadded = false; - for (int i = 0; i < members.Count; i++) { - PackedStructureMember psm = members[i]; + if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) { + stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1; + } else { + stride = 1; + } - if (firstField) { - firstField = false; - } + bool first = true; - if (!psm.IsPadded) { - previousFieldWasPadded = false; - ulong? expectedArraySize = psm.MemberInfo.IsNativeArray ? (ulong)((byte[])psm.Value).Length : null; - WriteStructureField (info, instance, psm.MemberInfo, i, bodyWriterOptions, structureBodyOutput, valueOverride: psm.Value, expectedArraySize: expectedArraySize); - continue; - } + // TODO: implement output in rows + foreach (object entry in entries) { + if (!first) { + context.Output.Write (','); + WritePrevItemCommentOrNewline (); + } else { + first = false; + } - if (!firstField && previousFieldWasPadded) { - structureBodyOutput.Write (", "); - } + prevItemComment = null; + if (variable.GetArrayItemCommentCallback != null) { + prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); + } - // $"{bodyWriterOptions.FieldIndent}<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}> <{{ {psm.ValueIRType} c{QuoteString ((byte[])psm.Value)}, {psm.PaddingIRType} zeroinitializer }}> " - structureBodyOutput.Write (bodyWriterOptions.FieldIndent); - structureBodyOutput.Write ("<{ "); - structureBodyOutput.Write (psm.ValueIRType); - structureBodyOutput.Write (", "); - structureBodyOutput.Write (psm.PaddingIRType); - structureBodyOutput.Write (" }> <{ "); - structureBodyOutput.Write (psm.ValueIRType); - structureBodyOutput.Write (" c"); - structureBodyOutput.Write (QuoteString ((byte []) psm.Value)); - structureBodyOutput.Write (", "); - structureBodyOutput.Write (psm.PaddingIRType); - structureBodyOutput.Write (" zeroinitializer }> "); - - MaybeWriteFieldComment (info, psm.MemberInfo, instance, bodyWriterOptions, value: null, output: structureBodyOutput); - previousFieldWasPadded = true; + if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { + prevItemComment = $" {counter}"; } - structureBodyOutput.WriteLine (); - structureBodyOutput.Write (Indent); - structureBodyOutput.Write ('}'); - } - structureOutput.WriteLine ("<{"); - structureOutput.Write (structureTypeOutput); - structureOutput.WriteLine (); - structureOutput.WriteLine ("}>"); + counter++; + context.Output.Write (context.CurrentIndent); + WriteType (context, elementType, entry, out _); + + context.Output.Write (' '); + WriteValue (context, elementType, entry); + } + WritePrevItemCommentOrNewline (); - structureOutput.WriteLine ("<{"); - structureOutput.Write (structureBodyOutput); - structureOutput.WriteLine (); - structureOutput.Write ("}>"); + context.DecreaseIndent (); + context.Output.Write (']'); - FinishStructureWrite (info, bodyWriterOptions); + void WritePrevItemCommentOrNewline () + { + if (!String.IsNullOrEmpty (prevItemComment)) { + context.Output.Write (' '); + WriteCommentLine (context, prevItemComment); + } else { + context.Output.WriteLine (); + } + } } - StructureBodyWriterOptions InitStructureWrite (StructureInfo info, LlvmIrVariableOptions options, string? symbolName, bool writeFieldComment, string? fieldIndent = null) + void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage) { - if (options.IsGlobal && String.IsNullOrEmpty (symbolName)) { - throw new ArgumentException ("must not be null or empty for global symbols", nameof (symbolName)); + if (linkage == LlvmIrLinkage.Default) { + return; } - var structureOutput = new StringWriter (); - bool named = !String.IsNullOrEmpty (symbolName); - if (named) { - WriteEOL (output: structureOutput); - WriteEOL (symbolName, structureOutput); - - WriteGlobalSymbolStart (symbolName, options, structureOutput); + try { + WriteAttribute (context, llvmLinkage[linkage]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{linkage}'", ex); } - - return new StructureBodyWriterOptions ( - writeFieldComment: writeFieldComment, - fieldIndent: fieldIndent ?? Indent, - structureOutput: structureOutput, - stringsOutput: info.HasStrings ? new StringWriter () : null, - buffersOutput: info.HasPreAllocatedBuffers ? new StringWriter () : null - ); } - void FinishStructureWrite (StructureInfo info, StructureBodyWriterOptions bodyWriterOptions) + void WriteWritability (GeneratorWriteContext context, LlvmIrWritability writability) { - bodyWriterOptions.StructureOutput.Write (", align "); - bodyWriterOptions.StructureOutput.WriteLine (info.MaxFieldAlignment.ToString (CultureInfo.InvariantCulture)); - - WriteBufferToOutput (bodyWriterOptions.StringsOutput); - WriteBufferToOutput (bodyWriterOptions.BuffersOutput); - WriteBufferToOutput (bodyWriterOptions.StructureOutput); + try { + WriteAttribute (context, llvmWritability[writability]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); + } } - public void WriteStructure (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions bodyWriterOptions, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) + void WriteAddressSignificance (GeneratorWriteContext context, LlvmIrAddressSignificance addressSignificance) { - WriteStructureBody (info, instance, bodyWriterOptions); - FinishStructureWrite (info, bodyWriterOptions); + if (addressSignificance == LlvmIrAddressSignificance.Default) { + return; + } + + try { + WriteAttribute (context, llvmAddressSignificance[addressSignificance]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported address significance '{addressSignificance}'", ex); + } } - public void WriteNestedStructure (StructureInfo info, StructureInstance instance, StructureBodyWriterOptions bodyWriterOptions) + void WriteVisibility (GeneratorWriteContext context, LlvmIrVisibility visibility) { - var options = new StructureBodyWriterOptions ( - bodyWriterOptions.WriteFieldComment, - bodyWriterOptions.FieldIndent + Indent, - bodyWriterOptions.FieldIndent, // structure indent should start at the original struct's field column - bodyWriterOptions.StructureOutput, - bodyWriterOptions.StringsOutput, - bodyWriterOptions.BuffersOutput - ); - WriteStructureBody (info, instance, options); + if (visibility == LlvmIrVisibility.Default) { + return; + } + + try { + WriteAttribute (context, llvmVisibility[visibility]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported visibility '{visibility}'", ex); + } } - /// - /// Write a structure represented by managed type , with optional data passed in (if null, the structure - /// is zero-initialized). specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructure (StructureInfo info, StructureInstance? instance, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) + void WritePreemptionSpecifier (GeneratorWriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) { - StructureBodyWriterOptions bodyWriterOptions = InitStructureWrite (info, options, symbolName, writeFieldComment); - WriteStructure (info, instance, bodyWriterOptions, options, symbolName, writeFieldComment); + if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { + return; + } + + try { + WriteAttribute (context, llvmRuntimePreemption[preemptionSpecifier]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported preemption specifier '{preemptionSpecifier}'", ex); + } } /// - /// Write a structure represented by managed type , with optional data passed in (if null, the structure - /// is zero-initialized). The structure will be generated as a local, writable symbol. + /// Write attribute named in followed by a single space /// - public void WriteStructure (StructureInfo info, StructureInstance? instance, string? symbolName = null, bool writeFieldComment = true) + void WriteAttribute (GeneratorWriteContext context, string attr) { - WriteStructure (info, instance, LlvmIrVariableOptions.Default, symbolName, writeFieldComment); + context.Output.Write (attr); + context.Output.Write (' '); } - void WriteBufferToOutput (TextWriter? writer) + void WriteStructureDeclarations (GeneratorWriteContext context) { - if (writer == null) { + if (context.Module.Structures == null || context.Module.Structures.Count == 0) { return; } - writer.Flush (); - string text = writer.ToString (); - if (text.Length > 0) { - Output.WriteLine (text); + foreach (StructureInfo si in context.Module.Structures) { + context.Output.WriteLine (); + WriteStructureDeclaration (context, si); } } - void WriteGetStringPointer (string? variableName, ulong size, bool indent = true, TextWriter? output = null) + void WriteStructureDeclaration (GeneratorWriteContext context, StructureInfo si) { - WriteGetBufferPointer (variableName, "i8*", size, indent, output); - } + // $"%{typeDesignator}.{name} = type " + context.Output.Write ('%'); + context.Output.Write (si.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Name); + context.Output.Write (" = type "); + + if (si.IsOpaque) { + context.Output.WriteLine ("opaque"); + } else { + context.Output.WriteLine ('{'); + } - void WriteGetBufferPointer (string? variableName, string irType, ulong size, bool indent = true, TextWriter? output = null) - { - output = EnsureOutput (output); - if (indent) { - output.Write (Indent); + if (si.IsOpaque) { + return; } - if (String.IsNullOrEmpty (variableName)) { - output.Write (irType); - output.Write (" null"); - } else { - string irBaseType; - if (irType[irType.Length - 1] == '*') { - irBaseType = irType.Substring (0, irType.Length - 1); + context.IncreaseIndent (); + for (int i = 0; i < si.Members.Count; i++) { + StructureMemberInfo info = si.Members[i]; + string nativeType = MapManagedTypeToNative (info.MemberType); + + // TODO: nativeType can be an array, update to indicate that (and get the size) + string arraySize; + if (info.IsNativeArray) { + arraySize = $"[{info.ArrayElements}]"; + } else { + arraySize = String.Empty; + } + + var comment = $" {nativeType} {info.Info.Name}{arraySize}"; + WriteStructureDeclarationField (info.IRType, comment, i == si.Members.Count - 1); + } + context.DecreaseIndent (); + + context.Output.WriteLine ('}'); + + void WriteStructureDeclarationField (string typeName, string comment, bool last) + { + context.Output.Write (context.CurrentIndent); + context.Output.Write (typeName); + if (!last) { + context.Output.Write (", "); } else { - irBaseType = irType; + context.Output.Write (' '); } - // $"{irType} getelementptr inbounds ([{size} x {irBaseType}], [{size} x {irBaseType}]* @{variableName}, i32 0, i32 0)" - string sizeStr = size.ToString (CultureInfo.InvariantCulture); - output.Write (irType); - output.Write (" getelementptr inbounds (["); - output.Write (sizeStr); - output.Write (" x "); - output.Write (irBaseType); - output.Write ("], ["); - output.Write (sizeStr); - output.Write (" x "); - output.Write (irBaseType); - output.Write ("]* @"); - output.Write (variableName); - output.Write (", i32 0, i32 0)"); + if (!String.IsNullOrEmpty (comment)) { + WriteCommentLine (context, comment); + } else { + context.Output.WriteLine (); + } } } - /// - /// Write an array of name/value pairs. The array symbol will be global and non-writable. - /// - public void WriteNameValueArray (string symbolName, IDictionary arrayContents) + // + // Functions syntax: https://llvm.org/docs/LangRef.html#functions + // + void WriteFunctions (GeneratorWriteContext context) { - WriteEOL (); - WriteEOL (symbolName); + if (context.Module.Functions == null || context.Module.Functions.Count == 0) { + return; + } - var strings = new List (); - long i = 0; - ulong arrayStringCounter = 0; + context.Output.WriteLine (); + WriteComment (context, " Functions"); - foreach (var kvp in arrayContents) { - string name = kvp.Key; - string value = kvp.Value; - string iStr = i.ToString (CultureInfo.InvariantCulture); + foreach (LlvmIrFunction function in context.Module.Functions) { + context.Output.WriteLine (); + WriteFunctionComment (context, function); - WriteArrayString (name, $"n_{iStr}"); - WriteArrayString (value, $"v_{iStr}"); - i++; + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); + WriteFunctionDefinitionLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: true); + WriteFunctionDefinitionTrailingDecorations (context, function); + WriteFunctionBody (context, function); + function.RestoreState (funcState); } + } - if (strings.Count > 0) { - Output.WriteLine (); + void WriteFunctionComment (GeneratorWriteContext context, LlvmIrFunction function) + { + if (String.IsNullOrEmpty (function.Comment)) { + return; } - WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); - - void WriteArrayString (string str, string symbolSuffix) - { - StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}_{symbolSuffix}", str, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); - strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); + foreach (string commentLine in function.Comment.Split ('\n')) { + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, commentLine); } } - void WriteStringArray (string symbolName, LlvmIrVariableOptions options, List strings) + void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) { - WriteGlobalSymbolStart (symbolName, options); + context.Output.WriteLine (); + context.Output.WriteLine ('{'); + context.IncreaseIndent (); - // $"[{strings.Count} x i8*]" - Output.Write ('['); - Output.Write (strings.Count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x i8*]"); + foreach (LlvmIrFunctionBodyItem item in function.Body.Items) { + item.Write (context, this); + } - if (strings.Count > 0) { - Output.WriteLine (" ["); + context.DecreaseIndent (); + context.Output.WriteLine ('}'); + } - for (int j = 0; j < strings.Count; j++) { - ulong size = strings[j].Size; - string varName = strings[j].SymbolName; + ILlvmIrSavedFunctionState WriteFunctionPreamble (GeneratorWriteContext context, LlvmIrFunction function, string keyword) + { + ILlvmIrSavedFunctionState funcState = function.SaveState (); - // - // Syntax: https://llvm.org/docs/LangRef.html#getelementptr-instruction - // the two indices following {varName} have the following meanings: - // - // - The first index is into the **pointer** itself - // - The second index is into the **pointed to** value - // - // Better explained here: https://llvm.org/docs/GetElementPtr.html#id4 - // - WriteGetStringPointer (varName, size); - if (j < strings.Count - 1) { - Output.WriteLine (','); - } - } - WriteEOL (); - } else { - Output.Write (" zeroinitializer"); + foreach (LlvmIrFunctionParameter parameter in function.Signature.Parameters) { + target.SetParameterFlags (parameter); } - var arraySize = (ulong)(strings.Count * PointerSize); - if (strings.Count > 0) { - Output.Write (']'); - } - Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (PointerSize, arraySize).ToString (CultureInfo.InvariantCulture)); + WriteFunctionAttributesComment (context, function); + context.Output.Write (keyword); + context.Output.Write (' '); + + return funcState; } - /// - /// Wries a global, constant variable - /// - public void WriteVariable (string symbolName, T value) + void WriteExternalFunctionDeclarations (GeneratorWriteContext context) { - WriteVariable (symbolName, value, LlvmIrVariableOptions.GlobalConstant); + if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { + return; + } + + context.Output.WriteLine (); + WriteComment (context, " External functions"); + foreach (LlvmIrFunction function in context.Module.ExternalFunctions) { + context.Output.WriteLine (); + + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "declare"); + WriteFunctionDeclarationLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (context, function); + + function.RestoreState (funcState); + } } - public void WriteVariable (string symbolName, T value, LlvmIrVariableOptions options) + void WriteFunctionAttributesComment (GeneratorWriteContext context, LlvmIrFunction func) { - if (typeof(T) == typeof(string)) { - WriteString (symbolName, (string)(object)value, options); + if (func.AttributeSet == null) { return; } - WriteEOL (); - string irType = GetIRType (out ulong size, value); - WriteGlobalSymbolStart (symbolName, options); + if (String.IsNullOrEmpty (func.Comment)) { + context.Output.WriteLine (); + } + WriteCommentLine (context, $" Function attributes: {func.AttributeSet.Render ()}"); + } - Output.Write (irType); - Output.Write (' '); - Output.Write (GetValue (value)); - Output.Write (", align "); - Output.WriteLine (size); + void WriteFunctionDeclarationLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) + { + WriteFunctionLeadingDecorations (context, func, declaration: true); } - /// - /// Writes a private string. Strings without symbol names aren't exported, but they may be referenced by other - /// symbols - /// - public string WriteString (string value) + void WriteFunctionDefinitionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - return WriteString (value, LlvmIrVariableOptions.LocalString); + WriteFunctionLeadingDecorations (context, func, declaration: false); } - /// - /// Writes a string with automatically generated symbol name and symbol options (writeability, visibility etc) specified in the parameter. - /// - public string WriteString (string value, LlvmIrVariableOptions options) + void WriteFunctionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { - string name = $"@.str"; - if (stringCounter > 0) { - name += $".{stringCounter.ToString (CultureInfo.InvariantCulture)}"; + if (func.Linkage != LlvmIrLinkage.Default) { + context.Output.Write (llvmLinkage[func.Linkage]); + context.Output.Write (' '); + } + + if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { + context.Output.Write (llvmRuntimePreemption[func.RuntimePreemption]); + context.Output.Write (' '); + } + + if (func.Visibility != LlvmIrVisibility.Default) { + context.Output.Write (llvmVisibility[func.Visibility]); + context.Output.Write (' '); } - stringCounter++; - return WriteString (name, value, options); } - /// - /// Writes a global, C++ constexpr style string - /// - public string WriteString (string symbolName, string value) + void WriteFunctionDeclarationTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - return WriteString (symbolName, value, LlvmIrVariableOptions.GlobalConstexprString); + WriteFunctionTrailingDecorations (context, func, declaration: true); } - /// - /// Writes a string with symbol options (writeability, visibility) options specified in the parameter. - /// - public string WriteString (string symbolName, string value, LlvmIrVariableOptions options) + void WriteFunctionDefinitionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - return WriteString (symbolName, value, options, out _); + WriteFunctionTrailingDecorations (context, func, declaration: false); } - /// - /// Writes a local, constexpr style string and returns its size in - /// - public string WriteString (string symbolName, string value, out ulong stringSize) + void WriteFunctionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { - return WriteString (symbolName, value, LlvmIrVariableOptions.LocalConstexprString, out stringSize); + if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { + context.Output.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); + } + + if (func.AttributeSet != null) { + context.Output.Write ($" #{func.AttributeSet.Number}"); + } } - /// - /// Writes a string with specified , and symbol options (writeability, visibility etc) specified in the - /// parameter. Returns string size (in bytes) in - /// - public string WriteString (string symbolName, string value, LlvmIrVariableOptions options, out ulong stringSize) + public static void WriteReturnAttributes (GeneratorWriteContext context, LlvmIrFunctionSignature.ReturnTypeAttributes returnAttrs) { - string strSymbolName; - bool global = options.IsGlobal; - if (global) { - strSymbolName = $"__{symbolName}"; - } else { - strSymbolName = symbolName; + if (AttributeIsSet (returnAttrs.NoUndef)) { + context.Output.Write ("noundef "); } - string quotedString = QuoteString (value, out stringSize); + if (AttributeIsSet (returnAttrs.NonNull)) { + context.Output.Write ("nonnull "); + } - // It might seem counter-intuitive that when we're requested to write a global string, here we generate a **local** one, - // but global strings are actually pointers to local storage. - WriteGlobalSymbolStart (strSymbolName, global ? LlvmIrVariableOptions.LocalConstexprString : options); + if (AttributeIsSet (returnAttrs.SignExt)) { + context.Output.Write ("signext "); + } - string stringSizeStr = stringSize.ToString (CultureInfo.InvariantCulture); - // WriteLine $"[{stringSize} x i8] c{quotedString}, align {GetAggregateAlignment (1, stringSize)}" - Output.Write ('['); - Output.Write (stringSizeStr); - Output.Write (" x i8] c"); - Output.Write (quotedString); - Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (1, stringSize).ToString (CultureInfo.InvariantCulture)); + if (AttributeIsSet (returnAttrs.ZeroExt)) { + context.Output.Write ("zeroext "); + } + } - if (!global) { - return symbolName; + void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, bool writeParameterNames) + { + if (func.ReturnsValue) { + WriteReturnAttributes (context, func.Signature.ReturnAttributes); } - string indexType = Is64Bit ? "i64" : "i32"; - WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); + context.Output.Write (MapToIRType (func.Signature.ReturnType)); + context.Output.Write (" @"); + context.Output.Write (func.Signature.Name); + context.Output.Write ('('); - // WriteLine $"i8* getelementptr inbounds ([{stringSize} x i8], [{stringSize} x i8]* @{strSymbolName}, {indexType} 0, {indexType} 0), align {GetAggregateAlignment (PointerSize, stringSize)}" - Output.Write ("i8* getelementptr inbounds (["); - Output.Write (stringSizeStr); - Output.Write (" x i8], ["); - Output.Write (stringSizeStr); - Output.Write (" x i8]* @"); - Output.Write (strSymbolName); - Output.Write (", "); - Output.Write (indexType); - Output.Write (" 0, "); - Output.Write (indexType); - Output.Write (" 0), align "); - Output.WriteLine (GetAggregateAlignment (PointerSize, stringSize).ToString (CultureInfo.InvariantCulture)); + bool first = true; + bool varargsFound = false; - return symbolName; - } + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + if (varargsFound) { + throw new InvalidOperationException ($"Internal error: function '{func.Signature.Name}' has extra parameters following the C varargs parameter. This is not allowed."); + } - /// - /// Writes a string, creating a new symbol if the is unique or returns name of a previously created symbol with the same - /// string value. If a new symbol is written, its name is constructed by combining prefix () with value - /// of a string counter referenced by the parameter. Symbol is created as a local, C++ constexpr style string. - /// - public StringSymbolInfo WriteUniqueString (string potentialSymbolName, string value, ref ulong counter) - { - return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString); + if (!first) { + context.Output.Write (", "); + } else { + first = false; + } + + if (parameter.IsVarArgs) { + context.Output.Write ("..."); + varargsFound = true; + continue; + } + + context.Output.Write (MapToIRType (parameter.Type)); + WriteParameterAttributes (context, parameter); + + if (writeParameterNames) { + if (String.IsNullOrEmpty (parameter.Name)) { + throw new InvalidOperationException ($"Internal error: parameter must have a name"); + } + context.Output.Write (" %"); // Function arguments are always local variables + context.Output.Write (parameter.Name); + } + } + + context.Output.Write (')'); } - /// - /// Writes a string, creating a new symbol if the is unique or returns name of a previously created symbol with the same - /// string value. If a new symbol is written, its name is constructed by combining prefix () with value - /// of a string counter referenced by the parameter. Symbol options (writeability, visibility etc) are specified in the parameter. String size (in bytes) is returned in . - /// - public StringSymbolInfo WriteUniqueString (string potentialSymbolNamePrefix, string value, ref ulong counter, LlvmIrVariableOptions options) + public static void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) { - if (value == null) { - return null; + var attributes = new List (); + if (AttributeIsSet (parameter.ImmArg)) { + attributes.Add ("immarg"); } - StringSymbolInfo info; - if (stringSymbolCache.TryGetValue (value, out info)) { - return info; + if (AttributeIsSet (parameter.AllocPtr)) { + attributes.Add ("allocptr"); } - string newSymbolName = $"{potentialSymbolNamePrefix}.{counter.ToString (CultureInfo.InvariantCulture)}"; - counter++; + if (AttributeIsSet (parameter.NoCapture)) { + attributes.Add ("nocapture"); + } - WriteString (newSymbolName, value, options, out ulong stringSize); - info = new StringSymbolInfo (newSymbolName, stringSize); - stringSymbolCache.Add (value, info); + if (AttributeIsSet (parameter.NonNull)) { + attributes.Add ("nonnull"); + } - return info; - } + if (AttributeIsSet (parameter.NoUndef)) { + attributes.Add ("noundef"); + } - public virtual void WriteFileTop () - { - WriteCommentLine ($"ModuleID = '{fileName}'"); - WriteDirective ("source_filename", QuoteStringNoEscape (fileName)); - WriteDirective ("target datalayout", QuoteStringNoEscape (DataLayout)); - WriteDirective ("target triple", QuoteStringNoEscape (Triple)); - } + if (AttributeIsSet (parameter.ReadNone)) { + attributes.Add ("readnone"); + } - public virtual void WriteFileEnd () - { - Output.WriteLine (); + if (AttributeIsSet (parameter.SignExt)) { + attributes.Add ("signext"); + } + + if (AttributeIsSet (parameter.ZeroExt)) { + attributes.Add ("zeroext"); + } + + if (parameter.Align.HasValue) { + attributes.Add ($"align({ValueOrPointerSize (parameter.Align.Value)})"); + } + + if (parameter.Dereferenceable.HasValue) { + attributes.Add ($"dereferenceable({ValueOrPointerSize (parameter.Dereferenceable.Value)})"); + } + + if (attributes.Count == 0) { + return; + } - WriteAttributeSets (); + context.Output.Write (' '); + context.Output.Write (String.Join (" ", attributes)); + + uint ValueOrPointerSize (uint? value) + { + if (value.Value == 0) { + return context.Target.NativePointerSize; + } - foreach (LlvmIrMetadataItem metadata in MetadataManager.Items) { - Output.WriteLine (metadata.Render ()); + return value.Value; } } - public void WriteStructureDeclarations () + static bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; + + void WriteAttributeSets (GeneratorWriteContext context) { - if (structures.Count == 0) { + if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { return; } - Output.WriteLine (); - foreach (IStructureInfo si in structures) { - si.RenderDeclaration (this); + context.Output.WriteLine (); + foreach (LlvmIrFunctionAttributeSet attrSet in context.Module.AttributeSets) { + // Must not modify the original set, it is shared with other targets. + var targetSet = new LlvmIrFunctionAttributeSet (attrSet); + if (!attrSet.DoNotAddTargetSpecificAttributes) { + target.AddTargetSpecificAttributes (targetSet); + } + + IList? privateTargetSet = attrSet.GetPrivateTargetAttributes (target.TargetArch); + if (privateTargetSet != null) { + targetSet.Add (privateTargetSet); + } + + context.Output.WriteLine ($"attributes #{targetSet.Number} = {{ {targetSet.Render ()} }}"); } } - public void WriteStructureDeclarationStart (string typeDesignator, string name, bool forOpaqueType = false) + void WriteMetadata (GeneratorWriteContext context) { - WriteEOL (); + if (context.MetadataManager.Items.Count == 0) { + return; + } - // $"%{typeDesignator}.{name} = type " - Output.Write ('%'); - Output.Write (typeDesignator); - Output.Write ('.'); - Output.Write (name); - Output.Write (" = type "); - - if (forOpaqueType) { - Output.WriteLine ("opaque"); - } else { - Output.WriteLine ("{"); + context.Output.WriteLine (); + WriteCommentLine (context, " Metadata"); + foreach (LlvmIrMetadataItem metadata in context.MetadataManager.Items) { + context.Output.WriteLine (metadata.Render ()); } } - public void WriteStructureDeclarationEnd () + public void WriteComment (GeneratorWriteContext context, string comment) { - Output.WriteLine ('}'); + context.Output.Write (';'); + context.Output.Write (comment); } - public void WriteStructureDeclarationField (string typeName, string comment, bool last) + public void WriteCommentLine (GeneratorWriteContext context, string comment) { - Output.Write (Indent); - Output.Write (typeName); - if (!last) { - Output.Write (","); - } - - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (comment); - } else { - WriteEOL (); - } + WriteComment (context, comment); + context.Output.WriteLine (); } - protected virtual void AddModuleFlagsMetadata (List flagsFields) + static Type GetActualType (Type type) { - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "wchar_size", 4)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Max, "PIC Level", 2)); + // Arrays of types are handled elsewhere, so we obtain the array base type here + if (type.IsArray) { + return type.GetElementType (); + } + + return type; } - // Alignment for arrays, structures and unions - protected virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + /// + /// Map a managed to its C++ counterpart. Only primitive types, + /// string and IntPtr are supported. + /// + public static string MapManagedTypeToNative (Type type) { - return maxFieldAlignment; + Type baseType = GetActualType (type); + + if (baseType == typeof (bool)) return "bool"; + if (baseType == typeof (byte)) return "uint8_t"; + if (baseType == typeof (char)) return "char"; + if (baseType == typeof (sbyte)) return "int8_t"; + if (baseType == typeof (short)) return "int16_t"; + if (baseType == typeof (ushort)) return "uint16_t"; + if (baseType == typeof (int)) return "int32_t"; + if (baseType == typeof (uint)) return "uint32_t"; + if (baseType == typeof (long)) return "int64_t"; + if (baseType == typeof (ulong)) return "uint64_t"; + if (baseType == typeof (float)) return "float"; + if (baseType == typeof (double)) return "double"; + if (baseType == typeof (string)) return "char*"; + if (baseType == typeof (IntPtr)) return "void*"; + + return type.GetShortName (); } - public void WriteCommentLine (string? comment = null, bool indent = false) + static string MapManagedTypeToNative (StructureMemberInfo smi) { - WriteCommentLine (Output, comment, indent); + string nativeType = MapManagedTypeToNative (smi.MemberType); + // Silly, but effective + if (nativeType[nativeType.Length - 1] == '*') { + return nativeType; + } + + if (!smi.IsNativePointer) { + return nativeType; + } + + return $"{nativeType}*"; } - public void WriteComment (TextWriter writer, string? comment = null, bool indent = false) + static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; + + object? GetTypedMemberValue (GeneratorWriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) { - if (indent) { - writer.Write (Indent); + object? value = smi.GetValue (instance.Obj); + if (value == null) { + return defaultValue; } - writer.Write (';'); + Type valueType = value.GetType (); + if (valueType != expectedType) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should have a value of '{expectedType}' type, instead it had a '{value.GetType ()}'"); + } - if (!String.IsNullOrEmpty (comment)) { - writer.Write (' '); - writer.Write (comment); + if (valueType == typeof(string)) { + return context.Module.LookupRequiredVariableForString ((string)value); } + + return value; } - public void WriteCommentLine (TextWriter writer, string? comment = null, bool indent = false) + public static string MapToIRType (Type type) { - WriteComment (writer, comment, indent); - writer.WriteLine (); + return MapToIRType (type, out _, out _); } - public void WriteEOL (string? comment = null, TextWriter? output = null) + public static string MapToIRType (Type type, out ulong size) { - WriteEOL (EnsureOutput (output), comment); + return MapToIRType (type, out size, out _); } - public void WriteEOL (TextWriter writer, string? comment = null) + public static string MapToIRType (Type type, out bool isPointer) { - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (writer, comment); - return; - } - writer.WriteLine (); + return MapToIRType (type, out _, out isPointer); } - public void WriteDirectiveWithComment (TextWriter writer, string name, string? comment, string? value) + /// + /// Maps managed type to equivalent IR type. Puts type size in and whether or not the type + /// is a pointer in . When a type is determined to be a pointer, + /// will be set to 0, because this method doesn't have access to the generator target. In order to adjust pointer + /// size, the instance method must be called (private to the generator as other classes should not + /// have any need to know the pointer size). + /// + public static string MapToIRType (Type type, out ulong size, out bool isPointer) { - writer.Write (name); - - if (!String.IsNullOrEmpty (value)) { - writer.Write (" = "); - writer.Write (value); + type = GetActualType (type); + if (!type.IsNativePointer () && basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + size = typeDesc.Size; + isPointer = false; + return typeDesc.Name; } - WriteEOL (writer, comment); + // if it's not a basic type, then it's an opaque pointer + size = 0; // Will be determined by the specific target architecture class + isPointer = true; + return IRPointerType; } - public void WriteDirectiveWithComment (string name, string? comment, string? value) + string GetIRType (Type type, out ulong size, out bool isPointer) { - WriteDirectiveWithComment (name, comment, value); - } + string ret = MapToIRType (type, out size, out isPointer); + if (isPointer && size == 0) { + size = target.NativePointerSize; + } - public void WriteDirective (TextWriter writer, string name, string? value) - { - WriteDirectiveWithComment (writer, name, comment: null, value: value); + return ret; } - public void WriteDirective (string name, string value) + public static bool IsFirstClassNonPointerType (Type type) { - WriteDirective (Output, name, value); + if (type == typeof(void)) { + return false; + } + + return basicTypeMap.ContainsKey (type); } public static string QuoteStringNoEscape (string s) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs new file mode 100644 index 00000000000..5ce7e98bd9d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -0,0 +1,627 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Xamarin.Android.Tasks.LLVMIR; + +abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem +{ + public string Mnemonic { get; } + public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } + + /// + /// TBAA (Type Based Alias Analysis) metadata item the instruction references, if any. + /// for more information about TBAA. + /// + public LlvmIrMetadataItem? TBAA { get; set; } + + protected LlvmIrInstruction (string mnemonic) + { + if (String.IsNullOrEmpty (mnemonic)) { + throw new ArgumentException ("must not be null or empty", nameof (mnemonic)); + } + + Mnemonic = mnemonic; + } + + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) + { + context.Output.Write (context.CurrentIndent); + WriteValueAssignment (context); + WritePreamble (context); + context.Output.Write (Mnemonic); + context.Output.Write (' '); + WriteBody (context); + + if (TBAA != null) { + context.Output.Write (", !tbaa !"); + context.Output.Write (TBAA.Name); + } + + if (AttributeSet != null) { + context.Output.Write (" #"); + context.Output.Write (AttributeSet.Number.ToString (CultureInfo.InvariantCulture)); + } + } + + /// + /// Write the '<variable_reference> = ' part of the instruction line. + /// + protected virtual void WriteValueAssignment (GeneratorWriteContext context) + {} + + /// + /// Write part of the instruction that comes between the optional value assignment and the instruction + /// mnemonic. If any text is written, it must end with a whitespace. + /// + protected virtual void WritePreamble (GeneratorWriteContext context) + {} + + /// + /// Write the "body" of the instruction, that is the part that follows instruction mnemonic but precedes the + /// metadata and attribute set references. + /// + protected virtual void WriteBody (GeneratorWriteContext context) + {} + + protected void WriteValue (GeneratorWriteContext context, Type type, object? value, bool isPointer) + { + if (value == null) { + if (!isPointer) { + throw new InvalidOperationException ($"Internal error: non-pointer type '{type}' must not have a `null` value"); + } + context.Output.Write ("null"); + } else if (value is LlvmIrVariable variable) { + context.Output.Write (variable.Reference); + } else { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + } + } + + protected void WriteAlignment (GeneratorWriteContext context, ulong typeSize, bool isPointer) + { + context.Output.Write (", align "); + + ulong alignment; + if (isPointer) { + alignment = context.Target.NativePointerSize; + } else { + alignment = typeSize; + } + context.Output.Write (alignment.ToString (CultureInfo.InvariantCulture)); + } +} + +abstract class LlvmIrInstructionArgumentValuePlaceholder +{ + protected LlvmIrInstructionArgumentValuePlaceholder () + {} + + public abstract object? GetValue (LlvmIrModuleTarget target); +} + +class LlvmIrInstructionPointerSizeArgumentPlaceholder : LlvmIrInstructionArgumentValuePlaceholder +{ + public override object? GetValue (LlvmIrModuleTarget target) + { + return target.NativePointerSize; + } +} + +sealed class LlvmIrInstructions +{ + public class Alloca : LlvmIrInstruction + { + LlvmIrVariable result; + + public Alloca (LlvmIrVariable result) + : base ("alloca") + { + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + if (result == null) { + return; + } + + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (result.Type, out ulong size, out bool isPointer); + + context.Output.Write (irType); + WriteAlignment (context, size, isPointer); + } + } + + public class Br : LlvmIrInstruction + { + const string OpName = "br"; + + LlvmIrVariable? cond; + LlvmIrFunctionLabelItem ifTrue; + LlvmIrFunctionLabelItem? ifFalse; + + /// + /// Outputs a conditional branch to label if condition is + /// true, and to label otherwise. must be a variable + /// of type bool + /// + public Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem ifTrue, LlvmIrFunctionLabelItem ifFalse) + : base (OpName) + { + if (cond.Type != typeof(bool)) { + throw new ArgumentException ($"Internal error: condition must refer to a variable of type 'bool', was 'cond.Type' instead", nameof (cond)); + } + + this.cond = cond; + this.ifTrue = ifTrue; + this.ifFalse = ifFalse; + } + + /// + /// Outputs an unconditional branch to label + /// + public Br (LlvmIrFunctionLabelItem label) + : base (OpName) + { + ifTrue = label; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + if (cond == null) { + context.Output.Write ("label %"); + context.Output.Write (ifTrue.Name); + return; + } + + context.Output.Write ("i1 "); + context.Output.Write (cond.Reference); + context.Output.Write (", label %"); + context.Output.Write (ifTrue.Name); + context.Output.Write (", label %"); + context.Output.Write (ifFalse.Name); + } + } + + public class Call : LlvmIrInstruction + { + LlvmIrFunction function; + IList? arguments; + LlvmIrVariable? result; + + public LlvmIrCallMarker CallMarker { get; set; } = LlvmIrCallMarker.None; + + /// + /// This needs to be set if we want to call a function via a local or global variable. passed + /// to the constructor is then used only to generate a type safe call, while function address comes from the variable assigned + /// to this property. + /// + public LlvmIrVariable? FuncPointer { get; set; } + + public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null, LlvmIrVariable? funcPointer = null) + : base ("call") + { + this.function = function; + this.result = result; + this.FuncPointer = funcPointer; + + if (function.Signature.ReturnType != typeof(void)) { + if (result == null) { + throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' returns '{function.Signature.ReturnType} and thus requires a result variable", nameof (result)); + } + } else if (result != null) { + throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' returns no value and yet a result variable was provided", nameof (result)); + } + + int argCount = function.Signature.Parameters.Count; + if (argCount != 0) { + if (arguments == null) { + throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments", nameof (arguments)); + } + + if (function.UsesVarArgs) { + if (arguments.Count < argCount) { + throw new ArgumentException ($"Internal error: varargs function '{function.Signature.Name}' needs at least {argCount} fixed arguments, got {arguments.Count} instead"); + } + } else if (arguments.Count != argCount) { + throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments, but {arguments.Count} were provided", nameof (arguments)); + } + + this.arguments = new List (arguments).AsReadOnly (); + } + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + if (result == null) { + return; + } + + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WritePreamble (GeneratorWriteContext context) + { + string? callMarker = CallMarker switch { + LlvmIrCallMarker.None => null, + LlvmIrCallMarker.Tail => "tail", + LlvmIrCallMarker.NoTail => "notail", + LlvmIrCallMarker.MustTail => "musttail", + _ => throw new InvalidOperationException ($"Internal error: call marker '{CallMarker}' not supported"), + }; + + if (!String.IsNullOrEmpty (callMarker)) { + context.Output.Write (callMarker); + context.Output.Write (' '); + } + } + + protected override void WriteBody (GeneratorWriteContext context) + { + if (function.ReturnsValue) { + LlvmIrGenerator.WriteReturnAttributes (context, function.Signature.ReturnAttributes); + } + + context.Output.Write (LlvmIrGenerator.MapToIRType (function.Signature.ReturnType)); + + if (function.UsesVarArgs) { + context.Output.Write (" ("); + for (int j = 0; j < function.Signature.Parameters.Count; j++) { + if (j > 0) { + context.Output.Write (", "); + } + + LlvmIrFunctionParameter parameter = function.Signature.Parameters[j]; + string irType = parameter.IsVarArgs ? "..." : LlvmIrGenerator.MapToIRType (parameter.Type); + context.Output.Write (irType); + } + context.Output.Write (')'); + } + + if (FuncPointer == null) { + context.Output.Write (" @"); + context.Output.Write (function.Signature.Name); + } else { + context.Output.Write (' '); + context.Output.Write (FuncPointer.Reference); + } + context.Output.Write ('('); + + bool isVararg = false; + int i; + for (i = 0; i < function.Signature.Parameters.Count; i++) { + if (i > 0) { + context.Output.Write (", "); + } + + LlvmIrFunctionParameter parameter = function.Signature.Parameters[i]; + if (parameter.IsVarArgs) { + isVararg = true; + } + + WriteArgument (context, parameter, i, isVararg); + } + + if (arguments != null) { + for (; i < arguments.Count; i++) { + context.Output.Write (", "); + WriteArgument (context, null, i, isVararg: true); + } + } + + context.Output.Write (')'); + } + + void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter? parameter, int index, bool isVararg) + { + object? value = arguments[index]; + if (value is LlvmIrInstructionArgumentValuePlaceholder placeholder) { + value = placeholder.GetValue (context.Target); + } + + string irType; + if (!isVararg) { + irType = LlvmIrGenerator.MapToIRType (parameter.Type); + } else if (value is LlvmIrVariable v1) { + irType = LlvmIrGenerator.MapToIRType (v1.Type); + } else { + if (value == null) { + // We have no way of verifying the vararg parameter type if value is null, so we'll assume it's a pointer. + // If our assumption is wrong, llc will fail and signal the error + irType = "ptr"; + } else { + irType = LlvmIrGenerator.MapToIRType (value.GetType ()); + } + } + + context.Output.Write (irType); + if (parameter != null) { + LlvmIrGenerator.WriteParameterAttributes (context, parameter); + } + context.Output.Write (' '); + + if (value == null) { + if (!parameter.Type.IsNativePointer ()) { + throw new InvalidOperationException ($"Internal error: value for argument {index} to function '{function.Signature.Name}' must not be null"); + } + + context.Output.Write ("null"); + return; + } + + if (value is LlvmIrVariable v2) { + context.Output.Write (v2.Reference); + return; + } + + if (parameter != null && !parameter.Type.IsAssignableFrom (value.GetType ())) { + throw new InvalidOperationException ($"Internal error: value type '{value.GetType ()}' for argument {index} to function '{function.Signature.Name}' is invalid. Expected '{parameter.Type}' or compatible"); + } + + if (value is string str) { + context.Output.Write (context.Module.LookupRequiredVariableForString (str).Reference); + return; + } + + if (LlvmIrGenerator.IsFirstClassNonPointerType (value.GetType ())) { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } + + throw new InvalidOperationException ($"Internal error: unsupported type '{value.GetType ()}' in call to function '{function.Signature.Name}'"); + } + } + + public class Ext : LlvmIrInstruction + { + const string FpextOpCode = "fpext"; + const string SextOpCode = "sext"; + const string ZextOpCode = "zext"; + + LlvmIrVariable result; + LlvmIrVariable source; + Type targetType; + + public Ext (LlvmIrVariable source, Type targetType, LlvmIrVariable result) + : base (GetOpCode (targetType)) + { + this.source = source; + this.targetType = targetType; + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (source.Type)); + context.Output.Write (' '); + context.Output.Write (source.Reference); + context.Output.Write (" to "); + context.Output.Write ( LlvmIrGenerator.MapToIRType (targetType)); + } + + static string GetOpCode (Type targetType) + { + if (targetType == typeof(double)) { + return FpextOpCode; + } else if (targetType == typeof(int)) { + return SextOpCode; + } else if (targetType == typeof(uint)) { + return ZextOpCode; + } else { + throw new InvalidOperationException ($"Unsupported target type for upcasting: {targetType}"); + } + } + } + + public class Icmp : LlvmIrInstruction + { + LlvmIrIcmpCond cond; + LlvmIrVariable op1; + object? op2; + LlvmIrVariable result; + + public Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) + : base ("icmp") + { + if (result.Type != typeof(bool)) { + throw new ArgumentException ($"Internal error: result must be a variable of type 'bool', was '{result.Type}' instead", nameof (result)); + } + + this.cond = cond; + this.op1 = op1; + this.op2 = op2; + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (op1.Type, out ulong size, out bool isPointer); + string condOp = cond switch { + LlvmIrIcmpCond.Equal => "eq", + LlvmIrIcmpCond.NotEqual => "ne", + LlvmIrIcmpCond.UnsignedGreaterThan => "ugt", + LlvmIrIcmpCond.UnsignedGreaterOrEqual => "uge", + LlvmIrIcmpCond.UnsignedLessThan => "ult", + LlvmIrIcmpCond.UnsignedLessOrEqual => "ule", + LlvmIrIcmpCond.SignedGreaterThan => "sgt", + LlvmIrIcmpCond.SignedGreaterOrEqual => "sge", + LlvmIrIcmpCond.SignedLessThan => "slt", + LlvmIrIcmpCond.SignedLessOrEqual => "sle", + _ => throw new InvalidOperationException ($"Unsupported `icmp` conditional '{cond}'"), + }; + + context.Output.Write (condOp); + context.Output.Write (' '); + context.Output.Write (irType); + context.Output.Write (' '); + context.Output.Write (op1.Reference); + context.Output.Write (", "); + WriteValue (context, op1.Type, op2, isPointer); + } + } + + public class Load : LlvmIrInstruction + { + LlvmIrVariable source; + LlvmIrVariable result; + + public Load (LlvmIrVariable source, LlvmIrVariable result) + : base ("load") + { + this.source = source; + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (result.Type, out ulong size, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (", ptr "); + WriteValue (context, result.Type, source, isPointer); + WriteAlignment (context, size, isPointer); + } + } + + public class Phi : LlvmIrInstruction + { + LlvmIrVariable result; + LlvmIrVariable val1; + LlvmIrFunctionLabelItem label1; + LlvmIrVariable val2; + LlvmIrFunctionLabelItem label2; + + /// + /// Represents the `phi` instruction form we use the most throughout marshal methods generator - one which refers to an if/else block and where + /// **both** value:label pairs are **required**. Parameters and are nullable because, in theory, + /// it is possible that hasn't had the required blocks defined prior to adding the `phi` instruction and, thus, + /// we must check for the possibility here. + /// + public Phi (LlvmIrVariable result, LlvmIrVariable val1, LlvmIrFunctionLabelItem? label1, LlvmIrVariable val2, LlvmIrFunctionLabelItem? label2) + : base ("phi") + { + this.result = result; + this.val1 = val1; + this.label1 = label1 ?? throw new ArgumentNullException (nameof (label1)); + this.val2 = val2; + this.label2 = label2 ?? throw new ArgumentNullException (nameof (label2)); + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (result.Type)); + context.Output.Write (" ["); + context.Output.Write (val1.Reference); + context.Output.Write (", %"); + context.Output.Write (label1.Name); + context.Output.Write ("], ["); + context.Output.Write (val2.Reference); + context.Output.Write (", %"); + context.Output.Write (label2.Name); + context.Output.Write (']'); + } + } + + public class Ret : LlvmIrInstruction + { + Type retvalType; + object? retVal; + + public Ret (Type retvalType, object? retval = null) + : base ("ret") + { + this.retvalType = retvalType; + retVal = retval; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + if (retvalType == typeof(void)) { + context.Output.Write ("void"); + return; + } + + string irType = LlvmIrGenerator.MapToIRType (retvalType, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (' '); + + WriteValue (context, retvalType, retVal, isPointer); + } + } + + public class Store : LlvmIrInstruction + { + const string Opcode = "store"; + + object? from; + LlvmIrVariable to; + + public Store (LlvmIrVariable from, LlvmIrVariable to) + : base (Opcode) + { + this.from = from; + this.to = to; + } + + /// + /// Stores `null` in the indicated variable + /// + public Store (LlvmIrVariable to) + : base (Opcode) + { + this.to = to; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (to.Type, out ulong size, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (' '); + + WriteValue (context, to.Type, from, isPointer); + + context.Output.Write (", ptr "); + context.Output.Write (to.Reference); + + WriteAlignment (context, size, isPointer); + } + } + + public class Unreachable : LlvmIrInstruction + { + public Unreachable () + : base ("unreachable") + {} + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs new file mode 100644 index 00000000000..dbd6ce0f69c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Android.Tasks.LLVMIR; + +sealed class LlvmIrKnownMetadata +{ + public const string LlvmModuleFlags = "llvm.module.flags"; + public const string LlvmIdent = "llvm.ident"; +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs index b2d8bf7379a..37acb17ce44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs @@ -10,6 +10,12 @@ class LlvmIrMetadataField public string Contents { get; } public bool IsReference { get; } + public LlvmIrMetadataField (LlvmIrMetadataField other) + { + Contents = other.Contents; + IsReference = other.IsReference; + } + public LlvmIrMetadataField (string value, bool isReference = false) { if (isReference) { @@ -35,7 +41,7 @@ string FormatValue (object value) return QuoteString ((string)value); } - string irType = LlvmIrGenerator.MapManagedTypeToIR (vt); + string irType = LlvmIrGenerator.MapToIRType (vt); return $"{irType} {MonoAndroidHelper.CultureInvariantToString (value)}"; } @@ -51,6 +57,15 @@ class LlvmIrMetadataItem public string Name { get; } + public LlvmIrMetadataItem (LlvmIrMetadataItem other) + { + Name = other.Name; + fields = new List (); + foreach (LlvmIrMetadataField field in other.fields) { + fields.Add (new LlvmIrMetadataField (field)); + } + } + public LlvmIrMetadataItem (string name) { if (name.Length == 0) { @@ -66,9 +81,19 @@ public void AddReferenceField (string referenceName) fields.Add (new LlvmIrMetadataField (referenceName, isReference: true)); } + public void AddReferenceField (LlvmIrMetadataItem referencedItem) + { + AddReferenceField (referencedItem.Name); + } + public void AddField (object value) { - fields.Add (new LlvmIrMetadataField (value)); + AddField (new LlvmIrMetadataField (value)); + } + + public void AddField (LlvmIrMetadataField field) + { + fields.Add (field); } public string Render () @@ -96,11 +121,29 @@ class LlvmIrMetadataManager { ulong counter = 0; List items = new List (); + Dictionary nameToItem = new Dictionary (StringComparer.Ordinal); public List Items => items; + public LlvmIrMetadataManager () + {} + + public LlvmIrMetadataManager (LlvmIrMetadataManager other) + { + foreach (LlvmIrMetadataItem item in other.items) { + var newItem = new LlvmIrMetadataItem (item); + items.Add (newItem); + nameToItem.Add (newItem.Name, newItem); + } + counter = other.counter; + } + public LlvmIrMetadataItem Add (string name, params object[]? values) { + if (nameToItem.ContainsKey (name)) { + throw new InvalidOperationException ($"Internal error: metadata item '{name}' has already been added"); + } + var ret = new LlvmIrMetadataItem (name); if (values != null && values.Length > 0) { @@ -110,6 +153,7 @@ public LlvmIrMetadataItem Add (string name, params object[]? values) } items.Add (ret); + nameToItem.Add (name, ret); return ret; } @@ -119,5 +163,14 @@ public LlvmIrMetadataItem AddNumbered (params object[]? values) counter++; return Add (name, values); } + + public LlvmIrMetadataItem? GetItem (string name) + { + if (nameToItem.TryGetValue (name, out LlvmIrMetadataItem? item)) { + return item; + } + + return null; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs new file mode 100644 index 00000000000..be1570dafe8 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + partial class LlvmIrModule + { + /// + /// Global variable type to be used to output name:value string arrays. This is a notational shortcut, + /// do **NOT** change the type without understanding how it affects the rest of code. + /// + public static readonly Type NameValueArrayType = typeof(IDictionary); + + public IList? ExternalFunctions { get; private set; } + public IList? Functions { get; private set; } + public IList? AttributeSets { get; private set; } + public IList? Structures { get; private set; } + public IList? GlobalVariables { get; private set; } + public IList? Strings { get; private set; } + + /// + /// TBAA stands for "Type Based Alias Analysis" and is used by LLVM to implemente a description of + /// a higher level language typesystem to LLVM IR (in which memory doesn't have types). This metadata + /// item describes pointer usage for certain instructions we output and is common enough to warrant + /// a shortcut property like that. More information about TBAA can be found at https://llvm.org/docs/LangRef.html#tbaa-metadata + /// + public LlvmIrMetadataItem TbaaAnyPointer => tbaaAnyPointer; + + Dictionary? attributeSets; + Dictionary? externalFunctions; + Dictionary? functions; + Dictionary? structures; + LlvmIrStringManager? stringManager; + LlvmIrMetadataManager metadataManager; + LlvmIrMetadataItem tbaaAnyPointer; + LlvmIrBufferManager? bufferManager; + + List? globalVariables; + + LlvmIrFunction? puts; + LlvmIrFunction? abort; + + public LlvmIrModule () + { + metadataManager = new LlvmIrMetadataManager (); + + // Only model agnostic items can be added here + LlvmIrMetadataItem flags = metadataManager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); + flags.AddReferenceField (metadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "wchar_size", 4)); + flags.AddReferenceField (metadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Max, "PIC Level", 2)); + + LlvmIrMetadataItem ident = metadataManager.Add (LlvmIrKnownMetadata.LlvmIdent); + LlvmIrMetadataItem identValue = metadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); + ident.AddReferenceField (identValue.Name); + + tbaaAnyPointer = metadataManager.AddNumbered (); + LlvmIrMetadataItem anyPointer = metadataManager.AddNumbered ("any pointer"); + LlvmIrMetadataItem omnipotentChar = metadataManager.AddNumbered ("omnipotent char"); + LlvmIrMetadataItem simpleCppTBAA = metadataManager.AddNumbered ("Simple C++ TBAA"); + + anyPointer.AddReferenceField (omnipotentChar.Name); + anyPointer.AddField ((ulong)0); + + omnipotentChar.AddReferenceField (simpleCppTBAA); + omnipotentChar.AddField ((ulong)0); + + tbaaAnyPointer.AddReferenceField (anyPointer); + tbaaAnyPointer.AddReferenceField (anyPointer); + tbaaAnyPointer.AddField ((ulong)0); + } + + /// + /// Return a metadata manager instance which includes copies of all the target-agnostic metadata items. + /// We must not modify the original manager since each target may have conflicting values for certain + /// flags. + /// + public LlvmIrMetadataManager GetMetadataManagerCopy () => new LlvmIrMetadataManager (metadataManager); + + /// + /// Perform any tasks that need to be done after construction is complete. + /// + public void AfterConstruction () + { + if (externalFunctions != null) { + List list = externalFunctions.Values.ToList (); + list.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); + ExternalFunctions = list.AsReadOnly (); + } + + if (functions != null) { + List list = functions.Values.ToList (); + // TODO: sort or not? + Functions = list.AsReadOnly (); + } + + if (attributeSets != null) { + List list = attributeSets.Values.ToList (); + list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); + AttributeSets = list.AsReadOnly (); + } + + if (structures != null) { + List list = structures.Values.ToList (); + list.Sort ((StructureInfo a, StructureInfo b) => a.Name.CompareTo (b.Name)); + Structures = list.AsReadOnly (); + } + + if (stringManager != null && stringManager.StringGroups.Count > 0) { + Strings = stringManager.StringGroups.AsReadOnly (); + } + + GlobalVariables = globalVariables?.AsReadOnly (); + } + + public void Add (LlvmIrFunction func) + { + if (functions == null) { + functions = new Dictionary (); + } + + if (functions.TryGetValue (func, out LlvmIrFunction existingFunc)) { + throw new InvalidOperationException ($"Internal error: identical function has already been added (\"{func.Signature.Name}\")"); + } + + functions.Add (func, func); + } + + public LlvmIrInstructions.Call CreatePuts (string text, LlvmIrVariable result) + { + EnsurePuts (); + RegisterString (text); + return new LlvmIrInstructions.Call (puts, result, new List { text }); + } + + /// + /// Generate code to call the `puts(3)` C library function to print a simple string to standard output. + /// + public LlvmIrInstructions.Call AddPuts (LlvmIrFunction function, string text, LlvmIrVariable result) + { + EnsurePuts (); + RegisterString (text); + return function.Body.Call (puts, result, new List { text }); + } + + void EnsurePuts () + { + if (puts != null) { + return; + } + + var puts_params = new List { + new (typeof(string), "s"), + }; + + var puts_sig = new LlvmIrFunctionSignature ( + name: "puts", + returnType: typeof(int), + parameters: puts_params + ); + puts_sig.ReturnAttributes.NoUndef = true; + + puts = DeclareExternalFunction (puts_sig, MakePutsAttributeSet ()); + } + + LlvmIrFunctionAttributeSet MakePutsAttributeSet () + { + var ret = new LlvmIrFunctionAttributeSet { + new NofreeFunctionAttribute (), + new NounwindFunctionAttribute (), + }; + + ret.DoNotAddTargetSpecificAttributes = true; + return AddAttributeSet (ret); + } + + public LlvmIrInstructions.Call CreateAbort () + { + EnsureAbort (); + return new LlvmIrInstructions.Call (abort); + } + + public LlvmIrInstructions.Call AddAbort (LlvmIrFunction function) + { + EnsureAbort (); + LlvmIrInstructions.Call ret = function.Body.Call (abort); + function.Body.Unreachable (); + + return ret; + } + + void EnsureAbort () + { + if (abort != null) { + return; + } + + var abort_sig = new LlvmIrFunctionSignature (name: "abort", returnType: typeof(void)); + abort = DeclareExternalFunction (abort_sig, MakeAbortAttributeSet ()); + } + + LlvmIrFunctionAttributeSet MakeAbortAttributeSet () + { + var ret = new LlvmIrFunctionAttributeSet { + new NoreturnFunctionAttribute (), + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return AddAttributeSet (ret); + } + + public void AddIfThenElse (LlvmIrFunction function, LlvmIrVariable result, LlvmIrIcmpCond condition, LlvmIrVariable conditionVariable, object? conditionComparand, ICollection codeIfThen, ICollection? codeIfElse = null) + { + function.Body.Icmp (condition, conditionVariable, conditionComparand, result); + + var labelIfThen = new LlvmIrFunctionLabelItem (); + LlvmIrFunctionLabelItem? labelIfElse = codeIfElse != null ? new LlvmIrFunctionLabelItem () : null; + var labelIfDone = new LlvmIrFunctionLabelItem (); + + function.Body.Br (result, labelIfThen, labelIfElse == null ? labelIfDone : labelIfElse); + function.Body.Add (labelIfThen); + + AddInstructions (codeIfThen); + + if (codeIfElse != null) { + function.Body.Add (labelIfElse); + AddInstructions (codeIfElse); + } + + function.Body.Add (labelIfDone); + + void AddInstructions (ICollection instructions) + { + foreach (LlvmIrInstruction ins in instructions) { + function.Body.Add (ins); + } + } + } + + /// + /// A shortcut way to add a global variable without first having to create an instance of first. This overload + /// requires the parameter to not be null. + /// + public LlvmIrGlobalVariable AddGlobalVariable (string name, object value, LlvmIrVariableOptions? options = null, string? comment = null) + { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + return AddGlobalVariable (value.GetType (), name, value, options, comment); + } + + /// + /// A shortcut way to add a global variable without first having to create an instance of first. + /// + public LlvmIrGlobalVariable AddGlobalVariable (Type type, string name, object? value, LlvmIrVariableOptions? options = null, string? comment = null) + { + var ret = new LlvmIrGlobalVariable (type, name, options) { + Value = value, + Comment = comment, + }; + Add (ret); + return ret; + } + + public void Add (LlvmIrGlobalVariable variable, string stringGroupName, string? stringGroupComment = null, string? symbolSuffix = null) + { + EnsureValidGlobalVariableType (variable); + + if (IsStringVariable (variable)) { + AddStringGlobalVariable (variable, stringGroupName, stringGroupComment, symbolSuffix); + return; + } + + if (IsStringArrayVariable (variable)) { + AddStringArrayGlobalVariable (variable, stringGroupName, stringGroupComment, symbolSuffix); + return; + } + + throw new InvalidOperationException ("Internal error: this overload is ONLY for adding string or array-of-string variables"); + } + + public void Add (IList variables) + { + foreach (LlvmIrGlobalVariable variable in variables) { + Add (variable); + } + } + + public void Add (LlvmIrGlobalVariable variable) + { + EnsureValidGlobalVariableType (variable); + + if (IsStringVariable (variable)) { + AddStringGlobalVariable (variable); + return; + } + + if (IsStringArrayVariable (variable)) { + AddStringArrayGlobalVariable (variable); + return; + } + + if (IsStructureArrayVariable (variable)) { + AddStructureArrayGlobalVariable (variable); + return; + } + + if (IsStructureVariable (variable)) { + PrepareStructure (variable); + } + + AddStandardGlobalVariable (variable); + } + + void PrepareStructure (LlvmIrGlobalVariable variable) + { + var structure = variable.Value as StructureInstance; + if (structure == null) { + return; + } + + PrepareStructure (structure); + } + + void PrepareStructure (StructureInstance structure) + { + foreach (StructureMemberInfo smi in structure.Info.Members) { + if (smi.IsIRStruct ()) { + object? instance = structure.Obj == null ? null : smi.GetValue (structure.Obj); + if (instance == null) { + continue; + } + + StructureInfo si = GetStructureInfo (smi.MemberType); + PrepareStructure (new GeneratorStructureInstance (si, instance)); + continue; + } + + if (smi.Info.IsNativePointerToPreallocatedBuffer (out ulong bufferSize)) { + if (bufferSize == 0) { + bufferSize = structure.Info.GetBufferSizeFromProvider (smi, structure); + } + + AddAutomaticBuffer (structure, smi, bufferSize); + continue; + } + + if (smi.MemberType != typeof(string)) { + continue; + } + + string? value = smi.GetValue (structure.Obj) as string; + if (value != null) { + RegisterString (value, stringGroupName: structure.Info.Name, symbolSuffix: smi.Info.Name); + } + } + } + + void AddAutomaticBuffer (StructureInstance structure, StructureMemberInfo smi, ulong bufferSize) + { + if (bufferManager == null) { + bufferManager = new LlvmIrBufferManager (); + } + + string bufferName = bufferManager.Allocate (structure, smi, bufferSize); + var buffer = new LlvmIrGlobalVariable (typeof(List), bufferName, LlvmIrVariableOptions.LocalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = bufferSize, + }; + Add (buffer); + } + + void AddStandardGlobalVariable (LlvmIrGlobalVariable variable) + { + if (globalVariables == null) { + globalVariables = new List (); + } + + globalVariables.Add (variable); + } + + void AddStringGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + RegisterString (variable, stringGroupName, stringGroupComment, symbolSuffix); + AddStandardGlobalVariable (variable); + } + + void RegisterString (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + RegisterString ((string)variable.Value, stringGroupName, stringGroupComment, symbolSuffix); + } + + public void RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + if (stringManager == null) { + stringManager = new LlvmIrStringManager (); + } + + stringManager.Add (value, stringGroupName, stringGroupComment, symbolSuffix); + } + + void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) + { + if (variable.Value == null) { + AddStandardGlobalVariable (variable); + return; + } + + // For simplicity we support only arrays with homogenous entry types + StructureInfo? info = null; + ulong index = 0; + + foreach (StructureInstance structure in (IEnumerable)variable.Value) { + if (info == null) { + info = structure.Info; + if (info.HasPreAllocatedBuffers) { + // let's group them... + Add (new LlvmIrGroupDelimiterVariable ()); + } + } + + if (structure.Type != info.Type) { + throw new InvalidOperationException ($"Internal error: only arrays with homogenous element types are currently supported. All entries were expected to be of type '{info.Type}', but the '{structure.Type}' type was encountered."); + } + + // This is a bit of a kludge to make a specific corner case work seamlessly from the LlvmIrModule user's point of view. + // The scenario is used in ApplicationConfigNativeAssemblyGenerator and it involves an array of structures where each + // array index contains the same object in structure.Obj but each instance needs to allocate a unique buffer at runtime. + // LlvmIrBufferManager makes it possible, but it must be able to uniquely identify each instance, which in this scenario + // wouldn't be possible if we had to rely only on the StructureInstance contents. Enter `StructureInstance.IndexInArray`, + // which is used to create unique buffers and unambiguously assign them to each structure instance. + // + // See LlvmIrBufferManager for how it is used. + structure.IndexInArray = index++; + + PrepareStructure (structure); + } + + if (info != null && info.HasPreAllocatedBuffers) { + Add (new LlvmIrGroupDelimiterVariable ()); + } + + AddStandardGlobalVariable (variable); + } + + void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + if (variable.Value == null) { + AddStandardGlobalVariable (variable); + return; + } + + List? entries = null; + if (NameValueArrayType.IsAssignableFrom (variable.Type)) { + entries = new List (); + var dict = (IDictionary)variable.Value; + foreach (var kvp in dict) { + Register (kvp.Key); + Register (kvp.Value); + } + } else if (typeof(ICollection).IsAssignableFrom (variable.Type)) { + foreach (string s in (ICollection)variable.Value) { + Register (s); + } + } else { + throw new InvalidOperationException ($"Internal error: unsupported string array type `{variable.Type}'"); + } + + AddStandardGlobalVariable (variable); + + void Register (string value) + { + RegisterString (value, stringGroupName, stringGroupComment, symbolSuffix); + } + } + + bool IsStringArrayVariable (LlvmIrGlobalVariable variable) + { + if (NameValueArrayType.IsAssignableFrom (variable.Type)) { + if (variable.Value != null && !NameValueArrayType.IsAssignableFrom (variable.Value.GetType ())) { + throw new InvalidOperationException ($"Internal error: name:value array variable must have its value set to either `null` or `{NameValueArrayType}`"); + } + + return true; + } + + var ctype = typeof(ICollection); + if (ctype.IsAssignableFrom (variable.Type)) { + if (variable.Value != null && !ctype.IsAssignableFrom (variable.Value.GetType ())) { + throw new InvalidOperationException ($"Internal error: string array variable must have its value set to either `null` or implement `{ctype}`"); + } + + return true; + } + + if (variable.Type == typeof(string[])) { + if (variable.Value != null && variable.Value.GetType () != typeof(string[])) { + throw new InvalidOperationException ($"Internal error: string array variable must have its value set to either `null` or be `{typeof(string[])}`"); + } + + return true; + } + + return false; + } + + bool IsStringVariable (LlvmIrGlobalVariable variable) + { + if (variable.Type != typeof(string)) { + return false; + } + + if (variable.Value != null && variable.Value.GetType () != typeof(string)) { + throw new InvalidOperationException ("Internal error: variable of string type must have its value set to either `null` or a string"); + } + + return true; + } + + bool IsStructureArrayVariable (LlvmIrGlobalVariable variable) + { + if (typeof(StructureInstance[]).IsAssignableFrom (variable.Type)) { + return true; + } + + if (!variable.Type.IsArray ()) { + return false; + } + + Type elementType = variable.Type.GetArrayElementType (); + return typeof(StructureInstance).IsAssignableFrom (elementType); + } + + bool IsStructureVariable (LlvmIrGlobalVariable variable) + { + if (!typeof(StructureInstance).IsAssignableFrom (variable.Type)) { + return false; + } + + if (variable.Value != null && !typeof(StructureInstance).IsAssignableFrom (variable.Value.GetType ())) { + throw new InvalidOperationException ("Internal error: variable referring to a structure instance must have its value set to either `null` or an instance of the StructureInstance class"); + } + + return true; + } + + void EnsureValidGlobalVariableType (LlvmIrGlobalVariable variable) + { + if (variable is LlvmIrStringVariable) { + throw new ArgumentException ("Internal error: do not add instances of LlvmIrStringVariable, simply set variable value to the desired string instead"); + } + } + + /// + /// Looks up LLVM variable for a previously registered string given in . If a variable isn't found, + /// an exception is thrown. This is primarily used by to look up variables related to strings which + /// are part of structure instances. Such strings **MUST** be registered by and, thus, failure to do + /// so is an internal error. + /// + public LlvmIrStringVariable LookupRequiredVariableForString (string value) + { + LlvmIrStringVariable? sv = stringManager?.Lookup (value); + if (sv == null) { + throw new InvalidOperationException ($"Internal error: string '{value}' wasn't registered with string manager"); + } + + return sv; + } + + public string LookupRequiredBufferVariableName (StructureInstance structure, StructureMemberInfo smi) + { + if (bufferManager == null) { + throw new InvalidOperationException ("Internal error: no buffer variables have been registed with the buffer manager"); + } + + string? variableName = bufferManager.GetBufferVariableName (structure, smi); + if (String.IsNullOrEmpty (variableName)) { + throw new InvalidOperationException ($"Internal error: buffer for member '{smi.Info.Name}' of structure '{structure.Info.Name}' (index {structure.IndexInArray}) not found"); + } + + return variableName; + } + + /// + /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed + /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to + /// the new one. + /// + public LlvmIrFunctionAttributeSet AddAttributeSet (LlvmIrFunctionAttributeSet attrSet) + { + if (attributeSets == null) { + attributeSets = new Dictionary (); + } + + if (attributeSets.TryGetValue (attrSet, out LlvmIrFunctionAttributeSet existingSet)) { + return existingSet; + } + attrSet.Number = (uint)attributeSets.Count; + attributeSets.Add (attrSet, attrSet); + + return attrSet; + } + + /// + /// Add a new external function declaration. The caller MUST use the returned value to refer to the function, instead + /// of the one passed as parameter, since this function de-duplicates function declarations and may return a previously + /// added one that's identical to the new one. + /// + public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) + { + if (externalFunctions == null) { + externalFunctions = new Dictionary (); + } + + if (externalFunctions.TryGetValue (func, out LlvmIrFunction existingFunc)) { + return existingFunc; + } + + externalFunctions.Add (func, func); + return func; + } + + public LlvmIrFunction DeclareExternalFunction (LlvmIrFunctionSignature sig, LlvmIrFunctionAttributeSet? attrSet = null) + { + return DeclareExternalFunction (new LlvmIrFunction (sig, attrSet)); + } + + /// + /// Since LLVM IR is strongly typed, it requires each structure to be properly declared before it is + /// used throughout the code. This method uses reflection to scan the managed type + /// and record the information for future use. The returned structure contains + /// the description. It is used later on not only to declare the structure in output code, but also to generate + /// data from instances of . This method is typically called from the + /// method. + /// + public StructureInfo MapStructure () + { + if (structures == null) { + structures = new Dictionary (); + } + + Type t = typeof(T); + if (!t.IsClass && !t.IsValueType) { + throw new InvalidOperationException ($"{t} must be a class or a struct"); + } + + // TODO: check if already there + if (structures.TryGetValue (t, out StructureInfo sinfo)) { + return (StructureInfo)sinfo; + } + + var ret = new StructureInfo (this, t); + structures.Add (t, ret); + + return ret; + } + + internal StructureInfo GetStructureInfo (Type type) + { + if (structures == null) { + throw new InvalidOperationException ($"Internal error: no structures have been mapped, cannot return info for {type}"); + } + + foreach (var kvp in structures) { + StructureInfo si = kvp.Value; + if (si.Type != type) { + continue; + } + + return si; + } + + throw new InvalidOperationException ($"Internal error: unmapped structure {type}"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs new file mode 100644 index 00000000000..131b79dcaa6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleAArch64 : LlvmIrModuleTarget +{ + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "aarch64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; + public override uint NativePointerSize => 8; + public override bool Is64Bit => true; + + public LlvmIrModuleAArch64 () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 8, abi: 8, pref: 32), // i8 + new LlvmIrDataLayoutIntegerAlignment (size: 16, abi: 16, pref: 32), // i16 + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + new LlvmIrDataLayoutIntegerAlignment (size: 128, abi: 128), // i128 + }, + + NativeIntegerWidths = new List { 32, 64}, + StackAlignment = 128, + }; + } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics,+v8a")); + } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs new file mode 100644 index 00000000000..f7ff54f8a48 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleArmV7a : LlvmIrModuleTarget +{ + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "armv7-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; + public override uint NativePointerSize => 4; + public override bool Is64Bit => false; + + public LlvmIrModuleArmV7a () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + }, + + FunctionPointerAlignment = new LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType.Independent, abi: 8), + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, + + VectorAlignment = new List { + new LlvmIrDataLayoutVectorAlignment (size: 128, abi: 64, pref: 128), // v128 + }, + + AggregateObjectAlignment = new LlvmIrDataLayoutAggregateObjectAlignment (abi: 0, pref: 32), + NativeIntegerWidths = new List { 32 }, + StackAlignment = 64, + }; + } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp")); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs new file mode 100644 index 00000000000..ad9a1d9c604 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -0,0 +1,73 @@ +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +abstract class LlvmIrModuleTarget +{ + public abstract LlvmIrDataLayout DataLayout { get; } + public abstract string Triple { get; } + public abstract AndroidTargetArch TargetArch { get; } + public abstract uint NativePointerSize { get; } + public abstract bool Is64Bit { get; } + + /// + /// Adds target-specific attributes which are common to many attribute sets. Usually this specifies CPU type, tuning and + /// features. + /// + public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + {} + + public virtual void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + {} + + public virtual void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + if (!parameter.NoUndef.HasValue) { + parameter.NoUndef = true; + } + } + + /// + /// Sets the zeroext or signext attributes on the parameter, if not set previously and if + /// the parameter is a small integral type. Out of our supported architectures, all except AArch64 set + /// the flags, thus the reason to put this method in the base class. + /// + protected void SetIntegerParameterUpcastFlags (LlvmIrFunctionParameter parameter) + { + if (parameter.Type == typeof(bool) || + parameter.Type == typeof(byte) || + parameter.Type == typeof(char) || + parameter.Type == typeof(ushort)) + { + if (!parameter.ZeroExt.HasValue) { + parameter.ZeroExt = true; + parameter.SignExt = false; + } + return; + } + + if (parameter.Type == typeof(sbyte) || + parameter.Type == typeof(short)) + { + if (!parameter.SignExt.HasValue) { + parameter.SignExt = true; + parameter.ZeroExt = false; + } + } + } + + public virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + return maxFieldAlignment; + } + + protected LlvmIrMetadataItem GetFlagsMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem? flags = manager.GetItem (LlvmIrKnownMetadata.LlvmModuleFlags); + if (flags == null) { + flags = manager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); + } + + return flags; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs new file mode 100644 index 00000000000..44eede79e54 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleX64 : LlvmIrModuleTarget +{ + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "x86_64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; + public override uint NativePointerSize => 8; + public override bool Is64Bit => true; + + public LlvmIrModuleX64 () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, + }, + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, + }, + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, + }, + }, + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, + + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 128), // f80 + }, + + NativeIntegerWidths = new List { 8, 16, 32, 64 }, + StackAlignment = 128, + }; + } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("x86-64")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } + + public override int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + // System V ABI for x86_64 mandates that any aggregates 16 bytes or more long will + // be aligned at at least 16 bytes + // + // See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf (Section '3.1.2 Data Representation', "Aggregates and Unions") + // + if (dataSize >= 16 && maxFieldAlignment < 16) { + return 16; + } + + return maxFieldAlignment; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs new file mode 100644 index 00000000000..7e43558cb84 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleX86 : LlvmIrModuleTarget +{ + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "i686-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; + public override uint NativePointerSize => 4; + public override bool Is64Bit => false; + + public LlvmIrModuleX86 () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, + }, + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, + }, + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, + }, + }, + + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 64, abi: 32, pref: 64), // f64 + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 32), // f80 + }, + + NativeIntegerWidths = new List { 8, 16, 32 }, + StackAlignment = 128, + }; + } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("i686")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + attrSet.Add (new StackrealignFunctionAttribute ()); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs new file mode 100644 index 00000000000..a20cae23a8b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVMIR; + +sealed class LlvmIrStringGroup +{ + public ulong Count; + public readonly string? Comment; + public readonly List Strings = new List (); + + public LlvmIrStringGroup (string? comment = null) + { + Comment = comment; + Count = 0; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs new file mode 100644 index 00000000000..9a49e544428 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVMIR; + +partial class LlvmIrModule +{ + protected class LlvmIrStringManager + { + Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); + List stringGroups = new List (); + + LlvmIrStringGroup defaultGroup; + + public List StringGroups => stringGroups; + + public LlvmIrStringManager () + { + defaultGroup = new LlvmIrStringGroup (); + stringGroupCache.Add (String.Empty, defaultGroup); + stringGroups.Add (defaultGroup); + } + + public LlvmIrStringVariable Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) + { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + LlvmIrStringVariable? stringVar; + if (stringSymbolCache.TryGetValue (value, out stringVar) && stringVar != null) { + return stringVar; + } + + LlvmIrStringGroup? group; + string groupPrefix; + if (String.IsNullOrEmpty (groupName) || String.Compare ("str", groupName, StringComparison.Ordinal) == 0) { + group = defaultGroup; + groupPrefix = ".str"; + } else if (!stringGroupCache.TryGetValue (groupName, out group) || group == null) { + group = new LlvmIrStringGroup (groupComment ?? groupName); + stringGroups.Add (group); + stringGroupCache[groupName] = group; + groupPrefix = $".{groupName}"; + } else { + groupPrefix = $".{groupName}"; + } + + string symbolName = $"{groupPrefix}.{group.Count++}"; + if (!String.IsNullOrEmpty (symbolSuffix)) { + symbolName = $"{symbolName}_{symbolSuffix}"; + } + + stringVar = new LlvmIrStringVariable (symbolName, value); + group.Strings.Add (stringVar); + stringSymbolCache.Add (value, stringVar); + + return stringVar; + } + + public LlvmIrStringVariable? Lookup (string value) + { + if (stringSymbolCache.TryGetValue (value, out LlvmIrStringVariable? sv)) { + return sv; + } + + return null; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 0abda63bfdd..7f741490ce0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -1,34 +1,226 @@ using System; +using System.Globalization; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVMIR; + +[Flags] +enum LlvmIrVariableWriteOptions +{ + None = 0x0000, + ArrayWriteIndexComments = 0x0001, + ArrayFormatInRows = 0x0002, +} + +abstract class LlvmIrVariable : IEquatable { + public abstract bool Global { get; } + public abstract string NamePrefix { get; } + + public string? Name { get; protected set; } + public Type Type { get; protected set; } + public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + + /// + /// Number of columns an array that is written in rows should have. By default, arrays are written one item in a line, but + /// when the flag is set in , then + /// the value of this property dictates how many items are to be placed in a single row. + /// + public uint ArrayStride { get; set; } = 8; + public object? Value { get; set; } + public string? Comment { get; set; } + + /// + /// Both global and local variables will want their names to matter in equality checks, but function + /// parameters must not take it into account, thus this property. If set to false, + /// will ignore name when checking for equality. + protected bool NameMatters { get; set; } = true; + /// - /// Base class for all the variable (local and global) as well as function parameter classes. + /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global + /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are + /// referenced. /// - abstract class LlvmIrVariable + public virtual string Reference { + get { + if (String.IsNullOrEmpty (Name)) { + throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); + } + + return $"{NamePrefix}{Name}"; + } + } + + /// + /// + /// Certain data must be calculated when the target architecture is known, because it may depend on certain aspects of + /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output + /// stream, allowing updating of any such data as described above. + /// + /// + /// First parameter passed to the callback is the variable itself, second parameter is the current + /// and the third is the value previously assigned to + /// + /// + public Action? BeforeWriteCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? BeforeWriteCallbackCallerState { get; set; } + + /// + /// + /// Callback used when processing array variables, called for each item of the array in order to obtain the item's comment, if any. + /// + /// + /// The first argument is the variable which contains the array, second is the item index, third is the item value and fourth is + /// the caller state object, previously assigned to the property. The callback + /// can return an empty string or null, in which case no comment is written. + /// + /// + public Func? GetArrayItemCommentCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? GetArrayItemCommentCallbackCallerState { get; set; } + + /// + /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. + /// + protected LlvmIrVariable (Type type, string? name = null) { - public LlvmNativeFunctionSignature? NativeFunction { get; } - public string? Name { get; } - public Type Type { get; } + Type = type; + Name = name; + } - // Used when we need a pointer to pointer (etc) or when the type itself is not a pointer but we need one - // in a given context (e.g. function parameters) - public bool IsNativePointer { get; } + public override int GetHashCode () + { + return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); + } - protected LlvmIrVariable (Type type, string name, LlvmNativeFunctionSignature? signature, bool isNativePointer) - { - Type = type ?? throw new ArgumentNullException (nameof (type)); - Name = name; - NativeFunction = signature; - IsNativePointer = isNativePointer; + public override bool Equals (object obj) + { + var irVar = obj as LlvmIrVariable; + if (irVar == null) { + return false; } - protected LlvmIrVariable (LlvmIrVariable variable, string name, bool isNativePointer) - { - Type = variable?.Type ?? throw new ArgumentNullException (nameof (variable)); - Name = name; - NativeFunction = variable.NativeFunction; - IsNativePointer = isNativePointer; + return Equals (irVar); + } + + public virtual bool Equals (LlvmIrVariable other) + { + if (other == null) { + return false; } + + return + Global == other.Global && + Type == other.Type && + String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && + (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); } } + +class LlvmIrLocalVariable : LlvmIrVariable +{ + public override bool Global => false; + public override string NamePrefix => "%"; + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in + /// which case they will be assigned a sequential number when function code is generated. + /// + public LlvmIrLocalVariable (Type type, string? name = null) + : base (type, name) + {} + + public void AssignNumber (ulong n) + { + Name = n.ToString (CultureInfo.InvariantCulture); + } +} + +class LlvmIrGlobalVariable : LlvmIrVariable +{ + /// + /// By default a global variable is constant and exported. + /// + public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; + + public override bool Global => true; + public override string NamePrefix => "@"; + + /// + /// Specify variable options. If omitted, it defaults to . + /// + /// + public virtual LlvmIrVariableOptions? Options { get; set; } + + public bool ZeroInitializeArray { get; set; } + public ulong ArrayItemCount { get; set; } + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is required because global variables must be named. + /// + public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) + : base (type, name) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Options = options; + } + + /// + /// Constructs a local variable and sets the property to and + /// property to its type. For that reason, **must not** be null. is + /// required because global variables must be named. + /// + public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? options = null) + : this ((value ?? throw new ArgumentNullException (nameof (value))).GetType (), name, options) + { + Value = value; + } + + /// + /// This is, unfortunately, needed to be able to address scenarios when a single symbol can have a different type when + /// generating output for a specific target (e.g. 32-bit vs 64-bit integer variables). If the variable requires such + /// type changes, this should be done at generation time from within the method. + /// + public void OverrideValueAndType (Type newType, object? newValue) + { + Type = newType; + Value = newValue; + } +} + +class LlvmIrStringVariable : LlvmIrGlobalVariable +{ + public LlvmIrStringVariable (string name, string value) + : base (typeof(string), name, LlvmIrVariableOptions.LocalString) + { + Value = value; + } +} + +/// +/// This is to address my dislike to have single-line variables separated by empty lines :P. +/// When an instance of this "variable" is first encountered, it enables variable grouping, that is +/// they will be followed by just a single newline. The next instance of this "variable" turns +/// grouping off, meaning the following variables will be followed by two newlines. +/// +class LlvmIrGroupDelimiterVariable : LlvmIrGlobalVariable +{ + public LlvmIrGroupDelimiterVariable () + : base (typeof(void), ".:!GroupDelimiter!:.", LlvmIrVariableOptions.LocalConstant) + {} +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs index 0c0b3c121b4..680881ca31f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -23,6 +23,7 @@ class LlvmIrVariableOptions /// public static readonly LlvmIrVariableOptions GlobalWritable = new LlvmIrVariableOptions { Writability = LlvmIrWritability.Writable, + AddressSignificance = LlvmIrAddressSignificance.LocalUnnamed, }; /// @@ -57,6 +58,7 @@ class LlvmIrVariableOptions Linkage = LlvmIrLinkage.Private, Writability = LlvmIrWritability.Constant, AddressSignificance = LlvmIrAddressSignificance.Unnamed, + RuntimePreemption = LlvmIrRuntimePreemption.Default, }; /// @@ -83,7 +85,7 @@ class LlvmIrVariableOptions }; public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; - public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; + public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.DSOLocal; public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.Default; public LlvmIrWritability Writability { get; set; } = LlvmIrWritability.Writable; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs deleted file mode 100644 index 628014eeb2a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - /// - /// References either a local or global variable. - /// - class LlvmIrVariableReference : LlvmIrVariable - { - public string Reference { get; } - - public LlvmIrVariableReference (Type type, string name, bool isGlobal, bool isNativePointer = false) - : base (type, name, signature: null, isNativePointer: isNativePointer) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - Reference = MakeReference (isGlobal, name); - } - - public LlvmIrVariableReference (LlvmNativeFunctionSignature signature, string name, bool isGlobal, bool isNativePointer = false) - : base (typeof(LlvmNativeFunctionSignature), name, signature, isNativePointer) - { - if (signature == null) { - throw new ArgumentNullException (nameof (signature)); - } - - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - - Reference = MakeReference (isGlobal, name); - } - - public LlvmIrVariableReference (LlvmIrVariable variable, bool isGlobal, bool isNativePointer = false) - : base (variable, variable?.Name, isNativePointer) - { - if (String.IsNullOrEmpty (variable?.Name)) { - throw new ArgumentException ("variable name must not be null or empty", nameof (variable)); - } - - Reference = MakeReference (isGlobal, variable?.Name); - } - - string MakeReference (bool isGlobal, string name) - { - return $"{(isGlobal ? '@' : '%')}{Name}"; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs deleted file mode 100644 index 01471c8199a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - /// - /// Contains signature/description of a native function. All the types used for parameters or return value must - /// be mappable to LLVM IR types. This class can be used to describe pointers to functions which have no corresponding - /// managed method (e.g. `xamarin_app_init` used by marshal methods). Additionally, an optional default value can be - /// specified, to be used whenever a variable of this type is emitted (e.g. - class LlvmNativeFunctionSignature - { - public Type ReturnType { get; } - public IList? Parameters { get; } - public object? FieldValue { get; set; } - - public LlvmNativeFunctionSignature (Type returnType, List? parameters = null) - { - ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); - Parameters = parameters?.Select (p => EnsureValidParameter (p))?.ToList ()?.AsReadOnly (); - - LlvmIrFunctionParameter EnsureValidParameter (LlvmIrFunctionParameter parameter) - { - if (parameter == null) { - throw new InvalidOperationException ("null parameters aren't allowed"); - } - - return parameter; - } - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs index 832a34c0fae..431d92b6229 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs @@ -25,7 +25,7 @@ public static bool IsNativePointerToPreallocatedBuffer (this MemberInfo mi, out public static bool PointsToSymbol (this MemberInfo mi, out string? symbolName) { var attr = mi.GetCustomAttribute (); - if (attr == null) { + if (attr == null || attr.PointsToSymbol == null) { symbolName = null; return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs index 5cc1dfa6589..066c788c93e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs @@ -1,68 +1,37 @@ using System; using System.Collections.Generic; -using System.IO; using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR { // TODO: add cache for members and data provider info - sealed class StructureInfo : IStructureInfo + sealed class StructureInfo { Type type; public Type Type => type; - public string Name { get; } = String.Empty; - public ulong Size { get; } - public List> Members { get; } = new List> (); + public string Name { get; } = String.Empty; + public ulong Size { get; } + public IList Members { get; } = new List (); public NativeAssemblerStructContextDataProvider? DataProvider { get; } - public int MaxFieldAlignment { get; private set; } = 0; - public bool HasStrings { get; private set; } - public bool HasPreAllocatedBuffers { get; private set; } + public ulong MaxFieldAlignment { get; private set; } = 0; + public bool HasStrings { get; private set; } + public bool HasPreAllocatedBuffers { get; private set; } + public bool HasPointers { get; private set; } - public bool IsOpaque => Members.Count == 0; - public string NativeTypeDesignator { get; } + public bool IsOpaque => Members.Count == 0; + public string NativeTypeDesignator { get; } - public StructureInfo (LlvmIrGenerator generator) + public StructureInfo (LlvmIrModule module, Type type) { - type = typeof(T); + this.type = type; Name = type.GetShortName (); - Size = GatherMembers (type, generator); + Size = GatherMembers (type, module); DataProvider = type.GetDataProvider (); NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; } - public void RenderDeclaration (LlvmIrGenerator generator) - { - TextWriter output = generator.Output; - generator.WriteStructureDeclarationStart (NativeTypeDesignator, Name, forOpaqueType: IsOpaque); - - if (IsOpaque) { - return; - } - - for (int i = 0; i < Members.Count; i++) { - StructureMemberInfo info = Members[i]; - string nativeType = LlvmIrGenerator.MapManagedTypeToNative (info.MemberType); - if (info.Info.IsNativePointer ()) { - nativeType += "*"; - } - - // TODO: nativeType can be an array, update to indicate that (and get the size) - string arraySize; - if (info.IsNativeArray) { - arraySize = $"[{info.ArrayElements}]"; - } else { - arraySize = String.Empty; - } - - var comment = $"{nativeType} {info.Info.Name}{arraySize}"; - generator.WriteStructureDeclarationField (info.IRType, comment, i == Members.Count - 1); - } - - generator.WriteStructureDeclarationEnd (); - } - - public string? GetCommentFromProvider (StructureMemberInfo smi, StructureInstance instance) + public string? GetCommentFromProvider (StructureMemberInfo smi, StructureInstance instance) { if (DataProvider == null || !smi.Info.UsesDataProvider ()) { return null; @@ -76,7 +45,7 @@ public void RenderDeclaration (LlvmIrGenerator generator) return ret; } - public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureInstance instance) + public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureInstance instance) { if (DataProvider == null) { return 0; @@ -85,7 +54,7 @@ public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureIns return DataProvider.GetBufferSize (instance.Obj, smi.Info.Name); } - ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = true) + ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) { ulong size = 0; foreach (MemberInfo mi in type.GetMembers ()) { @@ -93,13 +62,17 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = t continue; } - var info = new StructureMemberInfo (mi, generator); + var info = new StructureMemberInfo (mi, module); + if (info.IsNativePointer) { + HasPointers = true; + } + if (storeMembers) { Members.Add (info); size += info.Size; - if ((int)info.Alignment > MaxFieldAlignment) { - MaxFieldAlignment = (int)info.Alignment; + if (info.Alignment > MaxFieldAlignment) { + MaxFieldAlignment = info.Alignment; } } @@ -116,7 +89,7 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = t // The presence of strings/buffers is important at the generation time as it is used to decide whether we need separate stream writers for them and // if the owning structure does **not** have any of those, the generated code would be invalid if (info.IsIRStruct ()) { - GatherMembers (info.MemberType, generator, storeMembers: false); + GatherMembers (info.MemberType, module, storeMembers: false); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs index 3b171485f5e..3c0c53ab471 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs @@ -1,37 +1,62 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR { - class StructureInstance + abstract class StructureInstance { - Dictionary, StructurePointerData>? pointees; + StructureInfo info; - public T Obj { get; } + public object? Obj { get; } + public Type Type => info.Type; + public StructureInfo Info => info; - public StructureInstance (T instance) - { - Obj = instance; - } + /// + /// Do **not** set this property, it is used internally by , + /// and when dealing with arrays of objects where each + /// array index contains the same object instance + /// + internal ulong IndexInArray { get; set; } - public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) + /// + /// This is a cludge to support zero-initialized structures. In order to output proper variable type + /// when a structure is used, the generator must be able to read the structure descrption, which is + /// provided in the property and, thus, it requires a variable of structural type to + /// **always** have a non-null value. To support zero initialization of such structures, this property + /// can be set to true + /// + public bool IsZeroInitialized { get; set; } + + protected StructureInstance (StructureInfo info, object instance) { - if (pointees == null) { - pointees = new Dictionary, StructurePointerData> (); + if (instance == null) { + throw new ArgumentNullException (nameof (instance)); } - pointees.Add (smi, new StructurePointerData (variableName, dataSize)); - } - - public StructurePointerData? GetPointerData (StructureMemberInfo smi) - { - if (pointees != null && pointees.TryGetValue (smi, out StructurePointerData ssd)) { - return ssd; + if (!info.Type.IsAssignableFrom (instance.GetType ())) { + throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance)); } - return null; + this.info = info; + Obj = instance; } } + + /// + /// Represents a typed structure instance, derived from the class. The slightly weird + /// approach is because on one hand we need to operate on a heterogenous set of structures (in which generic types would + /// only get in the way), but on the other hand we need to be able to get the structure type (whose instance is in + /// and ) only by looking at the **type**. This is needed in situations when we have + /// an array of some structures that is empty - we wouldn't be able to gleam the structure type from any instance and we still + /// need to output a stronly typed LLVM IR declaration of the structure array. With this class, most of the code will use the + /// abstract type, and knowing we have only one non-abstract implementation of the class allows + /// us to use StructureInstance<T> in a cast, to get T via reflection. + /// + sealed class StructureInstance : StructureInstance + { + public T? Instance => (T)Obj; + + public StructureInstance (StructureInfo info, T instance) + : base (info, instance) + {} + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs index c09d46910fe..1f313a34f31 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs @@ -3,7 +3,7 @@ namespace Xamarin.Android.Tasks.LLVMIR { - sealed class StructureMemberInfo + sealed class StructureMemberInfo { public string IRType { get; } public MemberInfo Info { get; } @@ -27,7 +27,7 @@ sealed class StructureMemberInfo public bool IsInlineArray { get; } public bool NeedsPadding { get; } - public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) + public StructureMemberInfo (MemberInfo mi, LlvmIrModule module) { Info = mi; @@ -38,18 +38,19 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) }; ulong size = 0; + bool isPointer = false; if (MemberType != typeof(string) && !MemberType.IsArray && (MemberType.IsStructure () || MemberType.IsClass)) { IRType = $"%struct.{MemberType.GetShortName ()}"; // TODO: figure out how to get structure size if it isn't a pointer } else { - IRType = generator.MapManagedTypeToIR (MemberType, out size); + IRType = LlvmIrGenerator.MapToIRType (MemberType, out size, out isPointer); } - IsNativePointer = IRType[IRType.Length - 1] == '*'; + IsNativePointer = isPointer; if (!IsNativePointer) { IsNativePointer = mi.IsNativePointer (); if (IsNativePointer) { - IRType += "*"; + IRType = LlvmIrGenerator.IRPointerType; } } @@ -60,14 +61,18 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) Alignment = 0; if (IsNativePointer) { - size = (ulong)generator.PointerSize; + size = 0; // Real size will be determined when code is generated and we know the target architecture } else if (mi.IsInlineArray ()) { + if (!MemberType.IsArray) { + throw new InvalidOperationException ($"Internal error: member {mi.Name} of structure {mi.DeclaringType.Name} is marked as inline array, but is not of an array type."); + } + IsInlineArray = true; IsNativeArray = true; NeedsPadding = mi.InlineArrayNeedsPadding (); int arrayElements = mi.GetInlineArraySize (); if (arrayElements < 0) { - arrayElements = GetArraySizeFromProvider (typeof(T).GetDataProvider (), mi.Name); + arrayElements = GetArraySizeFromProvider (MemberType.GetDataProvider (), mi.Name); } if (arrayElements < 0) { @@ -77,7 +82,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) IRType = $"[{arrayElements} x {IRType}]"; ArrayElements = (ulong)arrayElements; } else if (this.IsIRStruct ()) { - IStructureInfo si = generator.GetStructureInfo (MemberType); + StructureInfo si = module.GetStructureInfo (MemberType); size = si.Size; Alignment = (ulong)si.MaxFieldAlignment; } @@ -92,7 +97,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) } } - public object? GetValue (T instance) + public object? GetValue (object instance) { if (Info is FieldInfo fi) { return fi.GetValue (instance); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs deleted file mode 100644 index 8e23e81b1e6..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Xamarin.Android.Tasks.LLVMIR -{ - sealed class StructurePointerData - { - public string? VariableName { get; } - public ulong Size { get; } - - public StructurePointerData (string? name, ulong size) - { - VariableName = name; - Size = size; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs index fd41c1790e8..b50b7290d90 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs @@ -1,8 +1,8 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Reflection; -using Xamarin.Android.Tasks; - namespace Xamarin.Android.Tasks.LLVMIR { static class TypeUtilities @@ -46,7 +46,7 @@ public static bool IsStructure (this Type type) type != typeof (object); } - public static bool IsIRStruct (this StructureMemberInfo smi) + public static bool IsIRStruct (this StructureMemberInfo smi) { Type type = smi.MemberType; @@ -73,5 +73,113 @@ public static bool IsNativeClass (this Type t) var attr = t.GetCustomAttribute (); return attr != null; } + + public static bool ImplementsInterface (this Type type, Type requiredIfaceType) + { + if (type == null || requiredIfaceType == null) { + return false; + } + + if (type == requiredIfaceType) { + return true; + } + + bool generic = requiredIfaceType.IsGenericType; + foreach (Type iface in type.GetInterfaces ()) { + if (iface == requiredIfaceType) { + return true; + } + + if (generic) { + if (!iface.IsGenericType) { + continue; + } + + if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { + return true; + } + } + } + + return false; + } + + public static bool IsStructureInstance (this Type type, out Type? structureType) + { + structureType = null; + if (!type.IsGenericType) { + return false; + } + + if (type.GetGenericTypeDefinition () != typeof(StructureInstance<>)) { + return false; + } + + structureType = type.GetGenericArguments ()[0]; + return true; + } + + /// + /// Return element type of a single-dimensional (with one exception, see below) array. Parameter **MUST** + /// correspond to one of the following array types: T[], ICollection<T> or IDictionary<string, string>. The latter is + /// used to comfortably represent name:value arrays, which are output as single dimensional arrays in the native code. + /// + /// + /// Thrown when is not one of the array types listed above. + /// + public static Type GetArrayElementType (this Type type) + { + if (type.IsArray) { + return type.GetElementType (); + } + + if (!type.IsGenericType) { + throw WrongTypeException (); + } + + Type genericType = type.GetGenericTypeDefinition (); + if (genericType.ImplementsInterface (typeof(ICollection<>))) { + Type[] genericArgs = type.GetGenericArguments (); + return genericArgs[0]; + } + + if (!genericType.ImplementsInterface (typeof(IDictionary))) { + throw WrongTypeException (); + } + + return typeof(string); + + // Dictionary + Exception WrongTypeException () => new InvalidOperationException ($"Internal error: type '{type}' is not an array, ICollection or IDictionary"); + } + + /// + /// Determine whether type represents an array, in our understanding. That means the type has to be + /// a standard single-dimensional language array (i.e. T[]), implement ICollection<T> together with ICollection or, + /// as a special case for name:value pair collections, implement IDictionary<string, string> + /// + public static bool IsArray (this Type t) + { + if (t.IsPrimitive) { + return false; + } + + if (t == typeof(string)) { + return false; + } + + if (t.IsArray) { + if (t.GetArrayRank () > 1) { + throw new NotSupportedException ("Internal error: multi-dimensional arrays aren't supported"); + } + + return true; + } + + // TODO: cache results here + // IDictionary is a special case for name:value string arrays which we use for some constructs. + return (t.ImplementsInterface (typeof(ICollection<>)) && t.ImplementsInterface (typeof(ICollection))) || + t.ImplementsInterface (typeof(IDictionary)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs deleted file mode 100644 index 81aa5dfdb9a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class X64LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"; - public override int PointerSize => 8; - protected override string Triple => "x86_64-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { - new FramePointerFunctionAttribute ("none"), - new TargetCpuFunctionAttribute ("x86-64"), - new TargetFeaturesFunctionAttribute ("+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), - new TuneCpuFunctionAttribute ("generic"), - }; - - public X64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) - { - // System V ABI for x86_64 mandates that any aggregates 16 bytes or more long will - // be aligned at at least 16 bytes - // - // See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf (Section '3.1.2 Data Representation', "Aggregates and Unions") - // - if (dataSize >= 16 && maxFieldAlignment < 16) { - return 16; - } - - return maxFieldAlignment; - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs deleted file mode 100644 index d779bf25bb2..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class X86LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"; - public override int PointerSize => 4; - protected override string Triple => "i686-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { - new FramePointerFunctionAttribute ("none"), - new TargetCpuFunctionAttribute ("i686"), - new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87"), - new TuneCpuFunctionAttribute ("generic"), - new StackrealignFunctionAttribute (), - }; - - public X86LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 229ba0397d7..f87cf118971 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -14,8 +14,10 @@ namespace Xamarin.Android.Tasks { - class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer + partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { + const string GetFunctionPointerVariableName = "get_function_pointer"; + // This is here only to generate strongly-typed IR internal sealed class MonoClass {} @@ -92,8 +94,8 @@ public MarshalMethodInfo (MarshalMethodEntry method, Type returnType, string nat } NativeSymbolName = nativeSymbolName; Parameters = new List { - new LlvmIrFunctionParameter (typeof (_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - new LlvmIrFunctionParameter (typeof (_jclass), "klass", isNativePointer: true), // jclass klass + new LlvmIrFunctionParameter (typeof (_JNIEnv), "env"), // JNIEnv *env + new LlvmIrFunctionParameter (typeof (_jclass), "klass"), // jclass klass }; ClassCacheIndex = (uint)classCacheIndex; } @@ -106,7 +108,7 @@ public override string GetComment (object data, string fieldName) var klass = EnsureType (data); if (String.Compare ("token", fieldName, StringComparison.Ordinal) == 0) { - return $"token 0x{klass.token:x}; class name: {klass.ClassName}"; + return $" token 0x{klass.token:x}; class name: {klass.ClassName}"; } return String.Empty; @@ -133,7 +135,7 @@ public override string GetComment (object data, string fieldName) var methodName = EnsureType (data); if (String.Compare ("id", fieldName, StringComparison.Ordinal) == 0) { - return $"id 0x{methodName.id:x}; name: {methodName.name}"; + return $" id 0x{methodName.id:x}; name: {methodName.name}"; } return String.Empty; @@ -143,11 +145,63 @@ public override string GetComment (object data, string fieldName) [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodNameDataProvider))] sealed class MarshalMethodName { + [NativeAssembler (Ignore = true)] + public ulong Id32; + + [NativeAssembler (Ignore = true)] + public ulong Id64; + [NativeAssembler (UsesDataProvider = true)] public ulong id; public string name; } + sealed class AssemblyCacheState + { + public Dictionary? AsmNameToIndexData32; + public Dictionary Hashes32; + public List Keys32; + public List Indices32; + + public Dictionary? AsmNameToIndexData64; + public Dictionary Hashes64; + public List Keys64; + public List Indices64; + } + + sealed class MarshalMethodsWriteState + { + public AssemblyCacheState AssemblyCacheState; + public LlvmIrFunctionAttributeSet AttributeSet; + public Dictionary UniqueAssemblyId; + public Dictionary UsedBackingFields; + public LlvmIrVariable GetFunctionPtrVariable; + public LlvmIrFunction GetFunctionPtrFunction; + } + + sealed class MarshalMethodAssemblyIndexValuePlaceholder : LlvmIrInstructionArgumentValuePlaceholder + { + MarshalMethodInfo mmi; + AssemblyCacheState acs; + + public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, AssemblyCacheState acs) + { + this.mmi = mmi; + this.acs = acs; + } + + public override object? GetValue (LlvmIrModuleTarget target) + { + // What a monstrosity... + string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; + Dictionary asmNameToIndex = target.Is64Bit ? acs.AsmNameToIndexData64 : acs.AsmNameToIndexData32; + if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { + throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); + } + return asmIndex; + } + } + static readonly Dictionary jniSimpleTypeMap = new Dictionary { { 'Z', typeof(bool) }, { 'B', typeof(byte) }, @@ -176,29 +230,14 @@ sealed class MarshalMethodName IDictionary> marshalMethods; TaskLoggingHelper logger; - StructureInfo monoImage; - StructureInfo marshalMethodsClass; - StructureInfo marshalMethodName; - StructureInfo monoClass; - StructureInfo<_JNIEnv> _jniEnvSI; - StructureInfo<_jobject> _jobjectSI; - StructureInfo<_jclass> _jclassSI; - StructureInfo<_jstring> _jstringSI; - StructureInfo<_jthrowable> _jthrowableSI; - StructureInfo<_jarray> _jarraySI; - StructureInfo<_jobjectArray> _jobjectArraySI; - StructureInfo<_jbooleanArray> _jbooleanArraySI; - StructureInfo<_jbyteArray> _jbyteArraySI; - StructureInfo<_jcharArray> _jcharArraySI; - StructureInfo<_jshortArray> _jshortArraySI; - StructureInfo<_jintArray> _jintArraySI; - StructureInfo<_jlongArray> _jlongArraySI; - StructureInfo<_jfloatArray> _jfloatArraySI; - StructureInfo<_jdoubleArray> _jdoubleArraySI; + StructureInfo marshalMethodsManagedClassStructureInfo; + StructureInfo marshalMethodNameStructureInfo; List methods; List> classes = new List> (); + LlvmIrCallMarker defaultCallMarker; + readonly bool generateEmptyCode; /// @@ -209,6 +248,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.numberOfAssembliesInApk = numberOfAssembliesInApk; this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); generateEmptyCode = true; + defaultCallMarker = LlvmIrCallMarker.Tail; } /// @@ -222,9 +262,10 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); generateEmptyCode = false; + defaultCallMarker = LlvmIrCallMarker.Tail; } - public override void Init () + void Init () { if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { return; @@ -354,7 +395,7 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry ClassName = klass, }; - classes.Add (new StructureInstance (mc)); + classes.Add (new StructureInstance (marshalMethodsManagedClassStructureInfo, mc)); } // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference @@ -541,64 +582,311 @@ void AddParameter (Type type) } // Every parameter which isn't a primitive type becomes a pointer - parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name, isNativePointer: type.IsNativeClass ())); + parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name)); } } - protected override void InitGenerator (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - generator.InitCodeOutput (); + MapStructures (module); + + Init (); + AddAssemblyImageCache (module, out AssemblyCacheState acs); + + // class cache + module.AddGlobalVariable ("marshal_methods_number_of_classes", (uint)classes.Count, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable ("marshal_methods_class_cache", classes, LlvmIrVariableOptions.GlobalWritable); + + // Marshal methods class names + var mm_class_names = new List (); + foreach (StructureInstance klass in classes) { + mm_class_names.Add (klass.Instance.ClassName); + } + module.AddGlobalVariable ("mm_class_names", mm_class_names, LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); + + AddMarshalMethodNames (module, acs); + (LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) = AddXamarinAppInitFunction (module); + + AddMarshalMethods (module, acs, getFunctionPtrVariable, getFunctionPtrFunction); } - protected override void MapStructures (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - monoImage = generator.MapStructure (); - monoClass = generator.MapStructure (); - marshalMethodsClass = generator.MapStructure (); - marshalMethodName = generator.MapStructure (); - _jniEnvSI = generator.MapStructure<_JNIEnv> (); - _jobjectSI = generator.MapStructure<_jobject> (); - _jclassSI = generator.MapStructure<_jclass> (); - _jstringSI = generator.MapStructure<_jstring> (); - _jthrowableSI = generator.MapStructure<_jthrowable> (); - _jarraySI = generator.MapStructure<_jarray> (); - _jobjectArraySI = generator.MapStructure<_jobjectArray> (); - _jbooleanArraySI = generator.MapStructure<_jbooleanArray> (); - _jbyteArraySI = generator.MapStructure<_jbyteArray> (); - _jcharArraySI = generator.MapStructure<_jcharArray> (); - _jshortArraySI = generator.MapStructure<_jshortArray> (); - _jintArraySI = generator.MapStructure<_jintArray> (); - _jlongArraySI = generator.MapStructure<_jlongArray> (); - _jfloatArraySI = generator.MapStructure<_jfloatArray> (); - _jdoubleArraySI = generator.MapStructure<_jdoubleArray> (); + marshalMethodsManagedClassStructureInfo = module.MapStructure (); + marshalMethodNameStructureInfo = module.MapStructure (); } - protected override void Write (LlvmIrGenerator generator) + void AddMarshalMethods (LlvmIrModule module, AssemblyCacheState acs, LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) { - WriteAssemblyImageCache (generator, out Dictionary asmNameToIndex); - WriteClassCache (generator); - LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); - WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); + if (generateEmptyCode || methods == null || methods.Count == 0) { + return; + } - var mm_class_names = new List (); - foreach (StructureInstance klass in classes) { - mm_class_names.Add (klass.Obj.ClassName); + // This will make all the backing fields to appear in a block without empty lines separating them. + module.Add ( + new LlvmIrGroupDelimiterVariable () { + Comment = " Marshal methods backing fields, pointers to native functions" + } + ); + + var writeState = new MarshalMethodsWriteState { + AssemblyCacheState = acs, + AttributeSet = MakeMarshalMethodAttributeSet (module), + UsedBackingFields = new Dictionary (StringComparer.Ordinal), + UniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase), + GetFunctionPtrVariable = getFunctionPtrVariable, + GetFunctionPtrFunction = getFunctionPtrFunction, + }; + foreach (MarshalMethodInfo mmi in methods) { + CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; + string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; + + if (!writeState.UniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { + asmId = (ulong)writeState.UniqueAssemblyId.Count; + writeState.UniqueAssemblyId.Add (asmName, asmId); + } + + AddMarshalMethod (module, mmi, asmId, writeState); + } + + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, MarshalMethodsWriteState writeState) + { + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{asmId}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; + + if (!writeState.UsedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { + backingField = module.AddGlobalVariable (typeof(IntPtr), backingFieldName, null, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + writeState.UsedBackingFields.Add (backingFieldName, backingField); } - generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); - var uniqueMethods = new Dictionary (); + var funcComment = new StringBuilder (" Method: "); + funcComment.AppendLine (nativeCallback.FullName); + funcComment.Append (" Assembly: "); + funcComment.AppendLine (nativeCallback.Module.Assembly.Name.FullName); + funcComment.Append (" Registered: "); + funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); + funcComment.Append (" Implemented: "); + funcComment.AppendLine (method.Method.ImplementedMethod?.FullName ?? "none"); + + var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, writeState.AttributeSet) { + Comment = funcComment.ToString (), + }; + + WriteBody (func.Body); + module.Add (func); + + void WriteBody (LlvmIrFunctionBody body) + { + LlvmIrLocalVariable cb1 = func.CreateLocalVariable (typeof(IntPtr), "cb1"); + body.Load (backingField, cb1, tbaa: module.TbaaAnyPointer); + + LlvmIrLocalVariable isNullResult = func.CreateLocalVariable (typeof(bool), "isNull"); + body.Icmp (LlvmIrIcmpCond.Equal, cb1, null, isNullResult); + + var loadCallbackLabel = new LlvmIrFunctionLabelItem ("loadCallback"); + var callbackLoadedLabel = new LlvmIrFunctionLabelItem ("callbackLoaded"); + body.Br (isNullResult, loadCallbackLabel, callbackLoadedLabel); + + // Callback variable was null + body.Add (loadCallbackLabel); + + LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr), "get_func_ptr"); + body.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult, tbaa: module.TbaaAnyPointer); + + var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + LlvmIrInstructions.Call call = body.Call ( + writeState.GetFunctionPtrFunction, + arguments: new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken.ToUInt32 (), backingField }, + funcPointer: getFuncPtrResult + ); + + LlvmIrLocalVariable cb2 = func.CreateLocalVariable (typeof(IntPtr), "cb2"); + body.Load (backingField, cb2, tbaa: module.TbaaAnyPointer); + body.Br (callbackLoadedLabel); + + // Callback variable has just been set or it wasn't null + body.Add (callbackLoadedLabel); + LlvmIrLocalVariable fn = func.CreateLocalVariable (typeof(IntPtr), "fn"); + + // Preceding blocks are ordered from the newest to the oldest, so we need to pass the variables referring to our callback in "reverse" order + body.Phi (fn, cb2, body.PrecedingBlock1, cb1, body.PrecedingBlock2); + + var nativeFunc = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters); + nativeFunc.Signature.ReturnAttributes.NoUndef = true; + + var arguments = new List (); + foreach (LlvmIrFunctionParameter parameter in nativeFunc.Signature.Parameters) { + arguments.Add (new LlvmIrLocalVariable (parameter.Type, parameter.Name)); + } + LlvmIrLocalVariable? result = nativeFunc.ReturnsValue ? func.CreateLocalVariable (nativeFunc.Signature.ReturnType) : null; + call = body.Call (nativeFunc, result, arguments, funcPointer: fn); + call.CallMarker = LlvmIrCallMarker.Tail; + + body.Ret (nativeFunc.Signature.ReturnType, result); + } + } + + LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) + { + var attrSet = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (attrSet); + } + + (LlvmIrVariable getFuncPtrVariable, LlvmIrFunction getFuncPtrFunction) AddXamarinAppInitFunction (LlvmIrModule module) + { + var getFunctionPtrParams = new List { + new (typeof(uint), "mono_image_index") { + NoUndef = true, + }, + new (typeof(uint), "class_index") { + NoUndef = true, + }, + new (typeof(uint), "method_token") { + NoUndef = true, + }, + new (typeof(IntPtr), "target_ptr") { + NoUndef = true, + NonNull = true, + Align = 0, // 0 means use natural pointer alignment + Dereferenceable = 0, // ditto 👆 + IsCplusPlusReference = true, + }, + }; + + var getFunctionPtrComment = new StringBuilder (" "); + getFunctionPtrComment.Append (GetFunctionPointerVariableName); + getFunctionPtrComment.Append (" ("); + for (int i = 0; i < getFunctionPtrParams.Count; i++) { + if (i > 0) { + getFunctionPtrComment.Append (", "); + } + LlvmIrFunctionParameter parameter = getFunctionPtrParams[i]; + getFunctionPtrComment.Append (LlvmIrGenerator.MapManagedTypeToNative (parameter.Type)); + if (parameter.IsCplusPlusReference.HasValue && parameter.IsCplusPlusReference.Value) { + getFunctionPtrComment.Append ('&'); + } + getFunctionPtrComment.Append (' '); + getFunctionPtrComment.Append (parameter.Name); + } + getFunctionPtrComment.Append (')'); + + LlvmIrFunction getFunctionPtrFunc = new LlvmIrFunction ( + name: GetFunctionPointerVariableName, + returnType: typeof(void), + parameters: getFunctionPtrParams + ); + + LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable ( + typeof(IntPtr), + GetFunctionPointerVariableName, + null, + LlvmIrVariableOptions.LocalWritableInsignificantAddr, + getFunctionPtrComment.ToString () + ); + + var init_params = new List { + new (typeof(_JNIEnv), "env") { + NoCapture = true, + NoUndef = true, + ReadNone = true, + }, + new (typeof(IntPtr), "fn") { + NoUndef = true, + }, + }; + + var init_signature = new LlvmIrFunctionSignature ( + name: "xamarin_app_init", + returnType: typeof(void), + parameters: init_params + ); + + LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); + var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); + + // If `fn` is nullptr, print a message and abort... + // + // We must allocate result variables for both the null comparison and puts call here and with names, because + // labels and local unnamed variables must be numbered sequentially otherwise and the `AddIfThenElse` call will + // allocate up to 3 labels which would have been **defined** after these labels, but **used** before them - and + // thus the numbering sequence would be out of order and the .ll file wouldn't build. + var fnNullResult = xamarin_app_init.CreateLocalVariable (typeof(bool), "fnIsNull"); + LlvmIrVariable putsResult = xamarin_app_init.CreateLocalVariable (typeof(int), "putsResult"); + var ifThenInstructions = new List { + module.CreatePuts ("get_function_pointer MUST be specified\n", putsResult), + module.CreateAbort (), + new LlvmIrInstructions.Unreachable (), + }; + + module.AddIfThenElse (xamarin_app_init, fnNullResult, LlvmIrIcmpCond.Equal, init_params[1], null, ifThenInstructions); + + // ...otherwise store the pointer and return + xamarin_app_init.Body.Store (init_params[1], getFunctionPtrVariable, module.TbaaAnyPointer); + xamarin_app_init.Body.Ret (typeof(void)); + + module.Add (xamarin_app_init); + + return (getFunctionPtrVariable, getFunctionPtrFunc); + } + + LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) + { + var attrSet = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NofreeFunctionAttribute (), + new NorecurseFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (attrSet); + } + + void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) + { + var uniqueMethods = new Dictionary (); + if (!generateEmptyCode && methods != null) { foreach (MarshalMethodInfo mmi in methods) { string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + + if (!acs.AsmNameToIndexData32.TryGetValue (asmName, out uint idx32)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 32-bit cache array index"); } - ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); - if (uniqueMethods.ContainsKey (id)) { + if (!acs.AsmNameToIndexData64.TryGetValue (asmName, out uint idx64)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 64-bit cache array index"); + } + + ulong methodToken = (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + ulong id32 = ((ulong)idx32 << 32) | methodToken; + if (uniqueMethods.ContainsKey (id32)) { continue; } - uniqueMethods.Add (id, mmi); + + ulong id64 = ((ulong)idx64 << 32) | methodToken; + uniqueMethods.Add (id32, (mmi, id32, id64)); } } @@ -607,25 +895,35 @@ protected override void Write (LlvmIrGenerator generator) var mm_method_names = new List> (); foreach (var kvp in uniqueMethods) { ulong id = kvp.Key; - MarshalMethodInfo mmi = kvp.Value; + (MarshalMethodInfo mmi, ulong id32, ulong id64) = kvp.Value; RenderMethodNameWithParams (mmi.Method.NativeCallback, methodName); name = new MarshalMethodName { + Id32 = id32, + Id64 = id64, + // Tokens are unique per assembly - id = id, + id = 0, name = methodName.ToString (), }; - mm_method_names.Add (new StructureInstance (name)); + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); } // Must terminate with an "invalid" entry name = new MarshalMethodName { + Id32 = 0, + Id64 = 0, + id = 0, name = String.Empty, }; - mm_method_names.Add (new StructureInstance (name)); + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); - generator.WriteStructureArray (marshalMethodName, mm_method_names, LlvmIrVariableOptions.GlobalConstant, "mm_method_names"); + var mm_method_names_variable = new LlvmIrGlobalVariable (mm_method_names, "mm_method_names", LlvmIrVariableOptions.GlobalConstant) { + BeforeWriteCallback = UpdateMarshalMethodNameIds, + BeforeWriteCallbackCallerState = acs, + }; + module.Add (mm_method_names_variable); void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) { @@ -650,210 +948,150 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) } } - void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) + void UpdateMarshalMethodNameIds (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { - if (generateEmptyCode || methods == null || methods.Count == 0) { - return; - } + var mm_method_names = (List>)variable.Value; + bool is64Bit = target.Is64Bit; - var usedBackingFields = new HashSet (StringComparer.Ordinal); - foreach (MarshalMethodInfo mmi in methods) { - CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; - string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; - if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { - throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); - } - mmi.AssemblyCacheIndex = asmIndex; - WriteMarshalMethod (generator, mmi, get_function_pointer_ref, usedBackingFields); + foreach (StructureInstance mmn in mm_method_names) { + mmn.Instance.id = is64Bit ? mmn.Instance.Id64 : mmn.Instance.Id32; } } - void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) + // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache + void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) { - var backingFieldSignature = new LlvmNativeFunctionSignature ( - returnType: method.ReturnType, - parameters: method.Parameters - ) { - FieldValue = "null", + var assembly_image_cache = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = (ulong)numberOfAssembliesInApk, }; + module.Add (assembly_image_cache); - CecilMethodDefinition nativeCallback = method.Method.NativeCallback; - string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; - var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); - - if (!usedBackingFields.Contains (backingFieldName)) { - generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); - usedBackingFields.Add (backingFieldName); - } - - var func = new LlvmIrFunction ( - name: method.NativeSymbolName, - returnType: method.ReturnType, - attributeSetID: LlvmIrGenerator.FunctionAttributesJniMethods, - parameters: method.Parameters - ); - - generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); + acs = new AssemblyCacheState { + AsmNameToIndexData32 = new Dictionary (StringComparer.Ordinal), + Indices32 = new List (), - LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); - var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); - - LlvmIrFunctionLocalVariable isNullVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.Equal, callbackVariable1Ref, expectedValue: "null", resultVariableName: "isNull"); - var isNullVariableRef = new LlvmIrVariableReference (isNullVariable, isGlobal: false); - - const string loadCallbackLabel = "loadCallback"; - const string callbackLoadedLabel = "callbackLoaded"; - - generator.EmitBrInstruction (func, isNullVariableRef, loadCallbackLabel, callbackLoadedLabel); - - generator.WriteEOL (); - generator.EmitLabel (func, loadCallbackLabel); - LlvmIrFunctionLocalVariable getFunctionPointerVariable = generator.EmitLoadInstruction (func, get_function_pointer_ref, "get_func_ptr"); - var getFunctionPtrRef = new LlvmIrVariableReference (getFunctionPointerVariable, isGlobal: false); - - generator.EmitCall ( - func, - getFunctionPtrRef, - new List { - new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (typeof(LlvmIrVariableReference), backingFieldRef), - } - ); - - LlvmIrFunctionLocalVariable callbackVariable2 = generator.EmitLoadInstruction (func, backingFieldRef, "cb2"); - var callbackVariable2Ref = new LlvmIrVariableReference (callbackVariable2, isGlobal: false); - - generator.EmitBrInstruction (func, callbackLoadedLabel); - - generator.WriteEOL (); - generator.EmitLabel (func, callbackLoadedLabel); - - LlvmIrFunctionLocalVariable fnVariable = generator.EmitPhiInstruction ( - func, - backingFieldRef, - new List<(LlvmIrVariableReference variableRef, string label)> { - (callbackVariable1Ref, func.ImplicitFuncTopLabel), - (callbackVariable2Ref, loadCallbackLabel), - }, - resultVariableName: "fn" - ); - var fnVariableRef = new LlvmIrVariableReference (fnVariable, isGlobal: false); + AsmNameToIndexData64 = new Dictionary (StringComparer.Ordinal), + Indices64 = new List (), + }; - LlvmIrFunctionLocalVariable? result = generator.EmitCall ( - func, - fnVariableRef, - func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList () - ); + acs.Hashes32 = new Dictionary (); + acs.Hashes64 = new Dictionary (); + uint index = 0; + + foreach (string name in uniqueAssemblyNames) { + // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here + string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); + ulong hashFull32 = GetXxHash (name, is64Bit: false); + ulong hashClipped32 = GetXxHash (clippedName, is64Bit: false); + ulong hashFull64 = GetXxHash (name, is64Bit: true); + ulong hashClipped64 = GetXxHash (clippedName, is64Bit: true); + + // + // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the + // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. + // + acs.Hashes32.Add ((uint)Convert.ChangeType (hashFull32, typeof(uint)), (name, index)); + acs.Hashes32.Add ((uint)Convert.ChangeType (hashClipped32, typeof(uint)), (clippedName, index)); + acs.Hashes64.Add (hashFull64, (name, index)); + acs.Hashes64.Add (hashClipped64, (clippedName, index)); + + index++; + } - if (result != null) { - generator.EmitReturnInstruction (func, result); + acs.Keys32 = acs.Hashes32.Keys.ToList (); + acs.Keys32.Sort (); + for (int i = 0; i < acs.Keys32.Count; i++) { + (string name, uint idx) = acs.Hashes32[acs.Keys32[i]]; + acs.Indices32.Add (idx); + acs.AsmNameToIndexData32.Add (name, idx); } - generator.WriteFunctionEnd (func); - } + acs.Keys64 = acs.Hashes64.Keys.ToList (); + acs.Keys64.Sort (); + for (int i = 0; i < acs.Keys64.Count; i++) { + (string name, uint idx) = acs.Hashes64[acs.Keys64[i]]; + acs.Indices64.Add (idx); + acs.AsmNameToIndexData64.Add (name, idx); + } - LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) - { - var get_function_pointer_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), - new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token"), - new LlvmIrFunctionParameter (typeof(IntPtr), "target_ptr", isNativePointer: true, isCplusPlusReference: true) - } - ) { - FieldValue = "null", + var assembly_image_cache_hashes = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_hashes", LlvmIrVariableOptions.GlobalConstant) { + Comment = " Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array", + BeforeWriteCallback = UpdateAssemblyImageCacheHashes, + BeforeWriteCallbackCallerState = acs, + GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment, + GetArrayItemCommentCallbackCallerState = acs, }; + module.Add (assembly_image_cache_hashes); - const string GetFunctionPointerFieldName = "get_function_pointer"; - generator.WriteVariable (GetFunctionPointerFieldName, get_function_pointer_sig, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + var assembly_image_cache_indices = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_indices", LlvmIrVariableOptions.GlobalConstant) { + WriteOptions = LlvmIrVariableWriteOptions.ArrayWriteIndexComments | LlvmIrVariableWriteOptions.ArrayFormatInRows, + BeforeWriteCallback = UpdateAssemblyImageCacheIndices, + BeforeWriteCallbackCallerState = acs, + }; + module.Add (assembly_image_cache_indices); + } - var fnParameter = new LlvmIrFunctionParameter (get_function_pointer_sig, "fn"); - var func = new LlvmIrFunction ( - name: "xamarin_app_init", - returnType: typeof (void), - attributeSetID: LlvmIrGenerator.FunctionAttributesXamarinAppInit, - parameters: new List { - fnParameter, - } - ); + void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; + Type type; - generator.WriteFunctionStart (func); - generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); - generator.WriteFunctionEnd (func); + if (target.Is64Bit) { + value = acs.Keys64; + type = typeof(List); + } else { + value = acs.Keys32; + type = typeof(List); + } - return new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (type, value); } - void WriteClassCache (LlvmIrGenerator generator) + string? GetAssemblyImageCacheItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) { - uint marshal_methods_number_of_classes = (uint)classes.Count; + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + + string name; + uint i; + if (target.Is64Bit) { + var v64 = (ulong)value; + name = acs.Hashes64[v64].name; + i = acs.Hashes64[v64].index; + } else { + var v32 = (uint)value; + name = acs.Hashes32[v32].name; + i = acs.Hashes32[v32].index; + } - generator.WriteVariable (nameof (marshal_methods_number_of_classes), marshal_methods_number_of_classes); - generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); + return $" {index}: {name} => 0x{value:x} => {i}"; } - // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache - void WriteAssemblyImageCache (LlvmIrGenerator generator, out Dictionary asmNameToIndex) + void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { - bool is64Bit = generator.Is64Bit; - generator.WriteStructureArray (monoImage, (ulong)numberOfAssembliesInApk, "assembly_image_cache", isArrayOfPointers: true); + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; - var asmNameToIndexData = new Dictionary (StringComparer.Ordinal); - if (is64Bit) { - WriteHashes (); + if (target.Is64Bit) { + value = acs.Indices64; } else { - WriteHashes (); + value = acs.Indices32; } - asmNameToIndex = asmNameToIndexData; - - void WriteHashes () where T: struct - { - var hashes = new Dictionary (); - uint index = 0; - - foreach (string name in uniqueAssemblyNames) { - // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here - string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); - ulong hashFull = HashName (name, is64Bit); - ulong hashClipped = HashName (clippedName, is64Bit); - - // - // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the - // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. - // - hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); - hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); - - index++; - } - List keys = hashes.Keys.ToList (); - keys.Sort (); - - generator.WriteCommentLine ("Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array"); - generator.WriteArray ( - keys, - LlvmIrVariableOptions.GlobalConstant, - "assembly_image_cache_hashes", - (int idx, T value) => $"{idx}: {hashes[value].name} => 0x{value:x} => {hashes[value].index}" - ); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (variable.Type, value); + } - var indices = new List (); - for (int i = 0; i < keys.Count; i++) { - (string name, uint idx) = hashes[keys[i]]; - indices.Add (idx); - asmNameToIndexData.Add (name, idx); - } - generator.WriteArray ( - indices, - LlvmIrVariableOptions.GlobalConstant, - "assembly_image_cache_indices" - ); + AssemblyCacheState EnsureAssemblyCacheState (object? callerState) + { + var acs = callerState as AssemblyCacheState; + if (acs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); } + + return acs; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 7e27360dd96..d72b347d5e9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -547,6 +547,37 @@ public static AndroidTargetArch AbiToTargetArch (string abi) }; } + public static string AbiToRid (string abi) + { + switch (abi) { + case "arm64-v8a": + return "android-arm64"; + + case "armeabi-v7a": + return "android-arm"; + + case "x86": + return "android-x86"; + + case "x86_64": + return "android-x64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + } + + public static string ArchToRid (AndroidTargetArch arch) + { + return arch switch { + AndroidTargetArch.Arm64 => "android-arm64", + AndroidTargetArch.Arm => "android-arm", + AndroidTargetArch.X86 => "android-x86", + AndroidTargetArch.X86_64 => "android-x64", + _ => throw new InvalidOperationException ($"Internal error: unsupported ABI '{arch}'") + }; + } + public static string? CultureInvariantToString (object? obj) { if (obj == null) { @@ -582,5 +613,35 @@ public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) return AbiToTargetArch (abi); } + + static string GetToolsRootDirectoryRelativePath (string androidBinUtilsDirectory) + { + // We need to link against libc and libm, but since NDK is not in use, the linker won't be able to find the actual Android libraries. + // Therefore, we will use their stubs to satisfy the linker. At runtime they will, of course, use the actual Android libraries. + string relPath = Path.Combine ("..", ".."); + if (!OS.IsWindows) { + // the `binutils` directory is one level down (${OS}/binutils) than the Windows one + relPath = Path.Combine (relPath, ".."); + } + + return relPath; + } + + public static string GetLibstubsArchDirectoryPath (string androidBinUtilsDirectory, AndroidTargetArch arch) + { + return Path.Combine (GetLibstubsRootDirectoryPath (androidBinUtilsDirectory), ArchToRid (arch)); + } + + public static string GetLibstubsRootDirectoryPath (string androidBinUtilsDirectory) + { + string relPath = GetToolsRootDirectoryRelativePath (androidBinUtilsDirectory); + return Path.GetFullPath (Path.Combine (androidBinUtilsDirectory, relPath, "libstubs")); + } + + public static string GetNativeLibsRootDirectoryPath (string androidBinUtilsDirectory) + { + string relPath = GetToolsRootDirectoryRelativePath (androidBinUtilsDirectory); + return Path.GetFullPath (Path.Combine (androidBinUtilsDirectory, relPath, "lib")); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs index f1a5205f746..be2d4ad71dc 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs @@ -12,7 +12,7 @@ class NativeTypeMappingData public uint MapModuleCount { get; } public uint JavaTypeCount { get; } - public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleReleaseData[] modules) + public NativeTypeMappingData (TypeMapGenerator.ModuleReleaseData[] modules) { Modules = modules ?? throw new ArgumentNullException (nameof (modules)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 1b020083282..3730176e751 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -113,7 +113,7 @@ public ReleaseGenerationState (string[] supportedAbis) if (TempModulesAbiAgnostic == null) { TempModulesAbiAgnostic = dict; } - tempModules.Add (AbiToArch (abi), dict); + tempModules.Add (MonoAndroidHelper.AbiToTargetArch (abi), dict); } TempModules = new ReadOnlyDictionary> (tempModules); @@ -257,9 +257,8 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L } GeneratedBinaryTypeMaps.Add (typeMapIndexPath); - var generator = new TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); + var composer = new TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); + GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; } @@ -290,9 +289,8 @@ bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttribu PrepareDebugMaps (data); - var generator = new TypeMappingDebugNativeAssemblyGenerator (data); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); + var composer = new TypeMappingDebugNativeAssemblyGenerator (data); + GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; } @@ -467,11 +465,10 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List } } - var mappingData = new Dictionary (); foreach (var kvp in state.TempModules) { AndroidTargetArch arch = kvp.Key; Dictionary tempModules = kvp.Value; - var modules = tempModules.Values.ToArray (); + ModuleReleaseData[] modules = tempModules.Values.ToArray (); Array.Sort (modules, new ModuleUUIDArrayComparer ()); foreach (ModuleReleaseData module in modules) { @@ -485,13 +482,10 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List module.Types = module.TypesScratch.Values.ToArray (); } - mappingData.Add (arch, new NativeTypeMappingData (logger, modules)); + var composer = new TypeMappingReleaseNativeAssemblyGenerator (new NativeTypeMappingData (modules)); + GenerateNativeAssembly (arch, composer, composer.Construct (), outputDirectory); } - var generator = new TypeMappingReleaseNativeAssemblyGenerator (mappingData); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); - return true; } @@ -500,28 +494,52 @@ bool ShouldSkipInJavaToManaged (TypeDefinition td) return td.IsInterface || td.HasGenericParameters; } - void GenerateNativeAssembly (TypeMappingAssemblyGenerator generator, string baseFileName) + string GetOutputFilePath (string baseFileName, string abi) => $"{baseFileName}.{abi}.ll"; + + void GenerateNativeAssembly (AndroidTargetArch arch, LLVMIR.LlvmIrComposer composer, LLVMIR.LlvmIrModule typeMapModule, string baseFileName) + { + WriteNativeAssembly ( + arch, + composer, + typeMapModule, + GetOutputFilePath (baseFileName, ArchToAbi (arch)) + ); + } + + void GenerateNativeAssembly (LLVMIR.LlvmIrComposer composer, LLVMIR.LlvmIrModule typeMapModule, string baseFileName) { - AndroidTargetArch arch; foreach (string abi in supportedAbis) { - arch = AbiToArch (abi); - string outputFile = $"{baseFileName}.{abi}.ll"; - using (var sw = MemoryStreamPool.Shared.CreateStreamWriter (outputEncoding)) { - generator.Write (arch, sw, outputFile); + WriteNativeAssembly ( + GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), + composer, + typeMapModule, + GetOutputFilePath (baseFileName, abi) + ); + } + } + + void WriteNativeAssembly (AndroidTargetArch arch, LLVMIR.LlvmIrComposer composer, LLVMIR.LlvmIrModule typeMapModule, string outputFile) + { + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + try { + composer.Generate (typeMapModule, arch, sw, outputFile); + } catch { + throw; + } finally { sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, outputFile); } } } - static AndroidTargetArch AbiToArch (string abi) + static string ArchToAbi (AndroidTargetArch arch) { - return abi switch { - "armeabi-v7a" => AndroidTargetArch.Arm, - "arm64-v8a" => AndroidTargetArch.Arm64, - "x86_64" => AndroidTargetArch.X86_64, - "x86" => AndroidTargetArch.X86, - _ => throw new InvalidOperationException ($"Unknown ABI {abi}") + return arch switch { + AndroidTargetArch.Arm => "armeabi-v7a", + AndroidTargetArch.Arm64 => "arm64-v8a", + AndroidTargetArch.X86_64 => "x86_64", + AndroidTargetArch.X86 => "x86", + _ => throw new InvalidOperationException ($"Unknown architecture {arch}") }; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs index 3f1917d791b..2d9e3388475 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -5,7 +5,7 @@ namespace Xamarin.Android.Tasks { - class TypeMappingDebugNativeAssemblyGenerator : TypeMappingAssemblyGenerator + class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer { const string JavaToManagedSymbol = "map_java_to_managed"; const string ManagedToJavaSymbol = "map_managed_to_java"; @@ -110,8 +110,8 @@ sealed class TypeMap readonly TypeMapGenerator.ModuleDebugData data; - StructureInfo typeMapEntryStructureInfo; - StructureInfo typeMapStructureInfo; + StructureInfo typeMapEntryStructureInfo; + StructureInfo typeMapStructureInfo; List> javaToManagedMap; List> managedToJavaMap; StructureInstance type_map; @@ -124,15 +124,17 @@ public TypeMappingDebugNativeAssemblyGenerator (TypeMapGenerator.ModuleDebugData managedToJavaMap = new List> (); } - public override void Init () + protected override void Construct (LlvmIrModule module) { + MapStructures (module); + if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { var m2j = new TypeMapEntry { from = entry.ManagedName, to = entry.JavaName, }; - managedToJavaMap.Add (new StructureInstance (m2j)); + managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); } } @@ -144,7 +146,7 @@ public override void Init () from = entry.JavaName, to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, }; - javaToManagedMap.Add (new StructureInstance (j2m)); + javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); } } @@ -154,26 +156,22 @@ public override void Init () entry_count = data.EntryCount, }; - type_map = new StructureInstance (map); - } - - protected override void MapStructures (LlvmIrGenerator generator) - { - typeMapEntryStructureInfo = generator.MapStructure (); - typeMapStructureInfo = generator.MapStructure (); - } + type_map = new StructureInstance (typeMapStructureInfo, map); + module.AddGlobalVariable (TypeMapSymbol, type_map, LlvmIrVariableOptions.GlobalConstant); - protected override void Write (LlvmIrGenerator generator) - { if (managedToJavaMap.Count > 0) { - generator.WriteStructureArray (typeMapEntryStructureInfo, managedToJavaMap, LlvmIrVariableOptions.LocalConstant, ManagedToJavaSymbol); + module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LlvmIrVariableOptions.LocalConstant); } if (javaToManagedMap.Count > 0) { - generator.WriteStructureArray (typeMapEntryStructureInfo, javaToManagedMap, LlvmIrVariableOptions.LocalConstant, JavaToManagedSymbol); + module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LlvmIrVariableOptions.LocalConstant); } + } - generator.WriteStructure (typeMapStructureInfo, type_map, LlvmIrVariableOptions.GlobalConstant, TypeMapSymbol); + void MapStructures (LlvmIrModule module) + { + typeMapEntryStructureInfo = module.MapStructure (); + typeMapStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index f92af2e902c..a6e4fd465df 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -1,15 +1,14 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO.Hashing; -using System.Linq; using System.Text; using Xamarin.Android.Tasks.LLVMIR; -using Xamarin.Android.Tools; namespace Xamarin.Android.Tasks { - class TypeMappingReleaseNativeAssemblyGenerator : TypeMappingAssemblyGenerator + partial class TypeMappingReleaseNativeAssemblyGenerator : LlvmIrComposer { sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextDataProvider { @@ -18,11 +17,11 @@ public override string GetComment (object data, string fieldName) var map_module = EnsureType (data); if (String.Compare ("module_uuid", fieldName, StringComparison.Ordinal) == 0) { - return $"module_uuid: {map_module.MVID}"; + return $" module_uuid: {map_module.MVID}"; } if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { - return $"assembly_name: {map_module.assembly_name}"; + return $" assembly_name: {map_module.assembly_name}"; } return String.Empty; @@ -59,14 +58,6 @@ public override ulong GetBufferSize (object data, string fieldName) } } - sealed class JavaNameHashComparer : IComparer> - { - public int Compare (StructureInstance a, StructureInstance b) - { - return a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash); - } - } - // This is here only to generate strongly-typed IR internal sealed class MonoImage {} @@ -75,6 +66,9 @@ internal sealed class MonoImage // src/monodroid/jni/xamarin-app.hh TypeMapModuleEntry structure sealed class TypeMapModuleEntry { + [NativeAssembler (Ignore = true)] + public TypeMapJava JavaTypeMapEntry; + public uint type_token_id; public uint java_map_index; } @@ -126,7 +120,10 @@ sealed class TypeMapJava public string JavaName; [NativeAssembler (Ignore = true)] - public ulong JavaNameHash; + public uint JavaNameHash32; + + [NativeAssembler (Ignore = true)] + public ulong JavaNameHash64; public uint module_index; public uint type_token_id; @@ -145,86 +142,195 @@ public ModuleMapData (string symbolLabel, List> { - public readonly List> MapModules; - public readonly List> JavaMap; - public readonly Dictionary JavaTypesByName; - public readonly List JavaNames; - public readonly NativeTypeMappingData MappingData; - public ulong ModuleCounter = 0; - - public ArchGenerationState (NativeTypeMappingData mappingData) + public int Compare (StructureInstance a, StructureInstance b) { - MapModules = new List> (); - JavaMap = new List> (); - JavaTypesByName = new Dictionary (StringComparer.Ordinal); - JavaNames = new List (); - MappingData = mappingData; + return a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32); } } - StructureInfo typeMapJavaStructureInfo; - StructureInfo typeMapModuleStructureInfo; - StructureInfo typeMapModuleEntryStructureInfo; - Dictionary archState; + sealed class JavaNameHash64Comparer : IComparer> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64); + } + } - JavaNameHashComparer javaNameHashComparer; + sealed class ConstructionState + { + public List> MapModules; + public Dictionary JavaTypesByName; + public List JavaNames; + public List> JavaMap; + public List AllModulesData; + } + + readonly NativeTypeMappingData mappingData; + StructureInfo typeMapJavaStructureInfo; + StructureInfo typeMapModuleStructureInfo; + StructureInfo typeMapModuleEntryStructureInfo; + JavaNameHash32Comparer javaNameHash32Comparer; + JavaNameHash64Comparer javaNameHash64Comparer; + + ulong moduleCounter = 0; + + public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) + { + this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); + javaNameHash32Comparer = new JavaNameHash32Comparer (); + javaNameHash64Comparer = new JavaNameHash64Comparer (); + } - public TypeMappingReleaseNativeAssemblyGenerator (Dictionary mappingData) + protected override void Construct (LlvmIrModule module) { - if (mappingData == null) { - throw new ArgumentNullException (nameof (mappingData)); + MapStructures (module); + + var cs = new ConstructionState (); + cs.JavaTypesByName = new Dictionary (StringComparer.Ordinal); + cs.JavaNames = new List (); + InitJavaMap (cs); + InitMapModules (cs); + HashJavaNames (cs); + PrepareModules (cs); + + module.AddGlobalVariable ("map_module_count", mappingData.MapModuleCount); + module.AddGlobalVariable ("java_type_count", cs.JavaMap.Count); + + var map_modules = new LlvmIrGlobalVariable (cs.MapModules, "map_modules", LlvmIrVariableOptions.GlobalWritable) { + Comment = " Managed modules map", + }; + module.Add (map_modules); + + // Java hashes are output bafore Java type map **and** managed modules, because they will also sort the Java map for us. + // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save + // time sorting only once. + var map_java_hashes = new LlvmIrGlobalVariable (typeof(List), "map_java_hashes") { + Comment = " Java types name hashes", + BeforeWriteCallback = GenerateAndSortJavaHashes, + BeforeWriteCallbackCallerState = cs, + GetArrayItemCommentCallback = GetJavaHashesItemComment, + GetArrayItemCommentCallbackCallerState = cs, + }; + map_java_hashes.WriteOptions &= ~LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + module.Add (map_java_hashes); + + foreach (ModuleMapData mmd in cs.AllModulesData) { + var mmdVar = new LlvmIrGlobalVariable (mmd.Entries, mmd.SymbolLabel, LlvmIrVariableOptions.LocalConstant) { + BeforeWriteCallback = UpdateJavaIndexes, + BeforeWriteCallbackCallerState = cs, + }; + module.Add (mmdVar); } - javaNameHashComparer = new JavaNameHashComparer (); - archState = new Dictionary (mappingData.Count); + module.AddGlobalVariable ("map_java", cs.JavaMap, LlvmIrVariableOptions.GlobalConstant, " Java to managed map"); + module.AddGlobalVariable ("java_type_names", cs.JavaNames, LlvmIrVariableOptions.GlobalConstant, " Java type names"); + } + + void UpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + IComparer> hashComparer = target.Is64Bit ? javaNameHash64Comparer : javaNameHash32Comparer; + + var entries = (List>)variable.Value; + foreach (StructureInstance entry in entries) { + entry.Instance.java_map_index = GetJavaEntryIndex (entry.Instance.JavaTypeMapEntry); + } - foreach (var kvp in mappingData) { - if (kvp.Value == null) { - throw new ArgumentException ("must not contain null values", nameof (mappingData)); + uint GetJavaEntryIndex (TypeMapJava javaEntry) + { + var key = new StructureInstance (typeMapJavaStructureInfo, javaEntry); + int idx = cs.JavaMap.BinarySearch (key, hashComparer); + if (idx < 0) { + throw new InvalidOperationException ($"Could not map entry '{javaEntry.JavaName}' to array index"); } - archState.Add (kvp.Key, new ArchGenerationState (kvp.Value)); + return (uint)idx; } } - public override void Init () + string? GetJavaHashesItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) { - foreach (var kvp in archState) { - InitMapModules (kvp.Value); - InitJavaMap (kvp.Value); + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); } + + return $" {index}: 0x{value:x} => {cs.JavaMap[(int)index].Instance.JavaName}"; } - void InitJavaMap (ArchGenerationState state) + void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + Type listType; + IList hashes; + if (target.Is64Bit) { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash64); + } + hashes = list; + } else { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash32); + } + hashes = list; + } + + gv.OverrideValueAndType (listType, hashes); + } + + ConstructionState EnsureConstructionState (object? callerState) + { + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } + + return cs; + } + + void InitJavaMap (ConstructionState cs) + { + cs.JavaMap = new List> (); TypeMapJava map_entry; - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in state.MappingData.JavaTypes) { - state.JavaNames.Add (entry.JavaName); + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { + cs.JavaNames.Add (entry.JavaName); map_entry = new TypeMapJava { module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, - java_name_index = (uint)(state.JavaNames.Count - 1), + java_name_index = (uint)(cs.JavaNames.Count - 1), JavaName = entry.JavaName, }; - state.JavaMap.Add (new StructureInstance (map_entry)); - state.JavaTypesByName.Add (map_entry.JavaName, map_entry); + cs.JavaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); + cs.JavaTypesByName.Add (map_entry.JavaName, map_entry); } } - void InitMapModules (ArchGenerationState state) + void InitMapModules (ConstructionState cs) { - foreach (TypeMapGenerator.ModuleReleaseData data in state.MappingData.Modules) { - string mapName = $"module{state.ModuleCounter++}_managed_to_java"; + cs.MapModules = new List> (); + foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { + string mapName = $"module{moduleCounter++}_managed_to_java"; string duplicateMapName; - if (data.DuplicateTypes.Count == 0) + if (data.DuplicateTypes.Count == 0) { duplicateMapName = String.Empty; - else + } else { duplicateMapName = $"{mapName}_duplicates"; + } var map_module = new TypeMapModule { MVID = data.Mvid, @@ -239,85 +345,69 @@ void InitMapModules (ArchGenerationState state) java_name_width = 0, }; - state.MapModules.Add (new StructureInstance (map_module)); + cs.MapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); } } - protected override void MapStructures (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - generator.MapStructure (); - typeMapJavaStructureInfo = generator.MapStructure (); - typeMapModuleStructureInfo = generator.MapStructure (); - typeMapModuleEntryStructureInfo = generator.MapStructure (); + typeMapJavaStructureInfo = module.MapStructure (); + typeMapModuleStructureInfo = module.MapStructure (); + typeMapModuleEntryStructureInfo = module.MapStructure (); } - // Prepare module map entries by sorting them on the managed token, and then mapping each entry to its corresponding Java type map index. - // Requires that `javaMap` is sorted on the type name hash. - void PrepareMapModuleData (ArchGenerationState state, string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData) + void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, ConstructionState cs) { var mapModuleEntries = new List> (); foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { + if (!cs.JavaTypesByName.TryGetValue (entry.JavaName, out TypeMapJava javaType)) { + throw new InvalidOperationException ($"Internal error: Java type '{entry.JavaName}' not found in cache"); + } + var map_entry = new TypeMapModuleEntry { + JavaTypeMapEntry = javaType, type_token_id = entry.Token, - java_map_index = GetJavaEntryIndex (entry.JavaName), + java_map_index = UInt32.MaxValue, // will be set later, when the target is known }; - mapModuleEntries.Add (new StructureInstance (map_entry)); + mapModuleEntries.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); } - mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Obj.type_token_id.CompareTo (b.Obj.type_token_id)); - allModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); - - uint GetJavaEntryIndex (string javaTypeName) - { - if (!state.JavaTypesByName.TryGetValue (javaTypeName, out TypeMapJava javaType)) { - throw new InvalidOperationException ($"INTERNAL ERROR: Java type '{javaTypeName}' not found in cache"); - } + mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Instance.type_token_id.CompareTo (b.Instance.type_token_id)); + cs.AllModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); + } - var key = new StructureInstance (javaType); - int idx = state.JavaMap.BinarySearch (key, javaNameHashComparer); - if (idx < 0) { - throw new InvalidOperationException ($"Could not map entry '{javaTypeName}' to array index"); + void PrepareModules (ConstructionState cs) + { + cs.AllModulesData = new List (); + foreach (StructureInstance moduleInstance in cs.MapModules) { + TypeMapModule module = moduleInstance.Instance; + PrepareMapModuleData (module.MapSymbolName, module.Data.Types, cs); + if (module.Data.DuplicateTypes.Count > 0) { + PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, cs); } - - return (uint)idx; } } - // Generate hashes for all Java type names, then sort javaMap on the name hash. This has to be done in the writing phase because hashes - // will depend on architecture (or, actually, on its bitness) and may differ between architectures (they will be the same for all architectures - // with the same bitness) - (List allMapModulesData, List javaMapHashes) PrepareMapsForWriting (ArchGenerationState state, LlvmIrGenerator generator) + void HashJavaNames (ConstructionState cs) { - bool is64Bit = generator.Is64Bit; + // We generate both 32-bit and 64-bit hashes at the construction time. Which set will be used depends on the target. + // Java map list will also be sorted when the target is known + var hashes32 = new HashSet (); + var hashes64 = new HashSet (); // Generate Java type name hashes... - for (int i = 0; i < state.JavaMap.Count; i++) { - TypeMapJava entry = state.JavaMap[i].Obj; - entry.JavaNameHash = HashName (entry.JavaName); - } - - // ...sort them... - state.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash)); + for (int i = 0; i < cs.JavaMap.Count; i++) { + TypeMapJava entry = cs.JavaMap[i].Instance; - var allMapModulesData = new List (); + // The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit + entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false); + hashes32.Add (entry.JavaNameHash32); - // ...and match managed types to Java... - foreach (StructureInstance moduleInstance in state.MapModules) { - TypeMapModule module = moduleInstance.Obj; - PrepareMapModuleData (state, module.MapSymbolName, module.Data.Types, allMapModulesData); - if (module.Data.DuplicateTypes.Count > 0) { - PrepareMapModuleData (state, module.DuplicateMapSymbolName, module.Data.DuplicateTypes, allMapModulesData); - } + entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true); + hashes64.Add (entry.JavaNameHash64); } - var javaMapHashes = new HashSet (); - foreach (StructureInstance entry in state.JavaMap) { - javaMapHashes.Add (entry.Obj.JavaNameHash); - } - - return (allMapModulesData, javaMapHashes.ToList ()); - - ulong HashName (string name) + ulong HashName (string name, bool is64Bit) { if (name.Length == 0) { return UInt64.MaxValue; @@ -325,10 +415,10 @@ ulong HashName (string name) // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do // the same - return HashBytes (Encoding.Unicode.GetBytes (name)); + return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit); } - ulong HashBytes (byte[] bytes) + ulong HashBytes (byte[] bytes, bool is64Bit) { if (is64Bit) { return XxHash64.HashToUInt64 (bytes); @@ -337,87 +427,5 @@ ulong HashBytes (byte[] bytes) return (ulong)XxHash32.HashToUInt32 (bytes); } } - - protected override void Write (LlvmIrGenerator generator) - { - ArchGenerationState state; - - try { - state = archState[generator.TargetArch]; - } catch (KeyNotFoundException ex) { - throw new InvalidOperationException ($"Internal error: architecture {generator.TargetArch} has not been prepared for writing.", ex); - } - - generator.WriteVariable ("map_module_count", state.MappingData.MapModuleCount); - generator.WriteVariable ("java_type_count", state.JavaMap.Count); // must include the padding item, if any - - (List allMapModulesData, List javaMapHashes) = PrepareMapsForWriting (state, generator); - WriteMapModules (state, generator, allMapModulesData); - WriteJavaMap (state, generator, javaMapHashes); - } - - void WriteJavaMap (ArchGenerationState state, LlvmIrGenerator generator, List javaMapHashes) - { - generator.WriteEOL (); - generator.WriteEOL ("Java to managed map"); - - generator.WriteStructureArray ( - typeMapJavaStructureInfo, - state.JavaMap, - LlvmIrVariableOptions.GlobalConstant, - "map_java" - ); - - if (generator.Is64Bit) { - WriteHashes (javaMapHashes); - } else { - // A bit ugly, but simple. We know that hashes are really 32-bit, so we can cast without - // worrying. - var hashes = new List (javaMapHashes.Count); - foreach (ulong hash in javaMapHashes) { - hashes.Add ((uint)hash); - } - WriteHashes (hashes); - } - - generator.WriteArray (state.JavaNames, "java_type_names"); - - void WriteHashes (List hashes) where T: struct - { - generator.WriteArray ( - hashes, - LlvmIrVariableOptions.GlobalConstant, - "map_java_hashes", - (int idx, T value) => $"{idx}: 0x{value:x} => {state.JavaMap[idx].Obj.JavaName}" - ); - } - } - - void WriteMapModules (ArchGenerationState state, LlvmIrGenerator generator, List mapModulesData) - { - if (state.MapModules.Count == 0) { - return; - } - - generator.WriteEOL (); - generator.WriteEOL ("Map modules data"); - - foreach (ModuleMapData mmd in mapModulesData) { - generator.WriteStructureArray ( - typeMapModuleEntryStructureInfo, - mmd.Entries, - LlvmIrVariableOptions.LocalConstant, - mmd.SymbolLabel - ); - } - - generator.WriteEOL ("Map modules"); - generator.WriteStructureArray ( - typeMapModuleStructureInfo, - state.MapModules, - LlvmIrVariableOptions.GlobalWritable, - "map_modules" - ); - } } } diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 3c4b7a69411..1e36816bed3 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -725,55 +725,39 @@ target_link_libraries( ) if(ANDROID AND ENABLE_NET AND (NOT ANALYZERS_ENABLED)) - add_library( - c - SHARED ${XAMARIN_STUB_LIB_SOURCES} - ) - - target_compile_definitions( - c - PRIVATE STUB_LIB_NAME=libc - ) - - target_compile_options( - c - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) - - target_link_options( - c - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + macro(xa_add_stub_library _libname) + add_library( + ${_libname} + SHARED ${XAMARIN_STUB_LIB_SOURCES} + ) - set_target_properties( - c - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" - ) + string(TOUPPER ${_libname} _libname_uc) + target_compile_definitions( + ${_libname} + PRIVATE STUB_LIB_NAME=lib${_libname} IN_LIB${_libname_uc} + ) - add_library( - m - SHARED ${XAMARIN_STUB_LIB_SOURCES} - ) + target_compile_options( + ${_libname} + PRIVATE -nostdlib -fno-exceptions -fno-rtti + ) - target_compile_definitions( - m - PRIVATE STUB_LIB_NAME=libm - ) + target_link_options( + ${_libname} + PRIVATE -nostdlib -fno-exceptions -fno-rtti + ) - target_compile_options( - m - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + set_target_properties( + ${_libname} + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" + ) + endmacro() - target_link_options( - m - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + xa_add_stub_library(c) + xa_add_stub_library(m) - set_target_properties( - m - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" - ) + # These two are used by the marshal methods tracing library when linking libxamarin-app.so + xa_add_stub_library(log) + xa_add_stub_library(dl) endif() diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 8a59a03e51a..97f554e4c25 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -202,7 +202,7 @@ const MarshalMethodName mm_method_names[] = { }, }; -void xamarin_app_init ([[maybe_unused]] get_function_pointer_fn fn) noexcept +void xamarin_app_init ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] get_function_pointer_fn fn) noexcept { // Dummy } diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 85128eb1915..84ac5f5188a 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -299,7 +299,7 @@ namespace xamarin::android::internal void set_debug_options (); void parse_gdb_options (); - void mono_runtime_init (dynamic_local_string& runtime_args); + void mono_runtime_init (JNIEnv *env, dynamic_local_string& runtime_args); #if defined (NET) void init_android_runtime (JNIEnv *env, jclass runtimeClass, jobject loader); #else //def NET diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index e7409351411..ab041f4b1c9 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -638,7 +638,7 @@ MonodroidRuntime::set_debug_options (void) } void -MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_string& runtime_args) +MonodroidRuntime::mono_runtime_init ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] dynamic_local_string& runtime_args) { #if defined (DEBUG) && !defined (WINDOWS) RuntimeOptions options{}; @@ -809,7 +809,9 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_stringstart_event (TimingEventKind::MonoRuntimeInit); } - mono_runtime_init (runtime_args); + mono_runtime_init (env, runtime_args); if (XA_UNLIKELY (FastTiming::enabled ())) { internal_timing->end_event (mono_runtime_init_index); @@ -2355,7 +2357,9 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl } #if defined (RELEASE) && defined (ANDROID) && defined (NET) - xamarin_app_init (get_function_pointer_at_runtime); + if (application_config.marshal_methods_enabled) { + xamarin_app_init (env, get_function_pointer_at_runtime); + } #endif // def RELEASE && def ANDROID && def NET startup_in_progress = false; } diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 3dbc037407b..d50fc39a683 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -351,7 +351,7 @@ MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); -MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; +MONO_API MONO_API_EXPORT void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept; #endif // def RELEASE && def ANDROID && def NET #endif // __XAMARIN_ANDROID_TYPEMAP_H diff --git a/src/monodroid/libstub/stub.cc b/src/monodroid/libstub/stub.cc index be0ec6b7ec7..1b1c91385d6 100644 --- a/src/monodroid/libstub/stub.cc +++ b/src/monodroid/libstub/stub.cc @@ -6,3 +6,17 @@ void STUB_LIB_NAME () { // no-op } + +#if defined(IN_LIBC) +extern "C" { + [[gnu::weak]] int puts ([[maybe_unused]] const char *s) + { + return -1; + } + + [[gnu::weak]] void abort () + { + // no-op + } +} +#endif