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