diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index 5752a818c29..a2cf405e4dc 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -324,6 +324,7 @@ <_MSBuildFilesWin Include="$(MSBuildSrcDir)\aapt2.exe" /> <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\as.exe" /> <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\ld.exe" /> + <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\llc.exe" /> <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\llvm-mc.exe" /> <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\llvm-strip.exe" /> <_MSBuildFilesWin Include="$(MSBuildSrcDir)\binutils\bin\aarch64-linux-android-as.cmd" /> diff --git a/build-tools/installers/unix-binutils.projitems b/build-tools/installers/unix-binutils.projitems index 24eb00b5d88..103ff7eb6be 100644 --- a/build-tools/installers/unix-binutils.projitems +++ b/build-tools/installers/unix-binutils.projitems @@ -17,6 +17,7 @@ <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\i686-linux-android-ld" /> <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\i686-linux-android-strip" /> <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\ld" /> + <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\llc" /> <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\llvm-mc" /> <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\llvm-strip" /> <_MSBuildFilesUnixSignAndHarden Include="$(MSBuildSrcDir)\$(HostOS)\binutils\bin\x86_64-linux-android-as" /> diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs index 5aa154417b3..2d82cda4f5d 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs @@ -227,6 +227,7 @@ public static partial class Defaults // Unprefixed tools new NDKTool (name: "as"), new NDKTool (name: "ld"), + new NDKTool (name: "llc"), new NDKTool (name: "llvm-mc"), new NDKTool (name: "llvm-strip"), }; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs index f084d749819..7a9780768bd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs @@ -31,7 +31,7 @@ sealed class Config public bool DebugBuild { get; set; } [Required] - public string WorkingDirectory { get; set; } + public new string WorkingDirectory { get; set; } [Required] public string AndroidBinUtilsDirectory { get; set; } @@ -57,7 +57,7 @@ void RunAssembler (Config config) }; string assemblerName = Path.GetFileName (config.AssemblerPath); - LogDebugMessage ($"[Native Assembler] {psi.FileName} {psi.Arguments}"); + LogDebugMessage ($"[LLVM llc] {psi.FileName} {psi.Arguments}"); using (var proc = new Process ()) { proc.OutputDataReceived += (s, e) => { if (e.Data != null) @@ -95,61 +95,26 @@ void RunAssembler (Config config) IEnumerable GetAssemblerConfigs () { - foreach (ITaskItem item in Sources) { - string abi = item.GetMetadata ("abi")?.ToLowerInvariant (); - string prefix = String.Empty; - AndroidTargetArch arch; - - switch (abi) { - case "armeabi-v7a": - prefix = Path.Combine (AndroidBinUtilsDirectory, "arm-linux-androideabi"); - arch = AndroidTargetArch.Arm; - break; - - case "arm64": - case "arm64-v8a": - case "aarch64": - prefix = Path.Combine (AndroidBinUtilsDirectory, "aarch64-linux-android"); - arch = AndroidTargetArch.Arm64; - break; - - case "x86": - prefix = Path.Combine (AndroidBinUtilsDirectory, "i686-linux-android"); - arch = AndroidTargetArch.X86; - break; - - case "x86_64": - prefix = Path.Combine (AndroidBinUtilsDirectory, "x86_64-linux-android"); - arch = AndroidTargetArch.X86_64; - break; - - default: - throw new NotSupportedException ($"Unsupported Android target architecture ABI: {abi}"); - } + const string assemblerOptions = + "-O2 " + + "--debugger-tune=lldb " + // NDK uses lldb now + "--debugify-level=location+variables " + + "--fatal-warnings " + + "--filetype=obj " + + "--relocation-model=pic"; + string llcPath = Path.Combine (AndroidBinUtilsDirectory, "llc"); - // We don't need the directory since our WorkingDirectory is (and must be) where all the - // sources are (because of the typemap.inc file being included by the other sources with - // a relative path of `.`) + foreach (ITaskItem item in Sources) { + // We don't need the directory since our WorkingDirectory is where all the sources are string sourceFile = Path.GetFileName (item.ItemSpec); - var assemblerOptions = new List { - "--warn", - "-o", - QuoteFileName (sourceFile.Replace (".s", ".o")) - }; - - if (DebugBuild) - assemblerOptions.Add ("-g"); - - assemblerOptions.Add (QuoteFileName (sourceFile)); - - string baseExecutablePath = $"{prefix}-as"; - string executableDir = Path.GetDirectoryName (baseExecutablePath); - string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (baseExecutablePath)); + string outputFile = QuoteFileName (sourceFile.Replace (".ll", ".o")); + string executableDir = Path.GetDirectoryName (llcPath); + string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (llcPath)); yield return new Config { InputSource = item.ItemSpec, AssemblerPath = Path.Combine (executableDir, executableName), - AssemblerOptions = String.Join (" ", assemblerOptions), + AssemblerOptions = $"{assemblerOptions} -o={outputFile} {QuoteFileName (sourceFile)}", }; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs index a0d59f83727..8367c21b744 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs @@ -74,16 +74,18 @@ void GenerateCompressedAssemblySources () void Generate (IDictionary dict) { + var llvmAsmgen = new CompressedAssembliesNativeAssemblyGenerator (dict); + llvmAsmgen.Init (); + foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}"); - string asmFilePath = $"{baseAsmFilePath}.s"; - var asmgen = new CompressedAssembliesNativeAssemblyGenerator (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), dict, baseAsmFilePath); + string llvmIrFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - asmgen.Write (sw, asmFilePath); + llvmAsmgen.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath); sw.Flush (); - if (Files.CopyIfStreamChanged (sw.BaseStream, asmFilePath)) { - Log.LogDebugMessage ($"File {asmFilePath} was regenerated"); + if (Files.CopyIfStreamChanged (sw.BaseStream, llvmIrFilePath)) { + Log.LogDebugMessage ($"File {llvmIrFilePath} was regenerated"); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 6e1ac59d7a8..32738106983 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -383,39 +383,39 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build); + var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { + IsBundledApp = IsBundledApplication, + UsesMonoAOT = usesMonoAOT, + UsesMonoLLVM = EnableLLVM, + UsesAssemblyPreload = usesAssemblyPreload, + MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), + AndroidPackageName = AndroidPackageName, + BrokenExceptionTransitions = brokenExceptionTransitions, + PackageNamingPolicy = pnp, + BoundExceptionType = boundExceptionType, + InstantRunEnabled = InstantRunEnabled, + JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false, + HaveRuntimeConfigBlob = haveRuntimeConfigBlob, + NumberOfAssembliesInApk = assemblyCount, + BundledAssemblyNameWidth = assemblyNameWidth, + NumberOfAssemblyStoresInApks = 2, // Until feature APKs are a thing, we're going to have just two stores in each app - one for arch-agnostic + // 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, + NativeLibraries = uniqueNativeLibraries, + HaveAssemblyStore = UseAssemblyStore, + }; + appConfigAsmGen.Init (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}"); - string asmFilePath = $"{baseAsmFilePath}.s"; - - var asmgen = new ApplicationConfigNativeAssemblyGenerator (GetAndroidTargetArchForAbi (abi), environmentVariables, systemProperties, Log) { - IsBundledApp = IsBundledApplication, - UsesMonoAOT = usesMonoAOT, - UsesMonoLLVM = EnableLLVM, - UsesAssemblyPreload = usesAssemblyPreload, - MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), - AndroidPackageName = AndroidPackageName, - BrokenExceptionTransitions = brokenExceptionTransitions, - PackageNamingPolicy = pnp, - BoundExceptionType = boundExceptionType, - InstantRunEnabled = InstantRunEnabled, - JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false, - HaveRuntimeConfigBlob = haveRuntimeConfigBlob, - NumberOfAssembliesInApk = assemblyCount, - BundledAssemblyNameWidth = assemblyNameWidth, - NumberOfAssemblyStoresInApks = 2, // Until feature APKs are a thing, we're going to have just two stores in each app - one for arch-agnostic - // 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, - NativeLibraries = uniqueNativeLibraries, - HaveAssemblyStore = UseAssemblyStore, - }; + string llFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - asmgen.Write (sw, asmFilePath); + appConfigAsmGen.Write (GetAndroidTargetArchForAbi (abi), sw, llFilePath); sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, asmFilePath); + Files.CopyIfStreamChanged (sw.BaseStream, llFilePath); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index f7f592e6ce4..0810ba6f0de 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -111,49 +111,47 @@ IEnumerable GetLinkerConfigs () abis [abi] = GatherFilesForABI(item.ItemSpec, abi, ObjectFiles); } + const string commonLinkerArgs = + "--unresolved-symbols=ignore-in-shared-libs " + + "--export-dynamic " + + "-soname libxamarin-app.so " + + "-z relro " + + "-z noexecstack " + + "--enable-new-dtags " + + "--eh-frame-hdr " + + "-shared " + + "--build-id " + + "--warn-shared-textrel " + + "--fatal-warnings"; + + string stripSymbolsArg = DebugBuild ? String.Empty : " -s"; + + string ld = Path.Combine (AndroidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, "ld")); + var targetLinkerArgs = new List (); foreach (var kvp in abis) { string abi = kvp.Key; InputFiles inputs = kvp.Value; - AndroidTargetArch arch; - - var linkerArgs = new List { - "--unresolved-symbols=ignore-in-shared-libs", - "--export-dynamic", - "-soname", "libxamarin-app.so", - "-z", "relro", - "-z", "noexecstack", - "--enable-new-dtags", - "--eh-frame-hdr", - "-shared", - "--build-id", - "--warn-shared-textrel", - "--fatal-warnings", - "-o", QuoteFileName (inputs.OutputSharedLibrary), - }; + targetLinkerArgs.Clear (); string elf_arch; switch (abi) { case "armeabi-v7a": - arch = AndroidTargetArch.Arm; - linkerArgs.Add ("-X"); + targetLinkerArgs.Add ("-X"); elf_arch = "armelf_linux_eabi"; break; case "arm64": case "arm64-v8a": case "aarch64": - arch = AndroidTargetArch.Arm64; - linkerArgs.Add ("--fix-cortex-a53-843419"); + targetLinkerArgs.Add ("--fix-cortex-a53-843419"); elf_arch = "aarch64linux"; break; case "x86": - arch = AndroidTargetArch.X86; elf_arch = "elf_i386"; break; case "x86_64": - arch = AndroidTargetArch.X86_64; elf_arch = "elf_x86_64"; break; @@ -161,17 +159,20 @@ IEnumerable GetLinkerConfigs () throw new NotSupportedException ($"Unsupported Android target architecture ABI: {abi}"); } - linkerArgs.Add ("-m"); - linkerArgs.Add (elf_arch); + targetLinkerArgs.Add ("-m"); + targetLinkerArgs.Add (elf_arch); foreach (string file in inputs.ObjectFiles) { - linkerArgs.Add (QuoteFileName (file)); + targetLinkerArgs.Add (QuoteFileName (file)); } - string ld = MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, $"{NdkTools.GetBinutilsToolchainPrefix (arch)}ld"); + targetLinkerArgs.Add ("-o"); + targetLinkerArgs.Add (QuoteFileName (inputs.OutputSharedLibrary)); + + string targetArgs = String.Join (" ", targetLinkerArgs); yield return new Config { - LinkerPath = Path.Combine (AndroidBinUtilsDirectory, ld), - LinkerOptions = String.Join (" ", linkerArgs), + LinkerPath = ld, + LinkerOptions = $"{commonLinkerArgs}{stripSymbolsArg} {targetArgs}", OutputSharedLibrary = inputs.OutputSharedLibrary, }; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs index 574f01d36f6..23d501ab4c7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs @@ -42,14 +42,10 @@ public override bool RunTask () { var sources = new List (); var includes = new List (); - bool haveSharedSource = false; - bool haveArmV7SharedSource = false; - bool typeMapMode = false; string baseName; if (String.Compare ("typemap", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = TypeMapBase; - typeMapMode = true; } else if (String.Compare ("environment", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = EnvBase; } else if (String.Compare ("compressed", Mode, StringComparison.OrdinalIgnoreCase) == 0) { @@ -61,36 +57,9 @@ public override bool RunTask () TaskItem item; foreach (string abi in BuildTargetAbis) { - if (typeMapMode) { - if (String.Compare (ArmV7a, abi, StringComparison.Ordinal) == 0) - haveArmV7SharedSource = true; - else - haveSharedSource = true; - } - - item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}.s")); + item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}.ll")); item.SetMetadata ("abi", abi); sources.Add (item); - - if (!typeMapMode) - continue; - - if (!InstantRunEnabled && !Debug) { - item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}-managed.inc")); - item.SetMetadata ("abi", abi); - includes.Add (item); - } - } - - if (haveArmV7SharedSource) { - item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{ArmV7a}-shared.inc")); - item.SetMetadata ("abi", ArmV7a); - includes.Add (item); - } - - if (haveSharedSource) { - item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.shared.inc")); - includes.Add (item); } AssemblySources = sources.ToArray (); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index 6984d2b1acf..7a8fdb178ea 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -76,7 +76,7 @@ public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, b Assert.IsTrue (b.Build (proj), "Build should have succeeded."); string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles); Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files"); Assert.IsTrue (app_config.mono_components_mask == expectedMask, "Expected Mono Components mask 0x{expectedMask:x}, got 0x{app_config.mono_components_mask:x}"); @@ -126,13 +126,13 @@ public void CheckAssemblyCounts (bool isRelease, bool aot) Assert.IsTrue (b.Build (proj), "Build should have succeeded."); string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles); Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files"); if (aot) { foreach (var env in envFiles) { - StringAssert.Contains ("libaot-Mono.Android.dll.so", File.ReadAllText (env)); + StringAssert.Contains ("libaot-Mono.Android.dll.so", File.ReadAllText (env.Path)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EmbeddedDSOTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EmbeddedDSOTests.cs index 5d46ded9cc2..fd450b6a952 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EmbeddedDSOTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EmbeddedDSOTests.cs @@ -119,7 +119,7 @@ public void DSOPageAlignment () public void EnvironmentFileContents () { string intermediateOutputDir = Path.Combine (testProjectPath, "obj", XABuildPaths.Configuration); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, Xamarin.Android.Tools.XABuildConfig.SupportedABIs, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, Xamarin.Android.Tools.XABuildConfig.SupportedABIs, true); EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles); Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files"); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs index 65d2270225a..b81f21fea37 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs @@ -51,7 +51,7 @@ public void BuildApplicationWithMonoEnvironment ([Values ("", "Normal", "Offline } string intermediateOutputDir = Path.Combine (Root, appb.ProjectDirectory, app.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.Count > 0, $"No environment variables defined"); @@ -94,7 +94,7 @@ public void CheckMonoDebugIsAddedToEnvironment ([Values ("", "Normal", "Offline" Assert.IsTrue (b.Build (proj), "Build should have succeeded."); string intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.Count > 0, $"No environment variables defined"); @@ -128,7 +128,7 @@ public void CheckConcurrentGC () Assert.IsTrue (b.Build (proj), "Build should have succeeded."); var intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); // AndroidEnableSGenConcurrent=False by default - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.ContainsKey (gcVarName), $"Environment should contain '{gcVarName}'."); Assert.AreEqual (expectedDefaultValue, envvars[gcVarName], $"'{gcVarName}' should have been '{expectedDefaultValue}' when concurrent GC is disabled."); @@ -170,7 +170,7 @@ public void CheckBuildIdIsUnique ([Values ("apk", "aab")] string packageFormat) Assert.IsTrue (allFilesInArchive.Any (x => Path.GetFileName (x) == $"{proj.ProjectName}.{extension}"), $"{proj.ProjectName}.{extension} should exist in {archivePath}"); string intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.Count > 0, $"No environment variables defined"); @@ -210,7 +210,7 @@ public void CheckHttpClientHandlerType () proj.SetProperty ("AndroidHttpClientHandlerType", expectedDefaultValue); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); var intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.ContainsKey (httpClientHandlerVarName), $"Environment should contain '{httpClientHandlerVarName}'."); Assert.AreEqual (expectedDefaultValue, envvars[httpClientHandlerVarName]); @@ -264,7 +264,7 @@ public void BuildWithTlsProvider (string androidTlsProvider, bool isRelease, boo } } } - List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, string.Join (";", supportedAbis), true); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, string.Join (";", supportedAbis), true); Dictionary envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles); Assert.IsTrue (envvars.ContainsKey ("XA_TLS_PROVIDER"), "Environment should contain XA_TLS_PROVIDER."); if (androidTlsProvider == string.Empty) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index 559ad865132..53e24f6886f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -983,10 +983,9 @@ public void GenerateJavaStubsAndAssembly ([Values (true, false)] bool isRelease) readonly string [] ExpectedAssemblyFiles = new [] { Path.Combine ("android", "environment.armeabi-v7a.o"), - Path.Combine ("android", "environment.armeabi-v7a.s"), - Path.Combine ("android", "typemaps.armeabi-v7a-shared.inc"), + Path.Combine ("android", "environment.armeabi-v7a.ll"), Path.Combine ("android", "typemaps.armeabi-v7a.o"), - Path.Combine ("android", "typemaps.armeabi-v7a.s"), + Path.Combine ("android", "typemaps.armeabi-v7a.ll"), Path.Combine ("app_shared_libraries", "armeabi-v7a", "libxamarin-app.so") }; 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 0d415ab5c0c..9b05e64abdc 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 @@ -16,6 +16,26 @@ namespace Xamarin.Android.Build.Tests { class EnvironmentHelper { + public sealed class EnvironmentFile + { + public readonly string Path; + public readonly string ABI; + + public EnvironmentFile (string path, string abi) + { + if (String.IsNullOrEmpty (path)) { + throw new ArgumentException ("must not be null or empty", nameof (path)); + } + + if (String.IsNullOrEmpty (abi)) { + throw new ArgumentException ("must not be null or empty", nameof (abi)); + } + + Path = path; + ABI = abi; + } + } + // This must be identical to the like-named structure in src/monodroid/jni/xamarin-app.h public sealed class ApplicationConfig { @@ -41,9 +61,12 @@ public sealed class ApplicationConfig }; const uint ApplicationConfigFieldCount = 19; + const string ApplicationConfigSymbolName = "application_config"; + const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; + static readonly object ndkInitLock = new object (); static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' }; - static readonly Regex stringLabelRegex = new Regex ("^\\.L\\.autostr\\.[0-9]+:", RegexOptions.Compiled); + static readonly Regex assemblerLabelRegex = new Regex ("^[_.a-zA-Z0-9]+:", RegexOptions.Compiled); static readonly HashSet expectedPointerTypes = new HashSet (StringComparer.Ordinal) { ".long", @@ -57,9 +80,9 @@ public sealed class ApplicationConfig }; static readonly string[] requiredSharedLibrarySymbols = { - "app_environment_variables", + AppEnvironmentVariablesSymbolName, "app_system_properties", - "application_config", + ApplicationConfigSymbolName, "map_modules", "map_module_count", "java_type_count", @@ -68,9 +91,76 @@ public sealed class ApplicationConfig "mono_aot_mode_name", }; + static readonly string executableExtension; + + static EnvironmentHelper () + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + executableExtension = ".exe"; + } else { + executableExtension = String.Empty; + } + } + + static string GenerateNativeAssemblyFromLLVM (string llvmIrFilePath) + { + Assert.AreEqual (".ll", Path.GetExtension (llvmIrFilePath), $"Invalid file extension for '{llvmIrFilePath}'"); + + // Should match the arguments passed to llc by the CompileNativeAssembly task (except for the `--filetype` argument) + const string assemblerOptions = + "-O2 " + + "--debugger-tune=lldb " + // NDK uses lldb now + "--debugify-level=location+variables " + + "--fatal-warnings " + + "--filetype=asm " + + "--relocation-model=pic"; + + string llc = Path.Combine (BaseTest.SetUp.OSBinDirectory, "binutils", "bin", $"llc{executableExtension}"); + string outputFilePath = Path.ChangeExtension (llvmIrFilePath, ".s"); + RunCommand (llc, $"{assemblerOptions} -o \"{outputFilePath}\" \"{llvmIrFilePath}\""); + + Assert.IsTrue (File.Exists (outputFilePath), $"Generated native assembler file '{outputFilePath}' does not exist"); + + return outputFilePath; + } + + static NativeAssemblyParser CreateAssemblyParser (EnvironmentFile envFile) + { + string assemblerFilePath = GenerateNativeAssemblyFromLLVM (envFile.Path); + return new NativeAssemblyParser (assemblerFilePath, envFile.ABI); + } + + static NativeAssemblyParser.AssemblerSymbol GetRequiredSymbol (string symbolName, EnvironmentFile envFile, NativeAssemblyParser parser) + { + if (!parser.Symbols.TryGetValue (symbolName, out NativeAssemblyParser.AssemblerSymbol symbol)) { + Assert.Fail ($"Required symbol '{symbolName}' cannot be found in the '{envFile.Path}' file."); + } + + Assert.IsNotNull (symbol, $"Assembler symbol '{symbolName}' must not be null in environment file '{envFile.Path}'"); + Assert.IsTrue (symbol.Contents.Count > 0, $"Assembler symbol '{symbolName}' must have at least one line of contents in environment file '{envFile.Path}'"); + + return symbol; + } + + static string GetStringContents (NativeAssemblyParser.AssemblerSymbol symbol, EnvironmentFile envFile, NativeAssemblyParser parser, int minimumLength = 1) + { + string[] field = GetField (envFile.Path, parser.SourceFilePath, symbol.Contents[0].Contents, symbol.Contents[0].LineNumber); + + Assert.IsTrue (field.Length >= 2, $"First line of symbol '{symbol.Name}' must have at least two fields in environment file '{parser.SourceFilePath}'. File generated from '{envFile.Path}'"); + Assert.IsTrue (String.Compare (".asciz", field[0], StringComparison.Ordinal) == 0, $"First line of symbol '{symbol.Name}' must begin with the '.asciz' directive in environment file '{parser.SourceFilePath}'. Instead it was '{field[0]}'. File generated from '{envFile.Path}'"); + + // the extra 2 characters account for the enclosing '"' + Assert.IsTrue (field[1].Length > 2 + minimumLength, $"Symbol '{symbol.Name}' must not be a string at least {minimumLength} characters long in environment file '{parser.SourceFilePath}'. File generated from '{envFile.Path}'"); + + string s = field[1].Trim (); + Assert.IsTrue (s[0] == '"' && s[s.Length - 1] == '"', $"Symbol '{symbol.Name}' value must be enclosed in double quotes in environment file '{parser.SourceFilePath}'. File generated from '{envFile.Path}'"); + + return field[1].Trim ('"'); + } + // Reads all the environment files, makes sure they all have identical contents in the // `application_config` structure and returns the config if the condition is true - public static ApplicationConfig ReadApplicationConfig (List envFilePaths) + public static ApplicationConfig ReadApplicationConfig (List envFilePaths) { if (envFilePaths.Count == 0) return null; @@ -78,164 +168,147 @@ public static ApplicationConfig ReadApplicationConfig (List envFilePaths ApplicationConfig app_config = ReadApplicationConfig (envFilePaths [0]); for (int i = 1; i < envFilePaths.Count; i++) { - AssertApplicationConfigIsIdentical (app_config, envFilePaths [0], ReadApplicationConfig (envFilePaths[i]), envFilePaths[i]); + AssertApplicationConfigIsIdentical (app_config, envFilePaths [0].Path, ReadApplicationConfig (envFilePaths[i]), envFilePaths[i].Path); } return app_config; } - static ApplicationConfig ReadApplicationConfig (string envFile) + static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) { - string[] lines = File.ReadAllLines (envFile, Encoding.UTF8); - var strings = new Dictionary (StringComparer.Ordinal); - var pointers = new List (); + NativeAssemblyParser parser = CreateAssemblyParser (envFile); + + if (!parser.Symbols.TryGetValue (ApplicationConfigSymbolName, out NativeAssemblyParser.AssemblerSymbol appConfigSymbol)) { + Assert.Fail ($"Symbol '{ApplicationConfigSymbolName}' not found in LLVM IR file '{envFile.Path}'"); + } + Assert.IsTrue (appConfigSymbol.Size != 0, $"{ApplicationConfigSymbolName} size as specified in the '.size' directive must not be 0"); + + var pointers = new List (); var ret = new ApplicationConfig (); - bool gatherFields = false; uint fieldCount = 0; - for (int i = 0; i < lines.Length; i++) { - string line = lines [i]; - if (IsCommentLine (line)) - continue; + string[] field; - string[] field; - if (stringLabelRegex.IsMatch (line)) { - string label = line.Substring (0, line.Length - 1); + foreach (NativeAssemblyParser.AssemblerSymbolItem item in appConfigSymbol.Contents) { + field = GetField (envFile.Path, parser.SourceFilePath, item.Contents, item.LineNumber); - line = lines [++i]; - field = GetField (envFile, line, i); - AssertFieldType (envFile, ".asciz", field [0], i); - strings [label] = AssertIsAssemblerString (envFile, field [1], i); - continue; - } - - if (String.Compare ("application_config:", line.Trim (), StringComparison.Ordinal) == 0) { - gatherFields = true; - continue; + if (String.Compare (".zero", field[0], StringComparison.Ordinal) == 0) { + continue; // padding, we can safely ignore it } - if (!gatherFields) - continue; - - field = GetField (envFile, line, i); - if (String.Compare (".zero", field [0], StringComparison.Ordinal) == 0) - continue; // structure padding - switch (fieldCount) { case 0: // uses_mono_llvm: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.uses_mono_llvm = ConvertFieldToBool ("uses_mono_llvm", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.uses_mono_llvm = ConvertFieldToBool ("uses_mono_llvm", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 1: // uses_mono_aot: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.uses_mono_aot = ConvertFieldToBool ("uses_mono_aot", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.uses_mono_aot = ConvertFieldToBool ("uses_mono_aot", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 2: // uses_assembly_preload: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.uses_assembly_preload = ConvertFieldToBool ("uses_assembly_preload", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.uses_assembly_preload = ConvertFieldToBool ("uses_assembly_preload", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 3: // is_a_bundled_app: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.is_a_bundled_app = ConvertFieldToBool ("is_a_bundled_app", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.is_a_bundled_app = ConvertFieldToBool ("is_a_bundled_app", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 4: // broken_exception_transitions: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.broken_exception_transitions = ConvertFieldToBool ("broken_exception_transitions", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.broken_exception_transitions = ConvertFieldToBool ("broken_exception_transitions", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 5: // instant_run_enabled: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.instant_run_enabled = ConvertFieldToBool ("instant_run_enabled", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.instant_run_enabled = ConvertFieldToBool ("instant_run_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 6: // jni_add_native_method_registration_attribute_present: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 7: // have_runtime_config_blob: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.have_runtime_config_blob = ConvertFieldToBool ("have_runtime_config_blob", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.have_runtime_config_blob = ConvertFieldToBool ("have_runtime_config_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 8: // have_assemblies_blob: bool / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 9: // bound_stream_io_exception_type: byte / .byte - AssertFieldType (envFile, ".byte", field [0], i); - ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile, i, field [1]); + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 10: // package_naming_policy: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 11: // environment_variable_count: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 12: // system_property_count: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 13: // number_of_assemblies_in_apk: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 14: // bundled_assembly_name_width: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 15: // number_of_assembly_store_files: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 16: // number_of_dso_cache_entries: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 17: // mono_components_mask: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}"); - ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile, i, field [1]); + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; case 18: // android_package_name: string / [pointer type] - Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}"); + Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; } fieldCount++; - - if (String.Compare (".size", field [0], StringComparison.Ordinal) == 0) { - fieldCount--; - Assert.IsTrue (field [1].StartsWith ("application_config", StringComparison.Ordinal), $"Mismatched .size directive in '{envFile}:{i}'"); - gatherFields = false; // We've reached the end of the application_config structure - } } - Assert.AreEqual (ApplicationConfigFieldCount, fieldCount, $"Invalid 'application_config' field count in environment file '{envFile}'"); - Assert.AreEqual (1, pointers.Count, $"Invalid number of string pointers in 'application_config' structure in environment file '{envFile}'"); - Assert.IsTrue (strings.TryGetValue (pointers [0], out ret.android_package_name), $"Invalid package name string pointer in 'application_config' structure in environment file '{envFile}'"); - Assert.IsFalse (String.IsNullOrEmpty (ret.android_package_name), $"Package name field in 'application_config' in environment file '{envFile}' must not be null or empty"); + + Assert.AreEqual (1, pointers.Count, $"Invalid number of string pointers in 'application_config' structure in environment file '{envFile.Path}'"); + + NativeAssemblyParser.AssemblerSymbol androidPackageNameSymbol = GetRequiredSymbol (pointers[0], envFile, parser); + ret.android_package_name = GetStringContents (androidPackageNameSymbol, envFile, parser); + + Assert.AreEqual (ApplicationConfigFieldCount, fieldCount, $"Invalid 'application_config' field count in environment file '{envFile.Path}'"); + Assert.IsFalse (String.IsNullOrEmpty (ret.android_package_name), $"Package name field in 'application_config' in environment file '{envFile.Path}' must not be null or empty"); return ret; } // Reads all the environment files, makes sure they contain the same environment variables (both count // and contents) and then returns a dictionary filled with the variables. - public static Dictionary ReadEnvironmentVariables (List envFilePaths) + public static Dictionary ReadEnvironmentVariables (List envFilePaths) { if (envFilePaths.Count == 0) return null; @@ -245,52 +318,28 @@ static ApplicationConfig ReadApplicationConfig (string envFile) return envvars; for (int i = 1; i < envFilePaths.Count; i++) { - AssertDictionariesAreEqual (envvars, envFilePaths [0], ReadEnvironmentVariables (envFilePaths[i]), envFilePaths[i]); + AssertDictionariesAreEqual (envvars, envFilePaths [0].Path, ReadEnvironmentVariables (envFilePaths[i]), envFilePaths[i].Path); } return envvars; } - static Dictionary ReadEnvironmentVariables (string envFile) + static Dictionary ReadEnvironmentVariables (EnvironmentFile envFile) { - string[] lines = File.ReadAllLines (envFile, Encoding.UTF8); - var strings = new Dictionary (StringComparer.Ordinal); - var pointers = new List (); + NativeAssemblyParser parser = CreateAssemblyParser (envFile); - bool gatherPointers = false; - for (int i = 0; i < lines.Length; i++) { - string line = lines [i]; - if (IsCommentLine (line)) - continue; - - string[] field; - if (stringLabelRegex.IsMatch (line)) { - string label = line.Substring (0, line.Length - 1); - - line = lines [++i]; - field = GetField (envFile, line, i); - - AssertFieldType (envFile, ".asciz", field [0], i); - strings [label] = AssertIsAssemblerString (envFile, field [1], i); - continue; - } - - if (String.Compare ("app_environment_variables:", line.Trim (), StringComparison.Ordinal) == 0) { - gatherPointers = true; - continue; - } + if (!parser.Symbols.TryGetValue (AppEnvironmentVariablesSymbolName, out NativeAssemblyParser.AssemblerSymbol appEnvvarsSymbol)) { + Assert.Fail ($"Symbol '{AppEnvironmentVariablesSymbolName}' not found in LLVM IR file '{envFile.Path}'"); + } - if (!gatherPointers) - continue; + Assert.IsTrue (appEnvvarsSymbol.Size != 0, $"{AppEnvironmentVariablesSymbolName} size as specified in the '.size' directive must not be 0"); - field = GetField (envFile, line, i); - if (String.Compare (".size", field [0], StringComparison.Ordinal) == 0) { - Assert.IsTrue (field [1].StartsWith ("app_environment_variables", StringComparison.Ordinal), $"Mismatched .size directive in '{envFile}:{i}'"); - gatherPointers = false; // We've reached the end of the environment variable array - continue; - } + string[] field; + var pointers = new List (); + foreach (NativeAssemblyParser.AssemblerSymbolItem item in appEnvvarsSymbol.Contents) { + field = GetField (envFile.Path, parser.SourceFilePath, item.Contents, item.LineNumber); - Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}"); + Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{parser.SourceFilePath}:{item.LineNumber}': {field [0]}. File generated from '{envFile.Path}'"); pointers.Add (field [1].Trim ()); } @@ -300,14 +349,11 @@ static ApplicationConfig ReadApplicationConfig (string envFile) Assert.IsTrue (pointers.Count % 2 == 0, "Environment variable array must have an even number of elements"); for (int i = 0; i < pointers.Count; i += 2) { - string name; - - Assert.IsTrue (strings.TryGetValue (pointers [i], out name), $"[name] String with label '{pointers [i]}' not found in '{envFile}'"); - Assert.IsFalse (String.IsNullOrEmpty (name), $"Environment variable name must not be null or empty in {envFile} for string label '{pointers [i]}'"); + NativeAssemblyParser.AssemblerSymbol symbol = GetRequiredSymbol (pointers[i], envFile, parser); + string name = GetStringContents (symbol, envFile, parser); - string value; - Assert.IsTrue (strings.TryGetValue (pointers [i + 1], out value), $"[value] String with label '{pointers [i + 1]}' not found in '{envFile}'"); - Assert.IsNotNull (value, $"Environnment variable value must not be null in '{envFile}' for string label '{pointers [i + 1]}'"); + symbol = GetRequiredSymbol (pointers[i + 1], envFile, parser); + string value = GetStringContents (symbol, envFile, parser, minimumLength: 0); ret [name] = value; } @@ -315,37 +361,17 @@ static ApplicationConfig ReadApplicationConfig (string envFile) return ret; } - static bool IsCommentLine (string line) - { - string l = line?.Trim (); - if (String.IsNullOrEmpty (l)) { - return false; - } - - return l.StartsWith ("/*", StringComparison.Ordinal) || - l.StartsWith ("//", StringComparison.Ordinal) || - l.StartsWith ("#", StringComparison.Ordinal) || - l.StartsWith ("@", StringComparison.Ordinal); - } - - static string[] GetField (string file, string line, int lineNumber) + 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 '{file}:{lineNumber}': '{line}'"); + Assert.IsTrue (ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'"); return ret; } - static void AssertFieldType (string file, string expectedType, string value, int lineNumber) - { - Assert.AreEqual (expectedType, value, $"Expected the '{expectedType}' field type in file '{file}:{lineNumber}': {value}"); - } - - static string AssertIsAssemblerString (string file, string value, int lineNumber) + static void AssertFieldType (string llvmAssemblerFile, string nativeAssemblerFile, string expectedType, string value, ulong lineNumber) { - string v = value.Trim (); - Assert.IsTrue (v.StartsWith ("\"", StringComparison.Ordinal) && v.EndsWith("\"", StringComparison.Ordinal), $"Field value is not a valid assembler string in '{file}:{lineNumber}': {v}"); - return v.Trim ('"'); + Assert.AreEqual (expectedType, value, $"Expected the '{expectedType}' field type in file '{nativeAssemblerFile}:{lineNumber}': {value}. File generated from '{llvmAssemblerFile}'"); } static void AssertDictionariesAreEqual (Dictionary d1, string d1FileName, Dictionary d2, string d2FileName) @@ -370,15 +396,15 @@ static void AssertApplicationConfigIsIdentical (ApplicationConfig firstAppConfig Assert.AreEqual (firstAppConfig.android_package_name, secondAppConfig.android_package_name, $"Field 'android_package_name' has different value in environment file '{secondEnvFile}' than in environment file '{firstEnvFile}'"); } - public static List GatherEnvironmentFiles (string outputDirectoryRoot, string supportedAbis, bool required) + public static List GatherEnvironmentFiles (string outputDirectoryRoot, string supportedAbis, bool required) { - var environmentFiles = new List (); + var environmentFiles = new List (); foreach (string abi in supportedAbis.Split (';')) { - string envFilePath = Path.Combine (outputDirectoryRoot, "android", $"environment.{abi}.s"); + string envFilePath = Path.Combine (outputDirectoryRoot, "android", $"environment.{abi}.ll"); Assert.IsTrue (File.Exists (envFilePath), $"Environment file {envFilePath} does not exist"); - environmentFiles.Add (envFilePath); + environmentFiles.Add (new EnvironmentFile (envFilePath, abi)); } if (required) @@ -427,10 +453,31 @@ public static void AssertValidEnvironmentSharedLibrary (string outputDirectoryRo } static void AssertSharedLibraryHasRequiredSymbols (string dsoPath, string readElfPath) + { + (List stdout_lines, List _) = RunCommand (readElfPath, $"--dyn-syms \"{dsoPath}\""); + + var symbols = new HashSet (StringComparer.Ordinal); + foreach (string line in stdout_lines) { + string[] fields = line.Split (readElfFieldSeparator, StringSplitOptions.RemoveEmptyEntries); + if (fields.Length < 8 || !fields [0].EndsWith (":", StringComparison.Ordinal)) + continue; + string symbolName = fields [7].Trim (); + if (String.IsNullOrEmpty (symbolName)) + continue; + + symbols.Add (symbolName); + } + + foreach (string symbol in requiredSharedLibrarySymbols) { + Assert.IsTrue (symbols.Contains (symbol), $"Symbol '{symbol}' is missing from '{dsoPath}'"); + } + } + + static (List stdout, List stderr) RunCommand (string executablePath, string arguments = null) { var psi = new ProcessStartInfo { - FileName = readElfPath, - Arguments = $"--dyn-syms \"{dsoPath}\"", + FileName = executablePath, + Arguments = arguments, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, @@ -477,25 +524,11 @@ static void AssertSharedLibraryHasRequiredSymbols (string dsoPath, string readEl if (!exited || process.ExitCode != 0) { DumpLines ("stdout", stdout_lines); DumpLines ("stderr", stderr_lines); - Assert.Fail ($"Failed to validate application environment SharedLibrary '{dsoPath}'"); + Assert.Fail ($"Command '{psi.FileName} {psi.Arguments}' failed to run or exited with error code"); } } - var symbols = new HashSet (StringComparer.Ordinal); - foreach (string line in stdout_lines) { - string[] fields = line.Split (readElfFieldSeparator, StringSplitOptions.RemoveEmptyEntries); - if (fields.Length < 8 || !fields [0].EndsWith (":", StringComparison.Ordinal)) - continue; - string symbolName = fields [7].Trim (); - if (String.IsNullOrEmpty (symbolName)) - continue; - - symbols.Add (symbolName); - } - - foreach (string symbol in requiredSharedLibrarySymbols) { - Assert.IsTrue (symbols.Contains (symbol), $"Symbol '{symbol}' is missing from '{dsoPath}'"); - } + return (stdout_lines, stderr_lines); } static void DumpLines (string streamName, List lines) @@ -509,34 +542,34 @@ static void DumpLines (string streamName, List lines) } } - static bool ConvertFieldToBool (string fieldName, string envFile, int fileLine, string value) + static bool ConvertFieldToBool (string fieldName, string llvmAssemblerEnvFile, string nativeAssemblerEnvFile, ulong fileLine, string value) { // Allow both decimal and hexadecimal values - Assert.IsTrue (value.Length > 0 && value.Length <= 3, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (length not between 1 and 3)"); + Assert.IsTrue (value.Length > 0 && value.Length <= 3, $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid boolean value (length not between 1 and 3). File generated from '{llvmAssemblerEnvFile}'"); uint fv; - Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (not a valid integer)"); - Assert.IsTrue (fv == 0 || fv == 1, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (not a valid boolean value 0 or 1)"); + Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid boolean value (not a valid integer). File generated from '{llvmAssemblerEnvFile}'"); + Assert.IsTrue (fv == 0 || fv == 1, $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid boolean value (not a valid boolean value 0 or 1). File generated from '{llvmAssemblerEnvFile}'"); return fv == 1; } - static uint ConvertFieldToUInt32 (string fieldName, string envFile, int fileLine, string value) + static uint ConvertFieldToUInt32 (string fieldName, string llvmAssemblerEnvFile, string nativeAssemblerEnvFile, ulong fileLine, string value) { - Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint32_t value (not long enough)"); + Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid uint32_t value (not long enough). File generated from '{llvmAssemblerEnvFile}'"); uint fv; - Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint32_t value (not a valid integer)"); + Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid uint32_t value (not a valid integer). File generated from '{llvmAssemblerEnvFile}'"); return fv; } - static byte ConvertFieldToByte (string fieldName, string envFile, int fileLine, string value) + static byte ConvertFieldToByte (string fieldName, string llvmAssemblerEnvFile, string nativeAssemblerEnvFile, ulong fileLine, string value) { - Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint8_t value (not long enough)"); + Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid uint8_t value (not long enough). File generated from '{llvmAssemblerEnvFile}'"); byte fv; - Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint8_t value (not a valid integer)"); + Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {nativeAssemblerEnvFile}:{fileLine} is not a valid uint8_t value (not a valid integer). File generated from '{llvmAssemblerEnvFile}'"); return fv; } 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 new file mode 100644 index 00000000000..bfbf5fb709c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace Xamarin.Android.Build.Tests +{ + [Flags] + enum ELFSectionFlags + { + None = 0, + Allocatable = 1 << 0, + GnuMbind = 1 << 1, + Excluded = 1 << 2, + ReferencesOtherSection = 1 << 3, + Writable = 1 << 4, + Executable = 1 << 5, + Mergeable = 1 << 6, + HasCStrings = 1 << 7, + GroupMember = 1 << 8, + ThreadLocalStorage = 1 << 9, + MemberOfPreviousGroup = 1 << 10, + Retained = 1 << 11, + Number = 1 << 12, + Custom = 1 << 13, + } + + enum ELFSectionType + { + None, + Progbits, // @progbits + Nobits, // @nobits + Note, // @note + InitArray, // @init_array + FiniArray, // @fini_array + PreInitArray, // @preinit_array + Number, // @ + Custom, // @ + } + + enum ELFSymbolType + { + Function, + GnuIndirectFunction, + GnuUniqueObject, + Object, + TlsObject, + Common, + NoType, + } + + class NativeAssemblyParser + { + public enum SymbolMetadataKind + { + Global, + Local, + Size, + Type, + } + + public sealed class AssemblerSection + { + public readonly ELFSectionType Type; + public readonly string Name; + public readonly ELFSectionFlags Flags; + + public AssemblerSection (string name, ELFSectionType type, ELFSectionFlags flags) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Name = name; + Type = type; + Flags = flags; + } + } + + public sealed class AssemblerSymbolItem + { + public readonly string Contents; + public readonly ulong LineNumber; + + public AssemblerSymbolItem (string contents, ulong lineNumber) + { + Contents = contents; + LineNumber = lineNumber; + } + } + + public sealed class AssemblerSymbol + { + public readonly AssemblerSection Section; + public readonly string Name; + public readonly List Contents; + public readonly ulong LineNumber; + + public bool IsGlobal { get; private set; } + public ulong Size { get; private set; } + public ELFSymbolType Type { get; private set; } + + public AssemblerSymbol (string name, AssemblerSection section, ulong lineNumber) + { + Section = section ?? throw new ArgumentNullException (nameof (section)); + + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Name = name; + Contents = new List (); + LineNumber = lineNumber; + } + + public void SetMetadata (List metadata) + { + IsGlobal = false; + Size = 0; + Type = ELFSymbolType.NoType; + + if (metadata == null || metadata.Count == 0) { + return; + } + + foreach (SymbolMetadata item in metadata) { + switch (item.Kind) { + case SymbolMetadataKind.Local: + IsGlobal = false; + break; + + case SymbolMetadataKind.Global: + IsGlobal = true; + break; + + case SymbolMetadataKind.Size: + if (!UInt64.TryParse (item.Value, out ulong size)) { + throw new InvalidOperationException ($"Unable to parse symbol size as ulong from '{item.Value}'"); + } + Size = size; + break; + + case SymbolMetadataKind.Type: + if (String.Compare ("function", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.Function; + } else if (String.Compare ("gnu_indirect_function", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.GnuIndirectFunction; + } else if (String.Compare ("object", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.Object; + } else if (String.Compare ("tls_object", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.TlsObject; + } else if (String.Compare ("common", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.Common; + } else if (String.Compare ("notype", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.NoType; + } else if (String.Compare ("gnu_unique_object", item.Value, StringComparison.OrdinalIgnoreCase) == 0) { + Type = ELFSymbolType.GnuUniqueObject; + } else { + throw new InvalidOperationException ($"Unsupported symbol type '{item.Value}'"); + } + break; + + default: + throw new InvalidOperationException ($"Unsupported symbol kind '{item.Kind}'"); + } + } + } + } + + public sealed class SymbolMetadata + { + public readonly SymbolMetadataKind Kind; + public readonly string Value; + + public SymbolMetadata (SymbolMetadataKind kind, string value = null) + { + Kind = kind; + Value = value; + } + } + + static readonly HashSet ignoredDirectives = new HashSet (StringComparer.OrdinalIgnoreCase) { + ".arch", + ".eabi_attribute", + ".file", + ".fpu", + ".ident", + ".p2align", + ".syntax", + }; + + 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); + + Dictionary symbols = new Dictionary (StringComparer.Ordinal); + Dictionary> symbolMetadata = new Dictionary> (StringComparer.Ordinal); + string abiCommentStart; + + public Dictionary Symbols => symbols; + public string SourceFilePath { get; } + + public NativeAssemblyParser (string sourceFilePath, string abi) + { + if (String.IsNullOrEmpty (sourceFilePath)) { + throw new ArgumentException ("must not be null or empty", nameof (sourceFilePath)); + } + + if (!File.Exists (sourceFilePath)) { + throw new InvalidOperationException ($"File '{sourceFilePath}' does not exist"); + } + + switch (abi) { + case "arm64-v8a": + abiCommentStart = "//"; + break; + + case "armeabi-v7a": + abiCommentStart = "@"; + break; + + case "x86": + case "x86_64": + abiCommentStart = "#"; + break; + + default: + throw new InvalidOperationException ($"Unsupported ABI '{abi}'"); + } + + SourceFilePath = sourceFilePath; + Load (sourceFilePath); + } + + void Load (string sourceFilePath) + { + AssemblerSection currentSection = null; + AssemblerSymbol currentSymbol = null; + + string symbolName; + ulong lineNumber = 0; + foreach (string l in File.ReadLines (sourceFilePath, Encoding.UTF8)) { + lineNumber++; + + string line = TrimLine (l); + if (String.IsNullOrEmpty (line)) { + continue; + } + + string[] parts = line.Split (splitOnWhitespace, 2, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0 || (ignoredDirectives.Contains (parts[0]))) { + continue; + } + + if (StartsNewSection (parts, ref currentSection)) { + currentSymbol = null; // Symbols cannot cross sections + continue; + } + + if (IsSymbolMetadata (parts)) { + continue; + } + + if (assemblerLabelRegex.IsMatch (line)) { + symbolName = GetSymbolName (line); + currentSymbol = AddNewSymbol (symbolName); + continue; + } + + if (IsCommSymbol (parts, out symbolName)) { + // .comm creates symbol in a single line, there won't be any "contents", so we add a new + // symbol and close the current one + AddNewSymbol (symbolName); + currentSymbol = null; + continue; + } + + if (currentSymbol == null) { + throw new InvalidOperationException ($"Found non-empty line, but there's no current symbol to append it to as contents. '{sourceFilePath}', line {lineNumber}"); + } + + currentSymbol.Contents.Add (new AssemblerSymbolItem (line, lineNumber)); + } + + foreach (var kvp in symbolMetadata) { + symbolName = kvp.Key; + List metadata = kvp.Value; + + if (!symbols.TryGetValue (symbolName, out AssemblerSymbol symbol)) { + continue; + } + symbol?.SetMetadata (metadata); + } + + string TrimLine (string line) + { + string ret = line.Trim (); + int commentStart = ret.IndexOf (abiCommentStart, StringComparison.Ordinal); + + if (commentStart < 0) { + return ret; + } + + if (commentStart == 0) { + return String.Empty; + } + + return ret.Substring (0, commentStart).Trim (); + } + + AssemblerSymbol AddNewSymbol (string symbolName) + { + var ret = new AssemblerSymbol (symbolName, currentSection, lineNumber); + symbols.Add (symbolName, ret); + return ret; + } + + bool IsCommSymbol (string[] parts, out string symbolName) + { + if (parts.Length < 2 || String.Compare (".comm", parts[0], StringComparison.Ordinal) != 0) { + symbolName = String.Empty; + return false; + } + + // .comm in ELF takes an optional 3rd parameter, which sets the desired alignment + // We ignore it here as it doesn't really matter to us + string[] commParts = GetSymbolMetadataParts (".comm", parts[1], 2); + symbolName = commParts[0].Trim (); + new SymbolMetadata (SymbolMetadataKind.Size, commParts[1].Trim ()); + + return true; + } + + bool IsSymbolMetadata (string[] parts) + { + if (parts.Length < 2) { + return false; + } + + string symbolName = null; + SymbolMetadata metadata = null; + if (String.Compare (".type", parts[0], StringComparison.Ordinal) == 0) { + (symbolName, metadata) = GetSymbolType (parts[1]); + } else if (String.Compare (".globl", parts[0], StringComparison.Ordinal) == 0 || String.Compare (".global", parts[0], StringComparison.Ordinal) == 0) { + (symbolName, metadata) = GetGlobal (parts[0], parts[1]); + } else if (String.Compare (".size", parts[0], StringComparison.Ordinal) == 0) { + (symbolName, metadata) = GetSymbolSize (parts[1]); + currentSymbol = null; // .size ends the symbol definition in the assembly generated by LLVM + } else if (String.Compare (".local", parts[0], StringComparison.Ordinal) == 0) { + // .local takes a comma-separated list of symbol names + foreach (string name in parts[1].Split (splitOnComma, StringSplitOptions.RemoveEmptyEntries)) { + StoreSymbolMetadata (name, new SymbolMetadata (SymbolMetadataKind.Local)); + } + return true; + } + + if (!String.IsNullOrEmpty (symbolName) && metadata != null) { + StoreSymbolMetadata (symbolName, metadata); + return true; + } + + return false; + } + + void StoreSymbolMetadata (string symbolName, SymbolMetadata metadata) + { + if (!symbolMetadata.TryGetValue (symbolName, out List metadataList)) { + metadataList = new List (); + symbolMetadata.Add (symbolName, metadataList); + } + metadataList.Add (metadata); + } + + (string symbolName, SymbolMetadata metadata) GetSymbolSize (string directive) + { + string[] parts = GetSymbolMetadataParts (".size", directive, 2); + return (parts[0].Trim (), new SymbolMetadata (SymbolMetadataKind.Size, parts[1].Trim ())); + } + + (string symbolName, SymbolMetadata metadata) GetGlobal (string name, string directive) + { + string[] parts = GetSymbolMetadataParts (name, directive, 1); + return (parts[0].Trim (), new SymbolMetadata (SymbolMetadataKind.Global)); + } + + (string symbolName, SymbolMetadata metadata) GetSymbolType (string directive) + { + string[] parts = GetSymbolMetadataParts (".type", directive, 2); + return (parts[0].Trim (), new SymbolMetadata (SymbolMetadataKind.Type, parts[1].Trim ().Substring (1))); + } + + string[] GetSymbolMetadataParts (string directiveName, string directiveValue, int requiredParts) + { + string[] parts = directiveValue.Split (splitOnComma, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < requiredParts) { + throw new InvalidOperationException ($"Invalid format of the '{directiveName}' directive in '{sourceFilePath}', line {lineNumber}. Expected {requiredParts}, got {parts.Length}"); + } + + return parts; + } + + bool StartsNewSection (string[] parts, ref AssemblerSection newSection) + { + if (String.Compare (".text", parts[0], StringComparison.Ordinal) == 0) { + newSection = new AssemblerSection (parts[0], ELFSectionType.Progbits, ELFSectionFlags.Allocatable | ELFSectionFlags.Executable); + return true; + } + + if (String.Compare (".data", parts[0], StringComparison.Ordinal) == 0) { + newSection = new AssemblerSection (parts[0], ELFSectionType.Progbits, ELFSectionFlags.Allocatable | ELFSectionFlags.Writable); + return true; + } + + if (String.Compare (".bss", parts[0], StringComparison.Ordinal) == 0) { + newSection = new AssemblerSection (parts[0], ELFSectionType.Nobits, ELFSectionFlags.Allocatable | ELFSectionFlags.Writable); + return true; + } + + if (String.Compare (".section", parts[0], StringComparison.Ordinal) != 0) { + return false; + } + + if (parts.Length < 2) { + throw new InvalidOperationException ($"Invalid format of the '.section' directive in '{sourceFilePath}', line {lineNumber}"); + } + + string[] sectionParts = parts[1].Split (splitOnComma, StringSplitOptions.RemoveEmptyEntries); + if (sectionParts.Length == 0) { + throw new InvalidOperationException ($"Invalid format of the '.section' directive in '{sourceFilePath}', line {lineNumber}"); + } + + string name = sectionParts[0].Trim (); + ELFSectionFlags flags = ELFSectionFlags.None; + + if (sectionParts.Length > 1) { + flags = ParseSectionFlags (sectionParts[1].Trim ()); + } + + ELFSectionType type = ELFSectionType.None; + if (sectionParts.Length > 2) { + type = ParseSectionType (sectionParts[2].Trim ()); + } + + newSection = new AssemblerSection (parts[0], type, flags); + return true; + } + + ELFSectionType ParseSectionType (string type) + { + if (String.IsNullOrEmpty (type)) { + return ELFSectionType.None; + } + + if (type.Length < 2) { + throw new InvalidOperationException ($"Invalid .section type '{type}' in '{sourceFilePath}', line {lineNumber}"); + } + + type = type.Substring (1); // ignore the arch-specific first character + if (String.Compare (type, "progbits", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.Progbits; + } + + if (String.Compare (type, "nobits", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.Nobits; + } + + if (String.Compare (type, "note", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.Note; + } + + if (String.Compare (type, "init_array", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.InitArray; + } + + if (String.Compare (type, "fini_array", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.FiniArray; + } + + if (String.Compare (type, "preinit_array", StringComparison.OrdinalIgnoreCase) == 0) { + return ELFSectionType.PreInitArray; + } + + if (UInt64.TryParse (type, out _)) { + return ELFSectionType.Number; + } + + return ELFSectionType.Custom; + } + + ELFSectionFlags ParseSectionFlags (string flags) + { + ELFSectionFlags ret = ELFSectionFlags.None; + + if (String.IsNullOrEmpty (flags)) { + return ret; + } + + foreach (char ch in flags) { + switch (ch) { + case 'a': + ret |= ELFSectionFlags.Allocatable; + break; + + case 'd': + ret |= ELFSectionFlags.GnuMbind; + break; + + case 'e': + ret |= ELFSectionFlags.Excluded; + break; + + case 'o': + ret |= ELFSectionFlags.ReferencesOtherSection; + break; + + case 'w': + ret |= ELFSectionFlags.Writable; + break; + + case 'x': + ret |= ELFSectionFlags.Executable; + break; + + case 'M': + ret |= ELFSectionFlags.Mergeable; + break; + + case 'S': + ret |= ELFSectionFlags.HasCStrings; + break; + + case 'G': + ret |= ELFSectionFlags.GroupMember; + break; + + case 'T': + ret |= ELFSectionFlags.ThreadLocalStorage; + break; + + case '?': + ret |= ELFSectionFlags.MemberOfPreviousGroup; + break; + + case 'R': + ret |= ELFSectionFlags.Retained; + break; + + case '"': + break; // ignore + + default: + throw new InvalidOperationException ($"Unknown .section flag '{ch}' in '{sourceFilePath}', line {lineNumber}"); + } + } + + return ret; + } + } + + string GetSymbolName (string line) + { + int colon = line.IndexOf (':'); + return line.Substring (0, colon); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj index 3bdb1b40f6b..ab1966eb6e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj @@ -57,6 +57,10 @@ + + + + %(FileName)%(Extension) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index b2476f7abfe..cfd7832cd49 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,37 +5,37 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 54996 + "Size": 55111 }, "assemblies/Mono.Android.dll": { - "Size": 88096 + "Size": 88334 }, "assemblies/rc.bin": { "Size": 1083 }, "assemblies/System.Linq.dll": { - "Size": 10113 + "Size": 10120 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 519420 + "Size": 519314 }, "assemblies/System.Runtime.CompilerServices.Unsafe.dll": { - "Size": 1163 + "Size": 1165 }, "assemblies/System.Runtime.dll": { - "Size": 2367 + "Size": 2374 }, "assemblies/UnnamedProject.dll": { - "Size": 3552 + "Size": 3551 }, "classes.dex": { "Size": 345328 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 382200 + "Size": 380832 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3171952 + "Size": 3192432 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { "Size": 776216 @@ -47,7 +47,7 @@ "Size": 150032 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 10104 + "Size": 8688 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index d32e3b73cc4..bc6f57e35fd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -7,7 +7,8 @@ using K4os.Hash.xxHash; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using Xamarin.Android.Tools; + +using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks { @@ -21,7 +22,7 @@ enum MonoComponent Tracing = 0x04, } - class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyComposer + class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer { sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextDataProvider { @@ -33,7 +34,7 @@ public override string GetComment (object data, string fieldName) } if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { - return $"hash, 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) { @@ -44,6 +45,9 @@ public override string GetComment (object data, string fieldName) } } + // Order of fields and their type must correspond *exactly* (with exception of the + // ignored managed members) to that in + // src/monodroid/jni/xamarin-app.hh DSOCacheEntry structure [NativeAssemblerStructContextDataProvider (typeof (DSOCacheEntryContextDataProvider))] sealed class DSOCacheEntry { @@ -54,48 +58,98 @@ sealed class DSOCacheEntry public ulong hash; public bool ignore; - [NativeAssemblerString (UsesDataProvider = true)] public string name; public IntPtr handle = IntPtr.Zero; } + // Order of fields and their type must correspond *exactly* to that in + // src/monodroid/jni/xamarin-app.hh AssemblyStoreAssemblyDescriptor structure + sealed class AssemblyStoreAssemblyDescriptor + { + public uint data_offset; + public uint data_size; + + public uint debug_data_offset; + public uint debug_data_size; + + public uint config_data_offset; + public uint config_data_size; + } + // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh AssemblyStoreSingleAssemblyRuntimeData structure sealed class AssemblyStoreSingleAssemblyRuntimeData { - public IntPtr image_data; - public IntPtr debug_info_data; - public IntPtr config_data; - public IntPtr descriptor; - }; + [NativePointer] + public byte image_data; + + [NativePointer] + public byte debug_info_data; + + [NativePointer] + public byte config_data; + + [NativePointer] + public AssemblyStoreAssemblyDescriptor descriptor; + } // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh AssemblyStoreRuntimeData structure sealed class AssemblyStoreRuntimeData { - public IntPtr data_start; - public uint assembly_count; - public IntPtr assemblies; - }; + [NativePointer] + public byte data_start; + public uint assembly_count; + + [NativePointer] + public AssemblyStoreAssemblyDescriptor assemblies; + } + + sealed class XamarinAndroidBundledAssemblyContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override ulong GetBufferSize (object data, string fieldName) + { + if (String.Compare ("name", fieldName, StringComparison.Ordinal) != 0) { + return 0; + } + + var xaba = EnsureType (data); + return xaba.name_length; + } + } // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh XamarinAndroidBundledAssembly structure + [NativeAssemblerStructContextDataProvider (typeof (XamarinAndroidBundledAssemblyContextDataProvider))] sealed class XamarinAndroidBundledAssembly { - public int apk_fd; - public uint data_offset; - public uint data_size; - public IntPtr data; - public uint name_length; + public int apk_fd; + public uint data_offset; + public uint data_size; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string name; - }; + [NativePointer] + public byte data; + public uint name_length; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] + public char name; + } + + // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh + const ulong FORMAT_TAG = 0x015E6972616D58; SortedDictionary environmentVariables; SortedDictionary systemProperties; - uint stringCounter = 0; - uint bufferCounter = 0; + TaskLoggingHelper log; + StructureInstance? application_config; + List>? dsoCache; + List>? xamarinAndroidBundledAssemblies; + + StructureInfo? applicationConfigStructureInfo; + StructureInfo? dsoCacheEntryStructureInfo; + StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; + StructureInfo assemblyStoreSingleAssemblyRuntimeDataStructureinfo; + StructureInfo assemblyStoreRuntimeDataStructureInfo; public bool IsBundledApp { get; set; } public bool UsesMonoAOT { get; set; } @@ -113,40 +167,26 @@ sealed class XamarinAndroidBundledAssembly public int NumberOfAssemblyStoresInApks { get; set; } public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL public MonoComponent MonoComponents { get; set; } - public List NativeLibraries { get; set; } - public PackageNamingPolicy PackageNamingPolicy { get; set; } + public List NativeLibraries { get; set; } - TaskLoggingHelper log; - - public ApplicationConfigNativeAssemblyGenerator (AndroidTargetArch arch, IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) - : base (arch) + public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) { - if (environmentVariables != null) + if (environmentVariables != null) { this.environmentVariables = new SortedDictionary (environmentVariables, StringComparer.Ordinal); - if (systemProperties != null) - this.systemProperties = new SortedDictionary (systemProperties, StringComparer.Ordinal); + } + + if (systemProperties != null) { + this.systemProperties = new SortedDictionary (systemProperties, StringComparer.Ordinal); + } + this.log = log; } - protected override void Write (NativeAssemblyGenerator generator) + public override void Init () { - if (String.IsNullOrEmpty (AndroidPackageName)) - throw new InvalidOperationException ("Android package name must be set"); - - if (UsesMonoAOT && String.IsNullOrEmpty (MonoAOTMode)) - throw new InvalidOperationException ("Mono AOT enabled but no AOT mode specified"); - - generator.WriteStringPointerSymbol ("mono_aot_mode_name", MonoAOTMode ?? String.Empty, global: true); - - WriteNameValueStringArray (generator, "app_environment_variables", environmentVariables); - WriteNameValueStringArray (generator, "app_system_properties", systemProperties); - - WriteBundledAssemblies (generator); - WriteAssemblyStoreAssemblies (generator); - - uint dsoCacheEntries = WriteDSOCache (generator); - var application_config = new ApplicationConfig { + dsoCache = InitDSOCache (); + var app_cfg = new ApplicationConfig { uses_mono_llvm = UsesMonoLLVM, uses_mono_aot = UsesMonoAOT, uses_assembly_preload = UsesAssemblyPreload, @@ -163,17 +203,31 @@ protected override void Write (NativeAssemblyGenerator generator) number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, number_of_assembly_store_files = (uint)NumberOfAssemblyStoresInApks, - number_of_dso_cache_entries = dsoCacheEntries, + number_of_dso_cache_entries = (uint)dsoCache.Count, mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; + application_config = new StructureInstance (app_cfg); + + if (!HaveAssemblyStore) { + xamarinAndroidBundledAssemblies = new List> (NumberOfAssembliesInApk); + + var emptyBundledAssemblyData = new XamarinAndroidBundledAssembly { + apk_fd = -1, + data_offset = 0, + data_size = 0, + data = 0, + name_length = (uint)BundledAssemblyNameWidth, + name = '\0', + }; - NativeAssemblyGenerator.StructureWriteContext structStatus = generator.StartStructure (); - generator.WriteStructure (structStatus, application_config); - generator.WriteSymbol (structStatus, "application_config", local: false); + for (int i = 0; i < NumberOfAssembliesInApk; i++) { + xamarinAndroidBundledAssemblies.Add (new StructureInstance (emptyBundledAssemblyData)); + } + } } - uint WriteDSOCache (NativeAssemblyGenerator generator) + List> InitDSOCache () { var dsos = new List<(string name, string nameLabel, bool ignore)> (); var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); @@ -192,7 +246,7 @@ uint WriteDSOCache (NativeAssemblyGenerator generator) dsos.Add ((name, $"dsoName{dsos.Count}", ELFHelper.IsEmptyAOTLibrary (log, item.ItemSpec))); } - var dsoCache = new List (); + var dsoCache = new List> (); var nameMutations = new List (); for (int i = 0; i < dsos.Count; i++) { @@ -201,37 +255,18 @@ uint WriteDSOCache (NativeAssemblyGenerator generator) AddNameMutations (name); // All mutations point to the actual library name, but have hash of the mutated one foreach (string entryName in nameMutations) { - dsoCache.Add ( - new DSOCacheEntry { - HashedName = entryName, - hash = HashName (entryName), - ignore = dsos[i].ignore, - name = name, - } - ); + var entry = new DSOCacheEntry { + HashedName = entryName, + hash = 0, // Hash is arch-specific, we compute it before writing + ignore = dsos[i].ignore, + name = name, + }; + + dsoCache.Add (new StructureInstance (entry)); } } - dsoCache.Sort ((DSOCacheEntry a, DSOCacheEntry b) => a.hash.CompareTo (b.hash)); - - NativeAssemblyGenerator.StructureWriteContext dsoCacheArray = generator.StartStructureArray (); - foreach (DSOCacheEntry entry in dsoCache) { - NativeAssemblyGenerator.StructureWriteContext dsoStruct = generator.AddStructureArrayElement (dsoCacheArray); - generator.WriteStructure (dsoStruct, entry); - } - generator.WriteSymbol (dsoCacheArray, "dso_cache", local: false); - - return (uint)dsoCache.Count; - - ulong HashName (string name) - { - byte[] nameBytes = Encoding.UTF8.GetBytes (name); - if (generator.Is64Bit) { - return XXH64.DigestOf (nameBytes, 0, nameBytes.Length); - } - - return (ulong)XXH32.DigestOf (nameBytes, 0, nameBytes.Length); - } + return dsoCache; void AddNameMutations (string name) { @@ -254,105 +289,66 @@ void AddNameMutations (string name) } } - void WriteAssemblyStoreAssemblies (NativeAssemblyGenerator generator) + protected override void MapStructures (LlvmIrGenerator generator) { - string label = "assembly_store_bundled_assemblies"; - generator.WriteCommentLine ("Assembly store individual assembly data"); - - if (HaveAssemblyStore) { - var emptyAssemblyData = new AssemblyStoreSingleAssemblyRuntimeData { - image_data = IntPtr.Zero, - debug_info_data = IntPtr.Zero, - config_data = IntPtr.Zero, - descriptor = IntPtr.Zero - }; + applicationConfigStructureInfo = generator.MapStructure (); + generator.MapStructure (); + assemblyStoreSingleAssemblyRuntimeDataStructureinfo = generator.MapStructure (); + assemblyStoreRuntimeDataStructureInfo = generator.MapStructure (); + xamarinAndroidBundledAssemblyStructureInfo = generator.MapStructure (); + dsoCacheEntryStructureInfo = generator.MapStructure (); + } - NativeAssemblyGenerator.StructureWriteContext assemblyArray = generator.StartStructureArray (); - for (int i = 0; i < NumberOfAssembliesInApk; i++) { - NativeAssemblyGenerator.StructureWriteContext assemblyStruct = generator.AddStructureArrayElement (assemblyArray); - generator.WriteStructure (assemblyStruct, emptyAssemblyData); - } - generator.WriteSymbol (assemblyArray, label, local: false); - } else { - generator.WriteEmptySymbol (SymbolType.Object, label, local: false); - } + protected override void Write (LlvmIrGenerator generator) + { + generator.WriteVariable ("format_tag", FORMAT_TAG); + generator.WriteString ("mono_aot_mode_name", MonoAOTMode); - label = "assembly_stores"; - generator.WriteCommentLine ("Assembly store data"); + generator.WriteNameValueArray ("app_environment_variables", environmentVariables); + generator.WriteNameValueArray ("app_system_properties", systemProperties); - if (HaveAssemblyStore) { - var emptyStoreData = new AssemblyStoreRuntimeData { - data_start = IntPtr.Zero, - assembly_count = 0, - assemblies = IntPtr.Zero, - }; + generator.WriteStructure (applicationConfigStructureInfo, application_config, LlvmIrVariableOptions.GlobalConstant, "application_config"); - NativeAssemblyGenerator.StructureWriteContext assemblyStoreArray = generator.StartStructureArray (); - for (int i = 0; i < NumberOfAssemblyStoresInApks; i++) { - NativeAssemblyGenerator.StructureWriteContext assemblyStoreStruct = generator.AddStructureArrayElement (assemblyStoreArray); - generator.WriteStructure (assemblyStoreStruct, emptyStoreData); - } - generator.WriteSymbol (assemblyStoreArray, label, local: false); - } else { - generator.WriteEmptySymbol (SymbolType.Object, label, local: false); - } + WriteDSOCache (generator); + WriteBundledAssemblies (generator); + WriteAssemblyStoreAssemblies (generator); } - void WriteBundledAssemblies (NativeAssemblyGenerator generator) + void WriteAssemblyStoreAssemblies (LlvmIrGenerator generator) { - generator.WriteCommentLine ($"Bundled assembly name buffers, all {BundledAssemblyNameWidth} bytes long"); - generator.WriteSection (".bss.bundled_assembly_names", SectionFlags.Writable | SectionFlags.Allocatable, SectionType.NoData); + ulong count = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); + generator.WriteStructureArray (assemblyStoreSingleAssemblyRuntimeDataStructureinfo, count, "assembly_store_bundled_assemblies", initialComment: "Assembly store individual assembly data"); - List? name_labels = null; - if (!HaveAssemblyStore) { - name_labels = new List (); - for (int i = 0; i < NumberOfAssembliesInApk; i++) { - name_labels.Add (generator.WriteEmptyBuffer ((uint)BundledAssemblyNameWidth, "env.buf")); - } - } - - string label = "bundled_assemblies"; - generator.WriteCommentLine ("Bundled assemblies data"); - - if (!HaveAssemblyStore) { - var emptyBundledAssemblyData = new XamarinAndroidBundledAssembly { - apk_fd = -1, - data_offset = 0, - data_size = 0, - data = IntPtr.Zero, - name_length = 0, - name = String.Empty, - }; + count = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); + generator.WriteStructureArray (assemblyStoreRuntimeDataStructureInfo, count, "assembly_stores", initialComment: "Assembly store data"); + } - NativeAssemblyGenerator.StructureWriteContext bundledAssemblyArray = generator.StartStructureArray (); - for (int i = 0; i < NumberOfAssembliesInApk; i++) { - emptyBundledAssemblyData.name = name_labels [i]!.Label; - NativeAssemblyGenerator.StructureWriteContext bundledAssemblyStruct = generator.AddStructureArrayElement (bundledAssemblyArray); - generator.WriteStructure (bundledAssemblyStruct, emptyBundledAssemblyData); - } - generator.WriteSymbol (bundledAssemblyArray, label, local: false); - } else { - generator.WriteEmptySymbol (SymbolType.Object, label, local: false); - } + void WriteBundledAssemblies (LlvmIrGenerator generator) + { + generator.WriteStructureArray (xamarinAndroidBundledAssemblyStructureInfo, xamarinAndroidBundledAssemblies, "bundled_assemblies", initialComment: $"Bundled assembly name buffers, all {BundledAssemblyNameWidth} bytes long"); } - void WriteNameValueStringArray (NativeAssemblyGenerator generator, string label, SortedDictionary entries) + void WriteDSOCache (LlvmIrGenerator generator) { - if (entries == null || entries.Count == 0) { - generator.WriteDataSection (); - generator.WriteEmptySymbol (SymbolType.Object, label, local: false); - return; + 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); } + dsoCache.Sort ((StructureInstance a, StructureInstance b) => a.Obj.hash.CompareTo (b.Obj.hash)); + + generator.WriteStructureArray (dsoCacheEntryStructureInfo, dsoCache, "dso_cache"); - NativeAssemblyGenerator.StructureWriteContext nameValueStruct = generator.StartStructure (); - foreach (var kvp in entries) { - string name = kvp.Key; - string value = kvp.Value ?? String.Empty; + ulong HashName (string name) + { + byte[] nameBytes = Encoding.UTF8.GetBytes (name); + if (is64Bit) { + return XXH64.DigestOf (nameBytes, 0, nameBytes.Length); + } - generator.WriteStringPointer (nameValueStruct, name); - generator.WriteStringPointer (nameValueStruct, value); + return (ulong)XXH32.DigestOf (nameBytes, 0, nameBytes.Length); } - generator.WriteSymbol (nameValueStruct, label, local: false); } - }; + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs index 01315503eab..39ce70a4d73 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs @@ -1,97 +1,119 @@ using System; using System.Collections.Generic; -using System.IO; -using Microsoft.Android.Build.Tasks; -using Xamarin.Android.Tools; + +using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks { - class CompressedAssembliesNativeAssemblyGenerator : NativeAssemblyComposer + 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) + { + if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) { + return 0; + } + + var descriptor = EnsureType (data); + return descriptor.uncompressed_file_size; + } + } // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh CompressedAssemblyDescriptor structure + [NativeAssemblerStructContextDataProvider (typeof (CompressedAssemblyDescriptorContextDataProvider))] sealed class CompressedAssemblyDescriptor { public uint uncompressed_file_size; public bool loaded; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string data = String.Empty; + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] + public byte data; }; + sealed class CompressedAssembliesContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override ulong GetBufferSize (object data, string fieldName) + { + if (String.Compare ("descriptors", fieldName, StringComparison.Ordinal) != 0) { + return 0; + } + + var cas = EnsureType (data); + return cas.count; + } + } + // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh CompressedAssemblies structure + [NativeAssemblerStructContextDataProvider (typeof (CompressedAssembliesContextDataProvider))] sealed class CompressedAssemblies { public uint count; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string descriptors; + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = DescriptorsArraySymbolName)] + public CompressedAssemblyDescriptor descriptors; }; - const string CompressedAssembliesField = "compressed_assemblies"; - const string DescriptorsField = "compressed_assembly_descriptors"; - IDictionary assemblies; - string dataIncludeFile; + StructureInfo compressedAssemblyDescriptorStructureInfo; + StructureInfo compressedAssembliesStructureInfo; + List>? compressedAssemblyDescriptors; + StructureInstance compressedAssemblies; - public CompressedAssembliesNativeAssemblyGenerator (AndroidTargetArch arch, IDictionary assemblies, string baseFilePath) - : base (arch) + public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies) { this.assemblies = assemblies; - dataIncludeFile = $"{baseFilePath}-data.inc"; } - protected override void Write (NativeAssemblyGenerator generator) + public override void Init () { if (assemblies == null || assemblies.Count == 0) { - WriteCompressedAssembliesStructure (generator, 0, null); return; } - generator.WriteInclude (Path.GetFileName (dataIncludeFile)); - - string label; - using (var dataOutput = MemoryStreamPool.Shared.CreateStreamWriter (generator.Output.Encoding)) { - generator.WriteDataSection (); + compressedAssemblyDescriptors = new List> (assemblies.Count); + foreach (var kvp in assemblies) { + string assemblyName = kvp.Key; + CompressedAssemblyInfo info = kvp.Value; var descriptor = new CompressedAssemblyDescriptor { + uncompressed_file_size = info.FileSize, loaded = false, + data = 0 }; - NativeAssemblyGenerator.StructureWriteContext descriptorArray = generator.StartStructureArray (); - foreach (var kvp in assemblies) { - string assemblyName = kvp.Key; - CompressedAssemblyInfo info = kvp.Value; - - NativeAssemblyGenerator.LabeledSymbol dataLabel = generator.WriteCommSymbol (dataOutput, "compressed_assembly_data", info.FileSize, alignment: 16); - descriptor.uncompressed_file_size = info.FileSize; - descriptor.data = dataLabel.Label; - - NativeAssemblyGenerator.StructureWriteContext descriptorStruct = generator.AddStructureArrayElement (descriptorArray); - generator.WriteStructure (descriptorStruct, descriptor); - } - label = generator.WriteSymbol (descriptorArray, DescriptorsField); - - dataOutput.Flush (); - Files.CopyIfStreamChanged (dataOutput.BaseStream, dataIncludeFile); + compressedAssemblyDescriptors.Add (new StructureInstance (descriptor)); } - WriteCompressedAssembliesStructure (generator, (uint)assemblies.Count, label); + compressedAssemblies = new StructureInstance (new CompressedAssemblies { count = (uint)assemblies.Count }); } - void WriteCompressedAssembliesStructure (NativeAssemblyGenerator generator, uint count, string descriptorsLabel) + protected override void MapStructures (LlvmIrGenerator generator) { - generator.WriteDataSection (); - var compressed_assemblies = new CompressedAssemblies { - count = count, - descriptors = descriptorsLabel, - }; - - NativeAssemblyGenerator.StructureWriteContext compressedAssemblies = generator.StartStructure (); - generator.WriteStructure (compressedAssemblies, compressed_assemblies); - generator.WriteSymbol (compressedAssemblies, CompressedAssembliesField, local: false); + compressedAssemblyDescriptorStructureInfo = generator.MapStructure (); + compressedAssembliesStructureInfo = generator.MapStructure (); + } + + protected override void Write (LlvmIrGenerator generator) + { + if (compressedAssemblyDescriptors == null) { + generator.WriteStructure (compressedAssembliesStructureInfo, null, CompressedAssembliesSymbolName); + return; + } + + generator.WriteStructureArray ( + compressedAssemblyDescriptorStructureInfo, + compressedAssemblyDescriptors, + LlvmIrVariableOptions.LocalWritable, + DescriptorsArraySymbolName, + initialComment: "Compressed assembly data storage" + ); + generator.WriteStructure (compressedAssembliesStructureInfo, compressedAssemblies, CompressedAssembliesSymbolName); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs new file mode 100644 index 00000000000..63cf752d6a6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs @@ -0,0 +1,29 @@ +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 + + 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)); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs new file mode 100644 index 00000000000..7e3a4ce43e4 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs @@ -0,0 +1,33 @@ +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 + + 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)); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs new file mode 100644 index 00000000000..1bead28a005 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Android.Tasks.LLVMIR +{ + interface IStructureInfo + { + void RenderDeclaration (LlvmIrGenerator generator); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrAddressSignificance.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrAddressSignificance.cs new file mode 100644 index 00000000000..5dc9203d385 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrAddressSignificance.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Address significance, see https://llvm.org/docs/LangRef.html#global-variables + /// + [Flags] + enum LlvmIrAddressSignificance + { + Default = 1 << 0, + Unnamed = 1 << 1, + LocalUnnamed = 1 << 2, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs new file mode 100644 index 00000000000..4f65a90ffac --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Base class for all classes which "compose" LLVM IR assembly. + /// + abstract class LlvmIrComposer + { + protected AndroidTargetArch TargetArch { get; } + + protected LlvmIrComposer () + {} + + public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) + { + LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); + + MapStructures (generator); + generator.WriteFileTop (); + generator.WriteStructureDeclarations (); + Write (generator); + generator.WriteFileEnd (); + } + + protected static string GetAbiName (AndroidTargetArch arch) + { + 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}"), + }; + } + + /// + /// 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); + + /// + /// 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); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs new file mode 100644 index 00000000000..d216ba52726 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -0,0 +1,1297 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Base class for all classes which implement architecture-specific code generators. + /// + abstract class LlvmIrGenerator + { + ref struct 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 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 PackedStructureMember + { + public readonly string ValueIRType; + public readonly string? PaddingIRType; + public readonly object? Value; + public readonly bool IsPadded; + public readonly StructureMemberInfo MemberInfo; + + 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); + } + } + + sealed class StringSymbolInfo + { + public readonly string SymbolName; + public readonly ulong Size; + + public StringSymbolInfo (string symbolName, ulong size) + { + SymbolName = symbolName; + Size = size; + } + } + + 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*" }, + }; + + // 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" }, + }; + + static readonly LlvmIrVariableOptions preAllocatedBufferVariableOptions = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Writable, + Linkage = LlvmIrLinkage.Internal, + }; + + string fileName; + ulong stringCounter = 0; + ulong structStringCounter = 0; + ulong structBufferCounter = 0; + + List structures = new List (); + Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + + protected abstract string DataLayout { get; } + public abstract int PointerSize { get; } + protected abstract string Triple { get; } + + public bool Is64Bit { get; } + public TextWriter Output { get; } + public AndroidTargetArch TargetArch { get; } + + protected string Indent => "\t"; + protected LlvmIrMetadataManager MetadataManager { get; } + + protected LlvmIrGenerator (AndroidTargetArch arch, TextWriter output, string fileName) + { + Output = output; + MetadataManager = new LlvmIrMetadataManager (); + TargetArch = arch; + Is64Bit = arch == AndroidTargetArch.X86_64 || arch == AndroidTargetArch.Arm64; + this.fileName = fileName; + } + + /// + /// 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) + { + 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}") + }; + } + } + + static string EnsureIrType (Type type) + { + if (!typeMap.TryGetValue (type, out string irType)) { + throw new InvalidOperationException ($"Unsupported managed type {type}"); + } + + 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 (); + } + + 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); + if (!typeSizes.TryGetValue (actualType, out size)) { + if (actualType == typeof (string) || actualType == typeof (IntPtr)) { + size = (ulong)PointerSize; + } else { + throw new InvalidOperationException ($"Unsupported managed type {type}"); + } + } + + return irType; + } + + /// + /// 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); + } + + /// + /// 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); + + 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 (); + } + + /// + /// Initialize the generator. It involves adding required LLVM IR module metadata (such as data model specification, + /// code generation flags etc) + /// + public virtual void Init () + { + LlvmIrMetadataItem flags = MetadataManager.Add ("llvm.module.flags"); + LlvmIrMetadataItem ident = MetadataManager.Add ("llvm.ident"); + + var flagsFields = new List (); + AddModuleFlagsMetadata (flagsFields); + + foreach (LlvmIrMetadataItem item in flagsFields) { + flags.AddReferenceField (item.Name); + } + + LlvmIrMetadataItem identValue = MetadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); + ident.AddReferenceField (identValue.Name); + } + + /// + /// 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"); + } + + var ret = new StructureInfo (this); + structures.Add (ret); + + return ret; + } + + TextWriter EnsureOutput (TextWriter? output) + { + return output ?? Output; + } + + void WriteGlobalSymbolStart (string symbolName, LlvmIrVariableOptions options, TextWriter? output = null) + { + output = EnsureOutput (output); + + var sb = new StringBuilder (llvmLinkage[options.Linkage]); + if (options.AddressSignificance != LlvmIrAddressSignificance.Default) { + if (sb.Length > 0) { + sb.Append (' '); + } + + sb.Append (llvmAddressSignificance[options.AddressSignificance]); + } + + if (sb.Length > 0) { + sb.Append (' '); + } + + sb.Append (llvmWritability[options.Writability]); + + output.Write ($"@{symbolName} = {sb.ToString ()} "); + } + + 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 (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 ()}'"); + } + + if (expectedType == typeof(bool)) { + return (bool)value ? 1 : 0; + } + + return value; + } + + bool MaybeWriteStructureString (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + { + 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; + } + + string symbolName = WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter, out ulong size); + instance.AddPointerData (smi, symbolName, size); + + return true; + } + + bool MaybeWritePreAllocatedBuffer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + { + 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++}"; + + WriteGlobalSymbolStart (variableName, preAllocatedBufferVariableOptions, output); + ulong size = bufferSize * smi.BaseTypeSize; + output.WriteLine ($"[{bufferSize} x {irType}] zeroinitializer, align {GetAggregateAlignment ((int)smi.BaseTypeSize, size)}"); + instance.AddPointerData (smi, variableName, size); + return true; + } + + 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)); + } + + bool named = !String.IsNullOrEmpty (symbolName); + if (named || !String.IsNullOrEmpty (initialComment)) { + WriteEOL (output: output); + WriteEOL (initialComment ?? symbolName, output); + } + + if (named) { + WriteGlobalSymbolStart (symbolName, options, output); + } + + return named; + } + + void WriteStructureArrayEnd (StructureInfo info, string? symbolName, ulong count, bool named, bool skipFinalComment = false, TextWriter? output = null) + { + output = EnsureOutput (output); + + output.Write ($", align {GetAggregateAlignment (info.MaxFieldAlignment, info.Size * count)}"); + 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 named = WriteStructureArrayStart (info, null, options, symbolName, initialComment); + + Output.Write ($"[{count} x %struct.{info.Name}] zeroinitializer"); + + WriteStructureArrayEnd (info, symbolName, (ulong)count, named, skipFinalComment: true); + } + + /// + /// 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) + { + WriteStructureArray (info, count, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment); + } + + /// + /// 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) + { + var arrayOutput = new StringWriter (); + bool named = WriteStructureArrayStart (info, instances, options, symbolName, initialComment, arrayOutput); + int count = instances != null ? instances.Count : 0; + + arrayOutput.Write ($"[{count} x %struct.{info.Name}] "); + 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 + ); + + arrayOutput.WriteLine ("["); + string fieldIndent = $"{Indent}{Indent}"; + for (int i = 0; i < count; i++) { + StructureInstance instance = instances[i]; + + WriteStructureBody (info, instance, bodyWriterOptions); + if (i < count - 1) { + arrayOutput.Write (", "); + } + WriteEOL (output: arrayOutput); + } + arrayOutput.Write ("]"); + + WriteBufferToOutput (bodyWriterOptions.StringsOutput); + WriteBufferToOutput (bodyWriterOptions.BuffersOutput); + } else { + arrayOutput.Write ("zeroinitializer"); + } + + 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); + } + + void AssertArraySize (StructureInfo info, 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{QuoteString (bytes, out _, nullTerminated: false)}"); + } + + void MaybeWriteStructureStringsAndBuffers (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options) + { + if (options.StringsOutput != null) { + MaybeWriteStructureString (info, smi, instance, options.StringsOutput); + } + + if (options.BuffersOutput != null) { + MaybeWritePreAllocatedBuffer (info, smi, instance, options.BuffersOutput); + } + } + + void WriteStructureField (StructureInfo info, StructureInstance instance, StructureMemberInfo smi, int fieldIndex, StructureBodyWriterOptions options, TextWriter output, object? valueOverride = null, ulong? expectedArraySize = null) + { + output.Write (options.FieldIndent); + + object? value = null; + if (smi.IsNativePointer) { + 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}')"); + } + + output.Write ($"{smi.IRType} "); + value = valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType); + + 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; + WritePrimitiveField (info, smi, instance, output); + } + + FinishStructureField (info, smi, instance, options, fieldIndex, value, output); + } + + void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options) + { + TextWriter structureOutput = EnsureOutput (options.StructureOutput); + structureOutput.Write ($"{options.StructIndent}%struct.{info.Name} "); + + 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); + } + + structureOutput.Write ($"{options.StructIndent}}}"); + } 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 (" ("); + sb.Append ($"0x{value:x})"); + } + comment = sb.ToString (); + } + 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 (", "); + } + 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} {value}"); + } + + void WritePointer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter output, object? overrideValue = null) + { + 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 (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 (String.IsNullOrEmpty (symbolName)) { + WriteNullPointer (smi, output); + return; + } + + ulong bufferSize = info.GetBufferSizeFromProvider (smi, instance); + WriteGetBufferPointer (symbolName, smi.IRType, bufferSize, indent: false, output: output); + return; + } + + object? value = overrideValue ?? smi.GetValue (instance.Obj); + if (value == null || ((value is IntPtr) && (IntPtr)value == IntPtr.Zero)) { + WriteNullPointer (smi, output); + return; + } + + if (value.GetType ().IsPrimitive) { + ulong v = Convert.ToUInt64 (value); + if (v == 0) { + WriteNullPointer (smi, output); + return; + } + } + + throw new InvalidOperationException ($"Non-null pointers to objects of managed type '{smi.Info.MemberType}' (IR type '{smi.IRType}') currently not supported (value: {value})"); + } + + void WriteNullPointer (StructureMemberInfo smi, TextWriter output) + { + output.Write ($"{smi.IRType} null"); + } + + // 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; + } + + foreach (StructureMemberInfo smi in info.Members) { + object? value = GetTypedMemberValue (info, smi, instance, smi.MemberType); + + if (!smi.NeedsPadding) { + members.Add (new PackedStructureMember (smi, value)); + continue; + } + + if (smi.MemberType != typeof(byte[])) { + throw new InvalidOperationException ($"Only byte arrays are supported currently (field '{smi.Info.Name}' of structure '{info.Name}')"); + } + + var array = (byte[])value; + var arrayLength = (ulong)array.Length; + + if (arrayLength > smi.ArrayElements) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should not have more than {smi.ArrayElements} elements"); + } + + ulong padding = smi.ArrayElements - arrayLength; + if (padding == 0) { + members.Add (new PackedStructureMember (smi, value)); + continue; + } + + 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; + } + + members.Add (new PackedStructureMember (smi, value, valueIRType: $"[{arrayLength} x i8]", paddingIRType: $"[{padding} x i8]")); + hasPaddedFields = true; + } + + bool firstField; + instanceType.Clear (); + if (!hasPaddedFields) { + instanceType.Append ($"\t%struct.{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; + } + + instanceType.Append ($"<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}>"); + } + + instanceType.Append (" }"); + } + structureTypeOutput.Write (instanceType.ToString ()); + + structureBodyOutput.Write (instanceType.ToString ()); + structureBodyOutput.WriteLine (" {"); + + firstField = true; + bool previousFieldWasPadded = false; + for (int i = 0; i < members.Count; i++) { + PackedStructureMember psm = members[i]; + + if (firstField) { + firstField = false; + } + + 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; + } + + if (!firstField && previousFieldWasPadded) { + structureBodyOutput.Write (", "); + } + structureBodyOutput.Write ($"{bodyWriterOptions.FieldIndent}<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}> <{{ {psm.ValueIRType} c{QuoteString ((byte[])psm.Value)}, {psm.PaddingIRType} zeroinitializer }}> "); + MaybeWriteFieldComment (info, psm.MemberInfo, instance, bodyWriterOptions, value: null, output: structureBodyOutput); + previousFieldWasPadded = true; + } + structureBodyOutput.WriteLine (); + structureBodyOutput.Write ($"{Indent}}}"); + } + + structureOutput.WriteLine ("<{"); + structureOutput.Write (structureTypeOutput); + structureOutput.WriteLine (); + structureOutput.WriteLine ("}>"); + + structureOutput.WriteLine ("<{"); + structureOutput.Write (structureBodyOutput); + structureOutput.WriteLine (); + structureOutput.Write ("}>"); + + FinishStructureWrite (info, bodyWriterOptions); + } + + StructureBodyWriterOptions InitStructureWrite (StructureInfo info, LlvmIrVariableOptions options, string? symbolName, bool writeFieldComment, string? fieldIndent = null) + { + if (options.IsGlobal && String.IsNullOrEmpty (symbolName)) { + throw new ArgumentException ("must not be null or empty for global symbols", nameof (symbolName)); + } + + var structureOutput = new StringWriter (); + bool named = !String.IsNullOrEmpty (symbolName); + if (named) { + WriteEOL (output: structureOutput); + WriteEOL (symbolName, structureOutput); + + WriteGlobalSymbolStart (symbolName, options, structureOutput); + } + + 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) + { + bodyWriterOptions.StructureOutput.WriteLine ($", align {info.MaxFieldAlignment}"); + + WriteBufferToOutput (bodyWriterOptions.StringsOutput); + WriteBufferToOutput (bodyWriterOptions.BuffersOutput); + WriteBufferToOutput (bodyWriterOptions.StructureOutput); + } + + /// + /// 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) + { + StructureBodyWriterOptions bodyWriterOptions = InitStructureWrite (info, options, symbolName, writeFieldComment); + + WriteStructureBody (info, instance, bodyWriterOptions); + + FinishStructureWrite (info, bodyWriterOptions); + } + + /// + /// 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. + /// + public void WriteStructure (StructureInfo info, StructureInstance? instance, string? symbolName = null, bool writeFieldComment = true) + { + WriteStructure (info, instance, LlvmIrVariableOptions.Default, symbolName, writeFieldComment); + } + + void WriteBufferToOutput (TextWriter? writer) + { + if (writer == null) { + return; + } + + writer.Flush (); + string text = writer.ToString (); + if (text.Length > 0) { + Output.WriteLine (text); + } + } + + void WriteGetStringPointer (string? variableName, ulong size, bool indent = true, TextWriter? output = null) + { + WriteGetBufferPointer (variableName, "i8*", size, indent, output); + } + + void WriteGetBufferPointer (string? variableName, string irType, ulong size, bool indent = true, TextWriter? output = null) + { + output = EnsureOutput (output); + if (indent) { + output.Write (Indent); + } + + if (String.IsNullOrEmpty (variableName)) { + output.Write ($"{irType} null"); + } else { + string irBaseType; + if (irType[irType.Length - 1] == '*') { + irBaseType = irType.Substring (0, irType.Length - 1); + } else { + irBaseType = irType; + } + + output.Write ($"{irType} getelementptr inbounds ([{size} x {irBaseType}], [{size} x {irBaseType}]* @{variableName}, i32 0, i32 0)"); + } + } + + /// + /// Write an array of name/value pairs. The array symbol will be global and non-writable. + /// + public void WriteNameValueArray (string symbolName, IDictionary arrayContents) + { + WriteEOL (); + WriteEOL (symbolName); + + var strings = new List<(ulong stringSize, string varName)> (); + long i = 0; + ulong arrayStringCounter = 0; + + foreach (var kvp in arrayContents) { + string name = kvp.Key; + string value = kvp.Value; + + WriteArrayString (name, $"n_{i}"); + WriteArrayString (value, $"v_{i}"); + i++; + } + if (strings.Count > 0) { + Output.WriteLine (); + } + + WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); + Output.Write ($"[{strings.Count} x i8*]"); + + if (strings.Count > 0) { + Output.WriteLine (" ["); + + for (int j = 0; j < strings.Count; j++) { + ulong size = strings[j].stringSize; + string varName = strings[j].varName; + + // + // 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"); + } + + var arraySize = (ulong)(strings.Count * PointerSize); + if (strings.Count > 0) { + Output.Write ("]"); + } + Output.WriteLine ($", align {GetAggregateAlignment (PointerSize, arraySize)}"); + + void WriteArrayString (string str, string symbolSuffix) + { + string name = WriteUniqueString ($"__{symbolName}_{symbolSuffix}", str, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString, out ulong size); + strings.Add (new (size, name)); + } + } + + /// + /// Wries a global, constant variable + /// + public void WriteVariable (string symbolName, T value) + { + WriteVariable (symbolName, value, LlvmIrVariableOptions.GlobalConstant); + } + + public void WriteVariable (string symbolName, T value, LlvmIrVariableOptions options) + { + if (typeof(T) == typeof(string)) { + WriteString (symbolName, (string)(object)value, options); + return; + } + + WriteEOL (); + string irType = MapManagedTypeToIR (out ulong size); + WriteGlobalSymbolStart (symbolName, options); + + Output.WriteLine ($"{irType} {value}, align {size}"); + } + + /// + /// Writes a private string. Strings without symbol names aren't exported, but they may be referenced by other + /// symbols + /// + public string WriteString (string value) + { + return WriteString (value, LlvmIrVariableOptions.LocalString); + } + + /// + /// 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) + { + string name = $"@.str"; + if (stringCounter > 0) { + name += $".{stringCounter}"; + } + stringCounter++; + return WriteString (name, value, options); + } + + /// + /// Writes a global, C++ constexpr style string + /// + public string WriteString (string symbolName, string value) + { + return WriteString (symbolName, value, LlvmIrVariableOptions.GlobalConstexprString); + } + + /// + /// Writes a string with symbol options (writeability, visibility) options specified in the parameter. + /// + public string WriteString (string symbolName, string value, LlvmIrVariableOptions options) + { + return WriteString (symbolName, value, options, out _); + } + + /// + /// Writes a local, constexpr style string and returns its size in + /// + public string WriteString (string symbolName, string value, out ulong stringSize) + { + return WriteString (symbolName, value, LlvmIrVariableOptions.LocalConstexprString, out stringSize); + } + + /// + /// 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) + { + string strSymbolName; + bool global = options.IsGlobal; + if (global) { + strSymbolName = $"__{symbolName}"; + } else { + strSymbolName = symbolName; + } + + string quotedString = QuoteString (value, out stringSize); + + // 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); + Output.WriteLine ($"[{stringSize} x i8] c{quotedString}, align {GetAggregateAlignment (1, stringSize)}"); + if (!global) { + return symbolName; + } + + string indexType = Is64Bit ? "i64" : "i32"; + WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); + Output.WriteLine ($"i8* getelementptr inbounds ([{stringSize} x i8], [{stringSize} x i8]* @{strSymbolName}, {indexType} 0, {indexType} 0), align {GetAggregateAlignment (PointerSize, stringSize)}"); + + return symbolName; + } + + /// + /// 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 string WriteUniqueString (string potentialSymbolName, string value, ref ulong counter) + { + return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString, out _); + } + + /// + /// 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. + // String size (in bytes) is returned in . + /// + public string WriteUniqueString (string potentialSymbolName, string value, ref ulong counter, out ulong stringSize) + { + return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString, out stringSize); + } + + /// + /// 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 string WriteUniqueString (string potentialSymbolNamePrefix, string value, ref ulong counter, LlvmIrVariableOptions options, out ulong stringSize) + { + if (value == null) { + stringSize = 0; + return null; + } + + StringSymbolInfo info; + if (stringSymbolCache.TryGetValue (value, out info)) { + stringSize = info.Size; + return info.SymbolName; + } + + string newSymbolName = $"{potentialSymbolNamePrefix}.{counter++}"; + WriteString (newSymbolName, value, options, out stringSize); + info = new StringSymbolInfo (newSymbolName, stringSize); + stringSymbolCache.Add (value, info); + + return info.SymbolName; + } + + public virtual void WriteFileTop () + { + WriteCommentLine ($"ModuleID = '{fileName}'"); + WriteDirective ("source_filename", QuoteStringNoEscape (fileName)); + WriteDirective ("target datalayout", QuoteStringNoEscape (DataLayout)); + WriteDirective ("target triple", QuoteStringNoEscape (Triple)); + } + + public virtual void WriteFileEnd () + { + Output.WriteLine (); + + foreach (LlvmIrMetadataItem metadata in MetadataManager.Items) { + Output.WriteLine (metadata.Render ()); + } + } + + public void WriteStructureDeclarations () + { + if (structures.Count == 0) { + return; + } + + Output.WriteLine (); + foreach (IStructureInfo si in structures) { + si.RenderDeclaration (this); + } + } + + public void WriteStructureDeclarationStart (string name, bool forOpaqueType = false) + { + WriteEOL (); + Output.Write ($"%struct.{name} = type "); + if (forOpaqueType) { + Output.WriteLine ("opaque"); + } else { + Output.WriteLine ("{"); + } + } + + public void WriteStructureDeclarationEnd () + { + Output.WriteLine ("}"); + } + + public void WriteStructureDeclarationField (string typeName, string comment, bool last) + { + Output.Write ($"{Indent}{typeName}"); + if (!last) { + Output.Write (","); + } + + if (!String.IsNullOrEmpty (comment)) { + WriteCommentLine (comment); + } else { + WriteEOL (); + } + } + + protected virtual void AddModuleFlagsMetadata (List flagsFields) + { + flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "wchar_size", 4)); + flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Max, "PIC Level", 2)); + } + + // Alignment for arrays, structures and unions + protected virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + return maxFieldAlignment; + } + + public void WriteCommentLine (string? comment = null, bool indent = false) + { + WriteCommentLine (Output, comment, indent); + } + + public void WriteComment (TextWriter writer, string? comment = null, bool indent = false) + { + if (indent) { + writer.Write (Indent); + } + + writer.Write (';'); + + if (!String.IsNullOrEmpty (comment)) { + writer.Write (' '); + writer.Write (comment); + } + } + + public void WriteCommentLine (TextWriter writer, string? comment = null, bool indent = false) + { + WriteComment (writer, comment, indent); + writer.WriteLine (); + } + + public void WriteEOL (string? comment = null, TextWriter? output = null) + { + WriteEOL (EnsureOutput (output), comment); + } + + public void WriteEOL (TextWriter writer, string? comment = null) + { + if (!String.IsNullOrEmpty (comment)) { + WriteCommentLine (writer, comment); + return; + } + writer.WriteLine (); + } + + public void WriteDirectiveWithComment (TextWriter writer, string name, string? comment, string? value) + { + writer.Write (name); + + if (!String.IsNullOrEmpty (value)) { + writer.Write ($" = {value}"); + } + + WriteEOL (writer, comment); + } + + public void WriteDirectiveWithComment (string name, string? comment, string? value) + { + WriteDirectiveWithComment (name, comment, value); + } + + public void WriteDirective (TextWriter writer, string name, string? value) + { + WriteDirectiveWithComment (writer, name, comment: null, value: value); + } + + public void WriteDirective (string name, string value) + { + WriteDirective (Output, name, value); + } + + public static string QuoteStringNoEscape (string s) + { + return $"\"{s}\""; + } + + public static string QuoteString (string value, bool nullTerminated = true) + { + return QuoteString (value, out _, nullTerminated); + } + + public static string QuoteString (byte[] bytes) + { + return QuoteString (bytes, out _, nullTerminated: false); + } + + public static string QuoteString (string value, out ulong stringSize, bool nullTerminated = true) + { + return QuoteString (Encoding.UTF8.GetBytes (value), out stringSize, nullTerminated); + } + + public static string QuoteString (byte[] bytes, out ulong stringSize, bool nullTerminated = true) + { + var sb = new StringBuilder (); + + foreach (byte b in bytes) { + if (b != '"' && b != '\\' && b >= 32 && b < 127) { + sb.Append ((char)b); + continue; + } + + sb.Append ($"\\{b:X2}"); + } + + stringSize = (ulong)bytes.Length; + if (nullTerminated) { + stringSize++; + sb.Append ("\\00"); + } + + return QuoteStringNoEscape (sb.ToString ()); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrLinkage.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrLinkage.cs new file mode 100644 index 00000000000..da11f93761b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrLinkage.cs @@ -0,0 +1,23 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Variable linkage, see https://llvm.org/docs/LangRef.html#linkage + /// + [Flags] + enum LlvmIrLinkage + { + Default = 0 << 0, + Private = 1 << 0, + Internal = 1 << 1, + AvailableExternally = 1 << 2, + LinkOnce = 1 << 3, + Weak = 1 << 4, + Common = 1 << 5, + Appending = 1 << 6, + ExternWeak = 1 << 7, + LinkOnceODR = 1 << 8, + External = 1 << 9, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs new file mode 100644 index 00000000000..543f5d5365a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrMetadataField + { + public string Contents { get; } + public bool IsReference { get; } + + public LlvmIrMetadataField (string value, bool isReference = false) + { + if (isReference) { + Contents = $"!{value}"; + } else { + Contents = QuoteString (value); + } + + IsReference = isReference; + } + + public LlvmIrMetadataField (object value) + { + Contents = FormatValue (value); + IsReference = false; + } + + string FormatValue (object value) + { + Type vt = value.GetType (); + + if (vt == typeof(string)) { + return QuoteString ((string)value); + } + + string irType = LlvmIrGenerator.MapManagedTypeToIR (vt); + return $"{irType} {value}"; + } + + string QuoteString (string value) + { + return $"!{LlvmIrGenerator.QuoteStringNoEscape (value)}"; + } + } + + class LlvmIrMetadataItem + { + List fields; + + public string Name { get; } + + public LlvmIrMetadataItem (string name) + { + if (name.Length == 0) { + throw new ArgumentException ("must not be empty", nameof (name)); + } + + Name = name; + fields = new List (); + } + + public void AddReferenceField (string referenceName) + { + fields.Add (new LlvmIrMetadataField (referenceName, isReference: true)); + } + + public void AddField (object value) + { + fields.Add (new LlvmIrMetadataField (value)); + } + + public string Render () + { + var sb = new StringBuilder ($"!{Name} = !{{"); + bool first = true; + + foreach (LlvmIrMetadataField field in fields) { + if (first) { + first = false; + } else { + sb.Append (", "); + } + + sb.Append (field.Contents); + } + + sb.Append ('}'); + + return sb.ToString (); + } + } + + class LlvmIrMetadataManager + { + ulong counter = 0; + List items = new List (); + + public List Items => items; + + public LlvmIrMetadataItem Add (string name, params object[]? values) + { + var ret = new LlvmIrMetadataItem (name); + + if (values != null && values.Length > 0) { + foreach (object v in values) { + ret.AddField (v); + } + } + items.Add (ret); + + return ret; + } + + public LlvmIrMetadataItem AddNumbered (params object[]? values) + { + string name = counter.ToString (); + counter++; + return Add (name, values); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleMergeBehavior.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleMergeBehavior.cs new file mode 100644 index 00000000000..54a20680799 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleMergeBehavior.cs @@ -0,0 +1,14 @@ +namespace Xamarin.Android.Tasks.LLVMIR +{ + // See: https://llvm.org/docs/LangRef.html#module-flags-metadata + static class LlvmIrModuleMergeBehavior + { + public const int Error = 1; + public const int Warning = 2; + public const int Require = 3; + public const int Override = 4; + public const int Append = 5; + public const int AppendUnique = 6; + public const int Max = 7; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrRuntimePreemption.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrRuntimePreemption.cs new file mode 100644 index 00000000000..ceab704634b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrRuntimePreemption.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Runtime preemption specifiers, see https://llvm.org/docs/LangRef.html#runtime-preemption-model + /// + [Flags] + enum LlvmIrRuntimePreemption + { + Default = 0 << 0, + DSOPreemptable = 1 << 0, + DSOLocal = 1 << 1, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs new file mode 100644 index 00000000000..c4abbc2a5a6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -0,0 +1,84 @@ +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrVariableOptions + { + /// + /// Options for a global, writable, symbol with locally (module wide) unimportant address + /// + public static readonly LlvmIrVariableOptions Default = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Writable, + AddressSignificance = LlvmIrAddressSignificance.LocalUnnamed, + }; + + /// + /// Options for a global, read-only, symbol with locally (module wide) unimportant address + /// + public static readonly LlvmIrVariableOptions GlobalConstant = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Constant, + AddressSignificance = LlvmIrAddressSignificance.LocalUnnamed, + }; + + /// + /// Options for a global, writable, symbol + /// + public static readonly LlvmIrVariableOptions GlobalWritable = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Writable, + }; + + /// + /// Options for a local, read-only, symbol + /// + public static readonly LlvmIrVariableOptions LocalConstant = new LlvmIrVariableOptions { + Linkage = LlvmIrLinkage.Internal, + Writability = LlvmIrWritability.Constant, + }; + + /// + /// Options for a local, writable, symbol + /// + public static readonly LlvmIrVariableOptions LocalWritable = new LlvmIrVariableOptions { + Linkage = LlvmIrLinkage.Internal, + Writability = LlvmIrWritability.Writable, + }; + + /// + /// Options for a local, read-only, string which will end up in a strings ELF section + /// + public static readonly LlvmIrVariableOptions LocalString = new LlvmIrVariableOptions { + Linkage = LlvmIrLinkage.Private, + Writability = LlvmIrWritability.Constant, + AddressSignificance = LlvmIrAddressSignificance.Unnamed, + }; + + /// + /// Options for a local, read-only, C++ constexpr style string which will remain in the rodata ELF section + /// + public static readonly LlvmIrVariableOptions LocalConstexprString = new LlvmIrVariableOptions { + Linkage = LlvmIrLinkage.Internal, + Writability = LlvmIrWritability.Constant, + }; + + /// + /// Options for a global, read-only, C++ constexpr style string which will remain in the rodata ELF section. + /// + public static readonly LlvmIrVariableOptions GlobalConstexprString = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Constant, + }; + + /// + /// Options for a global, read-only, constant pointer to string + /// + public static readonly LlvmIrVariableOptions GlobalConstantStringPointer = new LlvmIrVariableOptions { + Writability = LlvmIrWritability.Constant, + AddressSignificance = LlvmIrAddressSignificance.LocalUnnamed, + }; + + public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; + public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; + public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; + public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.Default; + public LlvmIrWritability Writability { get; set; } = LlvmIrWritability.Writable; + + public bool IsGlobal => Linkage == LlvmIrLinkage.Default || (Linkage != LlvmIrLinkage.Private && Linkage != LlvmIrLinkage.Internal); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrWritability.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrWritability.cs new file mode 100644 index 00000000000..ce17b357f8b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrWritability.cs @@ -0,0 +1,14 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Address significance, see https://llvm.org/docs/LangRef.html#global-variables + /// + [Flags] + enum LlvmIrWritability + { + Constant = 1 << 0, + Writable = 1 << 1, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmVisibility.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmVisibility.cs new file mode 100644 index 00000000000..59f43110291 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmVisibility.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Visibility style, see https://llvm.org/docs/BitCodeFormat.html#visibility + /// + [Flags] + enum LlvmIrVisibility + { + Default = 1 << 0, + Hidden = 1 << 1, + Protected = 1 << 2, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs new file mode 100644 index 00000000000..832a34c0fae --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs @@ -0,0 +1,75 @@ +using System; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + static class MemberInfoUtilities + { + public static bool IsNativePointer (this MemberInfo mi) + { + return mi.GetCustomAttribute () != null; + } + + public static bool IsNativePointerToPreallocatedBuffer (this MemberInfo mi, out ulong requiredBufferSize) + { + var attr = mi.GetCustomAttribute (); + if (attr == null) { + requiredBufferSize = 0; + return false; + } + + requiredBufferSize = attr.PreAllocatedBufferSize; + return attr.PointsToPreAllocatedBuffer; + } + + public static bool PointsToSymbol (this MemberInfo mi, out string? symbolName) + { + var attr = mi.GetCustomAttribute (); + if (attr == null) { + symbolName = null; + return false; + } + + symbolName = attr.PointsToSymbol; + return true; + } + + public static bool ShouldBeIgnored (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + return attr != null && attr.Ignore; + } + + public static bool UsesDataProvider (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + return attr != null && attr.UsesDataProvider; + } + + public static bool IsInlineArray (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + return attr != null && attr.InlineArray; + } + + public static int GetInlineArraySize (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + if (attr == null || !attr.InlineArray) { + return -1; + } + + return attr.InlineArraySize; + } + + public static bool InlineArrayNeedsPadding (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + if (attr == null || !attr.InlineArray) { + return false; + } + + return attr.NeedsPadding; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs new file mode 100644 index 00000000000..39fc5e094bb --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs @@ -0,0 +1,52 @@ +using System; + +namespace Xamarin.Android.Tasks +{ + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] + class NativeAssemblerAttribute : Attribute + { + /// + /// Tells the generator to ignore structure/class member if set to true + /// + public bool Ignore { get; set; } + + /// + /// Indicates that structure/class member uses a data context provider to obtain some data dynamically, + /// based on instances of the given structure/class. + /// + public bool UsesDataProvider { get; set; } + + /// + /// Indicates that the member is an array contained directly in the structure, as opposed to a pointer to array. + /// Size of the array is either specified statically in or by the data context + /// provider's method. + /// + public bool InlineArray { get; set; } + + /// + /// Size of , if set to a positive value. + /// + public int InlineArraySize { get; set; } = -1; + + /// + /// Indicates that the member needs to be padded to a certain size. Maximum array + /// size to which the member must be padded is specified by + /// + public bool NeedsPadding { get; set; } + } + + [AttributeUsage (AttributeTargets.Class, Inherited = true)] + class NativeAssemblerStructContextDataProviderAttribute : Attribute + { + public Type Type { get; } + + public NativeAssemblerStructContextDataProviderAttribute (Type type) + { + if (type != null && !type.IsSubclassOf (typeof(NativeAssemblerStructContextDataProvider))) { + throw new ArgumentException (nameof (type), "Must be derived from the AssemblerStructContextDataProvider class"); + } + + Type = type; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerContextDataProvider.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerContextDataProvider.cs new file mode 100644 index 00000000000..9651e27630e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerContextDataProvider.cs @@ -0,0 +1,50 @@ +using System; + +namespace Xamarin.Android.Tasks +{ + class NativeAssemblerStructContextDataProvider + { + /// + /// Return size of a buffer will point to, based on data passed in + /// + public virtual ulong GetBufferSize (object data, string fieldName) + { + return 0; + } + + /// + /// Return comment for the specified field, based on instance data passed in + /// + public virtual string GetComment (object data, string fieldName) + { + return String.Empty; + } + + /// + /// Get maximum width of data buffer allocated inline (that is as part of structure) + /// + public virtual uint GetMaxInlineWidth (object data, string fieldName) + { + return 0; + } + + /// + /// Returns name of the symbol the given field is supposed to point to. null or String.Empty + /// can be returned to make the pointer null + /// + public virtual string? GetPointedToSymbolName (object data, string fieldName) + { + return null; + } + + protected T EnsureType (object data) where T: class + { + var ret = data as T; + if (ret == null) { + throw new InvalidOperationException ($"Invalid data type, expected an instance of {typeof(T)}"); + } + + return ret; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativePointerAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativePointerAttribute.cs new file mode 100644 index 00000000000..7bcb3a32fdd --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativePointerAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] + class NativePointerAttribute : Attribute + { + /// + /// Indicates that the member points to a buffer pre-allocated at the compilation time, with size + /// provided by or, if present, + /// by the property. + /// + public bool PointsToPreAllocatedBuffer { get; set; } + + /// + /// Indicates that the member points to a buffer pre-allocated at the compilation time, with size + /// specified by the value of this property, unless the value is 0 in which case + /// is used. + /// + public ulong PreAllocatedBufferSize { get; set; } = 0; + + /// + /// Indicates the symbol to point to. If null the attribute is ignored, if String.Empty + /// the data context provider is queried (if the type uses it) + /// + public string? PointsToSymbol { get; set; } + + /// + /// A shortcut way to initialize a pointer to null without having to involve context data provider + /// + public bool IsNull { get; set; } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs new file mode 100644 index 00000000000..a8ddbfdbac1 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs @@ -0,0 +1,110 @@ +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 + { + public string Name { get; } = String.Empty; + public ulong Size { get; } + public List> 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 bool IsOpaque => Members.Count == 0; + + public StructureInfo (LlvmIrGenerator generator) + { + Type t = typeof(T); + Name = t.GetShortName (); + Size = GatherMembers (t, generator); + DataProvider = t.GetDataProvider (); + } + + public void RenderDeclaration (LlvmIrGenerator generator) + { + TextWriter output = generator.Output; + generator.WriteStructureDeclarationStart (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) + { + if (DataProvider == null || !smi.Info.UsesDataProvider ()) { + return null; + } + + string ret = DataProvider.GetComment (instance.Obj, smi.Info.Name); + if (ret.Length == 0) { + return null; + } + + return ret; + } + + public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureInstance instance) + { + if (DataProvider == null) { + return 0; + } + + return DataProvider.GetBufferSize (instance.Obj, smi.Info.Name); + } + + ulong GatherMembers (Type type, LlvmIrGenerator generator) + { + ulong size = 0; + foreach (MemberInfo mi in type.GetMembers ()) { + if (mi.ShouldBeIgnored () || (!(mi is FieldInfo) && !(mi is PropertyInfo))) { + continue; + } + + var info = new StructureMemberInfo (mi, generator); + Members.Add (info); + size += info.Size; + if ((int)info.Size > MaxFieldAlignment) { + MaxFieldAlignment = (int)info.Size; + } + + if (!HasStrings && info.MemberType == typeof (string)) { + HasStrings = true; + } + + if (!HasPreAllocatedBuffers && info.Info.IsNativePointerToPreallocatedBuffer (out ulong _)) { + HasPreAllocatedBuffers = true; + } + } + + return size; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs new file mode 100644 index 00000000000..3b171485f5e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class StructureInstance + { + Dictionary, StructurePointerData>? pointees; + + public T Obj { get; } + + public StructureInstance (T instance) + { + Obj = instance; + } + + public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) + { + if (pointees == null) { + pointees = new Dictionary, StructurePointerData> (); + } + + pointees.Add (smi, new StructurePointerData (variableName, dataSize)); + } + + public StructurePointerData? GetPointerData (StructureMemberInfo smi) + { + if (pointees != null && pointees.TryGetValue (smi, out StructurePointerData ssd)) { + return ssd; + } + + return null; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs new file mode 100644 index 00000000000..57a2f65018d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs @@ -0,0 +1,106 @@ +using System; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + sealed class StructureMemberInfo + { + public string IRType { get; } + public MemberInfo Info { get; } + public Type MemberType { get; } + + /// + /// Size of a variable with this IR type. May differ from because the field + /// can be a pointer to type + /// + public ulong Size { get; } + + public ulong ArrayElements { get; } + + /// + /// Size of the member's base IR type. If the variable is a pointer, this property will represent + /// the size of a base type, not the pointer. + /// + public ulong BaseTypeSize { get; } + public bool IsNativePointer { get; } + public bool IsNativeArray { get; } + public bool IsInlineArray { get; } + public bool NeedsPadding { get; } + + public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) + { + Info = mi; + + MemberType = mi switch { + FieldInfo fi => fi.FieldType, + PropertyInfo pi => pi.PropertyType, + _ => throw new InvalidOperationException ($"Unsupported member type {mi}") + }; + + ulong size = 0; + 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); + } + IsNativePointer = IRType[IRType.Length - 1] == '*'; + + if (!IsNativePointer) { + IsNativePointer = mi.IsNativePointer (); + if (IsNativePointer) { + IRType += "*"; + } + } + + BaseTypeSize = size; + ArrayElements = 0; + IsInlineArray = false; + NeedsPadding = false; + + if (IsNativePointer) { + size = (ulong)generator.PointerSize; + } else if (mi.IsInlineArray ()) { + IsInlineArray = true; + IsNativeArray = true; + NeedsPadding = mi.InlineArrayNeedsPadding (); + int arrayElements = mi.GetInlineArraySize (); + if (arrayElements < 0) { + arrayElements = GetArraySizeFromProvider (typeof(T).GetDataProvider (), mi.Name); + } + + if (arrayElements < 0) { + throw new InvalidOperationException ($"Array cannot have negative size (got {arrayElements})"); + } + + IRType = $"[{arrayElements} x {IRType}]"; + ArrayElements = (ulong)arrayElements; + } + + if (MemberType.IsArray && !IsInlineArray) { + throw new InvalidOperationException ("Out of line arrays in structures aren't currently supported"); + } + + Size = size; + } + + public object? GetValue (T instance) + { + if (Info is FieldInfo fi) { + return fi.GetValue (instance); + } + + var pi = Info as PropertyInfo; + return pi.GetValue (instance); + } + + int GetArraySizeFromProvider (NativeAssemblerStructContextDataProvider? provider, string fieldName) + { + if (provider == null) { + return -1; + } + + return (int)provider.GetMaxInlineWidth (null, fieldName); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs new file mode 100644 index 00000000000..8e23e81b1e6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..247554d32fb --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs @@ -0,0 +1,57 @@ +using System; +using System.Reflection; + +using Xamarin.Android.Tasks; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + static class TypeUtilities + { + public static string GetShortName (this Type type) + { + string? fullName = type.FullName; + + if (String.IsNullOrEmpty (fullName)) { + throw new InvalidOperationException ($"Unnamed types aren't supported ({type})"); + } + + int lastCharIdx = fullName.LastIndexOf ('.'); + string ret; + if (lastCharIdx < 0) { + ret = fullName; + } else { + ret = fullName.Substring (lastCharIdx + 1); + } + + lastCharIdx = ret.LastIndexOf ('+'); + if (lastCharIdx >= 0) { + ret = ret.Substring (lastCharIdx + 1); + } + + if (String.IsNullOrEmpty (ret)) { + throw new InvalidOperationException ($"Invalid type name ({type})"); + } + + return ret; + } + + public static bool IsStructure (this Type type) + { + return type.IsValueType && + !type.IsEnum && + !type.IsPrimitive && + type != typeof (decimal) && + type != typeof (DateTime); + } + + public static NativeAssemblerStructContextDataProvider? GetDataProvider (this Type t) + { + var attr = t.GetCustomAttribute (); + if (attr == null) { + return null; + } + + return Activator.CreateInstance (attr.Type) as NativeAssemblerStructContextDataProvider; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs new file mode 100644 index 00000000000..6d841fcfd59 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs @@ -0,0 +1,37 @@ +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 + + 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; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs new file mode 100644 index 00000000000..89483550cc5 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs @@ -0,0 +1,29 @@ +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 + + 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)); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm32NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm32NativeAssemblyGenerator.cs deleted file mode 100644 index 2f386a7308a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm32NativeAssemblyGenerator.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - class Arm32NativeAssemblyGenerator : ArmNativeAssemblyGenerator - { - static readonly NativeType pointer = new NativeType { - Size = 4, - Alignment = 4, - Name = ".long", - }; - - protected override string ArchName => "armv7-a"; - protected override string LineCommentStart => "@"; - protected override string TypeLead => "%"; - public override bool Is64Bit => false; - - public Arm32NativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - protected override NativeType GetPointerType () => pointer; - - public override uint WriteData (TextWriter writer, T value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - Type type = typeof(T); - if (type != typeof(long) && type != typeof(ulong)) { - return base.WriteData (writer, value, hex, comment, useBlockComment); - } - - string nativeTypeName = GetNativeTypeName (); - ulong high, low; - - if (type == typeof(long)) { - high = (ulong)((((ulong)(long)(object)value) & 0xFFFFFFFF00000000UL) >> 32); - low = (ulong)(((ulong)(long)(object)value) & 0xFFFFFFFFUL); - } else { - high = (((ulong)(object)value) & 0xFFFFFFFF00000000UL) >> 32; - low = ((ulong)(object)value) & 0xFFFFFFFFUL; - } - - WriteData (writer, nativeTypeName, ToString (low, hex), comment); - WriteData (writer, nativeTypeName, ToString (high, hex)); - - return GetNativeTypeSize (); - } - - protected override void ConfigureTypeMappings (Dictionary mapping) - { - base.ConfigureTypeMappings (mapping); - - // Alignments and sizes as per https://github.com/ARM-software/abi-aa/blob/320a56971fdcba282b7001cf4b84abb4fd993131/aapcs32/aapcs32.rst#fundamental-data-types - // Assembler type directives are described in https://sourceware.org/binutils/docs-2.37/as/index.html - ConfigureTypeMapping (".short", size: 2, alignment: 2); - ConfigureTypeMapping (".short", size: 2, alignment: 2); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 8, alignment: 8); - ConfigureTypeMapping (".long", size: 8, alignment: 8); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 8, alignment: 8); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - } - - public override void WriteFileTop () - { - base.WriteFileTop (); - - WriteDirective (".syntax", "unified"); - WriteDirectiveWithComment (".eabi_attribute", "Tag_conformance", 67, QuoteString ("2.09")); - WriteDirectiveWithComment (".eabi_attribute", "Tag_CPU_arch", 6, 10); - WriteDirectiveWithComment (".eabi_attribute", "Tag_CPU_arch_profile", 7, 65); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ARM_ISA_use", 8, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_THUMB_ISA_use", 9, 2); - WriteDirective (".fpu", "neon"); - WriteDirectiveWithComment (".eabi_attribute", "Tag_CPU_unaligned_access", 34, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_PCS_RW_data", 15, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_PCS_RO_data", 16, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_PCS_GOT_use", 17, 2); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_FP_denormal", 20, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_FP_exceptions", 21, 0); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_FP_number_model", 23, 3); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_align_needed", 24, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_align_preserved", 25, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_FP_16bit_format", 38, 1); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_PCS_wchar_t", 18, 4); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_enum_size", 26, 2); - WriteDirectiveWithComment (".eabi_attribute", "Tag_ABI_PCS_R9_use", 14, 0); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm64NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm64NativeAssemblyGenerator.cs deleted file mode 100644 index 2cffe2957e9..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/Arm64NativeAssemblyGenerator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - class Arm64NativeAssemblyGenerator : ArmNativeAssemblyGenerator - { - static readonly NativeType pointer = new NativeType { - Size = 8, - Alignment = 8, - Name = ".xword", - }; - - protected override string ArchName => "armv8-a"; - public override bool Is64Bit => true; - - public Arm64NativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - protected override NativeType GetPointerType () => pointer; - - protected override void ConfigureTypeMappings (Dictionary mapping) - { - base.ConfigureTypeMappings (mapping); - - // Alignments and sizes as per https://github.com/ARM-software/abi-aa/blob/320a56971fdcba282b7001cf4b84abb4fd993131/aapcs64/aapcs64.rst#fundamental-data-types - // Assembler type directives are described in https://sourceware.org/binutils/docs-2.37/as/index.html - ConfigureTypeMapping (".hword", size: 2, alignment: 2); - ConfigureTypeMapping (".hword", size: 2, alignment: 2); - ConfigureTypeMapping (".word", size: 4, alignment: 4); - ConfigureTypeMapping (".word", size: 4, alignment: 4); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - ConfigureTypeMapping (".word", size: 4, alignment: 4); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - ConfigureTypeMapping (".xword", size: 8, alignment: 8); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/ArmNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/ArmNativeAssemblyGenerator.cs deleted file mode 100644 index d722ccab0f3..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/ArmNativeAssemblyGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - abstract class ArmNativeAssemblyGenerator : NativeAssemblyGenerator - { - protected abstract string ArchName { get; } - - protected ArmNativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - public override void WriteFileTop () - { - base.WriteFileTop (); - WriteDirective (".arch", ArchName); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerAttribute.cs deleted file mode 100644 index e8b4d379aca..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerAttribute.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks -{ - [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] - class NativeAssemblerAttribute : Attribute - { - public bool Ignore { get; set; } - public bool UsesDataProvider { get; set; } - public string? Comment { get; set; } - public string? Name { get; set; } - } - - enum AssemblerStringFormat - { - /// - /// Local string label is automatically generated for the string, this is the default behavior - /// - Automatic, - - /// - /// String is output as an inline (no pointer) array/buffer filled directly with string data - /// - InlineArray, - - /// - /// String is output as a pointer to symbol whose name is contents of the string being processed - PointerToSymbol, - } - - [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] - class NativeAssemblerStringAttribute : NativeAssemblerAttribute - { - AssemblerStringFormat format; - - public bool Inline => format == AssemblerStringFormat.InlineArray; - public bool PadToMaxLength { get; set; } - public bool PointerToSymbol => format == AssemblerStringFormat.PointerToSymbol; - - public NativeAssemblerStringAttribute () - { - format = AssemblerStringFormat.Automatic; - } - - public NativeAssemblerStringAttribute (AssemblerStringFormat format) - { - this.format = format; - } - } - - [AttributeUsage (AttributeTargets.Class, Inherited = true)] - class NativeAssemblerStructContextDataProviderAttribute : Attribute - { - public Type Type { get; } - - public NativeAssemblerStructContextDataProviderAttribute (Type type) - { - if (type != null && !type.IsSubclassOf (typeof(NativeAssemblerStructContextDataProvider))) { - throw new ArgumentException (nameof (type), "Must be derived from the AssemblerStructContextDataProvider class"); - } - - Type = type; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerContextDataProvider.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerContextDataProvider.cs deleted file mode 100644 index 72aab8528d2..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblerContextDataProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks -{ - class NativeAssemblerStructContextDataProvider - { - public virtual string GetComment (object data, string fieldName) - { - return String.Empty; - } - - // Get maximum width of data buffer allocated inline (that is not pointed to) - public virtual uint GetMaxInlineWidth (object data, string fieldName) - { - return 0; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyComposer.cs deleted file mode 100644 index 270ba3d3285..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyComposer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks -{ - abstract class NativeAssemblyComposer - { - protected AndroidTargetArch TargetArch { get; } - - protected NativeAssemblyComposer (AndroidTargetArch arch) - { - TargetArch = arch; - } - - public void Write (StreamWriter output, string fileName) - { - NativeAssemblyGenerator generator = NativeAssemblyGenerator.Create (TargetArch, output, fileName); - - generator.WriteFileTop (); - Write (generator); - generator.WriteFileEnd (); - } - - protected abstract void Write (NativeAssemblyGenerator generator); - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyGenerator.cs deleted file mode 100644 index dbadb843ebe..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/NativeAssemblyGenerator.cs +++ /dev/null @@ -1,1223 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks -{ - abstract class NativeAssemblyGenerator - { - protected const string AutomaticStringSection = "autostr"; - - public class NativeType - { - public uint Size; - public uint Alignment; - public string? Name; - } - - public class StructureField - { - public NativeType NativeType; - public string Text; - public uint ArrayElements; - - public StructureField (NativeType nativeType, string text, uint arrayElements) - { - NativeType = nativeType; - Text = text; - ArrayElements = arrayElements; - } - } - - public class LabeledSymbol - { - public readonly string Label; - public readonly bool Global; - - internal LabeledSymbol (string label, bool global) - { - Label = label; - Global = global; - } - } - - public class LabeledStringSymbol : LabeledSymbol - { - public readonly string Contents; - - internal LabeledStringSymbol (string label, string contents, bool global) - : base (label, global) - { - Contents = contents; - } - } - - public class StructureWriteContext - { - internal uint ByteAlignment = 0; - internal uint Size = 0; - internal Type? ReflectedType = null; - internal List Fields = new List (); - internal StringWriter TempWriter = new StringWriter (); - internal List? ArrayOfStructures; - - internal StructureWriteContext (bool isArray = false) - { - if (isArray) { - ArrayOfStructures = new List (); - } - } - } - - sealed class StructureMemberInfo - { - public readonly MemberInfo Info; - public readonly Type Type; - public readonly NativeAssemblerAttribute? Attr; - - public StructureMemberInfo (MemberInfo info, Type type, NativeAssemblerAttribute? attr) - { - Info = info; - Type = type; - Attr = attr; - } - } - - sealed class StructureReflectionCacheEntry - { - public List MemberInfos; - public NativeAssemblerStructContextDataProvider? ContextDataProvider; - } - - string fileName; - bool typeMappingConfigured = false; - - Dictionary localLabels = new Dictionary (StringComparer.Ordinal); - - Dictionary typeMapping = new Dictionary () { - { typeof(bool), null }, - { typeof(byte), null }, - { typeof(double), null }, - { typeof(float), null }, - { typeof(int), null }, - { typeof(long), null }, - { typeof(nint), null }, - { typeof(nuint), null }, - { typeof(sbyte), null }, - { typeof(short), null }, - { typeof(uint), null }, - { typeof(ulong), null }, - { typeof(ushort), null }, - }; - - Dictionary structureReflectionCache = new Dictionary (); - - List stringTable = new List (); - - public TextWriter Output { get; } - protected string Indent => "\t"; - - protected virtual string TypeLead => "@"; - protected virtual string LineCommentStart => "//"; - protected virtual uint StructureFieldAlignment => 4; - - public abstract bool Is64Bit { get; } - - protected NativeAssemblyGenerator (TextWriter output, string fileName) - { - Output = output; - this.fileName = fileName; - } - - public static NativeAssemblyGenerator Create (AndroidTargetArch arch, StreamWriter output, string fileName) - { - return arch switch { - AndroidTargetArch.Arm => new Arm32NativeAssemblyGenerator (output, fileName), - AndroidTargetArch.Arm64 => new Arm64NativeAssemblyGenerator (output, fileName), - AndroidTargetArch.X86 => new X86_32NativeAssemblyGenerator (output, fileName), - AndroidTargetArch.X86_64 => new X86_64NativeAssemblyGenerator (output, fileName), - _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") - }; - } - - public static string GetAbiName (AndroidTargetArch arch) - { - 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}"), - }; - } - - protected void ConfigureTypeMapping (string name, uint size, uint alignment) - { - typeMapping[typeof(T)] = new NativeType { - Size = size, - Alignment = alignment, - Name = name, - }; - } - - protected abstract NativeType GetPointerType (); - - protected virtual void ConfigureTypeMappings (Dictionary mapping) - { - ConfigureTypeMapping (".byte", size: 1, alignment: 1); - ConfigureTypeMapping (".byte", size: 1, alignment: 1); - ConfigureTypeMapping (".byte", size: 1, alignment: 1); - } - - void EnsureTypeMapping () - { - if (typeMappingConfigured) { - return; - } - - ConfigureTypeMappings (typeMapping); - - foreach (var kvp in typeMapping) { - Type managedType = kvp.Key; - NativeType? nativeType = kvp.Value; - - if (nativeType == null) { - throw new InvalidOperationException ($"Missing managed nativeType {managedType} mapping"); - } - - if (nativeType.Size == 0) { - throw new InvalidOperationException ($"Missing size of native nativeType corresponding to managed nativeType {managedType}"); - } - - if (nativeType.Alignment == 0) { - throw new InvalidOperationException ($"Missing byte alignment of native nativeType corresponding to managed nativeType {managedType}"); - } - - if (String.IsNullOrEmpty (nativeType.Name)) { - throw new InvalidOperationException ($"Missing name of native nativeType corresponding to managed nativeType {managedType}"); - } - } - - typeMappingConfigured = true; - } - - public virtual void WriteFileTop () - { - WriteDirective (".file", QuoteString (fileName.Replace ("\\", "\\\\"))); - } - - public virtual void WriteFileEnd () - { - Output.WriteLine (); - - if (stringTable.Count > 0) { - WriteStringSection (Output, AutomaticStringSection); - - foreach (LabeledStringSymbol sym in stringTable) { - WriteSymbol (Output, sym, local: true, ownSection: false); - } - - Output.WriteLine (); - } - - WriteDirective (".ident", QuoteString ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}")); - } - - NativeType GetNativeType () - { - EnsureTypeMapping (); - if (!typeMapping.TryGetValue (typeof(T), out NativeType? nativeType) || nativeType == null) { - throw new InvalidOperationException ($"Managed nativeType {typeof(T)} has no native mapping"); - } - - return nativeType; - } - - protected uint GetNativeTypeAlignment () - { - EnsureTypeMapping (); - return GetNativeType ().Alignment; - } - - protected uint GetNativeTypeSize () - { - EnsureTypeMapping (); - return GetNativeType ().Size; - } - - protected string GetNativeTypeName () - { - EnsureTypeMapping (); - return GetNativeType ().Name!; - } - - protected string ToString (Type type, object value, bool hex) - { - if (type == typeof(bool)) { - return ToString (typeof(byte), (bool)value ? 1 : 0, hex); - } - - if (type == typeof(IntPtr)) { - if (value.GetType () == typeof(string)) { - // we have a pointer and a label - return (string)value; - } - } - - if (hex) { - return $"0x{value:x}"; - } - - return $"{value}"; - } - - protected string ToString (T value, bool hex) where T: struct - { - return ToString (typeof(T), value, hex); - } - - public void WriteEOL (string? comment = null, bool useBlockComment = false) - { - WriteEOL (Output, comment, useBlockComment); - } - - public void WriteEOL (TextWriter writer, string? comment = null, bool useBlockComment = false) - { - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (writer, comment, useBlockComment: useBlockComment); - return; - } - writer.WriteLine (); - } - - public void WriteInclude (string filePath, string? comment = null, bool useBlockComment = false) - { - WriteInclude (Output, filePath, comment, useBlockComment); - } - - public void WriteInclude (TextWriter writer, string filePath, string? comment = null, bool useBlockComment = false) - { - WriteDirectiveWithComment (writer, ".include", comment, useBlockComment, QuoteString (filePath)); - } - - public void WriteCommentLine (string? comment = null, bool indent = true, bool useBlockComment = false) - { - WriteCommentLine (Output, comment, indent); - } - - public void WriteCommentLine (TextWriter writer, string? comment = null, bool indent = true, bool useBlockComment = false) - { - if (indent) { - writer.Write (Indent); - } - - if (useBlockComment) { - writer.Write ("/* "); - } else { - writer.Write (LineCommentStart); - } - - if (!String.IsNullOrEmpty (comment)) { - writer.Write (' '); - writer.Write (comment); - } - - if (useBlockComment) { - writer.Write (" */"); - } - - writer.WriteLine (); - } - - public StructureWriteContext StartStructure () => new StructureWriteContext (); - public StructureWriteContext StartStructureArray () => new StructureWriteContext (isArray: true); - - List GatherMembers () where T: class - { - Type klass = typeof(T); - var ret = new List (); - - foreach (MemberInfo mi in klass.GetMembers ()) { - Type? memberType = mi switch { - FieldInfo fi => fi.FieldType, - PropertyInfo pi => GetPropertyType (pi), - _ => null - }; - - if (memberType != null) { - NativeAssemblerAttribute? attr = mi.GetCustomAttribute (); - if (attr == null || !attr.Ignore) { - ret.Add (new StructureMemberInfo (mi, memberType, attr)); - } - } - } - - return ret; - - Type GetPropertyType (PropertyInfo pi) - { - if (!pi.CanRead) { - throw new InvalidOperationException ($"Property '{pi.DeclaringType}.{pi.Name}' doesn't have a getter."); - } - - return pi.PropertyType; - } - } - - public StructureWriteContext AddStructureArrayElement (StructureWriteContext status) - { - if (status.ArrayOfStructures == null) { - throw new InvalidOperationException ("Structure context doesn't refer to array of structures, did you use StartStructureArray()?"); - } - - var ret = StartStructure (); - status.ArrayOfStructures.Add (ret); - - return ret; - } - - public void WriteStructure (StructureWriteContext status, T[] collection) where T: class - { - WriteStructure (status, (IEnumerable)collection); - } - - public void WriteStructure (StructureWriteContext status, IEnumerable collection) where T: class - { - status.ArrayOfStructures = new List (); - foreach (T obj in collection) { - StructureWriteContext context = StartStructure (); - status.ArrayOfStructures.Add (context); - WriteStructure (context, obj); - } - } - - public void WriteStructure (StructureWriteContext status, T obj) where T: class - { - Type type = typeof(T); - if (!structureReflectionCache.TryGetValue (type, out StructureReflectionCacheEntry entry)) { - var providerAttr = type.GetCustomAttribute (); - NativeAssemblerStructContextDataProvider? contextDataProvider = null; - - if (providerAttr != null) { - contextDataProvider = Activator.CreateInstance (providerAttr.Type) as NativeAssemblerStructContextDataProvider; - } - - entry = new StructureReflectionCacheEntry { - MemberInfos = GatherMembers (), - ContextDataProvider = contextDataProvider, - }; - structureReflectionCache.Add (type, entry); - } - - WriteStructure (status, entry.MemberInfos, entry.ContextDataProvider, obj); - } - - void WriteStructure (StructureWriteContext status, List members, NativeAssemblerStructContextDataProvider? contextDataProvider, T obj) where T: class - { - status.ReflectedType = typeof(T); - foreach (StructureMemberInfo smi in members) { - object? value = smi.Info switch { - FieldInfo fi => fi.GetValue (obj), - PropertyInfo pi => pi.GetValue (obj), - _ => throw new InvalidOperationException ($"Unsupported member nativeType {smi.Info}") - }; - - if (smi.Type == typeof(bool)) { - WriteField (smi, value); - } else if (smi.Type == typeof(byte)) { - WriteField (smi, value); - } else if (smi.Type == typeof(double)) { - WriteField (smi, value); - } else if (smi.Type == typeof(float)) { - WriteField (smi, value); - } else if (smi.Type == typeof(int)) { - WriteField (smi, value); - } else if (smi.Type == typeof(long)) { - WriteField (smi, value); - } else if (smi.Type == typeof(sbyte)) { - WriteField (smi, value); - } else if (smi.Type == typeof(short)) { - WriteField (smi, value); - } else if (smi.Type == typeof(uint)) { - WriteField (smi, value); - } else if (smi.Type == typeof(ulong)) { - WriteField (smi, value); - } else if (smi.Type == typeof(ushort)) { - WriteField (smi, value); - } else if (smi.Type == typeof(nint)) { - WriteField (smi, value); - } else if (smi.Type == typeof(nuint)) { - WriteField (smi, value); - } else if (smi.Type == typeof(string)) { - if (!HandleAttributedStrings (smi, smi.Attr as NativeAssemblerStringAttribute, (string)(object)value!)) { - WriteStringPointer (status, (string)(object)value!, global: false, label: null, comment: GetComment (smi)); - } - } else if (smi.Type == typeof(byte[])) { - WriteArrayField (smi, value); - } else if (smi.Type == typeof(int[])) { - WriteArrayField (smi, value); - } else { - throw new InvalidOperationException ($"Managed nativeType '{smi.Type}' is not supported"); - } - } - - bool HandleAttributedStrings (StructureMemberInfo smi, NativeAssemblerStringAttribute? attr, string value) - { - if (attr == null) { - return false; - } - - if (attr.PointerToSymbol) { - WritePointer (status, (string)(object)value!, comment: GetComment (smi)); - return true; - } - - if (!attr.Inline) { - return false; - } - - uint maxLength = 0; - if (attr.PadToMaxLength) { - EnsureContextDataProvider (); - maxLength = contextDataProvider.GetMaxInlineWidth (obj, smi.Info.Name); - } - - WriteInlineString (status, value, fixedWidth: maxLength, comment: GetComment (smi)); - return true; - } - - void WriteField (StructureMemberInfo smi, object? value) where MT: struct - { - WriteData (status, (MT)(object)value!, comment: GetComment (smi)); - } - - void WriteArrayField (StructureMemberInfo smi, object? values) where MT: struct - { - WriteData (status, (MT[])(object)values!, comment: GetComment (smi)); - } - - string GetComment (StructureMemberInfo smi) - { - if (smi.Attr != null) { - if (smi.Attr.UsesDataProvider) { - EnsureContextDataProvider (); - return contextDataProvider.GetComment (obj, smi.Info.Name); - } - - if (!String.IsNullOrEmpty (smi.Attr.Comment)) { - return smi.Attr.Comment; - } - } - - return smi.Info.Name; - } - - void EnsureContextDataProvider () - { - if (contextDataProvider == null) { - throw new InvalidOperationException ($"Type '{status.ReflectedType}' requires NativeAssemblerStructContextDataProviderAttribute to specify the context data provider"); - } - } - } - - void WriteSymbolType (TextWriter writer, string symbolName, SymbolType type, bool local, string? comment = null, bool useBlockComment = false) - { - string symbolType; - switch (type) { - case SymbolType.Object: - symbolType = "object"; - break; - - case SymbolType.Common: - symbolType = "common"; - break; - - case SymbolType.NoType: - symbolType = "notype"; - break; - - case SymbolType.Function: - symbolType = "function"; - break; - - case SymbolType.TlsObject: - symbolType = "tls_object"; - break; - - case SymbolType.GnuUniqueObject: - symbolType = "gnu_unique_object"; - break; - - case SymbolType.GnuIndirectFunction: - symbolType = "gnu_indirect_function"; - break; - - default: - throw new InvalidOperationException ($"Unknown symbol nativeType '{type}'"); - } - - WriteDirectiveWithComment (writer, ".type", comment, useBlockComment, symbolName, $"{TypeLead}{symbolType}"); - if (!local) { - WriteDirective (writer, ".global", symbolName); - } - } - - void WriteSymbolLabel (TextWriter writer, string symbolName) - { - writer.WriteLine ($"{symbolName}:"); - } - - void WriteSymbolSize (TextWriter writer, string symbolName, uint size) - { - WriteDirective (writer, ".size", symbolName, size); - } - - public string WriteEmptySymbol (SymbolType symbolType, string symbolName, bool local = true, bool skipLabelCounter = false) - { - return WriteEmptySymbol (Output, symbolType, symbolName, local, skipLabelCounter); - } - - public string WriteEmptySymbol (TextWriter writer, SymbolType symbolType, string symbolName, bool local = true, bool skipLabelCounter = false) - { - WriteSymbolType (writer, symbolName, symbolType, local); - - string label = local ? MakeLocalLabel (symbolName, skipCounter: skipLabelCounter) : symbolName; - WriteSymbolLabel (writer, label); - WriteSymbolSize (writer, label, 0); - - return label; - } - - public LabeledSymbol WriteCommSymbol (string labelNamespace, uint size, uint alignment) - { - return WriteCommSymbol (Output, labelNamespace, size, alignment); - } - - public LabeledSymbol WriteCommSymbol (TextWriter writer, string labelNamespace, uint size, uint alignment) - { - var sym = new LabeledSymbol (MakeLocalLabel (labelNamespace), global: false); - - WriteSymbolType (writer, sym.Label, SymbolType.Object, local: true); - WriteDirective (writer, ".local", sym.Label); - WriteDirective (writer, ".comm", sym.Label, size, alignment); - writer.WriteLine (); - - return sym; - } - - public string WriteSymbol (StructureWriteContext status, string symbolName, bool local = true, bool useBlockComment = false, bool alreadyInSection = false, bool skipLabelCounter = false) - { - return WriteSymbol (Output, status, symbolName, local, useBlockComment, alreadyInSection); - } - - public string WriteSymbol (TextWriter writer, StructureWriteContext status, string symbolName, bool local = true, bool useBlockComment = false, bool alreadyInSection = false, bool skipLabelCounter = false) - { - string label = local ? MakeLocalLabel (symbolName, skipCounter: skipLabelCounter) : symbolName; - writer.WriteLine (); - if (status.ReflectedType != null) { - WriteCommentLine (writer, useBlockComment: useBlockComment); - WriteCommentLine (writer, $"Generated from instance of: {status.ReflectedType.AssemblyQualifiedName}", useBlockComment: useBlockComment); - WriteCommentLine (writer, useBlockComment: useBlockComment); - } - - bool isArray; - if (status.ArrayOfStructures != null) { - if (status.ArrayOfStructures.Count == 0) { - if (!alreadyInSection) { - WriteDataSection (writer); - } - WriteEmptySymbol (writer, SymbolType.Object, label, local, skipLabelCounter); - return label; - } - - isArray = true; - } else { - isArray = false; - } - - WriteSymbolType (writer, label, SymbolType.Object, local); - if (!alreadyInSection) { - WriteDataSection (writer); - } - - StructureWriteContext c = isArray ? status.ArrayOfStructures![0] : status; - WriteAlignmentDirective (writer, c.ByteAlignment); - WriteSymbolLabel (writer, label); - - uint size = 0; - if (status.ArrayOfStructures != null) { - foreach (StructureWriteContext context in status.ArrayOfStructures) { - size += WriteSingleStructure (context); - writer.WriteLine (); - } - } else { - size = WriteSingleStructure (status); - } - - WriteSymbolSize (writer, label, size); - - return label; - - uint WriteSingleStructure (StructureWriteContext context) - { - uint sizeSoFar = 0; - uint padding; - - foreach (StructureField field in context.Fields) { - if (field.NativeType.Alignment > 1) { - padding = sizeSoFar % field.NativeType.Alignment; - if (padding > 0) { - padding = field.NativeType.Alignment - padding; - WriteDirective (writer, ".zero", padding); - sizeSoFar += padding; - context.Size += padding; - } - } - writer.Write (field.Text); - if (field.ArrayElements > 0) { - sizeSoFar += field.NativeType.Size * field.ArrayElements; - } else { - sizeSoFar += field.NativeType.Size; - } - } - - padding = context.Size % context.ByteAlignment; - if (padding > 0) { - padding = context.ByteAlignment - padding; - WriteDirective (writer, ".zero", padding); - context.Size += padding; - } - - return context.Size; - } - } - - public string WriteSymbol (LabeledStringSymbol str, bool local = true, bool ownSection = false) - { - return WriteSymbol (Output, str, local, ownSection); - } - - public string WriteSymbol (TextWriter writer, LabeledStringSymbol str, bool local = true, bool ownSection = false) - { - if (ownSection) { - WriteStringSection (writer, str.Label); - } - - WriteSymbolType (writer, str.Label, SymbolType.Object, local); - WriteSymbolLabel (writer, str.Label); - WriteDirective (writer, ".asciz", QuoteString (str.Contents)); - WriteSymbolSize (writer, str.Label, (uint)Encoding.UTF8.GetByteCount (str.Contents) + 1); - writer.WriteLine (); - - return str.Label; - } - - public string WriteSymbol (string symbolName, T value, bool local = true, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - return WriteSymbol (Output, symbolName, value, local, hex, comment, useBlockComment); - } - - public string WriteSymbol (TextWriter writer, string symbolName, T value, bool local = true, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - WriteSymbolType (writer, symbolName, SymbolType.Object, local, comment, useBlockComment); - NativeType nativeType = GetNativeType (); - WriteAlignmentDirective (writer, nativeType); - - string label = local ? MakeLocalLabel (symbolName, skipCounter: true) : symbolName; - WriteSymbolLabel (writer, label); - WriteData (writer, value, hex); - - WriteSymbolSize (writer, label, nativeType.Size); - - return label; - } - - public void WriteStringPointerSymbol (string symbolName, string symbolValue, bool global = true, bool alreadyInSection = false) - { - WriteStringPointerSymbol (Output, symbolName, symbolValue, global); - } - - public void WriteStringPointerSymbol (TextWriter writer, string symbolName, string symbolValue, bool global = true, bool alreadyInSection = false) - { - string symbolLabel = global ? symbolName : MakeLocalLabel (symbolName, skipCounter: true); - WriteSymbolType (writer, symbolLabel, SymbolType.Object, !global); - if (!alreadyInSection) { - WriteDataSection (writer, symbolLabel); - } - - NativeType nativeType = GetPointerType (); - WriteAlignmentDirective (writer, nativeType.Alignment); - WriteSymbolLabel (writer, symbolLabel); - - string stringLabel = MakeLocalLabel (AutomaticStringSection); - - var sym = new LabeledStringSymbol (stringLabel, symbolValue, global); - stringTable.Add (sym); - - WritePointer (writer, sym.Label); - WriteSymbolSize (writer, symbolLabel, nativeType.Size); - } - - public string WriteStringSymbol (string symbolName, string symbolValue, bool global = true) - { - return WriteStringSymbol (Output, symbolName, symbolValue, global); - } - - public string WriteStringSymbol (TextWriter writer, string symbolName, string symbolValue, bool global = true) - { - return WriteSymbol (writer, new LabeledStringSymbol (symbolName, symbolValue, global), !global); - } - - protected void WriteData (TextWriter writer, string typeName, string value, string? comment = null, bool useBlockComment = false) - { - WriteDirectiveWithComment (writer, typeName, comment, useBlockComment, value); - } - - protected void WriteData (string typeName, string value, string? comment = null, bool useBlockComment = false) - { - WriteData (Output, typeName, value, comment, useBlockComment); - } - - public uint WritePointer (string? label, string? comment = null, bool useBlockComment = false) - { - return WritePointer (Output, label, comment, useBlockComment); - } - - public uint WritePointer (TextWriter writer, string? label, string? comment = null, bool useBlockComment = false) - { - WriteData (writer, GetNativeTypeName (), label == null ? "0" : label, comment, useBlockComment); - return GetNativeTypeSize (); - } - - public uint WritePointer (StructureWriteContext status, string? label, string? comment = null, bool useBlockComment = false) - { - status.TempWriter.GetStringBuilder ().Clear (); - - uint ret = WritePointer (status.TempWriter, label, comment, useBlockComment); - AddStructureField (status, GetNativeType (), status.TempWriter.ToString ()); - - return ret; - } - - public uint WriteStringPointer (string value, bool global = false, string? label = null, string? comment = null, bool useBlockComment = false) - { - return WriteStringPointer (Output, value, global, label, comment, useBlockComment); - } - - public uint WriteStringPointer (TextWriter writer, string value, bool global = false, string? label = null, string? comment = null, bool useBlockComment = false) - { - if (global && String.IsNullOrEmpty (label)) { - throw new ArgumentException (nameof (label), "Must not be null or empty for global symbols"); - } - - if (String.IsNullOrEmpty (label)) { - label = MakeLocalLabel (AutomaticStringSection); - } - - var sym = new LabeledStringSymbol (label, value, global); - stringTable.Add (sym); - - return WritePointer (writer, sym.Label, comment, useBlockComment); - } - - public uint WriteStringPointer (StructureWriteContext status, string value, bool global = false, string? label = null, string? comment = null, bool useBlockComment = false) - { - status.TempWriter.GetStringBuilder ().Clear (); - - uint ret = WriteStringPointer (status.TempWriter, value, global, label, comment, useBlockComment); - AddStructureField (status, GetNativeType (), status.TempWriter.ToString ()); - - return ret; - } - - public uint WriteInlineString (StructureWriteContext status, string value, uint fixedWidth = 0, string? comment = null, bool useBlockComment = false) - { - status.TempWriter.GetStringBuilder ().Clear (); - - uint ret = WriteInlineString (status.TempWriter, value, fixedWidth, comment, useBlockComment); - AddStructureField (status, GetNativeType (), status.TempWriter.ToString (), arrayElements: ret); - return ret; - } - - public uint WriteInlineString (string value, uint fixedWidth = 0, string? comment = null, bool useBlockComment = false) - { - return WriteInlineString (Output, value, fixedWidth, comment, useBlockComment); - } - - // fixedWidth includes the terminating 0 - public uint WriteInlineString (TextWriter writer, string value, uint fixedWidth = 0, string? comment = null, bool useBlockComment = false) - { - int byteCount = Encoding.UTF8.GetByteCount (value); - if (fixedWidth > 0 && byteCount >= fixedWidth) { - throw new InvalidOperationException ($"String is too long, maximum allowed length is {fixedWidth - 1} bytes, the string is {byteCount} bytes long"); - } - - WriteDirectiveWithComment (writer, ".ascii", comment, useBlockComment, QuoteString (value)); - if (fixedWidth == 0) { - WriteDirective (writer, ".zero", 1); - return (uint)(byteCount + 1); - } - - int padding = (((int)fixedWidth) - byteCount); - WriteDirectiveWithComment (writer, ".zero", $"byteCount == {byteCount}; fixedWidth == {fixedWidth}; returned size == {byteCount + padding}", padding); - - return (uint)(byteCount + padding); - } - - public virtual LabeledSymbol WriteEmptyBuffer (uint bufferSize, string symbolLabelOrNamespace, bool local = true) - { - return WriteEmptyBuffer (Output, bufferSize, symbolLabelOrNamespace, local); - } - - public virtual LabeledSymbol WriteEmptyBuffer (TextWriter writer, uint bufferSize, string symbolLabelOrNamespace, bool local = true) - { - string label = local ? MakeLocalLabel (symbolLabelOrNamespace) : symbolLabelOrNamespace; - var sym = new LabeledSymbol (label, !local); - - WriteSymbolType (writer, sym.Label, SymbolType.Object, local); - WriteSymbolLabel (writer, sym.Label); - WriteDirective (writer, ".zero", bufferSize); - WriteSymbolSize (writer, sym.Label, bufferSize); - - return sym; - } - - public virtual uint WriteData (TextWriter writer, T value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - WriteData (writer, GetNativeTypeName (), ToString (value, hex), comment, useBlockComment); - return GetNativeTypeSize (); - } - - public virtual void WriteData (StructureWriteContext status, T value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - status.TempWriter.GetStringBuilder ().Clear (); - WriteData (status, status.TempWriter, value, hex, comment, useBlockComment); - } - - protected void WriteData (StructureWriteContext status, StringWriter sw, T value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - WriteData (sw, value, hex, comment, useBlockComment); - AddStructureField (status, GetNativeType (), sw.ToString ()); - } - - public virtual uint WriteData (T[] value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - return WriteData (Output, value, hex, comment, useBlockComment); - } - - public virtual uint WriteData (TextWriter writer, T[] value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - WriteDirectiveName (writer, GetNativeTypeName ()); - writer.Write (Indent); - - bool first = true; - foreach (T v in value) { - if (first) { - first = false; - } else { - writer.Write (", "); - } - - writer.Write (ToString (typeof(T), v, hex)); - } - WriteEOL (writer, comment, useBlockComment); - - return (uint)(GetNativeTypeSize () * value.Length); - } - - public virtual void WriteData (StructureWriteContext status, T[] value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - Type type = typeof(T); - if (type == typeof(byte) || type == typeof(sbyte)) { - status.TempWriter.GetStringBuilder ().Clear (); - - WriteData (status.TempWriter, value, hex, comment, useBlockComment); - AddStructureField (status, GetNativeType (), status.TempWriter.ToString (), (uint)value.Length); - return; - } - - bool first = true; - foreach (T v in value) { - WriteData (status, v, hex, first ? comment : null, useBlockComment); - if (first) { - first = false; - } - } - } - - void AddStructureField (StructureWriteContext status, NativeType nativeType, string contents, uint arrayElements = 0) - { - // Structure alignment is always the same as its most strictly aligned component, as per - // - // https://github.com/ARM-software/abi-aa/blob/320a56971fdcba282b7001cf4b84abb4fd993131/aapcs64/aapcs64.rst#59composite-types - // https://github.com/ARM-software/abi-aa/blob/320a56971fdcba282b7001cf4b84abb4fd993131/aapcs32/aapcs32.rst#53composite-types - if (nativeType.Alignment > status.ByteAlignment) { - status.ByteAlignment = nativeType.Alignment; - } - - if (arrayElements > 0) { - status.Size += nativeType.Size * arrayElements; - } else { - status.Size += nativeType.Size; - } - status.Fields.Add (new StructureField (nativeType, contents, arrayElements)); - } - - public virtual uint WriteData (T value, bool hex = true, string? comment = null, bool useBlockComment = false) where T: struct - { - return WriteData (Output, value, hex, comment, useBlockComment); - } - - public void WriteDataSection (TextWriter writer, string? name = null, bool writable = true) - { - string sectionName = writable ? ".data" : ".rodata"; - if (!String.IsNullOrEmpty (name)) { - sectionName = $"{sectionName}.{name}"; - } - - SectionFlags flags = SectionFlags.Allocatable; - if (writable) { - flags |= SectionFlags.Writable; - } - - WriteSection (writer, sectionName, flags, SectionType.Data); - } - - public void WriteDataSection (string? name = null, bool writable = true) - { - WriteDataSection (Output, name, writable); - } - - public void WriteStringSection (TextWriter writer, string name) - { - WriteSection ( - writer, - $".rodata.{name}", - SectionFlags.Allocatable | SectionFlags.HasCStrings | SectionFlags.Mergeable, - SectionType.Data, - flagSpecificArgs: "1" - ); - } - - public void WriteStringSection (string name) - { - WriteStringSection (Output, name); - } - - public void WriteSection (TextWriter writer, string name, SectionFlags flags = SectionFlags.None, SectionType type = SectionType.None, string? flagSpecificArgs = null, string? numberOrCustomFlag = null, string? numberOrCustomType = null) - { - writer.WriteLine (); - writer.Write ($"{Indent}.section{Indent}{name}"); - if (flags != SectionFlags.None) { - writer.Write (", \""); - - var list = new StringBuilder (); - if (flags.HasFlag (SectionFlags.Allocatable)) { - list.Append ("a"); - } - - if (flags.HasFlag (SectionFlags.GnuMbind)) { - list.Append ("d"); - } - - if (flags.HasFlag (SectionFlags.Excluded)) { - list.Append ("e"); - } - - if (flags.HasFlag (SectionFlags.ReferencesOtherSection)) { - list.Append ("o"); - } - - if (flags.HasFlag (SectionFlags.Writable)) { - list.Append ("w"); - } - - if (flags.HasFlag (SectionFlags.Executable)) { - list.Append ("x"); - } - - if (flags.HasFlag (SectionFlags.Mergeable)) { - list.Append ("M"); - } - - if (flags.HasFlag (SectionFlags.HasCStrings)) { - list.Append ("S"); - } - - if (flags.HasFlag (SectionFlags.GroupMember)) { - list.Append ("G"); - } - - if (flags.HasFlag (SectionFlags.ThreadLocalStorage)) { - list.Append ("T"); - } - - if (flags.HasFlag (SectionFlags.Retained)) { - list.Append ("R"); - } - - if (flags.HasFlag (SectionFlags.Number) || flags.HasFlag (SectionFlags.Custom)) { - if (String.IsNullOrEmpty (numberOrCustomFlag)) { - throw new InvalidOperationException ("Section number or target-specific flag value must be specified"); - } - - list.Append (numberOrCustomFlag); - } - - writer.Write ($"{list}\""); - } - - if (type != SectionType.None) { - writer.Write ($", {TypeLead}"); - switch (type) { - case SectionType.Data: - writer.Write ("progbits"); - break; - - case SectionType.NoData: - writer.Write ("nobits"); - break; - - case SectionType.InitArray: - writer.Write ("init_array"); - break; - - case SectionType.FiniArray: - writer.Write ("fini_array"); - break; - - case SectionType.PreInitArray: - writer.Write ("preinit_array"); - break; - - case SectionType.Number: - case SectionType.Custom: - if (String.IsNullOrEmpty (numberOrCustomType)) { - throw new InvalidOperationException ("NativeType number or target-specific nativeType name must be specified"); - } - writer.Write (numberOrCustomType); - break; - } - } - - if (!String.IsNullOrEmpty (flagSpecificArgs)) { - writer.Write ($", {flagSpecificArgs}"); - } - - WriteEOL (writer); - } - - public void WriteSection (string name, SectionFlags flags = SectionFlags.None, SectionType type = SectionType.None, string? flagSpecificArgs = null, string? numberOrCustomFlag = null, string? numberOrCustomType = null) - { - WriteSection (Output, name, flags, type, flagSpecificArgs, numberOrCustomFlag, numberOrCustomType); - } - - void WriteAlignmentDirective (TextWriter writer, NativeType nativeType) - { - WriteAlignmentDirective (writer, nativeType.Alignment); - } - - void WriteAlignmentDirective (TextWriter writer, uint byteAlignment) - { - WriteDirective (writer, ".p2align", Log2 (byteAlignment)); - } - - public void WriteDirective (TextWriter writer, string name, params object[]? args) - { - WriteDirectiveWithComment (writer, name, comment: null, useBlockComment: false, args: args); - } - - public void WriteDirective (string name, params object[]? args) - { - WriteDirective (Output, name, args); - } - - public void WriteDirectiveName (string name) - { - WriteDirectiveName (Output, name); - } - - public void WriteDirectiveName (TextWriter writer, string name) - { - writer.Write ($"{Indent}{name}"); - } - - public void WriteDirectiveWithComment (TextWriter writer, string name, string? comment, params object[]? args) - { - WriteDirectiveWithComment (writer, name, comment, useBlockComment: false, args: args); - } - - public void WriteDirectiveWithComment (TextWriter writer, string name, string? comment, bool useBlockComment, params object[]? args) - { - WriteDirectiveName (writer, name); - - if (args != null && args.Length > 0) { - writer.Write (Indent); - WriteCommaSeparatedList (writer, args); - } - - WriteEOL (writer, comment, useBlockComment); - } - - public void WriteDirectiveWithComment (string name, string? comment, params object[]? args) - { - WriteDirectiveWithComment (name, comment, useBlockComment: false, args: args); - } - - public void WriteDirectiveWithComment (string name, string? comment, bool useBlockComment, params object[]? args) - { - WriteDirectiveWithComment (Output, name, comment, useBlockComment, args); - } - - protected void WriteCommaSeparatedList (TextWriter writer, object[]? args) - { - if (args == null || args.Length == 0) { - return; - } - - writer.Write (String.Join (", ", args)); - } - - protected void WriteCommaSeparatedList (object[]? args) - { - WriteCommaSeparatedList (Output, args); - } - - protected string QuoteString (string s) - { - return $"\"{s}\""; - } - - public string MakeLocalLabel (string labelNamespaceOrSymbolName, bool skipCounter = false) - { - string label; - - if (skipCounter) { - label = labelNamespaceOrSymbolName; - } else { - if (!localLabels.TryGetValue (labelNamespaceOrSymbolName, out ulong counter)) { - counter = 0; - localLabels[labelNamespaceOrSymbolName] = counter; - } else { - localLabels[labelNamespaceOrSymbolName] = ++counter; - } - label = $"{labelNamespaceOrSymbolName}.{counter}"; - } - - return $".L.{label}"; - } - - static double Log2 (double x) - { - #if NETCOREAPP - return Math.Log2 (x); - #else - return Math.Log (x, 2); - #endif - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionFlags.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionFlags.cs deleted file mode 100644 index a9ad5e8fd74..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionFlags.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks -{ - [Flags] - enum SectionFlags - { - None = 0, - Allocatable = 1 << 0, - GnuMbind = 1 << 1, - Excluded = 1 << 2, - ReferencesOtherSection = 1 << 3, - Writable = 1 << 4, - Executable = 1 << 5, - Mergeable = 1 << 6, - HasCStrings = 1 << 7, - GroupMember = 1 << 8, - ThreadLocalStorage = 1 << 9, - MemberOfPreviousGroup = 1 << 10, - Retained = 1 << 11, - Number = 1 << 12, - Custom = 1 << 13, - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionType.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionType.cs deleted file mode 100644 index 61d641364ac..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SectionType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Xamarin.Android.Tasks -{ - enum SectionType - { - None, - Data, // @progbits - NoData, // @nobits - InitArray, // @init_array - FiniArray, // @fini_array - PreInitArray, // @preinit_array - Number, // @ - Custom, // @ - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SymbolType.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SymbolType.cs deleted file mode 100644 index f4be85fd299..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/SymbolType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Xamarin.Android.Tasks -{ - enum SymbolType - { - Function, - GnuIndirectFunction, - GnuUniqueObject, - Object, - TlsObject, - Common, - NoType, - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86NativeAssemblyGenerator.cs deleted file mode 100644 index bdebe47b58a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86NativeAssemblyGenerator.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - abstract class X86NativeAssemblyGenerator : NativeAssemblyGenerator - { - protected override string LineCommentStart => "#"; - - protected X86NativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - protected override void ConfigureTypeMappings (Dictionary mapping) - { - base.ConfigureTypeMappings (mapping); - - ConfigureTypeMapping (".short", size: 2, alignment: 2); - ConfigureTypeMapping (".short", size: 2, alignment: 2); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_32NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_32NativeAssemblyGenerator.cs deleted file mode 100644 index de0dc0584e3..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_32NativeAssemblyGenerator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - class X86_32NativeAssemblyGenerator : X86NativeAssemblyGenerator - { - static readonly NativeType pointer = new NativeType { - Size = 4, - Alignment = 4, - Name = ".long", - }; - - public override bool Is64Bit => false; - - public X86_32NativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - protected override NativeType GetPointerType () => pointer; - - protected override void ConfigureTypeMappings (Dictionary mapping) - { - base.ConfigureTypeMappings (mapping); - - // Alignments and sizes as per https://refspecs.linuxbase.org/elf/abi386-4.pdf section 3.2 (Fundamental Types), table 3.1 (Scalar Types) - // Assembler type directives are described in https://sourceware.org/binutils/docs-2.37/as/index.html - ConfigureTypeMapping (".quad", size: 8, alignment: 4); - ConfigureTypeMapping (".quad", size: 8, alignment: 4); - ConfigureTypeMapping (".quad", size: 8, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - ConfigureTypeMapping (".long", size: 4, alignment: 4); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_64NativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_64NativeAssemblyGenerator.cs deleted file mode 100644 index 8357625a6c1..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblyGenerator/X86_64NativeAssemblyGenerator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks -{ - class X86_64NativeAssemblyGenerator : X86NativeAssemblyGenerator - { - static readonly NativeType pointer = new NativeType { - Size = 8, - Alignment = 8, - Name = ".quad", - }; - - public override bool Is64Bit => true; - - public X86_64NativeAssemblyGenerator (StreamWriter output, string fileName) - : base (output, fileName) - {} - - protected override NativeType GetPointerType () => pointer; - - protected override void ConfigureTypeMappings (Dictionary mapping) - { - base.ConfigureTypeMappings (mapping); - - // Alignments and sizes as per https://refspecs.linuxbase.org/elf/x86_64-abi-0.95.pdf section 3.1.2 (Fundamental Types), table 3.1 (Scalar Types) - // Assembler type directives are described in https://sourceware.org/binutils/docs-2.37/as/index.html - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - ConfigureTypeMapping (".quad", size: 8, alignment: 8); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index aeba133dce6..15989928466 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -221,11 +221,9 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L } GeneratedBinaryTypeMaps.Add (typeMapIndexPath); - GenerateNativeAssembly ( - (AndroidTargetArch arch, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { - return new TypeMappingDebugNativeAssemblyGenerator (arch, new ModuleDebugData (), outputDirectory, sharedBitsWritten); - } - ); + var generator = new TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); + generator.Init (); + GenerateNativeAssembly (generator, outputDirectory); return true; } @@ -254,11 +252,10 @@ bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttribu }; PrepareDebugMaps (data); - GenerateNativeAssembly ( - (AndroidTargetArch arch, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { - return new TypeMappingDebugNativeAssemblyGenerator (arch, data, outputDirectory, sharedBitsWritten, sharedIncludeUsesAbiPrefix); - } - ); + + var generator = new TypeMappingDebugNativeAssemblyGenerator (data); + generator.Init (); + GenerateNativeAssembly (generator, outputDirectory); return true; } @@ -432,11 +429,9 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List NativeTypeMappingData data; data = new NativeTypeMappingData (logger, modules, maxJavaNameLength + 1); - GenerateNativeAssembly ( - (AndroidTargetArch arch, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { - return new TypeMappingReleaseNativeAssemblyGenerator (arch, data, outputDirectory, sharedBitsWritten, sharedIncludeUsesAbiPrefix); - } - ); + var generator = new TypeMappingReleaseNativeAssemblyGenerator (data); + generator.Init (); + GenerateNativeAssembly (generator, outputDirectory); return true; } @@ -446,18 +441,13 @@ bool ShouldSkipInJavaToManaged (TypeDefinition td) return td.IsInterface || td.HasGenericParameters; } - void GenerateNativeAssembly (Func getGenerator) + void GenerateNativeAssembly (TypeMappingAssemblyGenerator generator, string baseFileName) { AndroidTargetArch arch; - bool sharedBitsWritten = false; - bool sharedIncludeUsesAbiPrefix; foreach (string abi in supportedAbis) { - sharedIncludeUsesAbiPrefix = false; switch (abi.Trim ()) { case "armeabi-v7a": arch = AndroidTargetArch.Arm; - sharedIncludeUsesAbiPrefix = true; // ARMv7a is "special", it uses different directive prefix - // than the others and the "shared" code won't build for it break; case "arm64-v8a": @@ -476,14 +466,11 @@ void GenerateNativeAssembly (Func (data); if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { return "assembly_name (unused in this mode)"; @@ -29,16 +27,58 @@ public override string GetComment (object data, string fieldName) return String.Empty; } + + public override ulong GetBufferSize (object data, string fieldName) + { + var map_module = EnsureType (data); + if (String.Compare ("java_to_managed", fieldName, StringComparison.Ordinal) == 0 || + String.Compare ("managed_to_java", fieldName, StringComparison.Ordinal) == 0) { + return map_module.entry_count; + } + + return 0; + } + + public override string GetPointedToSymbolName (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("java_to_managed", fieldName, StringComparison.Ordinal) == 0) { + return map_module.JavaToManagedCount == 0 ? null : JavaToManagedSymbol; + } + + if (String.Compare ("managed_to_java", fieldName, StringComparison.Ordinal) == 0) { + return map_module.ManagedToJavaCount == 0 ? null : ManagedToJavaSymbol; + } + + return base.GetPointedToSymbolName (data, fieldName); + } + } + + sealed class TypeMapEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) { + return $"from: entry.from"; + } + + if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) { + return $"to: entry.to"; + } + + return String.Empty; + } } // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh TypeMapEntry structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapEntryContextDataProvider))] sealed class TypeMapEntry { - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] public string from; - - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] public string to; }; @@ -47,133 +87,93 @@ sealed class TypeMapEntry [NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))] sealed class TypeMap { - public uint entry_count; + [NativeAssembler (Ignore = true)] + public int JavaToManagedCount; - [NativeAssembler (UsesDataProvider = true)] - public IntPtr assembly_name = IntPtr.Zero; // unused in Debug mode + [NativeAssembler (Ignore = true)] + public int ManagedToJavaCount; - [NativeAssembler (UsesDataProvider = true)] - public IntPtr data = IntPtr.Zero; // unused in Debug mode + public uint entry_count; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string java_to_managed; + [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] + public string? assembly_name = null; // unused in Debug mode - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string managed_to_java; - }; + [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] + public byte data = 0; // unused in Debug mode - const string JavaToManagedSymbol = "map_java_to_managed"; - const string ManagedToJavaSymbol = "map_managed_to_java"; - const string TypeMapSymbol = "type_map"; // MUST match src/monodroid/xamarin-app.hh + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? java_to_managed = null; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? managed_to_java = null; + }; - readonly string baseFileName; - readonly bool sharedBitsWritten; readonly TypeMapGenerator.ModuleDebugData data; - public TypeMappingDebugNativeAssemblyGenerator (AndroidTargetArch arch, TypeMapGenerator.ModuleDebugData data, string baseFileName, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix = false) - : base (arch, baseFileName, sharedIncludeUsesAbiPrefix) + StructureInfo typeMapEntryStructureInfo; + StructureInfo typeMapStructureInfo; + List> javaToManagedMap; + List> managedToJavaMap; + StructureInstance type_map; + + public TypeMappingDebugNativeAssemblyGenerator (TypeMapGenerator.ModuleDebugData data) { - if (String.IsNullOrEmpty (baseFileName)) - throw new ArgumentException("must not be null or empty", nameof (baseFileName)); - this.data = data ?? throw new ArgumentNullException (nameof (data)); + this.data = data; - this.baseFileName = baseFileName; - this.sharedBitsWritten = sharedBitsWritten; + javaToManagedMap = new List> (); + managedToJavaMap = new List> (); } - protected override void Write (NativeAssemblyGenerator generator) + public override void Init () { - bool haveJavaToManaged = data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0; - bool haveManagedToJava = data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0; - - using (var sharedOutput = MemoryStreamPool.Shared.CreateStreamWriter (generator.Output.Encoding)) { - WriteSharedBits (generator, sharedOutput, haveJavaToManaged, haveManagedToJava); - sharedOutput.Flush (); - Files.CopyIfStreamChanged (sharedOutput.BaseStream, SharedIncludeFile); - } - - if (haveJavaToManaged || haveManagedToJava) { - generator.WriteInclude (Path.GetFileName (SharedIncludeFile)); - } - - string managedToJavaSymbolName; - generator.WriteCommentLine ("Managed to java map: START"); - generator.WriteDataSection ($"rel.{ManagedToJavaSymbol}"); - - if (haveManagedToJava) { - NativeAssemblyGenerator.StructureWriteContext mapArray = generator.StartStructureArray (); - var map_entry = new TypeMapEntry (); + if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { - map_entry.from = entry.ManagedLabel; - map_entry.to = entry.JavaLabel; - - NativeAssemblyGenerator.StructureWriteContext mapEntryStruct = generator.AddStructureArrayElement (mapArray); - generator.WriteStructure (mapEntryStruct, map_entry); + var m2j = new TypeMapEntry { + from = entry.ManagedName, + to = entry.JavaName, + }; + managedToJavaMap.Add (new StructureInstance (m2j)); } - managedToJavaSymbolName = generator.WriteSymbol (mapArray, ManagedToJavaSymbol); - } else { - managedToJavaSymbolName = generator.WriteEmptySymbol (SymbolType.Object, ManagedToJavaSymbol); } - generator.WriteCommentLine ("Managed to java map: END"); - string javaToManagedSymbolName; - generator.WriteCommentLine ("Java to managed map: START"); - generator.WriteDataSection ($"rel.{JavaToManagedSymbol}"); - - if (haveJavaToManaged) { - NativeAssemblyGenerator.StructureWriteContext mapArray = generator.StartStructureArray (); - var map_entry = new TypeMapEntry (); + if (data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { - map_entry.from = entry.JavaLabel; - TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; - map_entry.to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedLabel; - NativeAssemblyGenerator.StructureWriteContext mapEntryStruct = generator.AddStructureArrayElement (mapArray); - generator.WriteStructure (mapEntryStruct, map_entry); + var j2m = new TypeMapEntry { + from = entry.JavaName, + to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, + }; + javaToManagedMap.Add (new StructureInstance (j2m)); } - - javaToManagedSymbolName = generator.WriteSymbol (mapArray, JavaToManagedSymbol); - } else { - javaToManagedSymbolName = generator.WriteEmptySymbol (SymbolType.Object, JavaToManagedSymbol); } - generator.WriteCommentLine ("Java to managed map: END"); - generator.WriteCommentLine ("TypeMap structure"); - generator.WriteDataSection ($"rel.ro.{TypeMapSymbol}"); + var map = new TypeMap { + JavaToManagedCount = data.JavaToManagedMap == null ? 0 : data.JavaToManagedMap.Count, + ManagedToJavaCount = data.ManagedToJavaMap == null ? 0 : data.ManagedToJavaMap.Count, - var type_map = new TypeMap { - entry_count = (uint)data.EntryCount, - java_to_managed = javaToManagedSymbolName, - managed_to_java = managedToJavaSymbolName, + entry_count = data.EntryCount, }; + type_map = new StructureInstance (map); + } - NativeAssemblyGenerator.StructureWriteContext typeMapStruct = generator.StartStructure (); - generator.WriteStructure (typeMapStruct, type_map); - generator.WriteSymbol (typeMapStruct, TypeMapSymbol, local: false); + protected override void MapStructures (LlvmIrGenerator generator) + { + typeMapEntryStructureInfo = generator.MapStructure (); + typeMapStructureInfo = generator.MapStructure (); } - void WriteSharedBits (NativeAssemblyGenerator generator, StreamWriter output, bool haveJavaToManaged, bool haveManagedToJava) + protected override void Write (LlvmIrGenerator generator) { - if (haveJavaToManaged) { - generator.WriteStringSection (output, "java_type_names"); - generator.WriteCommentLine (output, "Java type names: START", useBlockComment: true); - foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { - entry.JavaLabel = generator.MakeLocalLabel ("java_type_name"); - generator.WriteStringSymbol (output, entry.JavaLabel, entry.JavaName, global: false); - } - generator.WriteCommentLine (output, "Java type names: END", useBlockComment: true); + if (managedToJavaMap.Count > 0) { + generator.WriteStructureArray (typeMapEntryStructureInfo, managedToJavaMap, LlvmIrVariableOptions.LocalConstant, ManagedToJavaSymbol); } - if (haveManagedToJava) { - generator.WriteStringSection (output, "managed_type_names"); - generator.WriteCommentLine (output, "Managed type names: START", useBlockComment: true); - foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { - entry.ManagedLabel = generator.MakeLocalLabel ("managed_type_name"); - generator.WriteStringSymbol (output, entry.ManagedLabel, entry.ManagedName, global: false); - } - generator.WriteCommentLine (output, "Managed type names: END", useBlockComment: true); + if (javaToManagedMap.Count > 0) { + generator.WriteStructureArray (typeMapEntryStructureInfo, javaToManagedMap, LlvmIrVariableOptions.LocalConstant, JavaToManagedSymbol); } + + generator.WriteStructure (typeMapStructureInfo, type_map, LlvmIrVariableOptions.GlobalConstant, TypeMapSymbol); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index 442b0bed57b..ecc42af1eb5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using Microsoft.Android.Build.Tasks; -using Xamarin.Android.Tools; +using System.Text; + +using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks { @@ -13,40 +13,69 @@ sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextData { public override string GetComment (object data, string fieldName) { - var map_module = data as TypeMapModule; - if (map_module == null) { - throw new InvalidOperationException ("Invalid data type, expected an instance of TypeMapModule"); - } + var map_module = EnsureType (data); if (String.Compare ("module_uuid", fieldName, StringComparison.Ordinal) == 0) { return $"module_uuid: {map_module.MVID}"; } if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { - return $"assembly_name: {map_module.AssemblyNameValue}"; + return $"assembly_name: {map_module.assembly_name}"; } return String.Empty; } + + public override string? GetPointedToSymbolName (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("map", fieldName, StringComparison.Ordinal) == 0) { + return map_module.MapSymbolName; + } + + if (String.Compare ("duplicate_map", fieldName, StringComparison.Ordinal) == 0) { + return map_module.DuplicateMapSymbolName; + } + + return null; + } + + public override ulong GetBufferSize (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("map", fieldName, StringComparison.Ordinal) == 0) { + return map_module.entry_count; + } + + if (String.Compare ("duplicate_map", fieldName, StringComparison.Ordinal) == 0) { + return map_module.duplicate_count; + } + + return base.GetBufferSize (data, fieldName); + } } sealed class TypeMapJavaContextDataProvider : NativeAssemblerStructContextDataProvider { public override uint GetMaxInlineWidth (object data, string fieldName) { - var map_java = data as TypeMapJava; - if (map_java == null) { - throw new InvalidOperationException ("Invalid data type, expected an instance of TypeMapJava"); - } - if (String.Compare ("java_name", fieldName, StringComparison.Ordinal) == 0) { - return map_java.MaxJavaNameLength; + // Using a static field for this is **very** clunky, but it works in our case since we will + // set that field only once per build session and it allows us to query the array size while + // generating the structure declarations (as required by LLVM IR) + return TypeMapJava.MaxJavaNameLength; } return 0; } } + // This is here only to generate strongly-typed IR + sealed class MonoImage + {} + // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh TypeMapModuleEntry structure sealed class TypeMapModuleEntry @@ -61,27 +90,34 @@ sealed class TypeMapModuleEntry sealed class TypeMapModule { [NativeAssembler (Ignore = true)] - public Guid MVID; + public Guid MVID; [NativeAssembler (Ignore = true)] - public string AssemblyNameValue; + public string? MapSymbolName; - [NativeAssembler (UsesDataProvider = true)] - public byte[] module_uuid; - public uint entry_count; - public uint duplicate_count; + [NativeAssembler (Ignore = true)] + public string? DuplicateMapSymbolName; + + [NativeAssembler (UsesDataProvider = true, InlineArray = true, InlineArraySize = 16)] + public byte[] module_uuid; + public uint entry_count; + public uint duplicate_count; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string map; + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapModuleEntry map; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol)] - public string duplicate_map; + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapModuleEntry duplicate_map; - [NativeAssemblerString (AssemblerStringFormat.PointerToSymbol, UsesDataProvider = true)] + [NativeAssembler (UsesDataProvider = true)] public string assembly_name; - public IntPtr image; + + [NativePointer (IsNull = true)] + public MonoImage image; public uint java_name_width; - public IntPtr java_map; + + [NativePointer (IsNull = true)] + public byte java_map; } // Order of fields and their type must correspond *exactly* to that in @@ -90,88 +126,101 @@ sealed class TypeMapModule sealed class TypeMapJava { [NativeAssembler (Ignore = true)] - public uint MaxJavaNameLength; + public static uint MaxJavaNameLength; public uint module_index; public uint type_token_id; - [NativeAssemblerString (AssemblerStringFormat.InlineArray, PadToMaxLength = true)] - public string java_name; + [NativeAssembler (UsesDataProvider = true, InlineArray = true, NeedsPadding = true)] + public byte[] java_name; + } + + sealed class ModuleMapData + { + public string SymbolLabel { get; } + public List> Entries { get; } + + public ModuleMapData (string symbolLabel) + { + SymbolLabel = symbolLabel; + Entries = new List> (); + } } - readonly string baseFileName; readonly NativeTypeMappingData mappingData; - readonly bool sharedBitsWritten; + StructureInfo typeMapJavaStructureInfo; + StructureInfo typeMapModuleStructureInfo; + StructureInfo typeMapModuleEntryStructureInfo; + List mapModulesData; + List> mapModules; + List> javaMap; + + ulong moduleCounter = 0; - public TypeMappingReleaseNativeAssemblyGenerator (AndroidTargetArch arch, NativeTypeMappingData mappingData, string baseFileName, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix = false) - : base (arch, baseFileName, sharedIncludeUsesAbiPrefix) + public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) { this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - this.baseFileName = baseFileName; - this.sharedBitsWritten = sharedIncludeUsesAbiPrefix ? false : sharedBitsWritten; + mapModulesData = new List (); + mapModules = new List> (); + javaMap = new List> (); } - protected override void Write (NativeAssemblyGenerator generator) + public override void Init () { - WriteHeaderField (generator, "map_module_count", mappingData.MapModuleCount); - WriteHeaderField (generator, "java_type_count", mappingData.JavaTypeCount); - WriteHeaderField (generator, "java_name_width", mappingData.JavaNameWidth); + TypeMapJava.MaxJavaNameLength = mappingData.JavaNameWidth; + InitMapModules (); + InitJavaMap (); + } - bool haveAssemblyNames = mappingData.AssemblyNames.Count > 0; - bool haveModules = mappingData.Modules.Length > 0; + void InitJavaMap () + { + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { + var map_entry = new TypeMapJava { + module_index = (uint)entry.ModuleIndex, + type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, + java_name = Encoding.UTF8.GetBytes (entry.JavaName), + }; - if (haveAssemblyNames) { - generator.WriteInclude (Path.GetFileName (SharedIncludeFile)); - } else { - generator.WriteCommentLine ($"No shared data present, {Path.GetFileName (SharedIncludeFile)} not generated"); + javaMap.Add (new StructureInstance (map_entry)); } + } - generator.WriteEOL (); - if (haveModules) { - generator.WriteInclude (Path.GetFileName (TypemapsIncludeFile)); - } else { - generator.WriteCommentLine ($"No modules defined, {Path.GetFileName (TypemapsIncludeFile)} not generated"); - } + void InitMapModules () + { + foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { + string mapName = $"module{moduleCounter++}_managed_to_java"; + string duplicateMapName; - if (!sharedBitsWritten && haveAssemblyNames) { - using (var sharedOutput = MemoryStreamPool.Shared.CreateStreamWriter (generator.Output.Encoding)) { - WriteAssemblyNames (generator, sharedOutput); - sharedOutput.Flush (); - Files.CopyIfStreamChanged (sharedOutput.BaseStream, SharedIncludeFile); - } - } + if (data.DuplicateTypes.Count == 0) + duplicateMapName = String.Empty; + else + duplicateMapName = $"{mapName}_duplicates"; - if (haveModules) { - using (var mapOutput = MemoryStreamPool.Shared.CreateStreamWriter (generator.Output.Encoding)) { - WriteMapModules (generator, mapOutput, "map_modules"); - mapOutput.Flush (); - Files.CopyIfStreamChanged (mapOutput.BaseStream, TypemapsIncludeFile); + var map_module = new TypeMapModule { + MVID = data.Mvid, + MapSymbolName = mapName, + DuplicateMapSymbolName = duplicateMapName.Length == 0 ? null : duplicateMapName, + + module_uuid = data.MvidBytes, + entry_count = (uint)data.Types.Length, + duplicate_count = (uint)data.DuplicateTypes.Count, + assembly_name = data.AssemblyName, + java_name_width = 0, + }; + + InitMapModuleData (mapName, data.Types, mapModulesData); + if (data.DuplicateTypes.Count > 0) { + InitMapModuleData (duplicateMapName, data.DuplicateTypes.Values, mapModulesData); } - } else { - WriteMapModules (generator, null, "map_modules"); - } - - WriteJavaMap (generator, "map_java"); - } - void WriteAssemblyNames (NativeAssemblyGenerator generator, StreamWriter output) - { - generator.WriteStringSection (output, "map_assembly_names"); - foreach (var kvp in mappingData.AssemblyNames) { - string label = kvp.Key; - string name = kvp.Value; - - generator.WriteStringSymbol (output, label, name, global: false); + mapModules.Add (new StructureInstance (map_module)); } } - void WriteManagedMaps (NativeAssemblyGenerator generator, StreamWriter output, string moduleSymbolName, IEnumerable entries) + void InitMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData) { - if (entries == null) - return; - var tokens = new Dictionary (); - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in entries) { + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { int idx = Array.BinarySearch (mappingData.JavaTypeNames, entry.JavaName, StringComparer.Ordinal); if (idx < 0) throw new InvalidOperationException ($"Could not map entry '{entry.JavaName}' to array index"); @@ -182,99 +231,74 @@ void WriteManagedMaps (NativeAssemblyGenerator generator, StreamWriter output, s var sortedTokens = tokens.Keys.ToArray (); Array.Sort (sortedTokens); - generator.WriteDataSection (output, moduleSymbolName, writable: false); - var map_entry = new TypeMapModuleEntry (); - - NativeAssemblyGenerator.StructureWriteContext mapArray = generator.StartStructureArray (); + var moduleData = new ModuleMapData (moduleDataSymbolLabel); foreach (uint token in sortedTokens) { - map_entry.type_token_id = token; - map_entry.java_map_index = tokens[token]; + var map_entry = new TypeMapModuleEntry { + type_token_id = token, + java_map_index = tokens[token], + }; - NativeAssemblyGenerator.StructureWriteContext mapEntryStruct = generator.AddStructureArrayElement (mapArray); - generator.WriteStructure (mapEntryStruct, map_entry); + moduleData.Entries.Add (new StructureInstance (map_entry)); } - generator.WriteSymbol (output, mapArray, moduleSymbolName, alreadyInSection: true, skipLabelCounter: true); + + allModulesData.Add (moduleData); } - void WriteMapModules (NativeAssemblyGenerator generator, StreamWriter mapOutput, string symbolName) + protected override void MapStructures (LlvmIrGenerator generator) { - generator.WriteEOL (); - generator.WriteCommentLine ("Managed to Java map: START"); - generator.WriteDataSection ($"rel.{symbolName}"); - - int moduleCounter = 0; - var map_module = new TypeMapModule { - image = IntPtr.Zero, - - // These two are used only in Debug builds with Instant Run enabled, but for simplicity we always output - // them. - java_name_width = 0, - java_map = IntPtr.Zero, - }; - - NativeAssemblyGenerator.StructureWriteContext mapModulesArray = generator.StartStructureArray (); - foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { - string mapName = $"module{moduleCounter++}_managed_to_java"; - string duplicateMapName; + generator.MapStructure (); + typeMapJavaStructureInfo = generator.MapStructure (); + typeMapModuleStructureInfo = generator.MapStructure (); + typeMapModuleEntryStructureInfo = generator.MapStructure (); + } - if (data.DuplicateTypes.Count == 0) - duplicateMapName = String.Empty; - else - duplicateMapName = $"{mapName}_duplicates"; + protected override void Write (LlvmIrGenerator generator) + { + generator.WriteVariable ("map_module_count", mappingData.MapModuleCount); + generator.WriteVariable ("java_type_count", mappingData.JavaTypeCount); + generator.WriteVariable ("java_name_width", mappingData.JavaNameWidth); - map_module.MVID = data.Mvid; - map_module.AssemblyNameValue = data.AssemblyName; - map_module.module_uuid = data.MvidBytes; - map_module.entry_count = (uint)data.Types.Length; - map_module.duplicate_count = (uint)data.DuplicateTypes.Count; - map_module.map = generator.MakeLocalLabel (mapName, skipCounter: true); - map_module.duplicate_map = duplicateMapName.Length == 0 ? null : generator.MakeLocalLabel (duplicateMapName, skipCounter: true); - map_module.assembly_name = data.AssemblyNameLabel; - - NativeAssemblyGenerator.StructureWriteContext mapModuleStruct = generator.AddStructureArrayElement (mapModulesArray); - generator.WriteStructure (mapModuleStruct, map_module); - - if (mapOutput != null) { - WriteManagedMaps (generator, mapOutput, mapName, data.Types); - if (data.DuplicateTypes.Count > 0) { - WriteManagedMaps (generator, mapOutput, duplicateMapName, data.DuplicateTypes.Values); - } - } - } - generator.WriteSymbol (mapModulesArray, symbolName, local: false, alreadyInSection: true); - generator.WriteCommentLine ("Managed to Java map: END"); + WriteMapModules (generator); + WriteJavaMap (generator); } - void WriteJavaMap (NativeAssemblyGenerator generator, string symbolName) + void WriteJavaMap (LlvmIrGenerator generator) { generator.WriteEOL (); - generator.WriteCommentLine ("Java to managed map: START"); - generator.WriteDataSection (symbolName, writable: false); - - var map_entry = new TypeMapJava { - MaxJavaNameLength = mappingData.JavaNameWidth, - }; - - NativeAssemblyGenerator.StructureWriteContext javaMapArray = generator.StartStructureArray (); - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { - map_entry.module_index = (uint)entry.ModuleIndex; - map_entry.type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token; - map_entry.java_name = entry.JavaName; - - NativeAssemblyGenerator.StructureWriteContext mapEntryStruct = generator.AddStructureArrayElement (javaMapArray); - generator.WriteStructure (mapEntryStruct, map_entry); - } - generator.WriteSymbol (javaMapArray, symbolName, local: false, alreadyInSection: true); - generator.WriteCommentLine ("Java to managed map: END"); + generator.WriteEOL ("Java to managed map"); + generator.WritePackedStructureArray ( + typeMapJavaStructureInfo, + javaMap, + LlvmIrVariableOptions.GlobalConstant, + "map_java" + ); } - void WriteHeaderField (NativeAssemblyGenerator generator, string name, uint value) + void WriteMapModules (LlvmIrGenerator generator) { + if (mapModules.Count == 0) { + return; + } + generator.WriteEOL (); - generator.WriteCommentLine ($"{name}: START"); - generator.WriteDataSection (name, writable: false); - generator.WriteSymbol (name, value, hex: false, local: false); - generator.WriteCommentLine ($"{name}: END"); + 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, + mapModules, + LlvmIrVariableOptions.GlobalWritable, + "map_modules" + ); } } } diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index dc3166aefee..2700caa4e79 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -6,7 +6,7 @@ // This file MUST have "valid" values everywhere - the DSO it is compiled into is loaded by the // designer on desktop. -uint64_t format_tag = FORMAT_TAG; +const uint64_t format_tag = FORMAT_TAG; #if defined (DEBUG) || !defined (ANDROID) static TypeMapEntry java_to_managed[] = {}; @@ -39,7 +39,8 @@ CompressedAssemblies compressed_assemblies = { // Config settings below **must** be valid for Desktop builds as the default `libxamarin-app.{dll,dylib,so}` is used by // the Designer // -ApplicationConfig application_config = { +constexpr char android_package_name[] = "com.xamarin.test"; +const ApplicationConfig application_config = { .uses_mono_llvm = false, .uses_mono_aot = false, .uses_assembly_preload = false, @@ -58,12 +59,12 @@ ApplicationConfig application_config = { .number_of_assembly_store_files = 2, .number_of_dso_cache_entries = 2, .mono_components_mask = MonoComponent::None, - .android_package_name = "com.xamarin.test", + .android_package_name = android_package_name, }; -const char* mono_aot_mode_name = "normal"; -const char* app_environment_variables[] = {}; -const char* app_system_properties[] = {}; +const char* const mono_aot_mode_name = "normal"; +const char* const app_environment_variables[] = {}; +const char* const app_system_properties[] = {}; static constexpr size_t AssemblyNameWidth = 128; diff --git a/src/monodroid/jni/embedded-assemblies.cc b/src/monodroid/jni/embedded-assemblies.cc index b1cb9c0e6c7..ee39aee5a7b 100644 --- a/src/monodroid/jni/embedded-assemblies.cc +++ b/src/monodroid/jni/embedded-assemblies.cc @@ -1131,7 +1131,7 @@ EmbeddedAssemblies::typemap_load_file (BinaryTypeMapHeader &header, const char * constexpr uint32_t INVALID_TYPE_INDEX = std::numeric_limits::max (); for (size_t i = 0; i < module.entry_count; i++) { - cur = &module.java_to_managed[i]; + cur = const_cast (&module.java_to_managed[i]); cur->from = reinterpret_cast(java_pos); uint32_t idx; @@ -1146,7 +1146,7 @@ EmbeddedAssemblies::typemap_load_file (BinaryTypeMapHeader &header, const char * } java_pos += java_entry_size; - cur = &module.managed_to_java[i]; + cur = const_cast(&module.managed_to_java[i]); cur->from = reinterpret_cast(managed_pos); memcpy (&idx, managed_pos + header.managed_name_width, sizeof (idx)); diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index e0060c6ab2e..a9fcca6e186 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -48,8 +48,8 @@ struct TypeMap uint32_t entry_count; char *assembly_name; uint8_t *data; - TypeMapEntry *java_to_managed; - TypeMapEntry *managed_to_java; + const TypeMapEntry *java_to_managed; + const TypeMapEntry *managed_to_java; }; #else struct TypeMapModuleEntry @@ -63,9 +63,9 @@ struct TypeMapModule uint8_t module_uuid[16]; uint32_t entry_count; uint32_t duplicate_count; - TypeMapModuleEntry *map; - TypeMapModuleEntry *duplicate_map; - char *assembly_name; + TypeMapModuleEntry const *map; + TypeMapModuleEntry const *duplicate_map; + char const *assembly_name; MonoImage *image; uint32_t java_name_width; uint8_t *java_map; @@ -228,28 +228,28 @@ struct DSOCacheEntry void *handle; }; -MONO_API uint64_t format_tag; +MONO_API MONO_API_EXPORT const uint64_t format_tag; #if defined (DEBUG) || !defined (ANDROID) -MONO_API const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +MONO_API MONO_API_EXPORT const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs #else -MONO_API const uint32_t map_module_count; -MONO_API const uint32_t java_type_count; -MONO_API const uint32_t java_name_width; -MONO_API const TypeMapModule map_modules[]; -MONO_API const TypeMapJava map_java[]; +MONO_API MONO_API_EXPORT const uint32_t map_module_count; +MONO_API MONO_API_EXPORT const uint32_t java_type_count; +MONO_API MONO_API_EXPORT const uint32_t java_name_width; +MONO_API MONO_API_EXPORT const TypeMapModule map_modules[]; +MONO_API MONO_API_EXPORT const TypeMapJava map_java[]; #endif -MONO_API CompressedAssemblies compressed_assemblies; -MONO_API ApplicationConfig application_config; -MONO_API const char* app_environment_variables[]; -MONO_API const char* app_system_properties[]; +MONO_API MONO_API_EXPORT CompressedAssemblies compressed_assemblies; +MONO_API MONO_API_EXPORT const ApplicationConfig application_config; +MONO_API MONO_API_EXPORT const char* const app_environment_variables[]; +MONO_API MONO_API_EXPORT const char* const app_system_properties[]; -MONO_API const char* mono_aot_mode_name; +MONO_API MONO_API_EXPORT const char* const mono_aot_mode_name; -MONO_API XamarinAndroidBundledAssembly bundled_assemblies[]; -MONO_API AssemblyStoreSingleAssemblyRuntimeData assembly_store_bundled_assemblies[]; -MONO_API AssemblyStoreRuntimeData assembly_stores[]; +MONO_API MONO_API_EXPORT XamarinAndroidBundledAssembly bundled_assemblies[]; +MONO_API MONO_API_EXPORT AssemblyStoreSingleAssemblyRuntimeData assembly_store_bundled_assemblies[]; +MONO_API MONO_API_EXPORT AssemblyStoreRuntimeData assembly_stores[]; -MONO_API DSOCacheEntry dso_cache[]; +MONO_API MONO_API_EXPORT DSOCacheEntry dso_cache[]; #endif // __XAMARIN_ANDROID_TYPEMAP_H