From 31fa878d5c277d131510a7322a6fa73e945d8b30 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 28 Mar 2023 09:03:27 +0200 Subject: [PATCH 01/60] Some debug messages --- src/Mono.Android/Java.Interop/TypeManager.cs | 4 +++- src/monodroid/jni/monodroid-glue.cc | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index e70d00bc424..afe5df17065 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -223,8 +223,10 @@ static Exception CreateJavaLocationException () if (!JNIEnvInit.IsRunningOnDesktop) { // Miss message is logged in the native runtime - if (JNIEnvInit.LogAssemblyCategory) + if (JNIEnvInit.LogAssemblyCategory) { JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, CreateJavaLocationException ().ToString ()); + } return null; } diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index d6a910d2c40..afef374dd18 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -2487,6 +2487,14 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag env->ReleaseStringChars (methods, methods_ptr); env->ReleaseStringChars (managedType, managedType_ptr); + const char *tmp = env->GetStringUTFChars (managedType, nullptr); + log_warn (LOG_ASSEMBLY, "Blazor: registering type %s", tmp); + env->ReleaseStringUTFChars (managedType, tmp); + + tmp = env->GetStringUTFChars (methods, nullptr); + log_warn (LOG_ASSEMBLY, "Blazor: methods: %s", tmp); + env->ReleaseStringUTFChars (methods, tmp); + if (XA_UNLIKELY (FastTiming::enabled ())) { internal_timing->end_event (total_time_index, true /* uses_more_info */); From a89f45b67a8abdc7489c179808ab7738624f3ba6 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 28 Mar 2023 23:06:10 +0200 Subject: [PATCH 02/60] Marshal methods tracing --- .../Microsoft.Android.Runtime.proj | 1 + .../Tasks/GenerateJavaStubs.cs | 22 +----- .../Tasks/GeneratePackageManagerJava.cs | 4 +- .../Tasks/LinkApplicationSharedLibraries.cs | 42 ++++++++++- .../LlvmIrGenerator/LlvmIrFunction.cs | 3 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 35 ++++++++-- .../MarshalMethodsNativeAssemblyGenerator.cs | 69 ++++++++++++++++++- .../Utilities/MonoAndroidHelper.cs | 40 +++++++++++ .../Xamarin.Android.Common.targets | 4 ++ src/monodroid/CMakeLists.txt | 12 ++++ src/monodroid/jni/logger.cc | 24 +++---- src/monodroid/jni/marshal-methods-tracing.cc | 41 +++++++++++ src/monodroid/jni/marshal-methods-tracing.hh | 10 +++ .../jni/marshal-methods-utilities.hh | 45 ++++++++++++ src/monodroid/jni/monodroid-glue-internal.hh | 1 - src/monodroid/jni/shared-constants.hh | 13 ++++ .../jni/xamarin-android-app-context.cc | 29 ++------ 17 files changed, 323 insertions(+), 72 deletions(-) create mode 100644 src/monodroid/jni/marshal-methods-tracing.cc create mode 100644 src/monodroid/jni/marshal-methods-tracing.hh create mode 100644 src/monodroid/jni/marshal-methods-utilities.hh diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 8982eb6f911..17a5bbec5fa 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -41,6 +41,7 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.debug.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.release.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-debug-app-helper.so" /> + <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmarshal-methods-tracing.a" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index e1c36a308d8..bad74480bd4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -249,7 +249,7 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); } else { foreach (string abi in SupportedAbis) { - targetPaths.Add (Path.Combine (IntermediateOutputDirectory, AbiToRid (abi), "linked")); + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, MonoAndroidHelper.AbiToRid (abi), "linked")); } } } @@ -432,26 +432,6 @@ void StoreMarshalAssemblyPath (string name, ITaskItem asm) assemblyPaths.Add (asm.ItemSpec); } - - string AbiToRid (string abi) - { - switch (abi) { - case "arm64-v8a": - return "android-arm64"; - - case "armeabi-v7a": - return "android-arm"; - - case "x86": - return "android-x86"; - - case "x86_64": - return "android-x64"; - - default: - throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); - } - } } bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 623b20976de..aba9b452a7f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -66,6 +66,7 @@ public class GeneratePackageManagerJava : AndroidTask public bool InstantRunEnabled { get; set; } public bool EnableMarshalMethods { get; set; } + public bool EnableMarshalMethodTracing { get; set; } public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } @@ -410,7 +411,8 @@ void AddEnvironment () assemblyCount, uniqueAssemblyNames, marshalMethodsState?.MarshalMethods, - Log + Log, + EnableMarshalMethodTracing ); } else { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index bbe49073e8c..682407ad692 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -29,6 +29,7 @@ sealed class InputFiles { public List ObjectFiles; public string OutputSharedLibrary; + public List ExtraLibraries; } [Required] @@ -43,6 +44,10 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } + public string AndroidNdkDirectory { get; set; } + + public bool EnableMarshalMethodTracing { get; set; } + public override System.Threading.Tasks.Task RunTaskAsync () { return this.WhenAll (GetLinkerConfigs (), RunLinker); @@ -112,14 +117,22 @@ void RunLinker (Config config) IEnumerable GetLinkerConfigs () { + NdkTools ndk = null; + if (EnableMarshalMethodTracing) { + ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: false, log: Log); + } + + string runtimeNativeLibsDir = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, "..", "..", "..", "lib")); var abis = new Dictionary (StringComparer.Ordinal); ITaskItem[] dsos = ApplicationSharedLibraries; foreach (ITaskItem item in dsos) { string abi = item.GetMetadata ("abi"); - abis [abi] = GatherFilesForABI(item.ItemSpec, abi, ObjectFiles); + abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, ndk); } const string commonLinkerArgs = + "--shared " + + "--allow-shlib-undefined " + "--unresolved-symbols=ignore-in-shared-libs " + "--export-dynamic " + "-soname libxamarin-app.so " + @@ -132,7 +145,7 @@ IEnumerable GetLinkerConfigs () "--warn-shared-textrel " + "--fatal-warnings"; - string stripSymbolsArg = DebugBuild ? String.Empty : " -s"; + string stripSymbolsArg = DebugBuild || EnableMarshalMethodTracing ? String.Empty : " -s"; string ld = Path.Combine (AndroidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, "ld")); var targetLinkerArgs = new List (); @@ -177,6 +190,12 @@ IEnumerable GetLinkerConfigs () targetLinkerArgs.Add ("-o"); targetLinkerArgs.Add (QuoteFileName (inputs.OutputSharedLibrary)); + if (inputs.ExtraLibraries != null) { + foreach (string lib in inputs.ExtraLibraries) { + targetLinkerArgs.Add (lib); + } + } + string targetArgs = String.Join (" ", targetLinkerArgs); yield return new Config { LinkerPath = ld, @@ -186,11 +205,28 @@ IEnumerable GetLinkerConfigs () } } - InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles) + InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, NdkTools ndk) { + List extraLibraries = null; + + if (EnableMarshalMethodTracing) { + if (ndk == null) { + throw new ArgumentNullException (nameof (ndk)); + } + + string libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, MonoAndroidHelper.AbiToTargetArch (abi), 21); // TODO: don't hardcode the API level + extraLibraries = new List { + Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libmarshal-methods-tracing.a"), + $"-L \"{libPath}\"", + "-lc", + "-llog", // tracing uses android logger + }; + } + return new InputFiles { OutputSharedLibrary = runtimeSharedLibrary, ObjectFiles = GetItemsForABI (abi, objectFiles), + ExtraLibraries = extraLibraries, }; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 219491437d5..dbf354c9a32 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Text; -using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR { @@ -90,7 +89,7 @@ class LlvmIrFunction uint localSlot = 0; uint indentLevel = 1; - public LlvmIrFunction (string name, Type returnType, int attributeSetID, List? parameters = null) + public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null) { if (String.IsNullOrEmpty (name)) { throw new ArgumentException ("must not be null or empty", nameof (name)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 85b9662c524..4850f6af2d1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -23,21 +23,20 @@ abstract partial class LlvmIrGenerator bool codeOutputInitialized = false; - /// - /// Writes the function definition up to the opening curly brace - /// - public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) + void ValidateFunction (LlvmIrFunction function, out LlvmFunctionAttributeSet? attributes) { if (function == null) { throw new ArgumentNullException (nameof (function)); } - LlvmFunctionAttributeSet? attributes = null; + attributes = null; if (function.AttributeSetID >= 0 && !FunctionAttributes.TryGetValue (function.AttributeSetID, out attributes)) { throw new InvalidOperationException ($"Function '{function.Name}' refers to attribute set that does not exist (ID: {function.AttributeSetID})"); } + } - Output.WriteLine (); + void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmFunctionAttributeSet? attributes, string? comment) + { if (!String.IsNullOrEmpty (comment)) { foreach (string line in comment.Split ('\n')) { WriteCommentLine (line); @@ -48,12 +47,34 @@ public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) WriteCommentLine ($"Function attributes: {attributes.Render ()}"); } - Output.Write ($"define {GetKnownIRType (function.ReturnType)} @{function.Name} ("); + Output.Write ($"{statementKindKeyword} {GetKnownIRType (function.ReturnType)} @{function.Name} ("); WriteFunctionParameters (function.Parameters, writeNames: true); Output.Write(") local_unnamed_addr "); if (attributes != null) { Output.Write ($"#{function.AttributeSetID}"); } + } + + /// + /// Writes function forward declaration + /// + public void WriteFunctionForwardDeclaration (LlvmIrFunction function, string? comment = null) + { + ValidateFunction (function, out LlvmFunctionAttributeSet? attributes); + + Output.WriteLine (); + WriteFunctionSignature (function, "declare", attributes, comment); + Output.WriteLine (";"); + } + + /// + /// Writes the function definition up to the opening curly brace + /// + public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) + { + ValidateFunction (function, out LlvmFunctionAttributeSet? attributes); + Output.WriteLine (); + WriteFunctionSignature (function, "define", attributes, comment); Output.WriteLine (); Output.WriteLine ("{"); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 229ba0397d7..ed4dca1df48 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -171,6 +171,9 @@ sealed class MarshalMethodName { 'L', typeof(_jobjectArray) }, }; + const string mm_trace_func_enter_name = "_mm_trace_func_enter"; + const string mm_trace_func_leave_name = "_mm_trace_func_leave"; + ICollection uniqueAssemblyNames; int numberOfAssembliesInApk; IDictionary> marshalMethods; @@ -199,7 +202,12 @@ sealed class MarshalMethodName List methods; List> classes = new List> (); + // Tracing + LlvmIrVariableReference? mm_trace_func_enter_ref; + LlvmIrVariableReference? mm_trace_func_leave_ref; + readonly bool generateEmptyCode; + readonly bool emitTracing; /// /// Constructor to be used ONLY when marshal methods are DISABLED @@ -214,7 +222,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl /// /// Constructor to be used ONLY when marshal methods are ENABLED /// - public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger) + public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger, bool emitTracing) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); @@ -222,6 +230,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); generateEmptyCode = false; + this.emitTracing = emitTracing; } public override void Init () @@ -577,6 +586,7 @@ protected override void Write (LlvmIrGenerator generator) { WriteAssemblyImageCache (generator, out Dictionary asmNameToIndex); WriteClassCache (generator); + WriteInitTracing (generator); LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); @@ -650,6 +660,47 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) } } + void WriteInitTracing (LlvmIrGenerator generator) + { + if (!emitTracing) { + return; + } + + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_func_enter_or_leave_params = new List { + new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), + new LlvmIrFunctionParameter (typeof(uint), "class_index"), + new LlvmIrFunctionParameter (typeof(uint), "method_token") + }; + + var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + + ); + mm_trace_func_enter_ref = new LlvmIrVariableReference (mm_trace_func_enter_sig, mm_trace_func_enter_name, isGlobal: true); + + var mm_trace_func_leave_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + ); + mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); + + WriteTraceDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig.ReturnType, mm_trace_func_enter_sig.Parameters); + WriteTraceDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig.ReturnType, mm_trace_func_leave_sig.Parameters); + + void WriteTraceDeclaration (string name, Type returnType, IList parameters) + { + var func = new LlvmIrFunction ( + name: name, + returnType: returnType, + attributeSetID: LlvmIrGenerator.FunctionAttributesJniMethods, + parameters: parameters + ); + generator.WriteFunctionForwardDeclaration (func); + } + } + void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) { if (generateEmptyCode || methods == null || methods.Count == 0) { @@ -695,6 +746,18 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); + List? trace_enter_leave_args = null; + + if (emitTracing) { + trace_enter_leave_args = new List { + new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), + new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), + new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()) + }; + + generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args); + } + LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); @@ -747,6 +810,10 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList () ); + if (emitTracing) { + generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args); + } + if (result != null) { generator.EmitReturnInstruction (func, result); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 179c78cb6b4..030dc1862e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -534,5 +534,45 @@ public static string GetRelativePathForAndroidAsset (string assetsDirectory, ITa path = head.Length == path.Length ? path : path.Substring ((head.Length == 0 ? 0 : head.Length + 1) + assetsDirectory.Length).TrimStart (DirectorySeparators); return path; } + + public static string AbiToRid (string abi) + { + switch (abi) { + case "arm64-v8a": + return "android-arm64"; + + case "armeabi-v7a": + return "android-arm"; + + case "x86": + return "android-x86"; + + case "x86_64": + return "android-x64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + } + + public static AndroidTargetArch AbiToTargetArch (string abi) + { + switch (abi) { + case "arm64-v8a": + return AndroidTargetArch.Arm64; + + case "armeabi-v7a": + return AndroidTargetArch.Arm; + + case "x86": + return AndroidTargetArch.X86; + + case "x86_64": + return AndroidTargetArch.X86_64; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index c642f4507a7..cb9d4dc2eea 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -340,6 +340,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) + <_AndroidEnableMarshalMethodTracing Condition=" '$(_AndroidEnableMarshalMethodTracing)' == '' ">False @@ -1791,6 +1792,7 @@ because xbuild doesn't support framework reference assemblies. UsingAndroidNETSdk="$(UsingAndroidNETSdk)" UseAssemblyStore="$(AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" + EnableMarshalMethodTracing="$(_AndroidEnableMarshalMethodTracing)" > @@ -2085,6 +2087,8 @@ because xbuild doesn't support framework reference assemblies. ApplicationSharedLibraries="@(_ApplicationSharedLibrary)" DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" + EnableMarshalMethodTracing="$(_AndroidEnableMarshalMethodTracing)" + AndroidNdkDirectory="$(_AndroidNdkDirectory)" /> diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 37679f63967..5fe07f6ea23 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -472,6 +472,7 @@ endif() set(XAMARIN_INTERNAL_API_LIB xa-internal-api${CHECKED_BUILD_INFIX}) set(XAMARIN_DEBUG_APP_HELPER_LIB xamarin-debug-app-helper${CHECKED_BUILD_INFIX}) set(XAMARIN_APP_STUB_LIB xamarin-app) +set(XAMARIN_MARSHAL_METHODS_TRACING_LIB marshal-methods-tracing) string(TOLOWER ${CMAKE_BUILD_TYPE} XAMARIN_MONO_ANDROID_SUFFIX) set(XAMARIN_MONO_ANDROID_LIB "mono-android${CHECKED_BUILD_INFIX}.${XAMARIN_MONO_ANDROID_SUFFIX}") @@ -539,6 +540,10 @@ if(UNIX) endif() if(ANDROID AND ENABLE_NET) + set(MARSHAL_METHODS_TRACING_SOURCES + ${SOURCES_DIR}/marshal-methods-tracing.cc + ) + list(APPEND XAMARIN_MONODROID_SOURCES ${SOURCES_DIR}/monovm-properties.cc ${SOURCES_DIR}/pinvoke-override-api.cc @@ -677,6 +682,13 @@ add_library( ) if(ANDROID) + if(ENABLE_NET AND NOT DEBUG_BUILD) + add_library( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + STATIC ${MARSHAL_METHODS_TRACING_SOURCES} + ) + endif() + # Ugly, but this is the only way to change LZ4 symbols visibility without modifying lz4.h set(LZ4_VISIBILITY_OPTS "-DLZ4LIB_VISIBILITY=__attribute__ ((visibility (\"hidden\")))") endif() diff --git a/src/monodroid/jni/logger.cc b/src/monodroid/jni/logger.cc index 57f1ea79957..940769f2622 100644 --- a/src/monodroid/jni/logger.cc +++ b/src/monodroid/jni/logger.cc @@ -28,18 +28,18 @@ using namespace xamarin::android::internal; // Must match the same ordering as LogCategories static const char* log_names[] = { - "*none*", - "monodroid", - "monodroid-assembly", - "monodroid-debug", - "monodroid-gc", - "monodroid-gref", - "monodroid-lref", - "monodroid-timing", - "monodroid-bundle", - "monodroid-network", - "monodroid-netlink", - "*error*", + SharedConstants::LOG_CATEGORY_NAME_NONE, + SharedConstants::LOG_CATEGORY_NAME_MONODROID, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_DEBUG, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_GC, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_GREF, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_LREF, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_TIMING, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_BUNDLE, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_NETWORK, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_NETLINK, + SharedConstants::LOG_CATEGORY_NAME_ERROR, }; #if defined(__i386__) && defined(__GNUC__) diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc new file mode 100644 index 00000000000..d87783518b9 --- /dev/null +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -0,0 +1,41 @@ +#include + +#define HIDE_EXPORTS +#include + +#include "marshal-methods-tracing.hh" +#include "marshal-methods-utilities.hh" + +using namespace xamarin::android::internal; + +constexpr int PRIORITY = ANDROID_LOG_INFO; +constexpr char LEAD[] = "MM: "; + +// TODO: implement backtrace() for native trace (available since Android API33+, so we're unlikely to be able to use it soon) +// TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) + +void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char *message) +{ + +} + +static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* which) +{ + uint64_t method_id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); + const char *method_name = MarshalMethodsUtilities::get_method_name (method_id); + const char *class_name = MarshalMethodsUtilities::get_class_name (class_index); + + __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "%s%s: %s in class %s", LEAD, which, method_name, class_name); +} + +void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token) +{ + constexpr char ENTER[] = "ENTER"; + _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, ENTER); +} + +void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token) +{ + constexpr char LEAVE[] = "LEAVE"; + _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, LEAVE); +} diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh new file mode 100644 index 00000000000..379ad16ac0d --- /dev/null +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -0,0 +1,10 @@ +#if !defined (__MARSHAL_METHODS_TRACING_HH) +#define __MARSHAL_METHODS_TRACING_HH + +#include "monodroid-glue-internal.hh" + +extern "C" void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char *message); +extern "C" void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token); +extern "C" void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token); + +#endif // ndef __MARSHAL_METHODS_TRACING_HH diff --git a/src/monodroid/jni/marshal-methods-utilities.hh b/src/monodroid/jni/marshal-methods-utilities.hh new file mode 100644 index 00000000000..e141178c75b --- /dev/null +++ b/src/monodroid/jni/marshal-methods-utilities.hh @@ -0,0 +1,45 @@ +#if !defined (__MARSHAL_METHODS_UTILITIES_HH) +#define __MARSHAL_METHODS_UTILITIES_HH + +#include + +#include "xamarin-app.hh" + +#if defined (ANDROID) && defined (RELEASE) +namespace xamarin::android::internal +{ + class MarshalMethodsUtilities + { + static constexpr char Unknown[] = "Unknown"; + + public: + static const char* get_method_name (uint64_t id) noexcept + { + size_t i = 0; + while (mm_method_names[i].id != 0) { + if (mm_method_names[i].id == id) { + return mm_method_names[i].name; + } + i++; + } + + return Unknown; + } + + static const char* get_class_name (uint32_t class_index) noexcept + { + if (class_index >= marshal_methods_number_of_classes) { + return Unknown; + } + + return mm_class_names[class_index]; + } + + static uint64_t get_method_id (uint32_t mono_image_index, uint32_t method_token) noexcept + { + return (static_cast(mono_image_index) << 32) | method_token; + } + }; +} +#endif // def ANDROID && def RELEASE +#endif // ndef __MARSHAL_METHODS_UTILITIES_HH diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 85128eb1915..0865e2742ae 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -351,7 +351,6 @@ namespace xamarin::android::internal #if defined (RELEASE) && defined (ANDROID) static const char* get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept; - static const char* get_class_name (uint32_t class_index) noexcept; template static void get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept; diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index a88dbcdf569..04b779a18a8 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -78,6 +78,19 @@ namespace xamarin::android::internal // Documented in NDK's comments static constexpr size_t MAX_LOGCAT_MESSAGE_LENGTH = 1023; + + static constexpr char LOG_CATEGORY_NAME_NONE[] = "*none*"; + static constexpr char LOG_CATEGORY_NAME_MONODROID[] = "monodroid"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_ASSEMBLY[] ="monodroid-assembly"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_DEBUG[] = "monodroid-debug"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_GC[] = "monodroid-gc"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_GREF[] = "monodroid-gref"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_LREF[] = "monodroid-lref"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_TIMING[] = "monodroid-timing"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_BUNDLE[] = "monodroid-bundle"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_NETWORK[] = "monodroid-network"; + static constexpr char LOG_CATEGORY_NAME_MONODROID_NETLINK[] = "monodroid-netlink"; + static constexpr char LOG_CATEGORY_NAME_ERROR[] = "*error*"; }; } #endif // __SHARED_CONSTANTS_HH diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index c1c401880e9..1539af96acf 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -3,36 +3,17 @@ #include "monodroid-glue-internal.hh" #include "mono-image-loader.hh" +#include "marshal-methods-utilities.hh" using namespace xamarin::android::internal; -static constexpr char Unknown[] = "Unknown"; - const char* MonodroidRuntime::get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept { - uint64_t id = (static_cast(mono_image_index) << 32) | method_token; + uint64_t id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); log_debug (LOG_ASSEMBLY, "MM: looking for name of method with id 0x%llx, in mono image at index %u", id, mono_image_index); - size_t i = 0; - while (mm_method_names[i].id != 0) { - if (mm_method_names[i].id == id) { - return mm_method_names[i].name; - } - i++; - } - - return Unknown; -} - -const char* -MonodroidRuntime::get_class_name (uint32_t class_index) noexcept -{ - if (class_index >= marshal_methods_number_of_classes) { - return Unknown; - } - - return mm_class_names[class_index]; + return MarshalMethodsUtilities::get_method_name (id); } template @@ -43,7 +24,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas LOG_ASSEMBLY, "MM: Trying to look up pointer to method '%s' (token 0x%x) in class '%s' (index %u)", get_method_name (mono_image_index, method_token), method_token, - get_class_name (class_index), class_index + MarshalMethodsUtilities::get_class_name (class_index), class_index ); if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { @@ -83,7 +64,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas LOG_DEFAULT, "Failed to obtain function pointer to method '%s' in class '%s'", get_method_name (mono_image_index, method_token), - get_class_name (class_index) + MarshalMethodsUtilities::get_class_name (class_index) ); log_fatal ( From fd06651c1fc1c5241dd94d73ba0ca94191614983 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 29 Mar 2023 21:58:13 +0200 Subject: [PATCH 03/60] Native backtrace implementation Better string handling in LLVM IR generator --- .../Tasks/LinkApplicationSharedLibraries.cs | 41 ++++- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 3 + .../LlvmIrGenerator/LlvmIrGenerator.cs | 154 ++++-------------- .../LlvmIrGenerator/LlvmIrStringManager.cs | 115 +++++++++++++ .../MarshalMethodsNativeAssemblyGenerator.cs | 6 +- .../Utilities/MonoAndroidHelper.cs | 40 +++++ src/monodroid/jni/marshal-methods-tracing.cc | 66 +++++++- src/monodroid/jni/marshal-methods-tracing.hh | 6 +- src/monodroid/jni/shared-constants.hh | 24 +++ 9 files changed, 312 insertions(+), 143 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 682407ad692..dc95fc1e299 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -16,6 +16,8 @@ namespace Xamarin.Android.Tasks { public class LinkApplicationSharedLibraries : AndroidAsyncTask { + const int NDK_API_LEVEL = 21; // TODO: don't hardcode the API level + public override string TaskPrefix => "LAS"; sealed class Config @@ -118,8 +120,30 @@ void RunLinker (Config config) IEnumerable GetLinkerConfigs () { NdkTools ndk = null; + string clangRuntimeDirTop = null; + if (EnableMarshalMethodTracing) { ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: false, log: Log); + + // Doesn't matter for which arch we run the compiler, they all share the same topmost runtime dir + string clangPath = ndk.GetToolPath (NdkToolKind.CompilerCPlusPlus, AndroidTargetArch.Arm64, NDK_API_LEVEL); + int ret = MonoAndroidHelper.RunProcess ( + clangPath, "-print-runtime-dir", + (object sender, DataReceivedEventArgs e) => { // stdout + if (clangRuntimeDirTop == null && !String.IsNullOrEmpty (e.Data)) { + clangRuntimeDirTop = e.Data; + } + }, + (object sender, DataReceivedEventArgs e) => { // stderr + if (!String.IsNullOrEmpty (e.Data)) { + Log.LogError (e.Data); + } + } + ); + + if (ret != 0) { + Log.LogError ($"Failed to obtain clang runtime path from {clangPath}"); + } } string runtimeNativeLibsDir = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, "..", "..", "..", "lib")); @@ -127,7 +151,7 @@ IEnumerable GetLinkerConfigs () ITaskItem[] dsos = ApplicationSharedLibraries; foreach (ITaskItem item in dsos) { string abi = item.GetMetadata ("abi"); - abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, ndk); + abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, ndk, clangRuntimeDirTop); } const string commonLinkerArgs = @@ -205,7 +229,7 @@ IEnumerable GetLinkerConfigs () } } - InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, NdkTools ndk) + InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, NdkTools ndk, string clangRuntimeDirTop) { List extraLibraries = null; @@ -214,11 +238,22 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem throw new ArgumentNullException (nameof (ndk)); } - string libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, MonoAndroidHelper.AbiToTargetArch (abi), 21); // TODO: don't hardcode the API level + AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); + string clangRuntimeAbi = MonoAndroidHelper.ArchToClangRuntimeAbi (targetArch); + string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); + string unwindLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, clangRuntimeAbi, "libunwind.a")); + string builtinsLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, $"libclang_rt.builtins-{clangLibraryAbi}-android.a")); + string libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, targetArch, NDK_API_LEVEL); + string cxxAbiLibPath = Path.GetFullPath (Path.Combine (libPath, "..", "libc++abi.a")); + extraLibraries = new List { Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libmarshal-methods-tracing.a"), $"-L \"{libPath}\"", + $"\"{builtinsLibPath}\"", + $"\"{cxxAbiLibPath}\"", + $"\"{unwindLibPath}\"", "-lc", + "-ldl", "-llog", // tracing uses android logger }; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 4850f6af2d1..41aeaafabd3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -418,6 +418,9 @@ public void EmitLabel (LlvmIrFunction function, string labelName) } else { throw new InvalidOperationException ($"Unexpected pointer type in argument {i}, '{argument.Type}'"); } + } else if (argument.Value is string str) { + StringSymbolInfo info = StringManager.Add (str); + WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true, skipPointerType: true); } else { Output.Write (argument.Value.ToString ()); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 2a35b407b67..a7897cc7503 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -52,18 +52,6 @@ public PackedStructureMember (StructureMemberInfo memberInfo, object? value, } } - public 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" }, @@ -165,11 +153,13 @@ public StringSymbolInfo (string symbolName, ulong size) public AndroidTargetArch TargetArch { get; } protected LlvmIrMetadataManager MetadataManager { get; } + protected LlvmIrStringManager StringManager { get; } protected LlvmIrGenerator (AndroidTargetArch arch, TextWriter output, string fileName) { Output = output; MetadataManager = new LlvmIrMetadataManager (); + StringManager = new LlvmIrStringManager (); TargetArch = arch; Is64Bit = arch == AndroidTargetArch.X86_64 || arch == AndroidTargetArch.Arm64; this.fileName = fileName; @@ -455,7 +445,7 @@ bool MaybeWriteStructureString (StructureInfo info, StructureMemberInfo return false; } - StringSymbolInfo stringSymbol = WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter); + StringSymbolInfo stringSymbol = StringManager.Add (str, groupName: info.Name, symbolSuffix: smi.Info.Name);//WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter); instance.AddPointerData (smi, stringSymbol.SymbolName, stringSymbol.Size); return true; @@ -622,12 +612,11 @@ public void WriteArray (IList values, string symbolName, string? initial WriteEOL (); WriteEOL (initialComment ?? symbolName); - ulong arrayStringCounter = 0; - var strings = new List (); + var strings = new List (); foreach (string s in values) { - StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}", s, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); - strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); + StringSymbolInfo symbol = StringManager.Add (s, groupName: symbolName); + strings.Add (symbol); } if (strings.Count > 0) { @@ -1137,12 +1126,12 @@ void WriteBufferToOutput (TextWriter? writer) } } - void WriteGetStringPointer (string? variableName, ulong size, bool indent = true, TextWriter? output = null) + void WriteGetStringPointer (string? variableName, ulong size, bool indent = true, TextWriter? output = null, bool detectBitness = false, bool skipPointerType = false) { - WriteGetBufferPointer (variableName, "i8*", size, indent, output); + WriteGetBufferPointer (variableName, "i8*", size, indent, output, detectBitness, skipPointerType); } - void WriteGetBufferPointer (string? variableName, string irType, ulong size, bool indent = true, TextWriter? output = null) + void WriteGetBufferPointer (string? variableName, string irType, ulong size, bool indent = true, TextWriter? output = null, bool detectBitness = false, bool skipPointerType = false) { output = EnsureOutput (output); if (indent) { @@ -1160,8 +1149,12 @@ void WriteGetBufferPointer (string? variableName, string irType, ulong size, boo irBaseType = irType; } + string indexType = detectBitness && Is64Bit ? "i64" : "i32"; // $"{irType} getelementptr inbounds ([{size} x {irBaseType}], [{size} x {irBaseType}]* @{variableName}, i32 0, i32 0)" - output.Write (irType); + if (!skipPointerType) { + output.Write (irType); + } + output.Write (" getelementptr inbounds (["); output.Write (size); output.Write (" x "); @@ -1172,7 +1165,11 @@ void WriteGetBufferPointer (string? variableName, string irType, ulong size, boo output.Write (irBaseType); output.Write ("]* @"); output.Write (variableName); - output.Write (", i32 0, i32 0)"); + output.Write (", "); + output.Write (indexType); + output.Write (" 0, "); + output.Write (indexType); + output.Write (" 0)"); } } @@ -1186,7 +1183,6 @@ public void WriteNameValueArray (string symbolName, IDictionary var strings = new List (); long i = 0; - ulong arrayStringCounter = 0; foreach (var kvp in arrayContents) { string name = kvp.Key; @@ -1205,8 +1201,8 @@ public void WriteNameValueArray (string symbolName, IDictionary void WriteArrayString (string str, string symbolSuffix) { - StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}_{symbolSuffix}", str, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); - strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); + StringSymbolInfo symbol = StringManager.Add (str, groupName: symbolName, symbolSuffix: symbolSuffix); + strings.Add (symbol); } } @@ -1279,28 +1275,6 @@ public void WriteVariable (string symbolName, T value, LlvmIrVariableOptions Output.WriteLine (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 /// @@ -1317,101 +1291,27 @@ public string WriteString (string symbolName, string value, LlvmIrVariableOption 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); - - // WriteLine $"[{stringSize} x i8] c{quotedString}, align {GetAggregateAlignment (1, stringSize)}" - Output.Write ('['); - Output.Write (stringSize); - Output.Write (" x i8] c"); - Output.Write (quotedString); - Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (1, stringSize)); - - if (!global) { + StringSymbolInfo info = StringManager.Add (value, groupName: symbolName); + stringSize = info.Size; + if (!options.IsGlobal) { return symbolName; } string indexType = Is64Bit ? "i64" : "i32"; WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); - - // WriteLine $"i8* getelementptr inbounds ([{stringSize} x i8], [{stringSize} x i8]* @{strSymbolName}, {indexType} 0, {indexType} 0), align {GetAggregateAlignment (PointerSize, stringSize)}" - Output.Write ("i8* getelementptr inbounds (["); - Output.Write (stringSize); - Output.Write (" x i8], ["); - Output.Write (stringSize); - Output.Write (" x i8]* @"); - Output.Write (strSymbolName); - Output.Write (", "); - Output.Write (indexType); - Output.Write (" 0, "); - Output.Write (indexType); - Output.Write (" 0), align "); + WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true); + Output.Write (", align "); Output.WriteLine (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 StringSymbolInfo WriteUniqueString (string potentialSymbolName, string value, ref ulong counter) - { - return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString); - } - - /// - /// Writes a string, creating a new symbol if the is unique or returns name of a previously created symbol with the same - /// string value. If a new symbol is written, its name is constructed by combining prefix () with value - /// of a string counter referenced by the parameter. Symbol options (writeability, visibility etc) are specified in the parameter. String size (in bytes) is returned in . - /// - public StringSymbolInfo WriteUniqueString (string potentialSymbolNamePrefix, string value, ref ulong counter, LlvmIrVariableOptions options) - { - if (value == null) { - return null; - } - - StringSymbolInfo info; - if (stringSymbolCache.TryGetValue (value, out info)) { - return info; - } - - string newSymbolName = $"{potentialSymbolNamePrefix}.{counter++}"; - WriteString (newSymbolName, value, options, out ulong stringSize); - info = new StringSymbolInfo (newSymbolName, stringSize); - stringSymbolCache.Add (value, info); - - return info; - } - public virtual void WriteFileTop () { WriteCommentLine ($"ModuleID = '{fileName}'"); @@ -1423,7 +1323,9 @@ public virtual void WriteFileTop () public virtual void WriteFileEnd () { Output.WriteLine (); + StringManager.Flush (this); + Output.WriteLine (); WriteAttributeSets (); foreach (LlvmIrMetadataItem metadata in MetadataManager.Items) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs new file mode 100644 index 00000000000..d7d4e7e6fc2 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Xamarin.Android.Tasks.LLVMIR; + +partial class LlvmIrGenerator +{ + public sealed class StringSymbolInfo + { + public readonly string SymbolName; + public readonly ulong Size; + public readonly string Value; + + public StringSymbolInfo (string symbolName, string value, ulong size) + { + SymbolName = symbolName; + Value = value; + Size = size; + } + } + + sealed class StringGroup + { + public ulong Count; + public readonly string? Comment; + public readonly List Strings = new List (); + + public StringGroup (string? comment = null) + { + Comment = comment; + Count = 0; + } + } + + protected class LlvmIrStringManager + { + Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); + List stringGroups = new List (); + + StringGroup defaultGroup; + + public LlvmIrStringManager () + { + defaultGroup = new StringGroup (); + stringGroupCache.Add (String.Empty, defaultGroup); + stringGroups.Add (defaultGroup); + } + + public StringSymbolInfo Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) + { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + StringSymbolInfo? info; + if (stringSymbolCache.TryGetValue (value, out info) && info != null) { + return info; + } + + StringGroup? group; + string groupPrefix; + if (String.IsNullOrEmpty (groupName) || String.Compare ("str", groupName, StringComparison.Ordinal) == 0) { + group = defaultGroup; + groupPrefix = "__str"; + } else if (!stringGroupCache.TryGetValue (groupName, out group) || group == null) { + group = new StringGroup (groupComment ?? groupName); + stringGroups.Add (group); + stringGroupCache[groupName] = group; + groupPrefix = $"__{groupName}"; + } else { + groupPrefix = $"__{groupName}"; + } + + string quotedString = QuoteString (value, out ulong stringSize); + string symbolName = $"{groupPrefix}.{group.Count++}"; + if (!String.IsNullOrEmpty (symbolSuffix)) { + symbolName = $"{symbolName}_{symbolSuffix}"; + } + + info = new StringSymbolInfo (symbolName, quotedString, stringSize); + group.Strings.Add (info); + stringSymbolCache.Add (value, info); + + return info; + } + + public void Flush (LlvmIrGenerator generator) + { + TextWriter output = generator.Output; + + generator.WriteEOL ("Strings"); + foreach (StringGroup group in stringGroups) { + if (group != defaultGroup) { + output.WriteLine (); + } + + if (!String.IsNullOrEmpty (group.Comment)) { + generator.WriteCommentLine (output, group.Comment); + } + + foreach (StringSymbolInfo info in group.Strings) { + generator.WriteGlobalSymbolStart (info.SymbolName, LlvmIrVariableOptions.LocalConstexprString); + output.Write ('['); + output.Write (info.Size); + output.Write (" x i8] c"); + output.Write (info.Value); + output.Write (", align "); + output.WriteLine (generator.GetAggregateAlignment (1, info.Size)); + } + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index ed4dca1df48..ea24dd75ce4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -670,7 +670,8 @@ void WriteInitTracing (LlvmIrGenerator generator) var mm_trace_func_enter_or_leave_params = new List { new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token") + new LlvmIrFunctionParameter (typeof(uint), "method_token"), + new LlvmIrFunctionParameter (typeof(string), "native_method_name"), }; var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( @@ -752,7 +753,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll trace_enter_leave_args = new List { new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()) + new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (typeof(string), method.NativeSymbolName), }; generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 030dc1862e4..9575c7533d0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -574,5 +574,45 @@ public static AndroidTargetArch AbiToTargetArch (string abi) throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); } } + + public static string ArchToClangRuntimeAbi (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm64: + return "aarch64"; + + case AndroidTargetArch.Arm: + return "arm"; + + case AndroidTargetArch.X86: + return "i386"; + + case AndroidTargetArch.X86_64: + return "x86_64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + } + } + + public static string ArchToClangLibraryAbi (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm64: + return "aarch64"; + + case AndroidTargetArch.Arm: + return "arm"; + + case AndroidTargetArch.X86: + return "i686"; + + case AndroidTargetArch.X86_64: + return "x86_64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + } + } } } diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index d87783518b9..b3846012da3 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -1,3 +1,7 @@ +#include +#include +#include + #include #define HIDE_EXPORTS @@ -11,31 +15,75 @@ using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; constexpr char LEAD[] = "MM: "; -// TODO: implement backtrace() for native trace (available since Android API33+, so we're unlikely to be able to use it soon) // TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) +// https://developer.android.com/reference/java/lang/Error?hl=en and use `getStackTrace` + +static _Unwind_Reason_Code backtrace_frame_callback (_Unwind_Context* context, [[maybe_unused]] void* arg) +{ + auto ptr = reinterpret_cast(_Unwind_GetIP (context)); + + Dl_info info {}; + const char *symbol_name = nullptr; + bool symbol_name_allocated = false; + + if (dladdr (ptr, &info) != 0) { + char *demangled_symbol_name; + int demangle_status; + + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler + demangled_symbol_name = abi::__cxa_demangle (info.dli_sname, nullptr, nullptr, &demangle_status); + symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; + symbol_name = symbol_name_allocated ? demangled_symbol_name : info.dli_sname; + } + + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + " %p %s %s (%p)", + ptr, + info.dli_fname == nullptr ? "[unknown file]" : info.dli_fname, + symbol_name == nullptr ? "[unknown symbol]" : symbol_name, + info.dli_saddr + ); + + if (symbol_name_allocated && symbol_name != nullptr) { + std::free (reinterpret_cast(const_cast(symbol_name))); + } + + return _URC_NO_REASON; +} + +static void print_native_backtrace () noexcept +{ + _Unwind_Backtrace (backtrace_frame_callback, nullptr); +} -void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char *message) +void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) { } -static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* which) +static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* which, const char* native_method_name, bool need_trace) { uint64_t method_id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); - const char *method_name = MarshalMethodsUtilities::get_method_name (method_id); + const char *managed_method_name = MarshalMethodsUtilities::get_method_name (method_id); const char *class_name = MarshalMethodsUtilities::get_class_name (class_index); - __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "%s%s: %s in class %s", LEAD, which, method_name, class_name); + __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "%s%s: %s (%s) in class %s", LEAD, which, native_method_name, managed_method_name, class_name); + if (need_trace) { + __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "Native stack trace:"); + print_native_backtrace (); + } } -void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token) +void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) { constexpr char ENTER[] = "ENTER"; - _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, ENTER); + _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */); } -void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token) +void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) { constexpr char LEAVE[] = "LEAVE"; - _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, LEAVE); + _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); } diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index 379ad16ac0d..898a54b82bd 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -3,8 +3,8 @@ #include "monodroid-glue-internal.hh" -extern "C" void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char *message); -extern "C" void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token); -extern "C" void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token); +extern "C" void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message); +extern "C" void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name); +extern "C" void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name); #endif // ndef __MARSHAL_METHODS_TRACING_HH diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 04b779a18a8..d73fb6cff58 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -91,6 +91,30 @@ namespace xamarin::android::internal static constexpr char LOG_CATEGORY_NAME_MONODROID_NETWORK[] = "monodroid-network"; static constexpr char LOG_CATEGORY_NAME_MONODROID_NETLINK[] = "monodroid-netlink"; static constexpr char LOG_CATEGORY_NAME_ERROR[] = "*error*"; + +#if defined (__aarch64__) + static constexpr bool IsARM64 = true; +#else + static constexpr bool IsARM64 = false; +#endif + +#if defined (__arm__) + static constexpr bool IsARM32 = true; +#else + static constexpr bool IsARM32 = false; +#endif + +#if defined (__i386__) + static constexpr bool IsX86 = true; +#else + static constexpr bool IsX86 = false; +#endif + +#if defined (__x86_64__) + static constexpr bool IsX64 = true; +#else + static constexpr bool IsX64 = false; +#endif }; } #endif // __SHARED_CONSTANTS_HH From 0564634bd154401f2dbd2108e3ec1ea2a4c7f654 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 31 Mar 2023 00:52:39 +0200 Subject: [PATCH 04/60] [WIP] Stack unwinding (doesn't build yet) Use standalone libunwind instead of the LLVM's Instead of using autoconf on *nix and cmake on Windows, provide our own CMakeLists.txt file which works everywhere we need. --- .gitmodules | 4 + Configuration.props | 2 + external/libunwind | 1 + src/libunwind-xamarin/CMakeLists.txt | 94 +++++++++++++++++++ src/libunwind-xamarin/config.h.in | 21 +++++ .../libunwind-xamarin.csproj | 15 +++ .../libunwind-xamarin.targets | 53 +++++++++++ src/monodroid/jni/marshal-methods-tracing.cc | 28 +++++- 8 files changed, 217 insertions(+), 1 deletion(-) create mode 160000 external/libunwind create mode 100644 src/libunwind-xamarin/CMakeLists.txt create mode 100644 src/libunwind-xamarin/config.h.in create mode 100644 src/libunwind-xamarin/libunwind-xamarin.csproj create mode 100644 src/libunwind-xamarin/libunwind-xamarin.targets diff --git a/.gitmodules b/.gitmodules index f8838a54d10..689b44a0cfe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,3 +42,7 @@ path = external/xamarin-android-tools url = https://github.com/xamarin/xamarin-android-tools branch = main +[submodule "external/libunwind"] + path = external/libunwind + url = https://github.com/libunwind/libunwind.git + branch = v1.7-stable diff --git a/Configuration.props b/Configuration.props index b817667364a..7e23cd38513 100644 --- a/Configuration.props +++ b/Configuration.props @@ -121,6 +121,7 @@ $(MingwZlibRootDirectory)\x86_64-w64-mingw32 libz.a $(MSBuildThisFileDirectory)external\sqlite + $(MSBuildThisFileDirectory)external\libunwind $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)src-ThirdParty\ armeabi-v7a;x86 @@ -175,6 +176,7 @@ $([System.IO.Path]::GetFullPath ('$(MonoSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(LinkerSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(SqliteSourceDirectory)')) + $([System.IO.Path]::GetFullPath ('$(LibUnwindSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(OpenTKSourceDirectory)')) diff --git a/external/libunwind b/external/libunwind new file mode 160000 index 00000000000..74ab1eb268e --- /dev/null +++ b/external/libunwind @@ -0,0 +1 @@ +Subproject commit 74ab1eb268eb6d8cde5db3274c12560a1cd2c73c diff --git a/src/libunwind-xamarin/CMakeLists.txt b/src/libunwind-xamarin/CMakeLists.txt new file mode 100644 index 00000000000..3238e81a01c --- /dev/null +++ b/src/libunwind-xamarin/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.18.1) + +# +# MUST be included before project()! +# +include("../../build-tools/cmake/xa_common.cmake") + +project(libunwind-xamarin C) + +set(CMAKE_C_STANDARD 11) + +include(CheckCSourceCompiles) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include("../../build-tools/cmake/xa_macros.cmake") + +if(NOT DEFINED LIBUNWIND_LIBRARY_NAME) + message(FATAL_ERROR "Please set the LIBUNWIND_LIBRARY_NAME variable on command line (-DLIBUNWIND_LIBRARY_NAME=base_library_name)") +endif() + +if(NOT DEFINED LIBUNWIND_SOURCE_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_SOURCE_DIR variable on command line (-DLIBUNWIND_SOURCE_DIR=source_dir_path)") +endif() + +if(NOT DEFINED LIBUNWIND_OUTPUT_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_OUTPUT_DIR variable on command line (-DLIBUNWIND_OUTPUT_DIR=output_dir_path)") +endif() + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) + +if(CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) + set(TARGET_AARCH64 TRUE) + set(arch aarch64) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) + set(TARGET_ARM TRUE) + set(arch arm) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) + set(TARGET_AMD64 TRUE) + set(arch x86_64) +elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86) + set(TARGET_X86 TRUE) + set(arch x86) +else() +endif() + +set(DSO_SYMBOL_VISIBILITY "hidden") +xa_common_prepare() + +if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND LOCAL_COMPILER_ARGS -g -fno-omit-frame-pointer) +else() + list(APPEND LOCAL_COMPILER_ARGS -s -fomit-frame-pointer) + add_compile_definitions(NDEBUG) +endif() + +xa_check_c_flags(XA_C_FLAGS "${LOCAL_COMPILER_ARGS}") +xa_check_c_linker_flags(XA_C_LINKER_FLAGS "${LOCAL_COMPILER_ARGS}") + +add_compile_options(${XA_C_FLAGS}) +add_link_options(${XA_C_LINKER_FLAGS}) + +add_compile_definitions(HAVE_CONFIG_H) +add_compile_definitions(_GNU_SOURCE) + +# Detect include files +set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) + +check_include_files(asm/vsyscall.h HAVE_ASM_VSYSCALL_H) +check_include_files(byteswap.h HAVE_BYTESWAP_H) +check_include_files(elf.h HAVE_ELF_H) +check_include_files(endian.h HAVE_ENDIAN_H) +check_include_files(link.h HAVE_LINK_H) +check_include_files(sys/endian.h HAVE_SYS_ENDIAN_H) +check_include_files(sys/link.h HAVE_SYS_LINK_H) +check_include_files(sys/param.h HAVE_SYS_PARAM_H) +check_include_files(sys/syscal.h HAVE_SYS_SYSCALL_H) + +# Detect functions +check_symbol_exists(mincore "sys/mman.h" HAVE_MINCORE) +check_symbol_exists(pipe2 "fcntl.h;unistd.h" HAVE_PIPE2) + +# TODO: consider enabling zlib + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/config.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind-common.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind-common.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind.h) +configure_file(${LIBUNWIND_SOURCE_DIR}/include/tdep/libunwind_i.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/tdep/libunwind_i.h) + +add_library(${LIBUNWIND_LIBRARY_NAME} + STATIC + ${LIBUNWIND_XAMARIN_SOURCES} +) diff --git a/src/libunwind-xamarin/config.h.in b/src/libunwind-xamarin/config.h.in new file mode 100644 index 00000000000..1935dd54003 --- /dev/null +++ b/src/libunwind-xamarin/config.h.in @@ -0,0 +1,21 @@ +#if !defined (__LIBUNWIND_CONFIG_H) +#define (__LIBUNWIND_CONFIG_H) + +#cmakedefine01 HAVE_ELF_H +#cmakedefine01 HAVE_ENDIAN_H +#cmakedefine01 HAVE_ASM_VSYSCALL_H +#cmakedefine01 HAVE_BYTESWAP_H +#cmakedefine01 HAVE_ELF_H +#cmakedefine01 HAVE_ENDIAN_H +#cmakedefine01 HAVE_LINK_H +#cmakedefine01 HAVE_SYS_ENDIAN_H +#cmakedefine01 HAVE_SYS_LINK_H +#cmakedefine01 HAVE_SYS_PARAM_H +#cmakedefine01 HAVE_SYS_SYSCALL_H +#cmakedefine01 HAVE_MINCORE +#cmakedefine01 HAVE_PIPE2 + +#define PACKAGE_STRING "@PACKAGE_STRING@" +#define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" + +#endif // ndef __LIBUNWIND_CONFIG_H diff --git a/src/libunwind-xamarin/libunwind-xamarin.csproj b/src/libunwind-xamarin/libunwind-xamarin.csproj new file mode 100644 index 00000000000..b7aba4a3cfb --- /dev/null +++ b/src/libunwind-xamarin/libunwind-xamarin.csproj @@ -0,0 +1,15 @@ + + + Exe + netstandard2.0 + False + + + + + + $(MicrosoftAndroidSdkOutDir)lib + + + + diff --git a/src/libunwind-xamarin/libunwind-xamarin.targets b/src/libunwind-xamarin/libunwind-xamarin.targets new file mode 100644 index 00000000000..9964c62ca93 --- /dev/null +++ b/src/libunwind-xamarin/libunwind-xamarin.targets @@ -0,0 +1,53 @@ + + + + + + + <_LibUnwindBaseLibName>unwind_xamarin + <_LibUnwindLibName>lib$(_LibUnwindBaseLibName).a + + + + + + + + <_ConfigureLibUnwindCommands + Include="@(AndroidSupportedTargetJitAbi)"> + $(CmakePath) + $(_CmakeAndroidFlags) -DANDROID_NATIVE_API_LEVEL=%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_PLATFORM=android-%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_ABI=%(AndroidSupportedTargetJitAbi.Identity) -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.Identity)" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.Identity)" -DCMAKE_BUILD_TYPE=$(Configuration) -DLIBUNWIND_SOURCE_DIR="$(LibUnwindSourceFullPath)" -DLIBUNWIND_LIBRARY_NAME="$(_LibUnwindBaseLibName)" -DLIBUNWIND_OUTPUT_DIR="@(AndroidSupportedTargetJitAbi->'$(OutputPath)\%(Identity)')" $(MSBuildThisFileDirectory) + $(IntermediateOutputPath)%(AndroidSupportedTargetJitAbi.Identity)-$(Configuration) + + + + + + + + + + + <_BuildLibUnwindCommands + Include="@(AndroidSupportedTargetJitAbi)"> + $(NinjaPath) + -v + $(IntermediateOutputPath)%(AndroidSupportedTargetJitAbi.Identity)-$(Configuration) + + + + + + + + + + + diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index b3846012da3..ab615651751 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -10,6 +11,19 @@ #include "marshal-methods-tracing.hh" #include "marshal-methods-utilities.hh" +struct unw_context_t { + uint64_t data[_LIBUNWIND_CONTEXT_SIZE]; +}; +typedef struct unw_context_t unw_context_t; + +struct unw_cursor_t { + uint64_t data[_LIBUNWIND_CURSOR_SIZE]; +} LIBUNWIND_CURSOR_ALIGNMENT_ATTR; +typedef struct unw_cursor_t unw_cursor_t; + +extern int unw_getcontext(unw_context_t *); +extern int unw_init_local(unw_cursor_t *, unw_context_t *); + using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; @@ -20,8 +34,19 @@ constexpr char LEAD[] = "MM: "; static _Unwind_Reason_Code backtrace_frame_callback (_Unwind_Context* context, [[maybe_unused]] void* arg) { - auto ptr = reinterpret_cast(_Unwind_GetIP (context)); + int ip_before_instruction = 0; + uintptr_t ip = _Unwind_GetIPInfo (context, &ip_before_instruction); + + if (ip_before_instruction == 0) { + if (ip == 0) { + // It's as if 0 - 1, but without tripping up static analyzers claiming an underflow + ip = std::numeric_limits::max(); + } else { + ip -= 1; + } + } + auto ptr = reinterpret_cast(ip); Dl_info info {}; const char *symbol_name = nullptr; bool symbol_name_allocated = false; @@ -55,6 +80,7 @@ static _Unwind_Reason_Code backtrace_frame_callback (_Unwind_Context* context, [ static void print_native_backtrace () noexcept { + _Unwind_Backtrace (backtrace_frame_callback, nullptr); } From 37323788294af0c1efdc4fa54f2c54cbbd1ed1d0 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 31 Mar 2023 22:34:46 +0200 Subject: [PATCH 05/60] Build and use our own libunwind Building application with tracing currently fails because of __cxa_demangle missing symbols (it requires the built-in clang libunwind which we cannot use) - we will need our own demangler. Next week. --- Configuration.props | 2 + Xamarin.Android.sln | 2 + .../GeneratedMonodroidCmakeFiles.cs | 8 + .../xaprepare/Application/KnownProperties.cs | 2 + .../Application/Properties.Defaults.cs.in | 2 + .../xaprepare/ConfigAndData/CmakeBuilds.cs | 8 +- .../xaprepare/xaprepare/xaprepare.targets | 1 + .../Tasks/LinkApplicationSharedLibraries.cs | 5 +- src/libunwind-xamarin/CMakeLists.txt | 300 +++++++++++++++++- src/libunwind-xamarin/config.h.in | 2 +- .../libunwind-xamarin.targets | 5 +- src/monodroid/CMakeLists.txt | 17 + src/monodroid/jni/marshal-methods-tracing.cc | 110 +++---- src/monodroid/monodroid.csproj | 5 +- 14 files changed, 388 insertions(+), 81 deletions(-) diff --git a/Configuration.props b/Configuration.props index 7e23cd38513..46e5db22fc5 100644 --- a/Configuration.props +++ b/Configuration.props @@ -122,6 +122,7 @@ libz.a $(MSBuildThisFileDirectory)external\sqlite $(MSBuildThisFileDirectory)external\libunwind + $(BootstrapOutputDirectory)\libunwind $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)src-ThirdParty\ armeabi-v7a;x86 @@ -177,6 +178,7 @@ $([System.IO.Path]::GetFullPath ('$(LinkerSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(SqliteSourceDirectory)')) $([System.IO.Path]::GetFullPath ('$(LibUnwindSourceDirectory)')) + $([System.IO.Path]::GetFullPath ('$(LibUnwindGeneratedHeadersDirectory)')) $([System.IO.Path]::GetFullPath ('$(OpenTKSourceDirectory)')) diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 480b25a4ca3..1558c8215ce 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Diagnost EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Cecil", "external\Java.Interop\src\Java.Interop.Tools.Cecil\Java.Interop.Tools.Cecil.csproj", "{D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libunwind", "src\libunwind-xamarin\libunwind-xamarin.csproj", "{F8E4961B-C427-47F9-92D6-0BEB5B76B3D7}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "monodroid", "src\monodroid\monodroid.csproj", "{53EE4C57-1C03-405A-8243-8DA539546C88}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.NUnitLite", "src\Xamarin.Android.NUnitLite\Xamarin.Android.NUnitLite.csproj", "{4D603AA3-3BFD-43C8-8050-0CD6C2601126}" diff --git a/build-tools/xaprepare/xaprepare/Application/GeneratedMonodroidCmakeFiles.cs b/build-tools/xaprepare/xaprepare/Application/GeneratedMonodroidCmakeFiles.cs index 746f3f50d89..250570c033e 100644 --- a/build-tools/xaprepare/xaprepare/Application/GeneratedMonodroidCmakeFiles.cs +++ b/build-tools/xaprepare/xaprepare/Application/GeneratedMonodroidCmakeFiles.cs @@ -63,6 +63,8 @@ void GenerateShellConfig (Context context, StreamWriter sw) { "@NinjaPath@", "${NINJA}" }, { "@OUTPUT_DIRECTORY@", "${__OUTPUT_DIR}" }, { "@SOURCE_DIRECTORY@", "${MONODROID_SOURCE_DIR}" }, + { "@LibUnwindSourceFullPath@", "${LIBUNWIND_SOURCE_FULL_PATH}" }, + { "@LibUnwindGeneratedHeadersFullPath@", "${LIBUNWIND_HEADERS_FULL_PATH}" }, }; var androidRuntimeReplacements = new Dictionary (StringComparer.Ordinal) { @@ -98,6 +100,8 @@ void GenerateShellConfig (Context context, StreamWriter sw) sw.WriteLine ($"NINJA=\"{context.Properties.GetRequiredValue(KnownProperties.NinjaPath)}\""); sw.WriteLine ($"XA_BUILD_CONFIGURATION={context.Configuration}"); sw.WriteLine ($"XA_INSTALL_DIR=\"{Configurables.Paths.InstallMSBuildDir}/lib\""); + sw.WriteLine ($"LIBUNWIND_SOURCE_FULL_PATH=\"{context.Properties.GetRequiredValue(KnownProperties.LibUnwindSourceFullPath)}\""); + sw.WriteLine ($"LIBUNWIND_HEADERS_FULL_PATH=\"{context.Properties.GetRequiredValue(KnownProperties.LibUnwindGeneratedHeadersFullPath)}\""); sw.WriteLine (); string indent = "\t"; @@ -329,6 +333,8 @@ void GenerateMonodroidTargets (Context context, StreamWriter sw) { "@BUILD_TYPE@", "" }, { "@CONFIGURATION@", "" }, { "@SOURCE_DIRECTORY@", $"$(MSBuildThisFileDirectory){sourceDir}" }, + { "@LibUnwindSourceFullPath@", "$(LibUnwindSourceFullPath)" }, + { "@LibUnwindGeneratedHeadersFullPath@", "$(LibUnwindGeneratedHeadersFullPath)" }, }; var androidRuntimeReplacements = new Dictionary (StringComparer.Ordinal) { @@ -445,6 +451,8 @@ void GenerateMSBuildProps (Context context, StreamWriter sw) { "@AndroidNdkDirectory@", "$(AndroidNdkDirectory)" }, { "@MonoSourceFullPath@", "$(MonoSourceFullPath)" }, { "@AndroidToolchainPath@", GetRelativeToolchainDefinitionPath () }, + { "@LibUnwindSourceFullPath@", "$(LibUnwindSourceFullPath)" }, + { "@LibUnwindGeneratedHeadersFullPath@", "$(LibUnwindGeneratedHeadersFullPath)" }, }; var MSBuildMingwReplacements = new Dictionary (StringComparer.Ordinal) { diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index 50768d4a6a4..64b617ea16b 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -39,6 +39,8 @@ static class KnownProperties public const string JavaInteropFullPath = "JavaInteropFullPath"; public const string JavaSdkDirectory = "JavaSdkDirectory"; public const string JdkIncludePath = "JdkIncludePath"; + public const string LibUnwindGeneratedHeadersFullPath = nameof (LibUnwindGeneratedHeadersFullPath); + public const string LibUnwindSourceFullPath = nameof (LibUnwindSourceFullPath); public const string LibZipSourceFullPath = "LibZipSourceFullPath"; public const string ManagedRuntime = "ManagedRuntime"; public const string MicrosoftAndroidSdkOutDir = "MicrosoftAndroidSdkOutDir"; diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index 486b859d669..ee63221d0f5 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -43,6 +43,8 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.JavaInteropFullPath, StripQuotes (@"@JavaInteropFullPath@")); properties.Add (KnownProperties.JavaSdkDirectory, StripQuotes (@"@JavaSdkDirectory@")); properties.Add (KnownProperties.JdkIncludePath, StripQuotes (@"@JdkIncludePath@")); + properties.Add (KnownProperties.LibUnwindGeneratedHeadersFullPath, StripQuotes (@"@LibUnwindGeneratedHeadersFullPath@")); + properties.Add (KnownProperties.LibUnwindSourceFullPath, StripQuotes (@"@LibUnwindSourceFullPath")); properties.Add (KnownProperties.LibZipSourceFullPath, StripQuotes (@"@LibZipSourceFullPath@")); properties.Add (KnownProperties.ManagedRuntime, StripQuotes (@"@ManagedRuntime@")); properties.Add (KnownProperties.MicrosoftAndroidSdkOutDir, StripQuotes (@"@MicrosoftAndroidSdkOutDir@")); diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/CmakeBuilds.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/CmakeBuilds.cs index 6763232c0e8..034229728e8 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/CmakeBuilds.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/CmakeBuilds.cs @@ -35,7 +35,9 @@ public sealed class RuntimeCommand "-GNinja", "-DCMAKE_MAKE_PROGRAM=\"@NinjaPath@\"", "-DXA_BUILD_CONFIGURATION=@XA_BUILD_CONFIGURATION@", - "-DXA_LIB_TOP_DIR=@XA_LIB_TOP_DIR@", + "-DXA_LIB_TOP_DIR=\"@XA_LIB_TOP_DIR@\"", + "-DLIBUNWIND_SOURCE_DIR=\"@LibUnwindSourceFullPath@\"", + "-DLIBUNWIND_HEADERS_DIR=\"@LibUnwindGeneratedHeadersFullPath@\"", }; public static readonly List AndroidFlags = new List { @@ -43,7 +45,9 @@ public sealed class RuntimeCommand "-DANDROID_CPP_FEATURES=\"no-rtti no-exceptions\"", "-DANDROID_TOOLCHAIN=clang", "-DCMAKE_TOOLCHAIN_FILE=\"@AndroidNdkDirectory@@AndroidToolchainPath@\"", - "-DANDROID_NDK=@AndroidNdkDirectory@" + "-DANDROID_NDK=@AndroidNdkDirectory@", + "-DLIBUNWIND_SOURCE_DIR=\"@LibUnwindSourceFullPath@\"", + "-DLIBUNWIND_HEADERS_DIR=\"@LibUnwindGeneratedHeadersFullPath@\"", }; public static readonly List MonodroidCommonDefines = new List { diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 37ecd5712bf..91ab11decb3 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -76,6 +76,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index dc95fc1e299..84aecdbe1ff 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -241,17 +241,16 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); string clangRuntimeAbi = MonoAndroidHelper.ArchToClangRuntimeAbi (targetArch); string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); - string unwindLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, clangRuntimeAbi, "libunwind.a")); string builtinsLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, $"libclang_rt.builtins-{clangLibraryAbi}-android.a")); string libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, targetArch, NDK_API_LEVEL); string cxxAbiLibPath = Path.GetFullPath (Path.Combine (libPath, "..", "libc++abi.a")); extraLibraries = new List { Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libmarshal-methods-tracing.a"), + Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libunwind_xamarin.a"), $"-L \"{libPath}\"", $"\"{builtinsLibPath}\"", - $"\"{cxxAbiLibPath}\"", - $"\"{unwindLibPath}\"", + $"\"{cxxAbiLibPath}\"", "-lc", "-ldl", "-llog", // tracing uses android logger diff --git a/src/libunwind-xamarin/CMakeLists.txt b/src/libunwind-xamarin/CMakeLists.txt index 3238e81a01c..74c8ae666f7 100644 --- a/src/libunwind-xamarin/CMakeLists.txt +++ b/src/libunwind-xamarin/CMakeLists.txt @@ -5,15 +5,6 @@ cmake_minimum_required(VERSION 3.18.1) # include("../../build-tools/cmake/xa_common.cmake") -project(libunwind-xamarin C) - -set(CMAKE_C_STANDARD 11) - -include(CheckCSourceCompiles) -include(CheckIncludeFiles) -include(CheckSymbolExists) -include("../../build-tools/cmake/xa_macros.cmake") - if(NOT DEFINED LIBUNWIND_LIBRARY_NAME) message(FATAL_ERROR "Please set the LIBUNWIND_LIBRARY_NAME variable on command line (-DLIBUNWIND_LIBRARY_NAME=base_library_name)") endif() @@ -26,6 +17,44 @@ if(NOT DEFINED LIBUNWIND_OUTPUT_DIR) message(FATAL_ERROR "Please set the LIBUNWIND_OUTPUT_DIR variable on command line (-DLIBUNWIND_OUTPUT_DIR=output_dir_path)") endif() +if(NOT DEFINED LIBUNWIND_HEADERS_OUTPUT_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_HEADERS_OUTPUT_DIR variable on command line (-DLIBUNWIND_HEADERS_OUTPUT_DIR=output_dir_path)") +endif() + +# +# Read libunwind version +# +set(CONFIGURE_AC "${LIBUNWIND_SOURCE_DIR}/configure.ac") +file(STRINGS "${CONFIGURE_AC}" UNWIND_MAJOR_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_major,[ \t]*(.*)\\)") +file(STRINGS "${CONFIGURE_AC}" UNWIND_MINOR_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_minor,[ \t]*(.*)\\)") +file(STRINGS "${CONFIGURE_AC}" UNWIND_EXTRA_VERSION_AC REGEX "^[ \t]*define[ \t]*\\([ \t]*pkg_extra,[ \t]*(.*)\\)") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_major,[ \t]*(.*)\\)" "\\1" UNWIND_MAJOR_VERSION "${UNWIND_MAJOR_VERSION_AC}") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_minor,[ \t]*(.*)\\)" "\\1" UNWIND_MINOR_VERSION "${UNWIND_MINOR_VERSION_AC}") +string(REGEX REPLACE "^[ \t]*define[ \t]*\\([ \t]*pkg_extra,[ \t]*(.*)\\)" "\\1" UNWIND_EXTRA_VERSION "${UNWIND_EXTRA_VERSION_AC}") + +set(PKG_MAJOR "${UNWIND_MAJOR_VERSION}") +set(PKG_MINOR "${UNWIND_MINOR_VERSION}") +set(PKG_EXTRA "${UNWIND_EXTRA_VERSION}") +set(PACKAGE_STRING "libunwind-xamarin") +set(PACKAGE_BUGREPORT "") + +message(STATUS "Major: ${PKG_MAJOR}; Minor: ${PKG_MINOR}; Extra: ${PKG_EXTRA}") + +project( + libunwind-xamarin +# VERSION "${PKG_MAJOR}.${PKG_MINOR}.${PKG_EXTRA}" + LANGUAGES C ASM +) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +include(CheckCSourceCompiles) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include("../../build-tools/cmake/xa_macros.cmake") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LIBUNWIND_OUTPUT_DIR}) @@ -43,6 +72,7 @@ elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86) set(TARGET_X86 TRUE) set(arch x86) else() + message(FATAL_ERROR "Unsupported Android ABI ${CMAKE_ANDROID_ARCH_ABI}") endif() set(DSO_SYMBOL_VISIBILITY "hidden") @@ -63,11 +93,13 @@ add_link_options(${XA_C_LINKER_FLAGS}) add_compile_definitions(HAVE_CONFIG_H) add_compile_definitions(_GNU_SOURCE) +add_compile_definitions(UNW_LOCAL_ONLY) # we don't need remote unwinding # Detect include files set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) -check_include_files(asm/vsyscall.h HAVE_ASM_VSYSCALL_H) +set(HAVE_ASM_VSYSCALL_H True) +#check_include_files(asm/vsyscall.h HAVE_ASM_VSYSCALL_H) check_include_files(byteswap.h HAVE_BYTESWAP_H) check_include_files(elf.h HAVE_ELF_H) check_include_files(endian.h HAVE_ENDIAN_H) @@ -75,7 +107,7 @@ check_include_files(link.h HAVE_LINK_H) check_include_files(sys/endian.h HAVE_SYS_ENDIAN_H) check_include_files(sys/link.h HAVE_SYS_LINK_H) check_include_files(sys/param.h HAVE_SYS_PARAM_H) -check_include_files(sys/syscal.h HAVE_SYS_SYSCALL_H) +check_include_files(sys/syscall.h HAVE_SYS_SYSCALL_H) # Detect functions check_symbol_exists(mincore "sys/mman.h" HAVE_MINCORE) @@ -88,6 +120,252 @@ configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind-common.h.in ${CMAKE_CUR configure_file(${LIBUNWIND_SOURCE_DIR}/include/libunwind.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind.h) configure_file(${LIBUNWIND_SOURCE_DIR}/include/tdep/libunwind_i.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/tdep/libunwind_i.h) +# The files are nearly identical, the only difference is name of the header included when remote-only libunwind is used +# We don't use it, but we still copy files to separate directories in order to avoid locking issues during parallel builds +set(HEADERS_DIR ${LIBUNWIND_HEADERS_OUTPUT_DIR}/${CMAKE_ANDROID_ARCH_ABI}) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind-common.h DESTINATION ${HEADERS_DIR}) +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/libunwind.h DESTINATION ${HEADERS_DIR}) +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/include/tdep/libunwind_i.h DESTINATION ${HEADERS_DIR}/tdep/) + +include_directories(${LIBUNWIND_SOURCE_DIR}/include/tdep) +include_directories(${LIBUNWIND_SOURCE_DIR}/include) +include_directories(${LIBUNWIND_SOURCE_DIR}/src) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include/tdep) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +if(TARGET_ARM) + # Ensure that the remote and local unwind code can reside in the same binary without name clashing + add_definitions("-Darm_search_unwind_table=UNW_OBJ(arm_search_unwind_table)") + # We compile code with -std=c99 and the asm keyword is not recognized as it is a gnu extension + #TODO: possibly not needed? add_definitions(-Dasm=__asm__) + # The arm sources include ex_tables.h from include/tdep-arm without going through a redirection + # in include/tdep like it works for similar files on other architectures. So we need to add + # the include/tdep-arm to include directories + include_directories(${LIBUNWIND_SOURCE_DIR}/include/tdep-arm) +elseif(TARGET_AARCH64) + # We compile code with -std=c99 and the asm keyword is not recognized as it is a gnu extension + #TODO: possibly not needed? add_definitions(-Dasm=__asm__) +endif() + +set(SOURCES_DIR ${LIBUNWIND_SOURCE_DIR}/src) + +set(LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/dwarf/Gexpr.c + ${SOURCES_DIR}/dwarf/Gfde.c + ${SOURCES_DIR}/dwarf/Gfind_proc_info-lsb.c + ${SOURCES_DIR}/dwarf/Gfind_unwind_table.c + ${SOURCES_DIR}/dwarf/Gparser.c + ${SOURCES_DIR}/dwarf/Gpe.c + ${SOURCES_DIR}/dwarf/Lexpr.c + ${SOURCES_DIR}/dwarf/Lfde.c + ${SOURCES_DIR}/dwarf/Lfind_proc_info-lsb.c + ${SOURCES_DIR}/dwarf/Lfind_unwind_table.c + ${SOURCES_DIR}/dwarf/Lparser.c + ${SOURCES_DIR}/dwarf/Lpe.c + ${SOURCES_DIR}/dwarf/global.c + ${SOURCES_DIR}/elfxx.c + ${SOURCES_DIR}/mi/Gdestroy_addr_space.c + ${SOURCES_DIR}/mi/Gdyn-extract.c + ${SOURCES_DIR}/mi/Gfind_dynamic_proc_info.c + ${SOURCES_DIR}/mi/Gget_fpreg.c + ${SOURCES_DIR}/mi/Gget_proc_info_by_ip.c + ${SOURCES_DIR}/mi/Gget_proc_info_in_range.c + ${SOURCES_DIR}/mi/Gget_proc_name.c + ${SOURCES_DIR}/mi/Gget_reg.c + ${SOURCES_DIR}/mi/Gput_dynamic_unwind_info.c + ${SOURCES_DIR}/mi/Gset_cache_size.c + ${SOURCES_DIR}/mi/Gset_caching_policy.c + ${SOURCES_DIR}/mi/Gset_fpreg.c + ${SOURCES_DIR}/mi/Gset_reg.c + ${SOURCES_DIR}/mi/Ldestroy_addr_space.c + ${SOURCES_DIR}/mi/Ldyn-extract.c + ${SOURCES_DIR}/mi/Lfind_dynamic_proc_info.c + ${SOURCES_DIR}/mi/Lget_accessors.c + ${SOURCES_DIR}/mi/Lget_fpreg.c + ${SOURCES_DIR}/mi/Lget_proc_info_by_ip.c + ${SOURCES_DIR}/mi/Lget_proc_info_in_range.c + ${SOURCES_DIR}/mi/Lget_proc_name.c + ${SOURCES_DIR}/mi/Lget_reg.c + ${SOURCES_DIR}/mi/Lput_dynamic_unwind_info.c + ${SOURCES_DIR}/mi/Lset_cache_size.c + ${SOURCES_DIR}/mi/Lset_caching_policy.c + ${SOURCES_DIR}/mi/Lset_fpreg.c + ${SOURCES_DIR}/mi/Lset_reg.c + ${SOURCES_DIR}/mi/backtrace.c + ${SOURCES_DIR}/mi/dyn-cancel.c + ${SOURCES_DIR}/mi/dyn-info-list.c + ${SOURCES_DIR}/mi/dyn-register.c + ${SOURCES_DIR}/mi/flush_cache.c + ${SOURCES_DIR}/mi/init.c + ${SOURCES_DIR}/mi/mempool.c + ${SOURCES_DIR}/mi/strerror.c + ${SOURCES_DIR}/os-linux.c +) + +if(TARGET_AMD64 OR TARGET_AARCH64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/elf64.c + ) +endif() + +if(TARGET_X86 OR TARGET_ARM) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/elf32.c + ) +endif() + +if(TARGET_X86) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/x86/Gapply_reg_state.c + ${SOURCES_DIR}/x86/Gcreate_addr_space.c + ${SOURCES_DIR}/x86/Gget_proc_info.c + ${SOURCES_DIR}/x86/Gget_save_loc.c + ${SOURCES_DIR}/x86/Gglobal.c + ${SOURCES_DIR}/x86/Ginit.c + ${SOURCES_DIR}/x86/Ginit_local.c + ${SOURCES_DIR}/x86/Ginit_remote.c + ${SOURCES_DIR}/x86/Gos-linux.c + ${SOURCES_DIR}/x86/Greg_states_iterate.c + ${SOURCES_DIR}/x86/Gregs.c + ${SOURCES_DIR}/x86/Gresume.c + ${SOURCES_DIR}/x86/Gstep.c + ${SOURCES_DIR}/x86/Lapply_reg_state.c + ${SOURCES_DIR}/x86/Lcreate_addr_space.c + ${SOURCES_DIR}/x86/Lget_proc_info.c + ${SOURCES_DIR}/x86/Lget_save_loc.c + ${SOURCES_DIR}/x86/Lglobal.c + ${SOURCES_DIR}/x86/Linit.c + ${SOURCES_DIR}/x86/Linit_local.c + ${SOURCES_DIR}/x86/Linit_remote.c + ${SOURCES_DIR}/x86/Los-linux.c + ${SOURCES_DIR}/x86/Lreg_states_iterate.c + ${SOURCES_DIR}/x86/Lregs.c + ${SOURCES_DIR}/x86/Lresume.c + ${SOURCES_DIR}/x86/Lstep.c + ${SOURCES_DIR}/x86/getcontext-linux.S + ${SOURCES_DIR}/x86/is_fpreg.c + ${SOURCES_DIR}/x86/regname.c + ) +endif(TARGET_X86) + +if(TARGET_AMD64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/x86_64/Gapply_reg_state.c + ${SOURCES_DIR}/x86_64/Gcreate_addr_space.c + ${SOURCES_DIR}/x86_64/Gget_proc_info.c + ${SOURCES_DIR}/x86_64/Gget_save_loc.c + ${SOURCES_DIR}/x86_64/Gglobal.c + ${SOURCES_DIR}/x86_64/Ginit.c + ${SOURCES_DIR}/x86_64/Ginit_local.c + ${SOURCES_DIR}/x86_64/Ginit_remote.c + ${SOURCES_DIR}/x86_64/Gos-linux.c + ${SOURCES_DIR}/x86_64/Greg_states_iterate.c + ${SOURCES_DIR}/x86_64/Gregs.c + ${SOURCES_DIR}/x86_64/Gresume.c + ${SOURCES_DIR}/x86_64/Gstash_frame.c + ${SOURCES_DIR}/x86_64/Gstep.c + ${SOURCES_DIR}/x86_64/Gtrace.c + ${SOURCES_DIR}/x86_64/Lapply_reg_state.c + ${SOURCES_DIR}/x86_64/Lcreate_addr_space.c + ${SOURCES_DIR}/x86_64/Lget_proc_info.c + ${SOURCES_DIR}/x86_64/Lget_save_loc.c + ${SOURCES_DIR}/x86_64/Lglobal.c + ${SOURCES_DIR}/x86_64/Linit.c + ${SOURCES_DIR}/x86_64/Linit_local.c + ${SOURCES_DIR}/x86_64/Linit_remote.c + ${SOURCES_DIR}/x86_64/Los-linux.c + ${SOURCES_DIR}/x86_64/Lreg_states_iterate.c + ${SOURCES_DIR}/x86_64/Lregs.c + ${SOURCES_DIR}/x86_64/Lresume.c + ${SOURCES_DIR}/x86_64/Lstash_frame.c + ${SOURCES_DIR}/x86_64/Lstep.c + ${SOURCES_DIR}/x86_64/Ltrace.c + ${SOURCES_DIR}/x86_64/getcontext.S + ${SOURCES_DIR}/x86_64/is_fpreg.c + ${SOURCES_DIR}/x86_64/regname.c + ${SOURCES_DIR}/x86_64/setcontext.S + ) +endif() + +if(TARGET_ARM) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/arm/Gapply_reg_state.c + ${SOURCES_DIR}/arm/Gcreate_addr_space.c + ${SOURCES_DIR}/arm/Gex_tables.c + ${SOURCES_DIR}/arm/Gget_proc_info.c + ${SOURCES_DIR}/arm/Gget_save_loc.c + ${SOURCES_DIR}/arm/Gglobal.c + ${SOURCES_DIR}/arm/Ginit.c + ${SOURCES_DIR}/arm/Ginit_local.c + ${SOURCES_DIR}/arm/Ginit_remote.c + ${SOURCES_DIR}/arm/Gos-linux.c + ${SOURCES_DIR}/arm/Greg_states_iterate.c + ${SOURCES_DIR}/arm/Gregs.c + ${SOURCES_DIR}/arm/Gresume.c + ${SOURCES_DIR}/arm/Gstash_frame.c + ${SOURCES_DIR}/arm/Gstep.c + ${SOURCES_DIR}/arm/Gtrace.c + ${SOURCES_DIR}/arm/Lapply_reg_state.c + ${SOURCES_DIR}/arm/Lcreate_addr_space.c + ${SOURCES_DIR}/arm/Lex_tables.c + ${SOURCES_DIR}/arm/Lget_proc_info.c + ${SOURCES_DIR}/arm/Lget_save_loc.c + ${SOURCES_DIR}/arm/Lglobal.c + ${SOURCES_DIR}/arm/Linit.c + ${SOURCES_DIR}/arm/Linit_local.c + ${SOURCES_DIR}/arm/Linit_remote.c + ${SOURCES_DIR}/arm/Los-linux.c + ${SOURCES_DIR}/arm/Lreg_states_iterate.c + ${SOURCES_DIR}/arm/Lregs.c + ${SOURCES_DIR}/arm/Lresume.c + ${SOURCES_DIR}/arm/Lstash_frame.c + ${SOURCES_DIR}/arm/Lstep.c + ${SOURCES_DIR}/arm/Ltrace.c + ${SOURCES_DIR}/arm/getcontext.S + ${SOURCES_DIR}/arm/is_fpreg.c + ${SOURCES_DIR}/arm/regname.c + ) +endif() + +if(TARGET_AARCH64) + list(APPEND LIBUNWIND_XAMARIN_SOURCES + ${SOURCES_DIR}/aarch64/Gapply_reg_state.c + ${SOURCES_DIR}/aarch64/Gcreate_addr_space.c + ${SOURCES_DIR}/aarch64/Gget_proc_info.c + ${SOURCES_DIR}/aarch64/Gget_save_loc.c + ${SOURCES_DIR}/aarch64/Gglobal.c + ${SOURCES_DIR}/aarch64/Ginit.c + ${SOURCES_DIR}/aarch64/Ginit_local.c + ${SOURCES_DIR}/aarch64/Ginit_remote.c + ${SOURCES_DIR}/aarch64/Gis_signal_frame.c + ${SOURCES_DIR}/aarch64/Greg_states_iterate.c + ${SOURCES_DIR}/aarch64/Gregs.c + ${SOURCES_DIR}/aarch64/Gresume.c + ${SOURCES_DIR}/aarch64/Gstash_frame.c + ${SOURCES_DIR}/aarch64/Gstep.c + ${SOURCES_DIR}/aarch64/Gtrace.c + ${SOURCES_DIR}/aarch64/Lapply_reg_state.c + ${SOURCES_DIR}/aarch64/Lcreate_addr_space.c + ${SOURCES_DIR}/aarch64/Lget_proc_info.c + ${SOURCES_DIR}/aarch64/Lget_save_loc.c + ${SOURCES_DIR}/aarch64/Lglobal.c + ${SOURCES_DIR}/aarch64/Linit.c + ${SOURCES_DIR}/aarch64/Linit_local.c + ${SOURCES_DIR}/aarch64/Linit_remote.c + ${SOURCES_DIR}/aarch64/Lis_signal_frame.c + ${SOURCES_DIR}/aarch64/Lreg_states_iterate.c + ${SOURCES_DIR}/aarch64/Lregs.c + ${SOURCES_DIR}/aarch64/Lresume.c + ${SOURCES_DIR}/aarch64/Lstash_frame.c + ${SOURCES_DIR}/aarch64/Lstep.c + ${SOURCES_DIR}/aarch64/Ltrace.c + ${SOURCES_DIR}/aarch64/getcontext.S + ${SOURCES_DIR}/aarch64/is_fpreg.c + ${SOURCES_DIR}/aarch64/regname.c + ) +endif() + add_library(${LIBUNWIND_LIBRARY_NAME} STATIC ${LIBUNWIND_XAMARIN_SOURCES} diff --git a/src/libunwind-xamarin/config.h.in b/src/libunwind-xamarin/config.h.in index 1935dd54003..52656bb9740 100644 --- a/src/libunwind-xamarin/config.h.in +++ b/src/libunwind-xamarin/config.h.in @@ -1,5 +1,5 @@ #if !defined (__LIBUNWIND_CONFIG_H) -#define (__LIBUNWIND_CONFIG_H) +#define __LIBUNWIND_CONFIG_H #cmakedefine01 HAVE_ELF_H #cmakedefine01 HAVE_ENDIAN_H diff --git a/src/libunwind-xamarin/libunwind-xamarin.targets b/src/libunwind-xamarin/libunwind-xamarin.targets index 9964c62ca93..e157862f527 100644 --- a/src/libunwind-xamarin/libunwind-xamarin.targets +++ b/src/libunwind-xamarin/libunwind-xamarin.targets @@ -6,6 +6,7 @@ <_LibUnwindBaseLibName>unwind_xamarin <_LibUnwindLibName>lib$(_LibUnwindBaseLibName).a + <_LibUnwindHeadersOutputDir>$(LibUnwindGeneratedHeadersFullPath) @@ -16,7 +17,7 @@ <_ConfigureLibUnwindCommands Include="@(AndroidSupportedTargetJitAbi)"> $(CmakePath) - $(_CmakeAndroidFlags) -DANDROID_NATIVE_API_LEVEL=%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_PLATFORM=android-%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_ABI=%(AndroidSupportedTargetJitAbi.Identity) -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.Identity)" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.Identity)" -DCMAKE_BUILD_TYPE=$(Configuration) -DLIBUNWIND_SOURCE_DIR="$(LibUnwindSourceFullPath)" -DLIBUNWIND_LIBRARY_NAME="$(_LibUnwindBaseLibName)" -DLIBUNWIND_OUTPUT_DIR="@(AndroidSupportedTargetJitAbi->'$(OutputPath)\%(Identity)')" $(MSBuildThisFileDirectory) + $(_CmakeAndroidFlags) -DANDROID_NATIVE_API_LEVEL=%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_PLATFORM=android-%(AndroidSupportedTargetJitAbi.ApiLevel) -DANDROID_ABI=%(AndroidSupportedTargetJitAbi.Identity) -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.AndroidRID)" -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$(OutputPath)%(AndroidSupportedTargetJitAbi.AndroidRID)" -DCMAKE_BUILD_TYPE=$(Configuration) -DLIBUNWIND_SOURCE_DIR="$(LibUnwindSourceFullPath)" -DLIBUNWIND_LIBRARY_NAME="$(_LibUnwindBaseLibName)" -DLIBUNWIND_OUTPUT_DIR="@(AndroidSupportedTargetJitAbi->'$(OutputPath)/%(AndroidRID)')" -DLIBUNWIND_HEADERS_OUTPUT_DIR="$(_LibUnwindHeadersOutputDir)" $(MSBuildThisFileDirectory) $(IntermediateOutputPath)%(AndroidSupportedTargetJitAbi.Identity)-$(Configuration) @@ -42,7 +43,7 @@ diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 5fe07f6ea23..ec3f5df55a0 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -101,6 +101,16 @@ if(NOT DEFINED XA_LIB_TOP_DIR) message(FATAL_ERROR "Please set the XA_LIB_TOP_DIR variable on command line (-DXA_LIB_TOP_DIR=path)") endif() +if(NOT DEFINED LIBUNWIND_SOURCE_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_SOURCE_DIR on command line (-DLIBUNWIND_SOURCE_DIR=path)") +endif() + +if(ANDROID AND ENABLE_NET) + if(NOT DEFINED LIBUNWIND_HEADERS_DIR) + message(FATAL_ERROR "Please set the LIBUNWIND_HEADERS_DIR on command line (-DLIBUNWIND_HEADERS_DIR=path)") + endif() +endif() + if(NOT ANDROID) if (NOT DEFINED JDK_INCLUDE) message(FATAL_ERROR "Please set the JDK_INCLUDE variable on command line (-DJDK_INCLUDE)") @@ -683,10 +693,17 @@ add_library( if(ANDROID) if(ENABLE_NET AND NOT DEBUG_BUILD) + # TODO: make it a shared library instead, will make it possible to not require NDK when building + # an app with tracing add_library( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} STATIC ${MARSHAL_METHODS_TRACING_SOURCES} ) + + target_include_directories( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + PRIVATE ${LIBUNWIND_SOURCE_DIR}/include ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ) endif() # Ugly, but this is the only way to change LZ4 symbols visibility without modifying lz4.h diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index ab615651751..35aa99e6797 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,25 +6,12 @@ #include -#define HIDE_EXPORTS -#include +#define UNW_LOCAL_ONLY +#include #include "marshal-methods-tracing.hh" #include "marshal-methods-utilities.hh" -struct unw_context_t { - uint64_t data[_LIBUNWIND_CONTEXT_SIZE]; -}; -typedef struct unw_context_t unw_context_t; - -struct unw_cursor_t { - uint64_t data[_LIBUNWIND_CURSOR_SIZE]; -} LIBUNWIND_CURSOR_ALIGNMENT_ATTR; -typedef struct unw_cursor_t unw_cursor_t; - -extern int unw_getcontext(unw_context_t *); -extern int unw_init_local(unw_cursor_t *, unw_context_t *); - using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; @@ -32,56 +20,58 @@ constexpr char LEAD[] = "MM: "; // TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) // https://developer.android.com/reference/java/lang/Error?hl=en and use `getStackTrace` -static _Unwind_Reason_Code backtrace_frame_callback (_Unwind_Context* context, [[maybe_unused]] void* arg) -{ - int ip_before_instruction = 0; - uintptr_t ip = _Unwind_GetIPInfo (context, &ip_before_instruction); +// https://www.nongnu.org/libunwind/docs.html - if (ip_before_instruction == 0) { - if (ip == 0) { - // It's as if 0 - 1, but without tripping up static analyzers claiming an underflow - ip = std::numeric_limits::max(); +static void print_native_backtrace () noexcept +{ + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t ip; + unw_word_t offp; + std::array name_buf; + const char *symbol_name; + Dl_info info; + bool valid_dlinfo; + bool symbol_name_allocated = false; + + unw_getcontext (&uc); + unw_init_local (&cursor, &uc); + + // TODO: improve presentation of collected data, most likely will need to use a dynamic buffer to make it + // less messy + while (unw_step (&cursor) > 0) { + unw_get_reg (&cursor, UNW_REG_IP, &ip); + + if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { + char *demangled_symbol_name; + int demangle_status; + + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler + demangled_symbol_name = abi::__cxa_demangle (name_buf.data (), nullptr, nullptr, &demangle_status); + symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; + symbol_name = symbol_name_allocated ? demangled_symbol_name : name_buf.data (); } else { - ip -= 1; + symbol_name_allocated = false; + symbol_name = nullptr; } - } - - auto ptr = reinterpret_cast(ip); - Dl_info info {}; - const char *symbol_name = nullptr; - bool symbol_name_allocated = false; - - if (dladdr (ptr, &info) != 0) { - char *demangled_symbol_name; - int demangle_status; - // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler - demangled_symbol_name = abi::__cxa_demangle (info.dli_sname, nullptr, nullptr, &demangle_status); - symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; - symbol_name = symbol_name_allocated ? demangled_symbol_name : info.dli_sname; - } - - __android_log_print ( - PRIORITY, - SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, - " %p %s %s (%p)", - ptr, - info.dli_fname == nullptr ? "[unknown file]" : info.dli_fname, - symbol_name == nullptr ? "[unknown symbol]" : symbol_name, - info.dli_saddr - ); - - if (symbol_name_allocated && symbol_name != nullptr) { - std::free (reinterpret_cast(const_cast(symbol_name))); + auto ptr = reinterpret_cast(ip); + valid_dlinfo = dladdr (ptr, &info) != 0; + + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + " %p %s %s (%p)", + ptr, + !valid_dlinfo || info.dli_fname == nullptr ? "[unknown file]" : info.dli_fname, + symbol_name == nullptr ? "[unknown symbol]" : symbol_name, + valid_dlinfo ? info.dli_saddr : 0 + ); + + if (symbol_name_allocated && symbol_name != nullptr) { + std::free (reinterpret_cast(const_cast(symbol_name))); + } } - - return _URC_NO_REASON; -} - -static void print_native_backtrace () noexcept -{ - - _Unwind_Backtrace (backtrace_frame_callback, nullptr); } void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) diff --git a/src/monodroid/monodroid.csproj b/src/monodroid/monodroid.csproj index c4dbc2d7b5c..100496fe8fc 100644 --- a/src/monodroid/monodroid.csproj +++ b/src/monodroid/monodroid.csproj @@ -6,7 +6,7 @@ Exe false - + @@ -14,8 +14,9 @@ - + + From 97c512707af41c1ddb890e49d739477958e2146b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 3 Apr 2023 22:33:29 +0200 Subject: [PATCH 06/60] Make lld really ignore missing symbols + other stuff * provide library stubs for liblog and libdl * include libc++abi demangler directly in the trace library (its sources are part of the NDK) * better trace output The only remaining dependency on NDK is the clang builtins library (compiler-rt). Should be able to eliminate that too, with some tweaks. --- .../Tasks/LinkApplicationSharedLibraries.cs | 24 ++-- src/monodroid/CMakeLists.txt | 106 ++++++++++-------- src/monodroid/jni/marshal-methods-tracing.cc | 104 +++++++++++++---- src/monodroid/jni/new_delete.cc | 2 + 4 files changed, 155 insertions(+), 81 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 84aecdbe1ff..a36815ac459 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -147,24 +147,23 @@ IEnumerable GetLinkerConfigs () } string runtimeNativeLibsDir = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, "..", "..", "..", "lib")); + string runtimeNativeLibStubsDir = Path.GetFullPath (Path.Combine (runtimeNativeLibsDir, "..", "libstubs")); var abis = new Dictionary (StringComparer.Ordinal); ITaskItem[] dsos = ApplicationSharedLibraries; foreach (ITaskItem item in dsos) { string abi = item.GetMetadata ("abi"); - abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, ndk, clangRuntimeDirTop); + abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir, clangRuntimeDirTop); } const string commonLinkerArgs = "--shared " + "--allow-shlib-undefined " + - "--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"; @@ -229,28 +228,23 @@ IEnumerable GetLinkerConfigs () } } - InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, NdkTools ndk, string clangRuntimeDirTop) + InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir, string clangRuntimeDirTop) { List extraLibraries = null; if (EnableMarshalMethodTracing) { - if (ndk == null) { - throw new ArgumentNullException (nameof (ndk)); - } - + string RID = MonoAndroidHelper.AbiToRid (abi); AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); string clangRuntimeAbi = MonoAndroidHelper.ArchToClangRuntimeAbi (targetArch); string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); string builtinsLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, $"libclang_rt.builtins-{clangLibraryAbi}-android.a")); - string libPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, targetArch, NDK_API_LEVEL); - string cxxAbiLibPath = Path.GetFullPath (Path.Combine (libPath, "..", "libc++abi.a")); + string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); extraLibraries = new List { - Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libmarshal-methods-tracing.a"), - Path.Combine (runtimeNativeLibsDir, MonoAndroidHelper.AbiToRid (abi), "libunwind_xamarin.a"), - $"-L \"{libPath}\"", - $"\"{builtinsLibPath}\"", - $"\"{cxxAbiLibPath}\"", + Path.Combine (runtimeNativeLibsDir, RID, "libmarshal-methods-tracing.a"), + Path.Combine (runtimeNativeLibsDir, RID, "libunwind_xamarin.a"), + $"-L \"{libStubsPath}\"", + $"\"{builtinsLibPath}\"", // for atomics "-lc", "-ldl", "-llog", // tracing uses android logger diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 42d330e8870..471e6787211 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -195,6 +195,13 @@ endif() include("${XA_BUILD_DIR}/xa_build_configuration.cmake") if(ENABLE_NET) + if(ANDROID) + if(ENABLE_NET) + set(NDK_CXX_SOURCES_PATH ${CMAKE_ANDROID_NDK}/sources/cxx-stl) + set(NDK_CXX_LIBCPPABI_SOURCE_PATH ${NDK_CXX_SOURCES_PATH}/llvm-libc++abi) + endif() + endif() + if(ANDROID_ABI MATCHES "^arm64-v8a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM64}") elseif(ANDROID_ABI MATCHES "^armeabi-v7a") @@ -552,8 +559,14 @@ if(UNIX) endif() if(ANDROID AND ENABLE_NET) + set(MARSHAL_METHODS_TRACING_SOURCES + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/src/cxa_demangle.cpp + ${SOURCES_DIR}/cxx-abi/string.cc + ${SOURCES_DIR}/cxx-abi/terminate.cc + ${SOURCES_DIR}/helpers.cc ${SOURCES_DIR}/marshal-methods-tracing.cc + ${SOURCES_DIR}/new_delete.cc ) list(APPEND XAMARIN_MONODROID_SOURCES @@ -702,9 +715,25 @@ if(ANDROID) STATIC ${MARSHAL_METHODS_TRACING_SOURCES} ) - target_include_directories( + target_compile_definitions( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} - PRIVATE ${LIBUNWIND_SOURCE_DIR}/include ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + PRIVATE + MARSHAL_METHODS_TRACING + ) + + target_compile_options( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} + PRIVATE + # Avoid the 'warning: dynamic exception specifications are deprecated' warning from libc++ headers + -Wno-deprecated-dynamic-exception-spec + ) + + target_include_directories( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include ) endif() @@ -754,55 +783,38 @@ target_link_libraries( ) if(ANDROID AND ENABLE_NET AND (NOT ANALYZERS_ENABLED)) - add_library( - c - SHARED ${XAMARIN_STUB_LIB_SOURCES} - ) - - target_compile_definitions( - c - PRIVATE STUB_LIB_NAME=libc - ) - - target_compile_options( - c - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) - - target_link_options( - c - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + macro(xa_add_stub_library _libname) + add_library( + ${_libname} + SHARED ${XAMARIN_STUB_LIB_SOURCES} + ) - set_target_properties( - c - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" - ) + target_compile_definitions( + ${_libname} + PRIVATE STUB_LIB_NAME=libc + ) - add_library( - m - SHARED ${XAMARIN_STUB_LIB_SOURCES} - ) + target_compile_options( + ${_libname} + PRIVATE -nostdlib -fno-exceptions -fno-rtti + ) - target_compile_definitions( - m - PRIVATE STUB_LIB_NAME=libm - ) + target_link_options( + ${_libname} + PRIVATE -nostdlib -fno-exceptions -fno-rtti + ) - target_compile_options( - m - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + set_target_properties( + ${_libname} + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" + ) + endmacro() - target_link_options( - m - PRIVATE -nostdlib -fno-exceptions -fno-rtti - ) + xa_add_stub_library(c) + xa_add_stub_library(m) - set_target_properties( - m - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${XA_LIBRARY_STUBS_OUTPUT_DIRECTORY}" - ) + # These two are used by the marshal methods tracing library when linking libxamarin-app.so + xa_add_stub_library(log) + xa_add_stub_library(dl) endif() diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 35aa99e6797..4a0b710418b 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -1,6 +1,9 @@ #include #include +#include #include +#include + #include #include @@ -20,58 +23,121 @@ constexpr char LEAD[] = "MM: "; // TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) // https://developer.android.com/reference/java/lang/Error?hl=en and use `getStackTrace` +// TODO: see if MonoVM could implement https://www.nongnu.org/libunwind/man/libunwind-dynamic(3).html +// + // https://www.nongnu.org/libunwind/docs.html static void print_native_backtrace () noexcept { + constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; + unw_cursor_t cursor; unw_context_t uc; unw_word_t ip; unw_word_t offp; std::array name_buf; + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) const char *symbol_name; Dl_info info; - bool valid_dlinfo; - bool symbol_name_allocated = false; unw_getcontext (&uc); unw_init_local (&cursor, &uc); // TODO: improve presentation of collected data, most likely will need to use a dynamic buffer to make it // less messy + std::string trace; + size_t frame_counter = 0; + bool got_anon = false; + while (unw_step (&cursor) > 0) { + if (!trace.empty ()) { + trace.append ("\n"); + } + unw_get_reg (&cursor, UNW_REG_IP, &ip); + auto ptr = reinterpret_cast(ip); + const char *fname = nullptr; + const void *symptr = nullptr; + unw_word_t frame_offset = 0; + bool info_valid = false; + + if (dladdr (ptr, &info) != 0) { + if (info.dli_fname != nullptr) { + fname = info.dli_fname; + } + symptr = info.dli_sname; + frame_offset = ip - reinterpret_cast(info.dli_fbase); + info_valid = true; + } else { + frame_offset = ip; + } + + // TODO: Bionic adjusts the IP (and, thus, frame offset) by an amount specific to the platform to point to the + // instruction **before** IP. Consider whether we want to do the same or not + trace.append (" #"); + std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", frame_counter++); + trace.append (num_buf.data ()); + std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); + trace.append (num_buf.data ()); + std::snprintf (num_buf.data (), num_buf.size (), "%p) ", ptr); + trace.append (num_buf.data ()); + + // TODO: consider searching /proc/self/maps for the beginning of the corresponding region to calculate the + // correct offset (like done in bionic stack trace) + trace.append (fname != nullptr ? fname : "[anonymous]"); + if (fname == nullptr) { + got_anon = true; + } + bool symbol_name_allocated = false; if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { + symbol_name = name_buf.data (); + } else if (info_valid && info.dli_sname != nullptr) { + symbol_name = info.dli_sname; + } else { + symbol_name = nullptr; + } + + if (symbol_name != nullptr) { char *demangled_symbol_name; int demangle_status; // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler - demangled_symbol_name = abi::__cxa_demangle (name_buf.data (), nullptr, nullptr, &demangle_status); + demangled_symbol_name = abi::__cxa_demangle (symbol_name, nullptr, nullptr, &demangle_status); symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; - symbol_name = symbol_name_allocated ? demangled_symbol_name : name_buf.data (); - } else { - symbol_name_allocated = false; - symbol_name = nullptr; + if (symbol_name_allocated) { + symbol_name = demangled_symbol_name; + } } - auto ptr = reinterpret_cast(ip); - valid_dlinfo = dladdr (ptr, &info) != 0; - - __android_log_print ( - PRIORITY, - SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, - " %p %s %s (%p)", - ptr, - !valid_dlinfo || info.dli_fname == nullptr ? "[unknown file]" : info.dli_fname, - symbol_name == nullptr ? "[unknown symbol]" : symbol_name, - valid_dlinfo ? info.dli_saddr : 0 - ); + if (symbol_name != nullptr) { + trace.append (" "); + trace.append (symbol_name); + if (offp != 0) { + trace.append (" + "); + std::snprintf (num_buf.data (), num_buf.size (), "%zu", offp); + trace.append (num_buf.data ()); + } + } + + if (symptr != nullptr) { + trace.append (" (symaddr: "); + std::snprintf (num_buf.data (), num_buf.size (), "%p", symptr); + trace.append (num_buf.data ()); + trace.append (")"); + } if (symbol_name_allocated && symbol_name != nullptr) { std::free (reinterpret_cast(const_cast(symbol_name))); } } + + __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); + // if (got_anon) { + // std::abort (); + // } } void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) diff --git a/src/monodroid/jni/new_delete.cc b/src/monodroid/jni/new_delete.cc index 074dfe35610..13ec5a7087b 100644 --- a/src/monodroid/jni/new_delete.cc +++ b/src/monodroid/jni/new_delete.cc @@ -22,7 +22,9 @@ operator new (size_t size) { void* p = do_alloc (size); if (p == nullptr) { +#if !defined (MARSHAL_METHODS_TRACING) log_fatal (LOG_DEFAULT, "Out of memory in the `new` operator"); +#endif xamarin::android::Helpers::abort_application (); } From d37a22a4453cf84822b6aa21f273dceb77fd9d0b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 4 Apr 2023 23:28:09 +0200 Subject: [PATCH 07/60] Eliminate the need to use NDK when enabling marshal methods tracing At XA build time we find and package the compiler's builtins libraries for all the ABIs we support. This allows us to link libxamarin-app.so without requiring NDK to be present on end user's machine. Additionally, traces have now adjusted addresses and offsets for all frames, to match bionic behavior. Finally, the entire trace output is logged in a single message. This makes everything faster, but also makes sure that the trace won't be interleaved with unrelated messages in logcat. --- .../Microsoft.Android.Runtime.proj | 1 + .../installers/create-installers.targets | 8 ++ .../Tasks/LinkApplicationSharedLibraries.cs | 45 ++-------- .../Xamarin.Android.Common.targets | 1 - src/monodroid/CMakeLists.txt | 20 +++++ src/monodroid/jni/marshal-methods-tracing.cc | 88 +++++++++++++++---- 6 files changed, 108 insertions(+), 55 deletions(-) diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 17a5bbec5fa..eb90019fdb4 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -42,6 +42,7 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.release.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-debug-app-helper.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmarshal-methods-tracing.a" /> + <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libclang_rt.builtins-*-android.a" /> diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index 6db89aba64b..4a2c4c2e42f 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -320,12 +320,20 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)ManifestOverlays\Timing.xml" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\liblog.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libc.so" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libm.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libdl.so" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\liblog.so" /> <_MSBuildTargetsSrcFiles Include="$(MSBuildTargetsSrcDir)\Xamarin.Android.AvailableItems.targets" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index a36815ac459..f7983e950f1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -16,8 +16,6 @@ namespace Xamarin.Android.Tasks { public class LinkApplicationSharedLibraries : AndroidAsyncTask { - const int NDK_API_LEVEL = 21; // TODO: don't hardcode the API level - public override string TaskPrefix => "LAS"; sealed class Config @@ -46,8 +44,6 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } - public string AndroidNdkDirectory { get; set; } - public bool EnableMarshalMethodTracing { get; set; } public override System.Threading.Tasks.Task RunTaskAsync () @@ -119,40 +115,13 @@ void RunLinker (Config config) IEnumerable GetLinkerConfigs () { - NdkTools ndk = null; - string clangRuntimeDirTop = null; - - if (EnableMarshalMethodTracing) { - ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: false, log: Log); - - // Doesn't matter for which arch we run the compiler, they all share the same topmost runtime dir - string clangPath = ndk.GetToolPath (NdkToolKind.CompilerCPlusPlus, AndroidTargetArch.Arm64, NDK_API_LEVEL); - int ret = MonoAndroidHelper.RunProcess ( - clangPath, "-print-runtime-dir", - (object sender, DataReceivedEventArgs e) => { // stdout - if (clangRuntimeDirTop == null && !String.IsNullOrEmpty (e.Data)) { - clangRuntimeDirTop = e.Data; - } - }, - (object sender, DataReceivedEventArgs e) => { // stderr - if (!String.IsNullOrEmpty (e.Data)) { - Log.LogError (e.Data); - } - } - ); - - if (ret != 0) { - Log.LogError ($"Failed to obtain clang runtime path from {clangPath}"); - } - } - string runtimeNativeLibsDir = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, "..", "..", "..", "lib")); string runtimeNativeLibStubsDir = Path.GetFullPath (Path.Combine (runtimeNativeLibsDir, "..", "libstubs")); var abis = new Dictionary (StringComparer.Ordinal); ITaskItem[] dsos = ApplicationSharedLibraries; foreach (ITaskItem item in dsos) { string abi = item.GetMetadata ("abi"); - abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir, clangRuntimeDirTop); + abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir); } const string commonLinkerArgs = @@ -228,23 +197,23 @@ IEnumerable GetLinkerConfigs () } } - InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir, string clangRuntimeDirTop) + InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir) { List extraLibraries = null; if (EnableMarshalMethodTracing) { string RID = MonoAndroidHelper.AbiToRid (abi); AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); - string clangRuntimeAbi = MonoAndroidHelper.ArchToClangRuntimeAbi (targetArch); string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); - string builtinsLibPath = Path.GetFullPath (Path.Combine (clangRuntimeDirTop, $"libclang_rt.builtins-{clangLibraryAbi}-android.a")); + string builtinsLibName = $"libclang_rt.builtins-{clangLibraryAbi}-android.a"; string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); + string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID); extraLibraries = new List { - Path.Combine (runtimeNativeLibsDir, RID, "libmarshal-methods-tracing.a"), - Path.Combine (runtimeNativeLibsDir, RID, "libunwind_xamarin.a"), + Path.Combine (runtimeLibsDir, "libmarshal-methods-tracing.a"), + Path.Combine (runtimeLibsDir, "libunwind_xamarin.a"), + Path.Combine (runtimeLibsDir, builtinsLibName), $"-L \"{libStubsPath}\"", - $"\"{builtinsLibPath}\"", // for atomics "-lc", "-ldl", "-llog", // tracing uses android logger diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index cb9d4dc2eea..b6f0f08ce81 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2088,7 +2088,6 @@ because xbuild doesn't support framework reference assemblies. DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" EnableMarshalMethodTracing="$(_AndroidEnableMarshalMethodTracing)" - AndroidNdkDirectory="$(_AndroidNdkDirectory)" /> diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 471e6787211..7f04c7d7044 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -204,12 +204,16 @@ if(ENABLE_NET) if(ANDROID_ABI MATCHES "^arm64-v8a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM64}") + set(COMPILER_BUILTINS_LIB_ABI "aarch64") elseif(ANDROID_ABI MATCHES "^armeabi-v7a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM}") + set(COMPILER_BUILTINS_LIB_ABI "arm") elseif(ANDROID_ABI MATCHES "^x86_64") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86_64}") + set(COMPILER_BUILTINS_LIB_ABI "x86_64") elseif(ANDROID_ABI MATCHES "^x86") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86}") + set(COMPILER_BUILTINS_LIB_ABI "i686") else() message(FATAL "${ANDROID_ABI} is not supported for .NET 6+ builds") endif() @@ -735,6 +739,22 @@ if(ANDROID) ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include ) + + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-runtime-dir + RESULT_VARIABLE _CXX_COMPILER_EXIT_CODE + OUTPUT_VARIABLE _CXX_COMPILER_RUNTIME_DIR + ECHO_OUTPUT_VARIABLE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(NOT ${_CXX_COMPILER_EXIT_CODE} EQUAL 0) + message(FATAL_ERROR "Unable to determine compiler runtime directory") + endif() + + message(STATUS "Runtime dir: ${_CXX_COMPILER_RUNTIME_DIR}") + set(BUILTINS_LIB ${_CXX_COMPILER_RUNTIME_DIR}/libclang_rt.builtins-${COMPILER_BUILTINS_LIB_ABI}-android.a) + file(COPY ${BUILTINS_LIB} DESTINATION ${XA_LIBRARY_OUTPUT_DIRECTORY}) endif() # Ugly, but this is the only way to change LZ4 symbols visibility without modifying lz4.h diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 4a0b710418b..4d8f10243c0 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -28,7 +28,46 @@ constexpr char LEAD[] = "MM: "; // https://www.nongnu.org/libunwind/docs.html -static void print_native_backtrace () noexcept +[[gnu::always_inline]] +unw_word_t adjust_address (unw_word_t addr) noexcept +{ + // This is what bionic does, let's do the same so that our backtrace addresses match bionic output + // Code copied verbatim from + // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 + if (addr != 0) { +#if defined (__arm__) + // If the address is suspiciously low, do nothing to avoid a segfault trying + // to access this memory. + if (addr >= 4096) { + // Check bits [15:11] of the first halfword assuming the instruction + // is 32 bits long. If the bits are any of these values, then our + // assumption was correct: + // b11101 + // b11110 + // b11111 + // Otherwise, this is a 16 bit instruction. + uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; + if (value == 0x1f || value == 0x1e || value == 0x1d) { + return addr - 4; + } + + return addr - 2; + } +#elif defined (__aarch64__) + // All instructions are 4 bytes long, skip back one instruction. + return addr - 4; +#elif defined (__i386__) || defined (__x86_64__) + // It's difficult to decode exactly where the previous instruction is, + // so subtract 1 to estimate where the instruction lives. + return addr - 1; +#endif + } + + return addr; +} + +[[gnu::always_inline]] +static void print_native_backtrace (std::string const& first_line) noexcept { constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; @@ -45,18 +84,17 @@ static void print_native_backtrace () noexcept unw_getcontext (&uc); unw_init_local (&cursor, &uc); - // TODO: improve presentation of collected data, most likely will need to use a dynamic buffer to make it - // less messy - std::string trace; + std::string trace {first_line}; size_t frame_counter = 0; bool got_anon = false; + trace.append ("\n Native stack trace:"); while (unw_step (&cursor) > 0) { - if (!trace.empty ()) { - trace.append ("\n"); - } + trace.append ("\n"); unw_get_reg (&cursor, UNW_REG_IP, &ip); + ip = adjust_address (ip); + auto ptr = reinterpret_cast(ip); const char *fname = nullptr; const void *symptr = nullptr; @@ -74,9 +112,7 @@ static void print_native_backtrace () noexcept frame_offset = ip; } - // TODO: Bionic adjusts the IP (and, thus, frame offset) by an amount specific to the platform to point to the - // instruction **before** IP. Consider whether we want to do the same or not - trace.append (" #"); + trace.append (" #"); std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", frame_counter++); trace.append (num_buf.data ()); std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); @@ -92,6 +128,7 @@ static void print_native_backtrace () noexcept } bool symbol_name_allocated = false; + offp = 0; if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { symbol_name = name_buf.data (); } else if (info_valid && info.dli_sname != nullptr) { @@ -99,6 +136,7 @@ static void print_native_backtrace () noexcept } else { symbol_name = nullptr; } + offp = adjust_address (offp); if (symbol_name != nullptr) { char *demangled_symbol_name; @@ -135,9 +173,9 @@ static void print_native_backtrace () noexcept } __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); - // if (got_anon) { - // std::abort (); - // } + if (got_anon) { + std::abort (); + } } void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) @@ -151,10 +189,28 @@ static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t clas const char *managed_method_name = MarshalMethodsUtilities::get_method_name (method_id); const char *class_name = MarshalMethodsUtilities::get_class_name (class_index); - __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "%s%s: %s (%s) in class %s", LEAD, which, native_method_name, managed_method_name, class_name); if (need_trace) { - __android_log_print (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, "Native stack trace:"); - print_native_backtrace (); + std::string first_line { LEAD }; + first_line.append (which); + first_line.append (": "); + first_line.append (native_method_name); + first_line.append (" ("); + first_line.append (managed_method_name); + first_line.append (") in class "); + first_line.append (class_name); + + print_native_backtrace (first_line); + } else { + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "%s%s: %s (%s) in class %s", + LEAD, + which, + native_method_name, + managed_method_name, + class_name + ); } } From fb7f7e60b363daaeb874ded5530ac2fc0bfa77e4 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 5 Apr 2023 22:24:20 +0200 Subject: [PATCH 08/60] Java stack trace implemented Also: introduce tracing modes, 'basic` for just the method enter/leave messages and 'full' to include native and java stack traces on method entry. --- .../Tasks/GeneratePackageManagerJava.cs | 7 +- .../Tasks/LinkApplicationSharedLibraries.cs | 12 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 5 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 38 ++++- .../Utilities/MarshalMethodsTracingMode.cs | 10 ++ .../Utilities/MonoAndroidHelper.cs | 17 +++ .../Xamarin.Android.Common.targets | 12 +- src/monodroid/jni/application_dso_stub.cc | 2 +- src/monodroid/jni/marshal-methods-tracing.cc | 143 ++++++++++++++---- src/monodroid/jni/marshal-methods-tracing.hh | 17 ++- src/monodroid/jni/monodroid-glue-internal.hh | 2 +- src/monodroid/jni/monodroid-glue.cc | 8 +- src/monodroid/jni/xamarin-app.hh | 2 +- 13 files changed, 220 insertions(+), 55 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index aba9b452a7f..5a2496719f2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -23,6 +23,7 @@ public class GeneratePackageManagerJava : AndroidTask public override string TaskPrefix => "GPM"; Guid buildId = Guid.NewGuid (); + MarshalMethodsTracingMode mmTracingMode; [Required] public ITaskItem[] ResolvedAssemblies { get; set; } @@ -66,7 +67,7 @@ public class GeneratePackageManagerJava : AndroidTask public bool InstantRunEnabled { get; set; } public bool EnableMarshalMethods { get; set; } - public bool EnableMarshalMethodTracing { get; set; } + public string MarshalMethodsTracingMode { get; set; } public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } @@ -93,6 +94,8 @@ bool _Debug { public override bool RunTask () { + mmTracingMode = MonoAndroidHelper.ParseMarshalMethodsTracingMode (MarshalMethodsTracingMode); + BuildId = buildId.ToString (); Log.LogDebugMessage (" [Output] BuildId: {0}", BuildId); @@ -412,7 +415,7 @@ void AddEnvironment () uniqueAssemblyNames, marshalMethodsState?.MarshalMethods, Log, - EnableMarshalMethodTracing + mmTracingMode ); } else { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index f7983e950f1..b37001c78e2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -44,10 +44,16 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } - public bool EnableMarshalMethodTracing { get; set; } + public string MarshalMethodsTracingMode { get; set; } + + MarshalMethodsTracingMode mmTracingMode; + bool mmTracingEnabled; public override System.Threading.Tasks.Task RunTaskAsync () { + mmTracingMode = MonoAndroidHelper.ParseMarshalMethodsTracingMode (MarshalMethodsTracingMode); + mmTracingEnabled = mmTracingMode != Tasks.MarshalMethodsTracingMode.None; + return this.WhenAll (GetLinkerConfigs (), RunLinker); } @@ -137,7 +143,7 @@ IEnumerable GetLinkerConfigs () "--warn-shared-textrel " + "--fatal-warnings"; - string stripSymbolsArg = DebugBuild || EnableMarshalMethodTracing ? String.Empty : " -s"; + string stripSymbolsArg = DebugBuild || mmTracingEnabled ? String.Empty : " -s"; string ld = Path.Combine (AndroidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, "ld")); var targetLinkerArgs = new List (); @@ -201,7 +207,7 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem { List extraLibraries = null; - if (EnableMarshalMethodTracing) { + if (mmTracingEnabled) { string RID = MonoAndroidHelper.AbiToRid (abi); AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 41aeaafabd3..32117f47533 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -336,7 +336,8 @@ public void EmitLabel (LlvmIrFunction function, string labelName) throw new ArgumentException ("must be reference to native function", nameof (targetRef)); } - if (targetSignature.Parameters.Count > 0) { + bool haveParameters = targetSignature.Parameters != null && targetSignature.Parameters.Count > 0; + if (haveParameters) { if (arguments == null) { throw new ArgumentNullException (nameof (arguments)); } @@ -377,7 +378,7 @@ public void EmitLabel (LlvmIrFunction function, string labelName) Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} {targetRef.Reference} ("); - if (targetSignature.Parameters.Count > 0) { + if (haveParameters) { for (int i = 0; i < targetSignature.Parameters.Count; i++) { LlvmIrFunctionParameter parameter = targetSignature.Parameters[i]; LlvmIrFunctionArgument argument = arguments[i]; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index ea24dd75ce4..559387ddd8e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -173,6 +173,7 @@ sealed class MarshalMethodName const string mm_trace_func_enter_name = "_mm_trace_func_enter"; const string mm_trace_func_leave_name = "_mm_trace_func_leave"; + const string mm_trace_init_name = "_mm_trace_init"; ICollection uniqueAssemblyNames; int numberOfAssembliesInApk; @@ -205,9 +206,10 @@ sealed class MarshalMethodName // Tracing LlvmIrVariableReference? mm_trace_func_enter_ref; LlvmIrVariableReference? mm_trace_func_leave_ref; + LlvmIrVariableReference? mm_trace_init_ref; readonly bool generateEmptyCode; - readonly bool emitTracing; + readonly MarshalMethodsTracingMode tracingMode; /// /// Constructor to be used ONLY when marshal methods are DISABLED @@ -222,7 +224,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl /// /// Constructor to be used ONLY when marshal methods are ENABLED /// - public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger, bool emitTracing) + public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger, MarshalMethodsTracingMode tracingMode) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); @@ -230,7 +232,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); generateEmptyCode = false; - this.emitTracing = emitTracing; + this.tracingMode = tracingMode; } public override void Init () @@ -662,12 +664,22 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) void WriteInitTracing (LlvmIrGenerator generator) { - if (!emitTracing) { + if (tracingMode == MarshalMethodsTracingMode.None) { return; } // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_init_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env + } + ); + mm_trace_init_ref = new LlvmIrVariableReference (mm_trace_init_sig, mm_trace_init_name, isGlobal: true); + var mm_trace_func_enter_or_leave_params = new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env + new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), new LlvmIrFunctionParameter (typeof(uint), "class_index"), new LlvmIrFunctionParameter (typeof(uint), "method_token"), @@ -687,6 +699,7 @@ void WriteInitTracing (LlvmIrGenerator generator) ); mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); + WriteTraceDeclaration (mm_trace_init_name, mm_trace_init_sig.ReturnType, mm_trace_init_sig.Parameters); WriteTraceDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig.ReturnType, mm_trace_func_enter_sig.Parameters); WriteTraceDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig.ReturnType, mm_trace_func_leave_sig.Parameters); @@ -749,8 +762,10 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll List? trace_enter_leave_args = null; - if (emitTracing) { + if (tracingMode != MarshalMethodsTracingMode.None) { trace_enter_leave_args = new List { + new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env + new LlvmIrFunctionArgument (typeof(int), (int)tracingMode), new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), @@ -812,7 +827,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList () ); - if (emitTracing) { + if (tracingMode != MarshalMethodsTracingMode.None) { generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args); } @@ -846,12 +861,23 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) returnType: typeof (void), attributeSetID: LlvmIrGenerator.FunctionAttributesXamarinAppInit, parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env fnParameter, } ); generator.WriteFunctionStart (func); generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); + if (tracingMode != MarshalMethodsTracingMode.None) { + generator.EmitCall ( + func, + mm_trace_init_ref, + new List { + new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv *env + } + ); + } + generator.WriteFunctionEnd (func); return new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs new file mode 100644 index 00000000000..1aa9e82c5d0 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Android.Tasks +{ + // Enumeration member numeric values MUST match those in src/monodroid/jni/marshal-methods-tracing.hh + public enum MarshalMethodsTracingMode + { + None = 0x00, + Basic = 0x01, + Full = 0x02, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 9575c7533d0..320f6ed8f72 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -614,5 +614,22 @@ public static string ArchToClangLibraryAbi (AndroidTargetArch arch) throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); } } + + public static MarshalMethodsTracingMode ParseMarshalMethodsTracingMode (string input) + { + if (String.IsNullOrEmpty (input) || String.Compare ("none", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.None; + } + + if (String.Compare ("basic", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.Basic; + } + + if (String.Compare ("full", input, StringComparison.InvariantCultureIgnoreCase) == 0) { + return MarshalMethodsTracingMode.Full; + } + + throw new InvalidOperationException ($"Unsupported marshal methods tracing mode '{input}'"); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index b6f0f08ce81..225ff46f254 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -340,7 +340,13 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) - <_AndroidEnableMarshalMethodTracing Condition=" '$(_AndroidEnableMarshalMethodTracing)' == '' ">False + + + <_AndroidMarshalMethodsTracingMode Condition=" '$(_AndroidMarshalMethodsTracingMode)' == '' ">none @@ -1792,7 +1798,7 @@ because xbuild doesn't support framework reference assemblies. UsingAndroidNETSdk="$(UsingAndroidNETSdk)" UseAssemblyStore="$(AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" - EnableMarshalMethodTracing="$(_AndroidEnableMarshalMethodTracing)" + MarshalMethodsTracingMode="$(_AndroidMarshalMethodsTracingMode)" > @@ -2087,7 +2093,7 @@ because xbuild doesn't support framework reference assemblies. ApplicationSharedLibraries="@(_ApplicationSharedLibrary)" DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" - EnableMarshalMethodTracing="$(_AndroidEnableMarshalMethodTracing)" + MarshalMethodsTracingMode="$(_AndroidMarshalMethodsTracingMode)" /> diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 8a59a03e51a..97f554e4c25 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -202,7 +202,7 @@ const MarshalMethodName mm_method_names[] = { }, }; -void xamarin_app_init ([[maybe_unused]] get_function_pointer_fn fn) noexcept +void xamarin_app_init ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] get_function_pointer_fn fn) noexcept { // Dummy } diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 4d8f10243c0..566e01a354e 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -14,12 +14,22 @@ #include "marshal-methods-tracing.hh" #include "marshal-methods-utilities.hh" +#include "helpers.hh" using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; constexpr char LEAD[] = "MM: "; +// java.lang.Thread +static jclass java_lang_Thread; +static jmethodID java_lang_Thread_currentThread; +static jmethodID java_lang_Thread_getStackTrace; + +// java.lang.StackTraceElement +static jclass java_lang_StackTraceElement; +static jmethodID java_lang_StackTraceElement_toString; + // TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) // https://developer.android.com/reference/java/lang/Error?hl=en and use `getStackTrace` @@ -67,7 +77,17 @@ unw_word_t adjust_address (unw_word_t addr) noexcept } [[gnu::always_inline]] -static void print_native_backtrace (std::string const& first_line) noexcept +static void append_frame_number (std::string &trace, size_t count) noexcept +{ + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + trace.append (" #"); + std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); + trace.append (num_buf.data ()); +} + +[[gnu::always_inline]] +static void get_native_backtrace (std::string& trace) noexcept { constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; @@ -84,9 +104,7 @@ static void print_native_backtrace (std::string const& first_line) noexcept unw_getcontext (&uc); unw_init_local (&cursor, &uc); - std::string trace {first_line}; size_t frame_counter = 0; - bool got_anon = false; trace.append ("\n Native stack trace:"); while (unw_step (&cursor) > 0) { @@ -112,9 +130,8 @@ static void print_native_backtrace (std::string const& first_line) noexcept frame_offset = ip; } - trace.append (" #"); - std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", frame_counter++); - trace.append (num_buf.data ()); + append_frame_number (trace, frame_counter++); + std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); trace.append (num_buf.data ()); std::snprintf (num_buf.data (), num_buf.size (), "%p) ", ptr); @@ -123,9 +140,6 @@ static void print_native_backtrace (std::string const& first_line) noexcept // TODO: consider searching /proc/self/maps for the beginning of the corresponding region to calculate the // correct offset (like done in bionic stack trace) trace.append (fname != nullptr ? fname : "[anonymous]"); - if (fname == nullptr) { - got_anon = true; - } bool symbol_name_allocated = false; offp = 0; @@ -171,35 +185,56 @@ static void print_native_backtrace (std::string const& first_line) noexcept std::free (reinterpret_cast(const_cast(symbol_name))); } } +} + +[[gnu::always_inline]] +static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept +{ + // TODO: error handling + trace.append ("\n Java stack trace:"); + jobject current_thread = env->CallStaticObjectMethod (java_lang_Thread, java_lang_Thread_currentThread); + auto stack_trace_array = static_cast(env->CallNonvirtualObjectMethod (current_thread, java_lang_Thread, java_lang_Thread_getStackTrace)); + jsize nframes = env->GetArrayLength (stack_trace_array); - __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); - if (got_anon) { - std::abort (); + for (jsize i = 0; i < nframes; i++) { + jobject frame = env->GetObjectArrayElement (stack_trace_array, i); + auto frame_desc_java = static_cast(env->CallNonvirtualObjectMethod (frame, java_lang_StackTraceElement, java_lang_StackTraceElement_toString)); + const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); + + trace.append ("\n"); + append_frame_number (trace, i); + trace.append (frame_desc); + env->ReleaseStringUTFChars (frame_desc_java, frame_desc); } } -void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) +void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept { } -static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* which, const char* native_method_name, bool need_trace) +static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, + const char* which, const char* native_method_name, bool need_trace) noexcept { uint64_t method_id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); const char *managed_method_name = MarshalMethodsUtilities::get_method_name (method_id); const char *class_name = MarshalMethodsUtilities::get_class_name (class_index); - if (need_trace) { - std::string first_line { LEAD }; - first_line.append (which); - first_line.append (": "); - first_line.append (native_method_name); - first_line.append (" ("); - first_line.append (managed_method_name); - first_line.append (") in class "); - first_line.append (class_name); - - print_native_backtrace (first_line); + if (need_trace && tracing_mode == TracingModeFull) { + std::string trace { LEAD }; + trace.append (which); + trace.append (": "); + trace.append (native_method_name); + trace.append (" ("); + trace.append (managed_method_name); + trace.append (") in class "); + trace.append (class_name); + + get_native_backtrace (trace); + trace.append ("\n"); + get_java_backtrace (env, trace); + + __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); } else { __android_log_print ( PRIORITY, @@ -214,14 +249,64 @@ static void _mm_trace_func_leave_enter (uint32_t mono_image_index, uint32_t clas } } -void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) +void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept { constexpr char ENTER[] = "ENTER"; - _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */); + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */); } -void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) +void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept { constexpr char LEAVE[] = "LEAVE"; - _mm_trace_func_leave_enter (mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); +} + +static bool assert_valid_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept +{ + if (o != nullptr) { + return true; + } + + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "%smissing Java %s: %s", + LEAD, + missing_kind, + missing_name + ); + + return false; +} + +void _mm_trace_init (JNIEnv *env) noexcept +{ + // We might be called more than once, ignore all but the first call + if (java_lang_Thread != nullptr) { + return; + } + + java_lang_Thread = env->FindClass ("java/lang/Thread"); + java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); + java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + java_lang_StackTraceElement = env->FindClass ("java/lang/StackTraceElement"); + java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); + + // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one + // would do the exception check for us. + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + + bool all_found = assert_valid_pointer (java_lang_Thread, "class", "java.lang.Thread"); + all_found &= assert_valid_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); + all_found &= assert_valid_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); + all_found &= assert_valid_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); + all_found &= assert_valid_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } } diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index 898a54b82bd..ae7e4a74345 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -1,10 +1,21 @@ #if !defined (__MARSHAL_METHODS_TRACING_HH) #define __MARSHAL_METHODS_TRACING_HH +#include + #include "monodroid-glue-internal.hh" -extern "C" void _mm_trace (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message); -extern "C" void _mm_trace_func_enter (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name); -extern "C" void _mm_trace_func_leave (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name); +// These values MUST match those in the MarshalMethodsTracingMode managed enum (src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsTracingMode.cs) + +inline constexpr int32_t TracingModeNone = 0x00; +inline constexpr int32_t TracingModeBasic = 0x01; +inline constexpr int32_t TracingModeFull = 0x02; + +extern "C" { + void _mm_trace_init (JNIEnv *env) noexcept; + void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; + void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; +} #endif // ndef __MARSHAL_METHODS_TRACING_HH diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 0865e2742ae..7aacfe88d52 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -299,7 +299,7 @@ namespace xamarin::android::internal void set_debug_options (); void parse_gdb_options (); - void mono_runtime_init (dynamic_local_string& runtime_args); + void mono_runtime_init (JNIEnv *env, dynamic_local_string& runtime_args); #if defined (NET) void init_android_runtime (JNIEnv *env, jclass runtimeClass, jobject loader); #else //def NET diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index afef374dd18..0f6ebcc65e6 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -638,7 +638,7 @@ MonodroidRuntime::set_debug_options (void) } void -MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_string& runtime_args) +MonodroidRuntime::mono_runtime_init (JNIEnv *env, [[maybe_unused]] dynamic_local_string& runtime_args) { #if defined (DEBUG) && !defined (WINDOWS) RuntimeOptions options{}; @@ -808,7 +808,7 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_stringstart_event (TimingEventKind::MonoRuntimeInit); } - mono_runtime_init (runtime_args); + mono_runtime_init (env, runtime_args); if (XA_UNLIKELY (FastTiming::enabled ())) { internal_timing->end_event (mono_runtime_init_index); @@ -2354,7 +2354,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl } #if defined (RELEASE) && defined (ANDROID) && defined (NET) - xamarin_app_init (get_function_pointer_at_runtime); + xamarin_app_init (env, get_function_pointer_at_runtime); #endif // def RELEASE && def ANDROID && def NET startup_in_progress = false; } diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 3dbc037407b..d50fc39a683 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -351,7 +351,7 @@ MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); -MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; +MONO_API MONO_API_EXPORT void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept; #endif // def RELEASE && def ANDROID && def NET #endif // __XAMARIN_ANDROID_TYPEMAP_H From e3d26498eb7a38ed4978337721f1954d3e647903 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 6 Apr 2023 21:58:09 +0200 Subject: [PATCH 09/60] Add signal handlers to the report --- src/monodroid/jni/marshal-methods-tracing.cc | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 566e01a354e..68dc7c4684d 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -208,6 +209,72 @@ static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept } } +[[gnu::always_inline]] +static void get_interesting_signal_handlers (std::string &trace) noexcept +{ + constexpr char SA_SIGNAL[] = "signal"; + constexpr char SA_SIGACTION[] = "sigaction"; + constexpr char SIG_IGNORED[] = "[ignored]"; + + trace.append ("\n Signal handlers:"); + + std::array num_buf; + Dl_info info; + struct sigaction cur_sa; + for (int i = 0; i < _NSIG; i++) { + if (sigaction (i, nullptr, &cur_sa) != 0) { + continue; // ignore + } + + void *handler; + const char *installed_with; + if (cur_sa.sa_flags & SA_SIGINFO) { + handler = reinterpret_cast(cur_sa.sa_sigaction); + installed_with = SA_SIGACTION; + } else { + handler = reinterpret_cast(cur_sa.sa_handler); + installed_with = SA_SIGNAL; + } + + if (handler == SIG_DFL) { + continue; + } + + trace.append ("\n"); + const char *symbol_name = nullptr; + const char *file_name = nullptr; + if (handler == SIG_IGN) { + symbol_name = SIG_IGNORED; + } else { + if (dladdr (handler, &info) != 0) { + symbol_name = info.dli_sname; + file_name = info.dli_fname; + } + } + + trace.append (" "); + trace.append (strsignal (i)); + trace.append (" ("); + std::snprintf (num_buf.data (), num_buf.size (), "%d", i); + trace.append (num_buf.data ()); + trace.append ("), with "); + trace.append (installed_with); + trace.append (": "); + + if (file_name != nullptr) { + trace.append (file_name); + trace.append (" "); + } + + if (symbol_name == nullptr) { + std::snprintf (num_buf.data (), num_buf.size (), "%p", handler); + trace.append (num_buf.data ()); + } else { + trace.append (symbol_name); + } + } +} + void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept { @@ -233,6 +300,8 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 get_native_backtrace (trace); trace.append ("\n"); get_java_backtrace (env, trace); + trace.append ("\n"); + get_interesting_signal_handlers (trace); __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); } else { From 0ba212e8996739d6e3f14d0c49638bb8daefa3e2 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 7 Apr 2023 21:46:24 +0200 Subject: [PATCH 10/60] Day spent debugging It appears that the following might be cause of the "hang": 04-07 19:39:36.121 4859 4859 I chromium: [INFO:CONSOLE(2)] "Uncaught ReferenceError: Blazor is not defined", source: https://0.0.0.0/ (2) It is probably caused by this JavaScript fragment from Blazor: Blazor.start(); window.__BlazorStarted = true; And the Blazor class might not be initialized because of a problem with delegates using the "old" registration mechanism when marshal methods are enabled (there are quite a few of them in the test app). lldb didn't reveal anything interesting, process state appeared to be fine. --- .../MarshalMethodsNativeAssemblyGenerator.cs | 3 +++ src/monodroid/jni/marshal-methods-tracing.cc | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 559387ddd8e..be910e480df 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -12,6 +12,9 @@ using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; +// TODO: generate code to check for pending Java exceptions (maybe?) +// TODO: check whether delegates not converted to marshale methods work correctly. It's possible something isn't called when it should be and that's +// why Blazor hangs. namespace Xamarin.Android.Tasks { class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 68dc7c4684d..76be449aaf0 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -348,6 +348,18 @@ static bool assert_valid_pointer (void *o, const char *missing_kind, const char return false; } +template +static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept +{ + if (lref == nullptr) { + return nullptr; + } + + auto ret = static_cast (env->NewGlobalRef (lref)); + env->DeleteLocalRef (lref); + return ret; +} + void _mm_trace_init (JNIEnv *env) noexcept { // We might be called more than once, ignore all but the first call @@ -355,10 +367,10 @@ void _mm_trace_init (JNIEnv *env) noexcept return; } - java_lang_Thread = env->FindClass ("java/lang/Thread"); + java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); - java_lang_StackTraceElement = env->FindClass ("java/lang/StackTraceElement"); + java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one From b8cb13dad40a5143544631abc2d0f3c1cb974d9f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 11 Apr 2023 23:22:45 +0200 Subject: [PATCH 11/60] Tracing code is going to live in a separate DSO The same library will be used by marshal methods tracing (by linking it into `libxamarin-app.so` when tracing is enabled) or will be used by `libmonodroid.so` (or p/invoked from managed land) if desired. --- src/monodroid/CMakeLists.txt | 82 ++++- src/monodroid/jni/marshal-methods-tracing.cc | 2 +- src/monodroid/jni/native-tracing.cc | 308 +++++++++++++++++++ src/monodroid/jni/native-tracing.hh | 42 +++ src/monodroid/jni/new_delete.cc | 2 +- 5 files changed, 417 insertions(+), 19 deletions(-) create mode 100644 src/monodroid/jni/native-tracing.cc create mode 100644 src/monodroid/jni/native-tracing.hh diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 7f04c7d7044..001d58579c2 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -205,15 +205,19 @@ if(ENABLE_NET) if(ANDROID_ABI MATCHES "^arm64-v8a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM64}") set(COMPILER_BUILTINS_LIB_ABI "aarch64") + set(SYSROOT_ABI_LIB_DIR "aarch64-linux-android") elseif(ANDROID_ABI MATCHES "^armeabi-v7a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM}") set(COMPILER_BUILTINS_LIB_ABI "arm") + set(SYSROOT_ABI_LIB_DIR "arm-linux-androideabi") elseif(ANDROID_ABI MATCHES "^x86_64") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86_64}") set(COMPILER_BUILTINS_LIB_ABI "x86_64") + set(SYSROOT_ABI_LIB_DIR "x86_64-linux-android") elseif(ANDROID_ABI MATCHES "^x86") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86}") set(COMPILER_BUILTINS_LIB_ABI "i686") + set(SYSROOT_ABI_LIB_DIR "i686-linux-android") else() message(FATAL "${ANDROID_ABI} is not supported for .NET 6+ builds") endif() @@ -495,6 +499,7 @@ endif() set(XAMARIN_INTERNAL_API_LIB xa-internal-api${CHECKED_BUILD_INFIX}) set(XAMARIN_DEBUG_APP_HELPER_LIB xamarin-debug-app-helper${CHECKED_BUILD_INFIX}) set(XAMARIN_APP_STUB_LIB xamarin-app) +set(XAMARIN_NATIVE_TRACING_LIB xamarin-native-tracing) set(XAMARIN_MARSHAL_METHODS_TRACING_LIB marshal-methods-tracing) string(TOLOWER ${CMAKE_BUILD_TYPE} XAMARIN_MONO_ANDROID_SUFFIX) @@ -563,6 +568,14 @@ if(UNIX) endif() if(ANDROID AND ENABLE_NET) + set(NATIVE_TRACING_SOURCES + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/src/cxa_demangle.cpp + ${SOURCES_DIR}/cxx-abi/string.cc + ${SOURCES_DIR}/cxx-abi/terminate.cc + ${SOURCES_DIR}/helpers.cc + ${SOURCES_DIR}/native-tracing.cc + ${SOURCES_DIR}/new_delete.cc + ) set(MARSHAL_METHODS_TRACING_SOURCES ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/src/cxa_demangle.cpp @@ -712,8 +725,57 @@ add_library( if(ANDROID) if(ENABLE_NET AND NOT DEBUG_BUILD) - # TODO: make it a shared library instead, will make it possible to not require NDK when building - # an app with tracing + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-runtime-dir + RESULT_VARIABLE _CXX_COMPILER_EXIT_CODE + OUTPUT_VARIABLE _CXX_COMPILER_RUNTIME_DIR + ECHO_OUTPUT_VARIABLE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(NOT ${_CXX_COMPILER_EXIT_CODE} EQUAL 0) + message(FATAL_ERROR "Unable to determine compiler runtime directory") + endif() + + message(STATUS "Clang runtime dir: ${_CXX_COMPILER_RUNTIME_DIR}") + message(STATUS "ABI dir: ${SYSROOT_ABI_LIB_DIR}") + set(BUILTINS_LIB ${_CXX_COMPILER_RUNTIME_DIR}/libclang_rt.builtins-${COMPILER_BUILTINS_LIB_ABI}-android.a) + set(CPP_ABI_PATH ${CMAKE_SYSROOT}/usr/lib/${SYSROOT_ABI_LIB_DIR}/libc++abi.a) + + add_library( + ${XAMARIN_NATIVE_TRACING_LIB} + SHARED ${NATIVE_TRACING_SOURCES} + ) + + target_include_directories( + ${XAMARIN_NATIVE_TRACING_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include + ) + + target_compile_options( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + # Avoid the 'warning: dynamic exception specifications are deprecated' warning from libc++ headers + -Wno-deprecated-dynamic-exception-spec + ) + + target_link_libraries( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + -llog + ${CPP_ABI_PATH} + ${XA_LIBRARY_OUTPUT_DIRECTORY}/libunwind_xamarin.a + ) + + target_compile_definitions( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE + XAMARIN_TRACING + ) + add_library( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} STATIC ${MARSHAL_METHODS_TRACING_SOURCES} @@ -722,7 +784,7 @@ if(ANDROID) target_compile_definitions( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} PRIVATE - MARSHAL_METHODS_TRACING + XAMARIN_TRACING ) target_compile_options( @@ -740,20 +802,6 @@ if(ANDROID) ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include ) - execute_process( - COMMAND ${CMAKE_CXX_COMPILER} -print-runtime-dir - RESULT_VARIABLE _CXX_COMPILER_EXIT_CODE - OUTPUT_VARIABLE _CXX_COMPILER_RUNTIME_DIR - ECHO_OUTPUT_VARIABLE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if(NOT ${_CXX_COMPILER_EXIT_CODE} EQUAL 0) - message(FATAL_ERROR "Unable to determine compiler runtime directory") - endif() - - message(STATUS "Runtime dir: ${_CXX_COMPILER_RUNTIME_DIR}") - set(BUILTINS_LIB ${_CXX_COMPILER_RUNTIME_DIR}/libclang_rt.builtins-${COMPILER_BUILTINS_LIB_ABI}-android.a) file(COPY ${BUILTINS_LIB} DESTINATION ${XA_LIBRARY_OUTPUT_DIRECTORY}) endif() diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 76be449aaf0..7f647c6f0f2 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -199,7 +199,7 @@ static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept for (jsize i = 0; i < nframes; i++) { jobject frame = env->GetObjectArrayElement (stack_trace_array, i); - auto frame_desc_java = static_cast(env->CallNonvirtualObjectMethod (frame, java_lang_StackTraceElement, java_lang_StackTraceElement_toString)); + auto frame_desc_java = static_cast(env->CallObjectMethod (frame, java_lang_StackTraceElement_toString)); const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); trace.append ("\n"); diff --git a/src/monodroid/jni/native-tracing.cc b/src/monodroid/jni/native-tracing.cc new file mode 100644 index 00000000000..e9f7e1dd15c --- /dev/null +++ b/src/monodroid/jni/native-tracing.cc @@ -0,0 +1,308 @@ +#include +#include + +#include +#include + +#include + +#include "native-tracing.hh" +#include "shared-constants.hh" + +using namespace xamarin::android::internal; + +[[gnu::always_inline]] +unw_word_t NativeTracing::adjust_address (unw_word_t addr) noexcept +{ + // This is what bionic does, let's do the same so that our backtrace addresses match bionic output + // Code copied verbatim from + // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 + if (addr != 0) { +#if defined (__arm__) + // If the address is suspiciously low, do nothing to avoid a segfault trying + // to access this memory. + if (addr >= 4096) { + // Check bits [15:11] of the first halfword assuming the instruction + // is 32 bits long. If the bits are any of these values, then our + // assumption was correct: + // b11101 + // b11110 + // b11111 + // Otherwise, this is a 16 bit instruction. + uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; + if (value == 0x1f || value == 0x1e || value == 0x1d) { + return addr - 4; + } + + return addr - 2; + } +#elif defined (__aarch64__) + // All instructions are 4 bytes long, skip back one instruction. + return addr - 4; +#elif defined (__i386__) || defined (__x86_64__) + // It's difficult to decode exactly where the previous instruction is, + // so subtract 1 to estimate where the instruction lives. + return addr - 1; +#endif + } + + return addr; +} + +[[gnu::always_inline]] +void NativeTracing::append_frame_number (std::string &trace, size_t count) noexcept +{ + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + trace.append (" #"); + std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); + trace.append (num_buf.data ()); +} + +void NativeTracing::get_native_backtrace (std::string& trace) noexcept +{ + constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; + + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t ip; + unw_word_t offp; + std::array name_buf; + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + const char *symbol_name; + Dl_info info; + + unw_getcontext (&uc); + unw_init_local (&cursor, &uc); + + size_t frame_counter = 0; + + trace.append ("\n Native stack trace:"); + while (unw_step (&cursor) > 0) { + trace.append ("\n"); + + unw_get_reg (&cursor, UNW_REG_IP, &ip); + ip = adjust_address (ip); + + auto ptr = reinterpret_cast(ip); + const char *fname = nullptr; + const void *symptr = nullptr; + unw_word_t frame_offset = 0; + bool info_valid = false; + + if (dladdr (ptr, &info) != 0) { + if (info.dli_fname != nullptr) { + fname = info.dli_fname; + } + symptr = info.dli_sname; + frame_offset = ip - reinterpret_cast(info.dli_fbase); + info_valid = true; + } else { + frame_offset = ip; + } + + append_frame_number (trace, frame_counter++); + + std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); + trace.append (num_buf.data ()); + std::snprintf (num_buf.data (), num_buf.size (), "%p) ", ptr); + trace.append (num_buf.data ()); + + // TODO: consider searching /proc/self/maps for the beginning of the corresponding region to calculate the + // correct offset (like done in bionic stack trace) + trace.append (fname != nullptr ? fname : "[anonymous]"); + + bool symbol_name_allocated = false; + offp = 0; + if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { + symbol_name = name_buf.data (); + } else if (info_valid && info.dli_sname != nullptr) { + symbol_name = info.dli_sname; + } else { + symbol_name = nullptr; + } + offp = adjust_address (offp); + + if (symbol_name != nullptr) { + char *demangled_symbol_name; + int demangle_status; + + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler + demangled_symbol_name = abi::__cxa_demangle (symbol_name, nullptr, nullptr, &demangle_status); + symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; + if (symbol_name_allocated) { + symbol_name = demangled_symbol_name; + } + } + + if (symbol_name != nullptr) { + trace.append (" "); + trace.append (symbol_name); + if (offp != 0) { + trace.append (" + "); + std::snprintf (num_buf.data (), num_buf.size (), "%zu", offp); + trace.append (num_buf.data ()); + } + } + + if (symptr != nullptr) { + trace.append (" (symaddr: "); + std::snprintf (num_buf.data (), num_buf.size (), "%p", symptr); + trace.append (num_buf.data ()); + trace.append (")"); + } + + if (symbol_name_allocated && symbol_name != nullptr) { + std::free (reinterpret_cast(const_cast(symbol_name))); + } + } +} + +void NativeTracing::get_java_backtrace (JNIEnv *env, std::string &trace) noexcept +{ + // TODO: error handling + trace.append ("\n Java stack trace:"); + jobject current_thread = env->CallStaticObjectMethod (java_lang_Thread, java_lang_Thread_currentThread); + auto stack_trace_array = static_cast(env->CallNonvirtualObjectMethod (current_thread, java_lang_Thread, java_lang_Thread_getStackTrace)); + jsize nframes = env->GetArrayLength (stack_trace_array); + + for (jsize i = 0; i < nframes; i++) { + jobject frame = env->GetObjectArrayElement (stack_trace_array, i); + auto frame_desc_java = static_cast(env->CallObjectMethod (frame, java_lang_StackTraceElement_toString)); + const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); + + trace.append ("\n"); + append_frame_number (trace, i); + trace.append (frame_desc); + env->ReleaseStringUTFChars (frame_desc_java, frame_desc); + } +} + +template +TJavaPointer NativeTracing::to_gref (JNIEnv *env, TJavaPointer lref) noexcept +{ + if (lref == nullptr) { + return nullptr; + } + + auto ret = static_cast (env->NewGlobalRef (lref)); + env->DeleteLocalRef (lref); + return ret; +} + +void NativeTracing::init_jni (JNIEnv *env) noexcept +{ + // We might be called more than once, ignore all but the first call + if (java_lang_Thread != nullptr) { + return; + } + + // TODO: locking + + java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); + java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); + java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); + java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); + + // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one + // would do the exception check for us. + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + + bool all_found = assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.Thread"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } +} + +bool NativeTracing::assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept +{ + if (o != nullptr) { + return true; + } + + __android_log_print ( + PRIORITY, + SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "missing Java %s: %s", + missing_kind, + missing_name + ); + + return false; +} + +void NativeTracing::get_interesting_signal_handlers (std::string &trace) noexcept +{ + constexpr char SA_SIGNAL[] = "signal"; + constexpr char SA_SIGACTION[] = "sigaction"; + constexpr char SIG_IGNORED[] = "[ignored]"; + + trace.append ("\n Signal handlers:"); + + std::array num_buf; + Dl_info info; + struct sigaction cur_sa; + for (int i = 0; i < _NSIG; i++) { + if (sigaction (i, nullptr, &cur_sa) != 0) { + continue; // ignore + } + + void *handler; + const char *installed_with; + if (cur_sa.sa_flags & SA_SIGINFO) { + handler = reinterpret_cast(cur_sa.sa_sigaction); + installed_with = SA_SIGACTION; + } else { + handler = reinterpret_cast(cur_sa.sa_handler); + installed_with = SA_SIGNAL; + } + + if (handler == SIG_DFL) { + continue; + } + + trace.append ("\n"); + const char *symbol_name = nullptr; + const char *file_name = nullptr; + if (handler == SIG_IGN) { + symbol_name = SIG_IGNORED; + } else { + if (dladdr (handler, &info) != 0) { + symbol_name = info.dli_sname; + file_name = info.dli_fname; + } + } + + trace.append (" "); + trace.append (strsignal (i)); + trace.append (" ("); + std::snprintf (num_buf.data (), num_buf.size (), "%d", i); + trace.append (num_buf.data ()); + trace.append ("), with "); + trace.append (installed_with); + trace.append (": "); + + if (file_name != nullptr) { + trace.append (file_name); + trace.append (" "); + } + + if (symbol_name == nullptr) { + std::snprintf (num_buf.data (), num_buf.size (), "%p", handler); + trace.append (num_buf.data ()); + } else { + trace.append (symbol_name); + } + } +} diff --git a/src/monodroid/jni/native-tracing.hh b/src/monodroid/jni/native-tracing.hh new file mode 100644 index 00000000000..29a5db05bd7 --- /dev/null +++ b/src/monodroid/jni/native-tracing.hh @@ -0,0 +1,42 @@ +#if !defined (__NATIVE_TRACING_HH) +#define __NATIVE_TRACING_HH + +#include +#include +#include + +#define UNW_LOCAL_ONLY +#include + +namespace xamarin::android::internal +{ + class NativeTracing final + { + static constexpr int PRIORITY = ANDROID_LOG_INFO; + + public: + static void get_native_backtrace (std::string& trace) noexcept; + static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept; + static void get_interesting_signal_handlers (std::string &trace) noexcept; + + private: + static void append_frame_number (std::string &trace, size_t count) noexcept; + static unw_word_t adjust_address (unw_word_t addr) noexcept; + static void init_jni (JNIEnv *env) noexcept; + static bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; + + template + static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept; + + private: + // java.lang.Thread + inline static jclass java_lang_Thread; + inline static jmethodID java_lang_Thread_currentThread; + inline static jmethodID java_lang_Thread_getStackTrace; + + // java.lang.StackTraceElement + inline static jclass java_lang_StackTraceElement; + inline static jmethodID java_lang_StackTraceElement_toString; + }; +} +#endif // ndef __NATIVE_TRACING_HH diff --git a/src/monodroid/jni/new_delete.cc b/src/monodroid/jni/new_delete.cc index 13ec5a7087b..dcd3eb1d20d 100644 --- a/src/monodroid/jni/new_delete.cc +++ b/src/monodroid/jni/new_delete.cc @@ -22,7 +22,7 @@ operator new (size_t size) { void* p = do_alloc (size); if (p == nullptr) { -#if !defined (MARSHAL_METHODS_TRACING) +#if !defined (XAMARIN_TRACING) log_fatal (LOG_DEFAULT, "Out of memory in the `new` operator"); #endif xamarin::android::Helpers::abort_application (); From d0ef3e77b5b2b2919c76728c6eee0e4c0c831e92 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 13 Apr 2023 00:25:36 +0200 Subject: [PATCH 12/60] Put tracing code in a separate library Eventually to be used, optionally, by libmonodroid --- build-tools/cmake/xa_macros.cmake | 3 +- .../Microsoft.Android.Runtime.proj | 2 +- .../Tasks/LinkApplicationSharedLibraries.cs | 8 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 4 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 20 -- src/libunwind-xamarin/CMakeLists.txt | 10 + src/monodroid/CMakeLists.txt | 49 +-- src/monodroid/jni/cpp-util.hh | 12 +- src/monodroid/jni/helpers.hh | 2 +- src/monodroid/jni/marshal-methods-tracing.cc | 340 +----------------- src/monodroid/jni/marshal-methods-tracing.hh | 6 +- src/monodroid/jni/native-tracing.cc | 257 +++++++------ src/monodroid/jni/native-tracing.hh | 46 +-- 13 files changed, 229 insertions(+), 530 deletions(-) diff --git a/build-tools/cmake/xa_macros.cmake b/build-tools/cmake/xa_macros.cmake index 58e75192bfd..24812dfd82b 100644 --- a/build-tools/cmake/xa_macros.cmake +++ b/build-tools/cmake/xa_macros.cmake @@ -200,7 +200,8 @@ function(xa_common_prepare) -fstack-protector-strong -fstrict-return -fno-strict-aliasing - -ffunction-sections + -fno-function-sections + -fno-data-sections -funswitch-loops -finline-limit=300 -Wa,-noexecstack diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index eb90019fdb4..41fbd68991d 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -42,7 +42,7 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.release.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-debug-app-helper.so" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmarshal-methods-tracing.a" /> - <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libclang_rt.builtins-*-android.a" /> + <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libxamarin-native-tracing.so" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index b37001c78e2..5baaca5e93c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -130,6 +130,7 @@ IEnumerable GetLinkerConfigs () abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir); } + // "--eh-frame-hdr " + const string commonLinkerArgs = "--shared " + "--allow-shlib-undefined " + @@ -138,7 +139,6 @@ IEnumerable GetLinkerConfigs () "-z relro " + "-z noexecstack " + "--enable-new-dtags " + - "--eh-frame-hdr " + "--build-id " + "--warn-shared-textrel " + "--fatal-warnings"; @@ -210,16 +210,14 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem if (mmTracingEnabled) { string RID = MonoAndroidHelper.AbiToRid (abi); AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); - string clangLibraryAbi = MonoAndroidHelper.ArchToClangLibraryAbi (targetArch); - string builtinsLibName = $"libclang_rt.builtins-{clangLibraryAbi}-android.a"; string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID); extraLibraries = new List { Path.Combine (runtimeLibsDir, "libmarshal-methods-tracing.a"), - Path.Combine (runtimeLibsDir, "libunwind_xamarin.a"), - Path.Combine (runtimeLibsDir, builtinsLibName), + $"-L \"{runtimeLibsDir}\"", $"-L \"{libStubsPath}\"", + "-lxamarin-native-tracing", "-lc", "-ldl", "-llog", // tracing uses android logger diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 32117f47533..44559414348 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -529,7 +529,7 @@ protected virtual void InitFunctionAttributes () new NounwindFunctionAttribute (), new SspstrongFunctionAttribute (), new StackProtectorBufferSizeFunctionAttribute (8), - new UwtableFunctionAttribute (), +// new UwtableFunctionAttribute (), new WillreturnFunctionAttribute (), new WriteonlyFunctionAttribute (), }; @@ -541,7 +541,7 @@ protected virtual void InitFunctionAttributes () new NounwindFunctionAttribute (), new SspstrongFunctionAttribute (), new StackProtectorBufferSizeFunctionAttribute (8), - new UwtableFunctionAttribute (), +// new UwtableFunctionAttribute (), }; FunctionAttributes[FunctionAttributesCall] = new LlvmFunctionAttributeSet { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index be910e480df..42b0232e4bc 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -176,7 +176,6 @@ sealed class MarshalMethodName const string mm_trace_func_enter_name = "_mm_trace_func_enter"; const string mm_trace_func_leave_name = "_mm_trace_func_leave"; - const string mm_trace_init_name = "_mm_trace_init"; ICollection uniqueAssemblyNames; int numberOfAssembliesInApk; @@ -209,7 +208,6 @@ sealed class MarshalMethodName // Tracing LlvmIrVariableReference? mm_trace_func_enter_ref; LlvmIrVariableReference? mm_trace_func_leave_ref; - LlvmIrVariableReference? mm_trace_init_ref; readonly bool generateEmptyCode; readonly MarshalMethodsTracingMode tracingMode; @@ -672,14 +670,6 @@ void WriteInitTracing (LlvmIrGenerator generator) } // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - var mm_trace_init_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - } - ); - mm_trace_init_ref = new LlvmIrVariableReference (mm_trace_init_sig, mm_trace_init_name, isGlobal: true); - var mm_trace_func_enter_or_leave_params = new List { new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), @@ -702,7 +692,6 @@ void WriteInitTracing (LlvmIrGenerator generator) ); mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); - WriteTraceDeclaration (mm_trace_init_name, mm_trace_init_sig.ReturnType, mm_trace_init_sig.Parameters); WriteTraceDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig.ReturnType, mm_trace_func_enter_sig.Parameters); WriteTraceDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig.ReturnType, mm_trace_func_leave_sig.Parameters); @@ -871,15 +860,6 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) generator.WriteFunctionStart (func); generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); - if (tracingMode != MarshalMethodsTracingMode.None) { - generator.EmitCall ( - func, - mm_trace_init_ref, - new List { - new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv *env - } - ); - } generator.WriteFunctionEnd (func); diff --git a/src/libunwind-xamarin/CMakeLists.txt b/src/libunwind-xamarin/CMakeLists.txt index 74c8ae666f7..7eb15e9edc3 100644 --- a/src/libunwind-xamarin/CMakeLists.txt +++ b/src/libunwind-xamarin/CMakeLists.txt @@ -84,6 +84,11 @@ else() list(APPEND LOCAL_COMPILER_ARGS -s -fomit-frame-pointer) add_compile_definitions(NDEBUG) endif() +list(APPEND LOCAL_COMPILER_ARGS + ${XA_DEFAULT_SYMBOL_VISIBILITY} + -fno-asynchronous-unwind-tables + -fno-unwind-tables +) xa_check_c_flags(XA_C_FLAGS "${LOCAL_COMPILER_ARGS}") xa_check_c_linker_flags(XA_C_LINKER_FLAGS "${LOCAL_COMPILER_ARGS}") @@ -370,3 +375,8 @@ add_library(${LIBUNWIND_LIBRARY_NAME} STATIC ${LIBUNWIND_XAMARIN_SOURCES} ) + +target_link_options( + ${LIBUNWIND_LIBRARY_NAME} + PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} +) diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 001d58579c2..4ad50cedee2 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -204,19 +204,15 @@ if(ENABLE_NET) if(ANDROID_ABI MATCHES "^arm64-v8a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM64}") - set(COMPILER_BUILTINS_LIB_ABI "aarch64") set(SYSROOT_ABI_LIB_DIR "aarch64-linux-android") elseif(ANDROID_ABI MATCHES "^armeabi-v7a") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_ARM}") - set(COMPILER_BUILTINS_LIB_ABI "arm") set(SYSROOT_ABI_LIB_DIR "arm-linux-androideabi") elseif(ANDROID_ABI MATCHES "^x86_64") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86_64}") - set(COMPILER_BUILTINS_LIB_ABI "x86_64") set(SYSROOT_ABI_LIB_DIR "x86_64-linux-android") elseif(ANDROID_ABI MATCHES "^x86") set(NET_RUNTIME_DIR "${NETCORE_APP_RUNTIME_DIR_X86}") - set(COMPILER_BUILTINS_LIB_ABI "i686") set(SYSROOT_ABI_LIB_DIR "i686-linux-android") else() message(FATAL "${ANDROID_ABI} is not supported for .NET 6+ builds") @@ -578,7 +574,6 @@ if(ANDROID AND ENABLE_NET) ) set(MARSHAL_METHODS_TRACING_SOURCES - ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/src/cxa_demangle.cpp ${SOURCES_DIR}/cxx-abi/string.cc ${SOURCES_DIR}/cxx-abi/terminate.cc ${SOURCES_DIR}/helpers.cc @@ -725,21 +720,6 @@ add_library( if(ANDROID) if(ENABLE_NET AND NOT DEBUG_BUILD) - execute_process( - COMMAND ${CMAKE_CXX_COMPILER} -print-runtime-dir - RESULT_VARIABLE _CXX_COMPILER_EXIT_CODE - OUTPUT_VARIABLE _CXX_COMPILER_RUNTIME_DIR - ECHO_OUTPUT_VARIABLE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if(NOT ${_CXX_COMPILER_EXIT_CODE} EQUAL 0) - message(FATAL_ERROR "Unable to determine compiler runtime directory") - endif() - - message(STATUS "Clang runtime dir: ${_CXX_COMPILER_RUNTIME_DIR}") - message(STATUS "ABI dir: ${SYSROOT_ABI_LIB_DIR}") - set(BUILTINS_LIB ${_CXX_COMPILER_RUNTIME_DIR}/libclang_rt.builtins-${COMPILER_BUILTINS_LIB_ABI}-android.a) set(CPP_ABI_PATH ${CMAKE_SYSROOT}/usr/lib/${SYSROOT_ABI_LIB_DIR}/libc++abi.a) add_library( @@ -760,6 +740,14 @@ if(ANDROID) PRIVATE # Avoid the 'warning: dynamic exception specifications are deprecated' warning from libc++ headers -Wno-deprecated-dynamic-exception-spec + ${XA_DEFAULT_SYMBOL_VISIBILITY} + # Prevent genration of the .eh_frame section (we don't use exceptions and don't need it) + -fno-asynchronous-unwind-tables + ) + + target_link_options( + ${XAMARIN_NATIVE_TRACING_LIB} + PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} ) target_link_libraries( @@ -781,6 +769,13 @@ if(ANDROID) STATIC ${MARSHAL_METHODS_TRACING_SOURCES} ) + target_include_directories( + ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} + ) + target_compile_definitions( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} PRIVATE @@ -790,19 +785,9 @@ if(ANDROID) target_compile_options( ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} PRIVATE - # Avoid the 'warning: dynamic exception specifications are deprecated' warning from libc++ headers - -Wno-deprecated-dynamic-exception-spec + # Prevent genration of the .eh_frame section (we don't use exceptions and don't need it) + -fno-asynchronous-unwind-tables ) - - target_include_directories( - ${XAMARIN_MARSHAL_METHODS_TRACING_LIB} BEFORE - PRIVATE - ${LIBUNWIND_SOURCE_DIR}/include - ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} - ${NDK_CXX_LIBCPPABI_SOURCE_PATH}/include - ) - - file(COPY ${BUILTINS_LIB} DESTINATION ${XA_LIBRARY_OUTPUT_DIRECTORY}) endif() # Ugly, but this is the only way to change LZ4 symbols visibility without modifying lz4.h diff --git a/src/monodroid/jni/cpp-util.hh b/src/monodroid/jni/cpp-util.hh index eac76f0625e..961fca4d876 100644 --- a/src/monodroid/jni/cpp-util.hh +++ b/src/monodroid/jni/cpp-util.hh @@ -49,9 +49,19 @@ namespace xamarin::android template struct CDeleter final { + using UnderlyingType = std::remove_cv_t; + void operator() (T* p) { - std::free (p); + UnderlyingType *ptr; + + if constexpr (std::is_const_v) { + ptr = const_cast*> (p); + } else { + ptr = p; + } + + std::free (reinterpret_cast(ptr)); } }; diff --git a/src/monodroid/jni/helpers.hh b/src/monodroid/jni/helpers.hh index 2725b4d428c..66832645dfb 100644 --- a/src/monodroid/jni/helpers.hh +++ b/src/monodroid/jni/helpers.hh @@ -12,7 +12,7 @@ namespace xamarin::android #define ADD_WITH_OVERFLOW_CHECK(__ret_type__, __a__, __b__) xamarin::android::Helpers::add_with_overflow_check<__ret_type__>(__FILE__, __LINE__, (__a__), (__b__)) #define MULTIPLY_WITH_OVERFLOW_CHECK(__ret_type__, __a__, __b__) xamarin::android::Helpers::multiply_with_overflow_check<__ret_type__>(__FILE__, __LINE__, (__a__), (__b__)) - class Helpers + class [[gnu::visibility("hidden")]] Helpers { public: template diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 7f647c6f0f2..7f2b9ea03fc 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -2,279 +2,23 @@ #include #include #include +#include #include -#include -#include -#include - #include -#define UNW_LOCAL_ONLY -#include - #include "marshal-methods-tracing.hh" #include "marshal-methods-utilities.hh" +#include "cpp-util.hh" #include "helpers.hh" +#include "native-tracing.hh" +using namespace xamarin::android; using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; constexpr char LEAD[] = "MM: "; -// java.lang.Thread -static jclass java_lang_Thread; -static jmethodID java_lang_Thread_currentThread; -static jmethodID java_lang_Thread_getStackTrace; - -// java.lang.StackTraceElement -static jclass java_lang_StackTraceElement; -static jmethodID java_lang_StackTraceElement_toString; - -// TODO: implement Java trace (use similar approach as our managed code uses, by instantiating a Java exception) -// https://developer.android.com/reference/java/lang/Error?hl=en and use `getStackTrace` - -// TODO: see if MonoVM could implement https://www.nongnu.org/libunwind/man/libunwind-dynamic(3).html -// - -// https://www.nongnu.org/libunwind/docs.html - -[[gnu::always_inline]] -unw_word_t adjust_address (unw_word_t addr) noexcept -{ - // This is what bionic does, let's do the same so that our backtrace addresses match bionic output - // Code copied verbatim from - // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 - if (addr != 0) { -#if defined (__arm__) - // If the address is suspiciously low, do nothing to avoid a segfault trying - // to access this memory. - if (addr >= 4096) { - // Check bits [15:11] of the first halfword assuming the instruction - // is 32 bits long. If the bits are any of these values, then our - // assumption was correct: - // b11101 - // b11110 - // b11111 - // Otherwise, this is a 16 bit instruction. - uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; - if (value == 0x1f || value == 0x1e || value == 0x1d) { - return addr - 4; - } - - return addr - 2; - } -#elif defined (__aarch64__) - // All instructions are 4 bytes long, skip back one instruction. - return addr - 4; -#elif defined (__i386__) || defined (__x86_64__) - // It's difficult to decode exactly where the previous instruction is, - // so subtract 1 to estimate where the instruction lives. - return addr - 1; -#endif - } - - return addr; -} - -[[gnu::always_inline]] -static void append_frame_number (std::string &trace, size_t count) noexcept -{ - std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible - // additions (sign, padding, punctuation etc) - trace.append (" #"); - std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); - trace.append (num_buf.data ()); -} - -[[gnu::always_inline]] -static void get_native_backtrace (std::string& trace) noexcept -{ - constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; - - unw_cursor_t cursor; - unw_context_t uc; - unw_word_t ip; - unw_word_t offp; - std::array name_buf; - std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible - // additions (sign, padding, punctuation etc) - const char *symbol_name; - Dl_info info; - - unw_getcontext (&uc); - unw_init_local (&cursor, &uc); - - size_t frame_counter = 0; - - trace.append ("\n Native stack trace:"); - while (unw_step (&cursor) > 0) { - trace.append ("\n"); - - unw_get_reg (&cursor, UNW_REG_IP, &ip); - ip = adjust_address (ip); - - auto ptr = reinterpret_cast(ip); - const char *fname = nullptr; - const void *symptr = nullptr; - unw_word_t frame_offset = 0; - bool info_valid = false; - - if (dladdr (ptr, &info) != 0) { - if (info.dli_fname != nullptr) { - fname = info.dli_fname; - } - symptr = info.dli_sname; - frame_offset = ip - reinterpret_cast(info.dli_fbase); - info_valid = true; - } else { - frame_offset = ip; - } - - append_frame_number (trace, frame_counter++); - - std::snprintf (num_buf.data (), num_buf.size (), "%0*zx (", FRAME_OFFSET_WIDTH, frame_offset); - trace.append (num_buf.data ()); - std::snprintf (num_buf.data (), num_buf.size (), "%p) ", ptr); - trace.append (num_buf.data ()); - - // TODO: consider searching /proc/self/maps for the beginning of the corresponding region to calculate the - // correct offset (like done in bionic stack trace) - trace.append (fname != nullptr ? fname : "[anonymous]"); - - bool symbol_name_allocated = false; - offp = 0; - if (unw_get_proc_name (&cursor, name_buf.data (), name_buf.size (), &offp) == 0) { - symbol_name = name_buf.data (); - } else if (info_valid && info.dli_sname != nullptr) { - symbol_name = info.dli_sname; - } else { - symbol_name = nullptr; - } - offp = adjust_address (offp); - - if (symbol_name != nullptr) { - char *demangled_symbol_name; - int demangle_status; - - // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler - demangled_symbol_name = abi::__cxa_demangle (symbol_name, nullptr, nullptr, &demangle_status); - symbol_name_allocated = demangle_status == 0 && demangled_symbol_name != nullptr; - if (symbol_name_allocated) { - symbol_name = demangled_symbol_name; - } - } - - if (symbol_name != nullptr) { - trace.append (" "); - trace.append (symbol_name); - if (offp != 0) { - trace.append (" + "); - std::snprintf (num_buf.data (), num_buf.size (), "%zu", offp); - trace.append (num_buf.data ()); - } - } - - if (symptr != nullptr) { - trace.append (" (symaddr: "); - std::snprintf (num_buf.data (), num_buf.size (), "%p", symptr); - trace.append (num_buf.data ()); - trace.append (")"); - } - - if (symbol_name_allocated && symbol_name != nullptr) { - std::free (reinterpret_cast(const_cast(symbol_name))); - } - } -} - -[[gnu::always_inline]] -static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept -{ - // TODO: error handling - trace.append ("\n Java stack trace:"); - jobject current_thread = env->CallStaticObjectMethod (java_lang_Thread, java_lang_Thread_currentThread); - auto stack_trace_array = static_cast(env->CallNonvirtualObjectMethod (current_thread, java_lang_Thread, java_lang_Thread_getStackTrace)); - jsize nframes = env->GetArrayLength (stack_trace_array); - - for (jsize i = 0; i < nframes; i++) { - jobject frame = env->GetObjectArrayElement (stack_trace_array, i); - auto frame_desc_java = static_cast(env->CallObjectMethod (frame, java_lang_StackTraceElement_toString)); - const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); - - trace.append ("\n"); - append_frame_number (trace, i); - trace.append (frame_desc); - env->ReleaseStringUTFChars (frame_desc_java, frame_desc); - } -} - -[[gnu::always_inline]] -static void get_interesting_signal_handlers (std::string &trace) noexcept -{ - constexpr char SA_SIGNAL[] = "signal"; - constexpr char SA_SIGACTION[] = "sigaction"; - constexpr char SIG_IGNORED[] = "[ignored]"; - - trace.append ("\n Signal handlers:"); - - std::array num_buf; - Dl_info info; - struct sigaction cur_sa; - for (int i = 0; i < _NSIG; i++) { - if (sigaction (i, nullptr, &cur_sa) != 0) { - continue; // ignore - } - - void *handler; - const char *installed_with; - if (cur_sa.sa_flags & SA_SIGINFO) { - handler = reinterpret_cast(cur_sa.sa_sigaction); - installed_with = SA_SIGACTION; - } else { - handler = reinterpret_cast(cur_sa.sa_handler); - installed_with = SA_SIGNAL; - } - - if (handler == SIG_DFL) { - continue; - } - - trace.append ("\n"); - const char *symbol_name = nullptr; - const char *file_name = nullptr; - if (handler == SIG_IGN) { - symbol_name = SIG_IGNORED; - } else { - if (dladdr (handler, &info) != 0) { - symbol_name = info.dli_sname; - file_name = info.dli_fname; - } - } - - trace.append (" "); - trace.append (strsignal (i)); - trace.append (" ("); - std::snprintf (num_buf.data (), num_buf.size (), "%d", i); - trace.append (num_buf.data ()); - trace.append ("), with "); - trace.append (installed_with); - trace.append (": "); - - if (file_name != nullptr) { - trace.append (file_name); - trace.append (" "); - } - - if (symbol_name == nullptr) { - std::snprintf (num_buf.data (), num_buf.size (), "%p", handler); - trace.append (num_buf.data ()); - } else { - trace.append (symbol_name); - } - } -} - void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept { @@ -297,11 +41,19 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 trace.append (") in class "); trace.append (class_name); - get_native_backtrace (trace); + trace.append ("\n Native stack trace:\n"); + c_unique_ptr native_trace { xa_get_native_backtrace () }; + trace.append (native_trace.get ()); trace.append ("\n"); - get_java_backtrace (env, trace); + + trace.append ("\n Java stack trace:\n"); + c_unique_ptr java_trace { xa_get_java_backtrace (env) }; + trace.append (java_trace.get ()); trace.append ("\n"); - get_interesting_signal_handlers (trace); + + trace.append ("\n Installed signal handlers:\n"); + c_unique_ptr signal_handlers { xa_get_interesting_signal_handlers () }; + trace.append (signal_handlers.get ()); __android_log_write (PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, trace.c_str ()); } else { @@ -329,65 +81,3 @@ void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_imag constexpr char LEAVE[] = "LEAVE"; _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); } - -static bool assert_valid_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept -{ - if (o != nullptr) { - return true; - } - - __android_log_print ( - PRIORITY, - SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, - "%smissing Java %s: %s", - LEAD, - missing_kind, - missing_name - ); - - return false; -} - -template -static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept -{ - if (lref == nullptr) { - return nullptr; - } - - auto ret = static_cast (env->NewGlobalRef (lref)); - env->DeleteLocalRef (lref); - return ret; -} - -void _mm_trace_init (JNIEnv *env) noexcept -{ - // We might be called more than once, ignore all but the first call - if (java_lang_Thread != nullptr) { - return; - } - - java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); - java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); - java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); - java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); - java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); - - // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one - // would do the exception check for us. - if (env->ExceptionOccurred ()) { - env->ExceptionDescribe (); - env->ExceptionClear (); - xamarin::android::Helpers::abort_application (); - } - - bool all_found = assert_valid_pointer (java_lang_Thread, "class", "java.lang.Thread"); - all_found &= assert_valid_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); - all_found &= assert_valid_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); - all_found &= assert_valid_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); - all_found &= assert_valid_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); - - if (!all_found) { - xamarin::android::Helpers::abort_application (); - } -} diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index ae7e4a74345..31f21100da3 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -12,9 +12,13 @@ inline constexpr int32_t TracingModeBasic = 0x01; inline constexpr int32_t TracingModeFull = 0x02; extern "C" { - void _mm_trace_init (JNIEnv *env) noexcept; + [[gnu::visibility("hidden")]] void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; + + [[gnu::visibility("hidden")]] void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + + [[gnu::visibility("hidden")]] void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; } diff --git a/src/monodroid/jni/native-tracing.cc b/src/monodroid/jni/native-tracing.cc index e9f7e1dd15c..554d52514b3 100644 --- a/src/monodroid/jni/native-tracing.cc +++ b/src/monodroid/jni/native-tracing.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -9,57 +10,26 @@ #include "native-tracing.hh" #include "shared-constants.hh" -using namespace xamarin::android::internal; +constexpr int PRIORITY = ANDROID_LOG_INFO; -[[gnu::always_inline]] -unw_word_t NativeTracing::adjust_address (unw_word_t addr) noexcept -{ - // This is what bionic does, let's do the same so that our backtrace addresses match bionic output - // Code copied verbatim from - // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 - if (addr != 0) { -#if defined (__arm__) - // If the address is suspiciously low, do nothing to avoid a segfault trying - // to access this memory. - if (addr >= 4096) { - // Check bits [15:11] of the first halfword assuming the instruction - // is 32 bits long. If the bits are any of these values, then our - // assumption was correct: - // b11101 - // b11110 - // b11111 - // Otherwise, this is a 16 bit instruction. - uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; - if (value == 0x1f || value == 0x1e || value == 0x1d) { - return addr - 4; - } +static void append_frame_number (std::string &trace, size_t count) noexcept; +static unw_word_t adjust_address (unw_word_t addr) noexcept; +static void init_jni (JNIEnv *env) noexcept; +static bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; - return addr - 2; - } -#elif defined (__aarch64__) - // All instructions are 4 bytes long, skip back one instruction. - return addr - 4; -#elif defined (__i386__) || defined (__x86_64__) - // It's difficult to decode exactly where the previous instruction is, - // so subtract 1 to estimate where the instruction lives. - return addr - 1; -#endif - } +template +static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept; - return addr; -} +// java.lang.Thread +static jclass java_lang_Thread; +static jmethodID java_lang_Thread_currentThread; +static jmethodID java_lang_Thread_getStackTrace; -[[gnu::always_inline]] -void NativeTracing::append_frame_number (std::string &trace, size_t count) noexcept -{ - std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible - // additions (sign, padding, punctuation etc) - trace.append (" #"); - std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); - trace.append (num_buf.data ()); -} +// java.lang.StackTraceElement +static jclass java_lang_StackTraceElement; +static jmethodID java_lang_StackTraceElement_toString; -void NativeTracing::get_native_backtrace (std::string& trace) noexcept +const char* xa_get_native_backtrace () noexcept { constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; @@ -78,9 +48,11 @@ void NativeTracing::get_native_backtrace (std::string& trace) noexcept size_t frame_counter = 0; - trace.append ("\n Native stack trace:"); + std::string trace; while (unw_step (&cursor) > 0) { - trace.append ("\n"); + if (!trace.empty ()) { + trace.append ("\n"); + } unw_get_reg (&cursor, UNW_REG_IP, &ip); ip = adjust_address (ip); @@ -157,102 +129,86 @@ void NativeTracing::get_native_backtrace (std::string& trace) noexcept std::free (reinterpret_cast(const_cast(symbol_name))); } } + + return strdup (trace.c_str ()); } -void NativeTracing::get_java_backtrace (JNIEnv *env, std::string &trace) noexcept +const char* xa_get_java_backtrace (JNIEnv *env) noexcept { + init_jni (env); + // TODO: error handling - trace.append ("\n Java stack trace:"); jobject current_thread = env->CallStaticObjectMethod (java_lang_Thread, java_lang_Thread_currentThread); auto stack_trace_array = static_cast(env->CallNonvirtualObjectMethod (current_thread, java_lang_Thread, java_lang_Thread_getStackTrace)); jsize nframes = env->GetArrayLength (stack_trace_array); + std::string trace; for (jsize i = 0; i < nframes; i++) { jobject frame = env->GetObjectArrayElement (stack_trace_array, i); auto frame_desc_java = static_cast(env->CallObjectMethod (frame, java_lang_StackTraceElement_toString)); const char *frame_desc = env->GetStringUTFChars (frame_desc_java, nullptr); - trace.append ("\n"); + if (!trace.empty ()) { + trace.append ("\n"); + } + append_frame_number (trace, i); trace.append (frame_desc); env->ReleaseStringUTFChars (frame_desc_java, frame_desc); } -} -template -TJavaPointer NativeTracing::to_gref (JNIEnv *env, TJavaPointer lref) noexcept -{ - if (lref == nullptr) { - return nullptr; - } - - auto ret = static_cast (env->NewGlobalRef (lref)); - env->DeleteLocalRef (lref); - return ret; + return strdup (trace.c_str ()); } -void NativeTracing::init_jni (JNIEnv *env) noexcept +[[gnu::always_inline]] +unw_word_t adjust_address (unw_word_t addr) noexcept { - // We might be called more than once, ignore all but the first call - if (java_lang_Thread != nullptr) { - return; - } - - // TODO: locking - - java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); - java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); - java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); - java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); - java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); - - // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one - // would do the exception check for us. - if (env->ExceptionOccurred ()) { - env->ExceptionDescribe (); - env->ExceptionClear (); - xamarin::android::Helpers::abort_application (); - } - - bool all_found = assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.Thread"); - all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); - all_found &= assert_valid_jni_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); - all_found &= assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); - all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); - - if (!all_found) { - xamarin::android::Helpers::abort_application (); - } -} + // This is what bionic does, let's do the same so that our backtrace addresses match bionic output + // Code copied verbatim from + // https://android.googlesource.com/platform/bionic/+/refs/tags/android-13.0.0_r37/libc/bionic/execinfo.cpp#50 + if (addr != 0) { +#if defined (__arm__) + // If the address is suspiciously low, do nothing to avoid a segfault trying + // to access this memory. + if (addr >= 4096) { + // Check bits [15:11] of the first halfword assuming the instruction + // is 32 bits long. If the bits are any of these values, then our + // assumption was correct: + // b11101 + // b11110 + // b11111 + // Otherwise, this is a 16 bit instruction. + uint16_t value = (*reinterpret_cast(addr - 2)) >> 11; + if (value == 0x1f || value == 0x1e || value == 0x1d) { + return addr - 4; + } -bool NativeTracing::assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept -{ - if (o != nullptr) { - return true; + return addr - 2; + } +#elif defined (__aarch64__) + // All instructions are 4 bytes long, skip back one instruction. + return addr - 4; +#elif defined (__i386__) || defined (__x86_64__) + // It's difficult to decode exactly where the previous instruction is, + // so subtract 1 to estimate where the instruction lives. + return addr - 1; +#endif } - __android_log_print ( - PRIORITY, - SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, - "missing Java %s: %s", - missing_kind, - missing_name - ); - - return false; + return addr; } -void NativeTracing::get_interesting_signal_handlers (std::string &trace) noexcept +const char* xa_get_interesting_signal_handlers () noexcept { constexpr char SA_SIGNAL[] = "signal"; constexpr char SA_SIGACTION[] = "sigaction"; constexpr char SIG_IGNORED[] = "[ignored]"; - trace.append ("\n Signal handlers:"); - std::array num_buf; Dl_info info; struct sigaction cur_sa; + std::string trace; + for (int i = 0; i < _NSIG; i++) { if (sigaction (i, nullptr, &cur_sa) != 0) { continue; // ignore @@ -272,7 +228,10 @@ void NativeTracing::get_interesting_signal_handlers (std::string &trace) noexcep continue; } - trace.append ("\n"); + if (!trace.empty ()) { + trace.append ("\n"); + } + const char *symbol_name = nullptr; const char *file_name = nullptr; if (handler == SIG_IGN) { @@ -305,4 +264,80 @@ void NativeTracing::get_interesting_signal_handlers (std::string &trace) noexcep trace.append (symbol_name); } } + + return strdup (trace.c_str ()); +} + +[[gnu::always_inline]] +void append_frame_number (std::string &trace, size_t count) noexcept +{ + std::array num_buf; // Enough for text representation of a decimal 64-bit integer + some possible + // additions (sign, padding, punctuation etc) + trace.append (" #"); + std::snprintf (num_buf.data (), num_buf.size (), "%-3zu: ", count); + trace.append (num_buf.data ()); +} + +template +[[gnu::always_inline]] +TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept +{ + if (lref == nullptr) { + return nullptr; + } + + auto ret = static_cast (env->NewGlobalRef (lref)); + env->DeleteLocalRef (lref); + return ret; +} + +void init_jni (JNIEnv *env) noexcept +{ + // We might be called more than once, ignore all but the first call + if (java_lang_Thread != nullptr) { + return; + } + + // TODO: locking + + java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); + java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); + java_lang_Thread_getStackTrace = env->GetMethodID (java_lang_Thread, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + java_lang_StackTraceElement = to_gref (env, env->FindClass ("java/lang/StackTraceElement")); + java_lang_StackTraceElement_toString = env->GetMethodID (java_lang_StackTraceElement, "toString", "()Ljava/lang/String;"); + + // We check for the Java exception and possible null pointers only here, since all the calls JNI before the last one + // would do the exception check for us. + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + + bool all_found = assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.Thread"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.Thread.currentThread ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_getStackTrace, "method", "java.lang.Thread.getStackTrace ()"); + all_found &= assert_valid_jni_pointer (java_lang_Thread, "class", "java.lang.StackTraceElement"); + all_found &= assert_valid_jni_pointer (java_lang_Thread_currentThread, "method", "java.lang.StackTraceElement.toString ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } +} + +bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept +{ + if (o != nullptr) { + return true; + } + + __android_log_print ( + PRIORITY, + xamarin::android::internal::SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, + "missing Java %s: %s", + missing_kind, + missing_name + ); + + return false; } diff --git a/src/monodroid/jni/native-tracing.hh b/src/monodroid/jni/native-tracing.hh index 29a5db05bd7..4278bacbbe6 100644 --- a/src/monodroid/jni/native-tracing.hh +++ b/src/monodroid/jni/native-tracing.hh @@ -8,35 +8,21 @@ #define UNW_LOCAL_ONLY #include -namespace xamarin::android::internal -{ - class NativeTracing final - { - static constexpr int PRIORITY = ANDROID_LOG_INFO; - - public: - static void get_native_backtrace (std::string& trace) noexcept; - static void get_java_backtrace (JNIEnv *env, std::string &trace) noexcept; - static void get_interesting_signal_handlers (std::string &trace) noexcept; - - private: - static void append_frame_number (std::string &trace, size_t count) noexcept; - static unw_word_t adjust_address (unw_word_t addr) noexcept; - static void init_jni (JNIEnv *env) noexcept; - static bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; - - template - static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept; - - private: - // java.lang.Thread - inline static jclass java_lang_Thread; - inline static jmethodID java_lang_Thread_currentThread; - inline static jmethodID java_lang_Thread_getStackTrace; - - // java.lang.StackTraceElement - inline static jclass java_lang_StackTraceElement; - inline static jmethodID java_lang_StackTraceElement_toString; - }; +// Public API must not expose any types that are part of libc++ - we don't know what version of the +// library (if any) is used by the application we're embedded in. +// +// For the same reason, we cannot return memory allocated with the `new` operator - the implementation +// used by the application's C++ code might be incompatible. For this reason, any dynamically allocated +// memory we return to the caller is allocated with the libc's `malloc` +// +extern "C" { + [[gnu::visibility("default")]] + const char* xa_get_native_backtrace () noexcept; + + [[gnu::visibility("default")]] + const char* xa_get_java_backtrace (JNIEnv *env) noexcept; + + [[gnu::visibility("default")]] + const char* xa_get_interesting_signal_handlers () noexcept; } #endif // ndef __NATIVE_TRACING_HH From 1076ed5f46fae890b136f0240a14c6398bff6697 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 13 Apr 2023 23:28:57 +0200 Subject: [PATCH 13/60] Don't package the tracing DSO unless told so The DSO is packaged only if marshal methods tracing is enabled or if native stack traces are enabled (via the `_UseNativeStackTraces` MSBuild property) Added a p/invoke which can log any combination of: native, java, managed stack traces as well as the state of signal handlers. Updated p/invoke dispatch tables --- .../Android.Runtime/JNIEnvInit.cs | 3 + .../Android.Runtime/RuntimeNativeMethods.cs | 15 ++++ ...oft.Android.Sdk.AssemblyResolution.targets | 3 +- ...soft.Android.Sdk.DefaultProperties.targets | 12 +++ .../Tasks/LinkApplicationSharedLibraries.cs | 2 - .../Tasks/ProcessNativeLibraries.cs | 4 + .../Xamarin.Android.Common.targets | 7 -- src/monodroid/CMakeLists.txt | 8 ++ src/monodroid/jni/generate-pinvoke-tables.cc | 5 ++ src/monodroid/jni/monodroid-glue-internal.hh | 29 ++++++ src/monodroid/jni/monodroid-tracing.cc | 89 +++++++++++++++++++ src/monodroid/jni/native-tracing.cc | 12 ++- src/monodroid/jni/native-tracing.hh | 3 + src/monodroid/jni/pinvoke-override-api.cc | 9 ++ src/monodroid/jni/pinvoke-tables.include | 14 ++- 15 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 src/monodroid/jni/monodroid-tracing.cc diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 9311757806e..eb30211a3c0 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -80,6 +80,9 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In #endif internal static unsafe void Initialize (JnienvInitializeArgs* args) { +#if NETCOREAPP + RuntimeNativeMethods.monodroid_log_traces (TraceKind.All, "In JNIEnvInit.Initialize"); +#endif IntPtr total_timing_sequence = IntPtr.Zero; IntPtr partial_timing_sequence = IntPtr.Zero; diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs index 3487514a6a7..9afa41e83a7 100644 --- a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -6,8 +6,23 @@ namespace Android.Runtime { + // Values must be identical to those in src/monodroid/jni/monodroid-glue-internal.hh + [Flags] + enum TraceKind : uint + { + Java = 0x01, + Managed = 0x02, + Native = 0x04, + Signals = 0x08, + + All = Java | Managed | Native | Signals, + } + internal static class RuntimeNativeMethods { + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_log_traces (TraceKind kind, string first_line); + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 51a009bd776..a492bc1e60c 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -220,7 +220,8 @@ _ResolveAssemblies MSBuild target. + IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)" + IncludeNativeTracing="$(_UseNativeStackTraces)"> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 51ed5880c64..41ed02a7e16 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -39,6 +39,18 @@ false + + + + <_AndroidMarshalMethodsTracingMode Condition=" '$(_AndroidMarshalMethodsTracingMode)' == '' ">none + <_AndroidUseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == '' And '$(_AndroidMarshalMethodsTracingMode)' == 'none' ">false + <_AndroidUseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == '' And '$(_AndroidMarshalMethodsTracingMode)' != 'none' ">true + <_UseNativeStackTraces Condition=" '$(_AndroidUseNativeStackTraces)' == 'true' Or '$(_AndroidMarshalMethodsTracingMode)' != 'none' ">true + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 5baaca5e93c..64b27983784 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -219,8 +219,6 @@ InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem $"-L \"{libStubsPath}\"", "-lxamarin-native-tracing", "-lc", - "-ldl", - "-llog", // tracing uses android logger }; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs index f853c2c98b2..af6008885ae 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs @@ -28,6 +28,7 @@ public class ProcessNativeLibraries : AndroidTask public ITaskItem [] Components { get; set; } public bool IncludeDebugSymbols { get; set; } + public bool IncludeNativeTracing { get; set; } [Output] public ITaskItem [] OutputLibraries { get; set; } @@ -80,6 +81,9 @@ public override bool RunTask () if (!wantedComponents.Contains (fileName)) { continue; } + } else if (!IncludeNativeTracing && String.Compare ("libxamarin-native-tracing", fileName, StringComparison.Ordinal) == 0) { + Log.LogDebugMessage ($"Excluding '{library.ItemSpec}' because native stack traces support is disabled"); + continue; } output.Add (library); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 225ff46f254..9763058d7d4 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -340,13 +340,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) - - - <_AndroidMarshalMethodsTracingMode Condition=" '$(_AndroidMarshalMethodsTracingMode)' == '' ">none diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 4ad50cedee2..aedccc1dfb5 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -582,6 +582,7 @@ if(ANDROID AND ENABLE_NET) ) list(APPEND XAMARIN_MONODROID_SOURCES + ${SOURCES_DIR}/monodroid-tracing.cc ${SOURCES_DIR}/monovm-properties.cc ${SOURCES_DIR}/pinvoke-override-api.cc ${SOURCES_DIR}/java_interop_api.c @@ -799,6 +800,13 @@ target_compile_options( PRIVATE ${XA_DEFAULT_SYMBOL_VISIBILITY} "${LZ4_VISIBILITY_OPTS}" ) +target_include_directories( + ${XAMARIN_MONO_ANDROID_LIB} BEFORE + PRIVATE + ${LIBUNWIND_SOURCE_DIR}/include + ${LIBUNWIND_HEADERS_DIR}/${CMAKE_ANDROID_ARCH_ABI} +) + if(APPLE) set_target_properties( ${XAMARIN_MONO_ANDROID_LIB} diff --git a/src/monodroid/jni/generate-pinvoke-tables.cc b/src/monodroid/jni/generate-pinvoke-tables.cc index 85ab21c1d8f..aaa3e765ddb 100644 --- a/src/monodroid/jni/generate-pinvoke-tables.cc +++ b/src/monodroid/jni/generate-pinvoke-tables.cc @@ -249,6 +249,7 @@ const std::vector internal_pinvoke_names = { "_monodroid_gref_log_delete", "_monodroid_gref_log_new", "monodroid_log", + "monodroid_log_traces", "_monodroid_lookup_replacement_type", "_monodroid_lookup_replacement_method_info", "_monodroid_lref_log_delete", @@ -310,6 +311,8 @@ const std::vector dotnet_pinvoke_names = { "CompressionNative_InflateEnd", "CompressionNative_InflateInit2_", "CompressionNative_InflateReset", + "_kBrotliContextLookupTable", + "_kBrotliPrefixCodeRanges", // libSystem.Native.so "SystemNative_Abort", @@ -437,6 +440,7 @@ const std::vector dotnet_pinvoke_names = { "SystemNative_GetSystemTimeAsTicks", "SystemNative_GetTcpGlobalStatistics", "SystemNative_GetTimestamp", + "SystemNative_GetTimeZoneData", "SystemNative_GetUdpGlobalStatistics", "SystemNative_GetUnixRelease", "SystemNative_GetUnixVersion", @@ -705,6 +709,7 @@ const std::vector dotnet_pinvoke_names = { "CryptoNative_HmacOneShot", "CryptoNative_HmacReset", "CryptoNative_HmacUpdate", + "Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate", }; template diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 7aacfe88d52..5bc0447af9a 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -2,6 +2,7 @@ #ifndef __MONODROID_GLUE_INTERNAL_H #define __MONODROID_GLUE_INTERNAL_H +#include #include #include #include "android-system.hh" @@ -66,6 +67,15 @@ namespace xamarin::android::internal } }; + // Values must be identical to those in src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs + enum class TraceKind : uint32_t + { + Java = 0x01, + Managed = 0x02, + Native = 0x04, + Signals = 0x08, + }; + class MonodroidRuntime { #if defined (NET) @@ -159,6 +169,8 @@ namespace xamarin::android::internal static constexpr char mono_component_diagnostics_tracing_name[] = "libmono-component-diagnostics_tracing.so"; static constexpr hash_t mono_component_diagnostics_tracing_hash = xxhash::hash (mono_component_diagnostics_tracing_name); + static constexpr char xamarin_native_tracing_name[] = "libxamarin-native-tracing.so"; + #if !defined (NET) #define MAKE_API_DSO_NAME(_ext_) "libxa-internal-api." # _ext_ #if defined (WINDOWS) @@ -218,6 +230,7 @@ namespace xamarin::android::internal } #if defined (NET) + void log_traces (JNIEnv *env, TraceKind kind, const char *first_line) noexcept; void propagate_uncaught_exception (JNIEnv *env, jobject javaThread, jthrowable javaException); #else // def NET void propagate_uncaught_exception (MonoDomain *domain, JNIEnv *env, jobject javaThread, jthrowable javaException); @@ -271,6 +284,22 @@ namespace xamarin::android::internal static PinvokeEntry* find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept; static void* handle_other_pinvoke_request (const char *library_name, hash_t library_name_hash, const char *entrypoint_name, hash_t entrypoint_name_hash) noexcept; static void* monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name); + + template + static void load_symbol (void *handle, const char *name, TFunc*& fnptr) noexcept + { + char *err = nullptr; + void *symptr = monodroid_dlsym (handle, name, &err, nullptr); + + if (symptr == nullptr) { + log_warn (LOG_DEFAULT, "Failed to load symbol '%s' library with handle %p. %s", name, handle, err == nullptr ? "Unknown error" : err); + fnptr = nullptr; + return; + } + + fnptr = reinterpret_cast(symptr); + } + #endif // def NET static void* monodroid_dlopen_ignore_component_or_load (hash_t hash, const char *name, int flags, char **err) noexcept; static void* monodroid_dlopen (const char *name, int flags, char **err) noexcept; diff --git a/src/monodroid/jni/monodroid-tracing.cc b/src/monodroid/jni/monodroid-tracing.cc new file mode 100644 index 00000000000..53731ff28fa --- /dev/null +++ b/src/monodroid/jni/monodroid-tracing.cc @@ -0,0 +1,89 @@ +#include +#include + +#include "java-interop-logger.h" +#include "mono/utils/details/mono-dl-fallback-types.h" +#include "monodroid-glue-internal.hh" +#include "native-tracing.hh" +#include "cppcompat.hh" +#include + +using namespace xamarin::android::internal; + +static decltype(xa_get_native_backtrace)* _xa_get_native_backtrace; +static decltype(xa_get_managed_backtrace)* _xa_get_managed_backtrace; +static decltype(xa_get_java_backtrace)* _xa_get_java_backtrace; +static decltype(xa_get_interesting_signal_handlers)* _xa_get_interesting_signal_handlers; +static bool tracing_init_done; + +static std::mutex tracing_init_lock {}; + +void +MonodroidRuntime::log_traces (JNIEnv *env, TraceKind kind, const char *first_line) noexcept +{ + if (!tracing_init_done) { + std::lock_guard lock (tracing_init_lock); + + char *err = nullptr; + void *handle = monodroid_dlopen (xamarin_native_tracing_name, MONO_DL_EAGER, &err, nullptr); + if (handle == nullptr) { + log_warn (LOG_DEFAULT, "Failed to load native tracing library '%s'. %s", xamarin_native_tracing_name, err == nullptr ? "Unknown error" : err); + } else { + load_symbol (handle, "xa_get_native_backtrace", _xa_get_native_backtrace); + load_symbol (handle, "xa_get_managed_backtrace", _xa_get_managed_backtrace); + load_symbol (handle, "xa_get_java_backtrace", _xa_get_java_backtrace); + load_symbol (handle, "xa_get_interesting_signal_handlers", _xa_get_interesting_signal_handlers); + } + + tracing_init_done = true; + } + + std::string trace; + if (first_line != nullptr) { + trace.append (first_line); + trace.append ("\n"); + } + + bool need_newline = false; + auto add_trace = [&] (c_unique_ptr const& data, const char *banner) -> void { + if (need_newline) { + trace.append ("\n "); + } else { + trace.append (" "); + } + + trace.append (banner); + if (!data) { + trace.append (": unavailable"); + } else { + trace.append (":\n"); + trace.append (data.get ()); + trace.append ("\n"); + } + need_newline = true; + }; + + if ((kind & TraceKind::Native) == TraceKind::Native) { + c_unique_ptr native { _xa_get_native_backtrace != nullptr ? _xa_get_native_backtrace () : nullptr }; + add_trace (native, "Native stacktrace"); + } + + if ((kind & TraceKind::Java) == TraceKind::Java && env != nullptr) { + c_unique_ptr java { _xa_get_java_backtrace != nullptr ?_xa_get_java_backtrace (env) : nullptr }; + add_trace (java, "Java stacktrace"); + } + + if ((kind & TraceKind::Managed) == TraceKind::Managed) { + c_unique_ptr managed { _xa_get_managed_backtrace != nullptr ? _xa_get_managed_backtrace () : nullptr }; + add_trace (managed, "Managed stacktrace"); + } + + if ((kind & TraceKind::Signals) == TraceKind::Signals) { + c_unique_ptr signals { _xa_get_interesting_signal_handlers != nullptr ? _xa_get_interesting_signal_handlers () : nullptr }; + add_trace (signals, "Signal handlers"); + } + + // Use this call because it is slightly faster (doesn't need to parse the format) and it doesn't truncate longer + // strings (like the stack traces we've just produced), unlike __android_log_vprint used by our `log_*` functions + __android_log_write (ANDROID_LOG_INFO, SharedConstants::LOG_CATEGORY_NAME_MONODROID, trace.c_str ()); +} diff --git a/src/monodroid/jni/native-tracing.cc b/src/monodroid/jni/native-tracing.cc index 554d52514b3..7db29ef5069 100644 --- a/src/monodroid/jni/native-tracing.cc +++ b/src/monodroid/jni/native-tracing.cc @@ -9,6 +9,7 @@ #include "native-tracing.hh" #include "shared-constants.hh" +#include "cppcompat.hh" constexpr int PRIORITY = ANDROID_LOG_INFO; @@ -29,6 +30,15 @@ static jmethodID java_lang_Thread_getStackTrace; static jclass java_lang_StackTraceElement; static jmethodID java_lang_StackTraceElement_toString; +static std::mutex java_init_lock; + +const char* xa_get_managed_backtrace () noexcept +{ + std::string trace { "TODO: implement" }; + + return strdup (trace.c_str ()); +} + const char* xa_get_native_backtrace () noexcept { constexpr int FRAME_OFFSET_WIDTH = sizeof(uintptr_t) * 2; @@ -298,7 +308,7 @@ void init_jni (JNIEnv *env) noexcept return; } - // TODO: locking + std::lock_guard lock (java_init_lock); java_lang_Thread = to_gref (env, env->FindClass ("java/lang/Thread")); java_lang_Thread_currentThread = env->GetStaticMethodID (java_lang_Thread, "currentThread", "()Ljava/lang/Thread;"); diff --git a/src/monodroid/jni/native-tracing.hh b/src/monodroid/jni/native-tracing.hh index 4278bacbbe6..4bb19988bde 100644 --- a/src/monodroid/jni/native-tracing.hh +++ b/src/monodroid/jni/native-tracing.hh @@ -22,6 +22,9 @@ extern "C" { [[gnu::visibility("default")]] const char* xa_get_java_backtrace (JNIEnv *env) noexcept; + [[gnu::visibility("default")]] + const char* xa_get_managed_backtrace () noexcept; + [[gnu::visibility("default")]] const char* xa_get_interesting_signal_handlers () noexcept; } diff --git a/src/monodroid/jni/pinvoke-override-api.cc b/src/monodroid/jni/pinvoke-override-api.cc index ab808b33646..0306f1487b6 100644 --- a/src/monodroid/jni/pinvoke-override-api.cc +++ b/src/monodroid/jni/pinvoke-override-api.cc @@ -367,6 +367,15 @@ _monodroid_lookup_replacement_method_info (const char *jniSourceType, const char return JniRemapping::lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); } +static void +monodroid_log_traces (uint32_t kind, const char *first_line) +{ + JNIEnv *env = osBridge.ensure_jnienv (); + auto tk = static_cast(kind); + + monodroidRuntime.log_traces (env, tk, first_line); +} + #include "pinvoke-tables.include" MonodroidRuntime::pinvoke_library_map MonodroidRuntime::other_pinvoke_map (MonodroidRuntime::LIBRARY_MAP_INITIAL_BUCKET_COUNT); diff --git a/src/monodroid/jni/pinvoke-tables.include b/src/monodroid/jni/pinvoke-tables.include index 37c91a53f3f..c4b3234194b 100644 --- a/src/monodroid/jni/pinvoke-tables.include +++ b/src/monodroid/jni/pinvoke-tables.include @@ -159,6 +159,7 @@ static PinvokeEntry internal_pinvokes[] = { {0x9ae63744afbe1583, "java_interop_jnienv_pop_local_frame", reinterpret_cast(&java_interop_jnienv_pop_local_frame)}, {0x9b58050ba8a4ab05, "java_interop_jnienv_set_byte_array_region", reinterpret_cast(&java_interop_jnienv_set_byte_array_region)}, {0x9c8899c01b365588, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, + {0x9dd1797d09aee43f, "monodroid_log_traces", reinterpret_cast(&monodroid_log_traces)}, {0x9df3b909de1a9e86, "java_interop_jnienv_define_class", reinterpret_cast(&java_interop_jnienv_define_class)}, {0x9e1d58b0ef53edf6, "java_interop_jnienv_get_byte_array_elements", reinterpret_cast(&java_interop_jnienv_get_byte_array_elements)}, {0x9eb010982a69aa1a, "java_interop_jnienv_new_weak_global_ref", reinterpret_cast(&java_interop_jnienv_new_weak_global_ref)}, @@ -377,6 +378,7 @@ static PinvokeEntry dotnet_pinvokes[] = { {0x534a064c11a1da14, "SystemNative_Sync", nullptr}, {0x536599a0b4941c95, "SystemNative_InterfaceNameToIndex", nullptr}, {0x53cb0f96f17cff42, "AndroidCryptoNative_SSLStreamGetProtocol", nullptr}, + {0x53e595a8ecfa62da, "_kBrotliContextLookupTable", nullptr}, {0x54524b9f89e55d3d, "BrotliDecoderTakeOutput", nullptr}, {0x5648a01a8d57fdf3, "AndroidCryptoNative_X509DecodeCollection", nullptr}, {0x5823d482b63b2860, "SystemNative_GetIcmpv4GlobalStatistics", nullptr}, @@ -431,6 +433,7 @@ static PinvokeEntry dotnet_pinvokes[] = { {0x6fcf389a08fab67e, "BrotliDecoderGetErrorCode", nullptr}, {0x701cf6776d74a875, "SystemNative_Read", nullptr}, {0x70677741626a2e15, "AndroidCryptoNative_SSLStreamCreateWithCertificates", nullptr}, + {0x70a8c39859645567, "Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate", nullptr}, {0x71218925a8d060b1, "SystemNative_GetNetworkInterfaces", nullptr}, {0x7301d7267cea8ff8, "AndroidCryptoNative_Aes128Cbc", nullptr}, {0x734d29a9e11cade9, "AndroidCryptoNative_SetRsaParameters", nullptr}, @@ -464,6 +467,7 @@ static PinvokeEntry dotnet_pinvokes[] = { {0x86178ff6f2ff90f4, "SystemNative_PlatformSupportsDualModeIPv4PacketInfo", nullptr}, {0x866d289872995b56, "AndroidCryptoNative_X509StoreEnumerateCertificates", nullptr}, {0x86b09bf921350f8c, "SystemNative_Log", nullptr}, + {0x8755aae823b5825d, "_kBrotliPrefixCodeRanges", nullptr}, {0x87c7d4815a4fd057, "CryptoNative_EvpSha1", nullptr}, {0x885b44a0a26bd2b1, "SystemNative_iOSSupportVersion", nullptr}, {0x88609da0c505eef2, "SystemNative_GetSockName", nullptr}, @@ -522,6 +526,7 @@ static PinvokeEntry dotnet_pinvokes[] = { {0xa23f5e3f758fc4c0, "SystemNative_GetPid", nullptr}, {0xa2cd91a41d9cfa7c, "AndroidCryptoNative_Aes256Cbc", nullptr}, {0xa482e9592d7fc872, "SystemNative_CloseSocketEventPort", nullptr}, + {0xa5c28f6810917d53, "SystemNative_GetTimeZoneData", nullptr}, {0xa60707ebce568092, "SystemNative_GetPeerID", nullptr}, {0xa78db9df16110d0e, "SystemNative_LowLevelMonitor_Destroy", nullptr}, {0xa7cafc1044da3306, "AndroidCryptoNative_SSLStreamGetPeerCertificate", nullptr}, @@ -894,6 +899,7 @@ static PinvokeEntry internal_pinvokes[] = { {0xe215a17c, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, {0xe2434b6d, "java_interop_jnienv_set_static_double_field", reinterpret_cast(&java_interop_jnienv_set_static_double_field)}, {0xe3f8dcd5, "java_interop_jnienv_set_short_array_region", reinterpret_cast(&java_interop_jnienv_set_short_array_region)}, + {0xe4c3ee19, "monodroid_log_traces", reinterpret_cast(&monodroid_log_traces)}, {0xe7e77ca5, "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, {0xea2184e3, "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, {0xeac6aa37, "java_interop_jnienv_call_static_void_method", reinterpret_cast(&java_interop_jnienv_call_static_void_method)}, @@ -972,6 +978,7 @@ static PinvokeEntry dotnet_pinvokes[] = { {0x1bf277c4, "SystemNative_WaitForSocketEvents", nullptr}, {0x1c4778bf, "SystemNative_AlignedFree", nullptr}, {0x1cb466df, "AndroidCryptoNative_RsaGenerateKeyEx", nullptr}, + {0x1cc522bb, "_kBrotliContextLookupTable", nullptr}, {0x1cf7b52c, "SystemNative_MAdvise", nullptr}, {0x1eb6eaaa, "CryptoNative_GetRandomBytes", nullptr}, {0x1ebc63c1, "AndroidCryptoNative_X509ChainDestroyContext", nullptr}, @@ -1123,12 +1130,15 @@ static PinvokeEntry dotnet_pinvokes[] = { {0x70e91ddd, "SystemNative_FChMod", nullptr}, {0x71698a7f, "SystemNative_GetDomainSocketSizes", nullptr}, {0x7243c4b4, "AndroidCryptoNative_Des3Cfb8", nullptr}, + {0x74ebdcdf, "_kBrotliPrefixCodeRanges", nullptr}, + {0x758dd6aa, "SystemNative_GetTimeZoneData", nullptr}, {0x759f5b1e, "AndroidCryptoNative_Aes256Cfb8", nullptr}, {0x75b11f61, "BrotliDecoderGetErrorCode", nullptr}, {0x76e97b2e, "SystemNative_Rename", nullptr}, {0x77cb373b, "SystemNative_GetIPSocketAddressSizes", nullptr}, {0x78c1eb52, "AndroidCryptoNative_Des3Ecb", nullptr}, {0x7a0529c1, "SystemNative_InitializeTerminalAndSignalHandling", nullptr}, + {0x7aa30494, "Java_net_dot_android_crypto_DotnetProxyTrustManager_verifyRemoteCertificate", nullptr}, {0x7ad3b820, "AndroidCryptoNative_Aes192Cfb128", nullptr}, {0x7cb19137, "SystemNative_GetIcmpv6GlobalStatistics", nullptr}, {0x7d0c477d, "CryptoNative_ErrPeekLastError", nullptr}, @@ -1365,5 +1375,5 @@ constexpr hash_t system_io_compression_native_library_hash = 0xafe3142c; constexpr hash_t system_security_cryptography_native_android_library_hash = 0x93625cd; #endif -constexpr size_t internal_pinvokes_count = 237; -constexpr size_t dotnet_pinvokes_count = 428; +constexpr size_t internal_pinvokes_count = 238; +constexpr size_t dotnet_pinvokes_count = 432; From c291dc4b73914749702fddb811861df79657f2a4 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 17 Apr 2023 23:05:42 +0200 Subject: [PATCH 14/60] Spent the day tracking registered and executed methods Next week: * see what other types we should trace with Mono * check whether all JS callbacks are invoked --- .../Android.Runtime/AndroidRuntime.cs | 17 +++--- .../Tasks/GenerateJavaStubs.cs | 3 +- .../Utilities/MarshalMethodsClassifier.cs | 54 +++++++++++++------ 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index b2436fb01b6..4f020d0c876 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -485,8 +485,11 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< { try { if (methods.IsEmpty) { - if (jniAddNativeMethodRegistrationAttributePresent) + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} registers no methods"); + if (jniAddNativeMethodRegistrationAttributePresent) { + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} has [AddNativeMethodRegistration] attribute"); base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); + } return; } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { return; @@ -533,6 +536,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ())); callback = CreateDynamicCallback (minfo); needToRegisterNatives = true; + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNativesMethod: [ ] {type.FullName}: __export__ found for method '{name.ToString ()}'"); } else { Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { @@ -550,6 +554,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< if (callback != null) { needToRegisterNatives = true; natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNativesMethod: [ ] {type.FullName}.{name.ToString ()}"); } } @@ -557,20 +562,12 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< } if (needToRegisterNatives) { + RuntimeNativeMethods.monodroid_log (LogLevel.Info, LogCategories.Assembly, $"RegisterJniNatives: Type {type.FullName} calling JniEnvironment.Types.RegisterNatives"); JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); } } catch (Exception e) { JniEnvironment.Runtime.RaisePendingException (e); } - - bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName) - { - if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) { - return false; - } - - return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0; - } } static int CountMethods (ReadOnlySpan methodsSpan) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index bad74480bd4..575d535074f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -222,7 +222,7 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) MarshalMethodsClassifier classifier = null; if (useMarshalMethods) { - classifier = new MarshalMethodsClassifier (cache, res, Log); + classifier = new MarshalMethodsClassifier (cache, res, Log, IntermediateOutputDirectory); } // Step 2 - Generate Java stub code @@ -405,6 +405,7 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) if (useMarshalMethods) { classifier.AddSpecialCaseMethods (); + classifier.FlushAndCloseOutputs (); Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 3a7d4712b3f..d41f8c5c8c0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Java.Interop.Tools.Cecil; @@ -225,13 +226,14 @@ public bool Matches (MethodDefinition method) HashSet typesWithDynamicallyRegisteredMethods; ulong rejectedMethodCount = 0; ulong wrappedMethodCount = 0; + StreamWriter ignoredMethodsLog; public IDictionary> MarshalMethods => marshalMethods; public ICollection Assemblies => assemblies; public ulong RejectedMethodCount => rejectedMethodCount; public ulong WrappedMethodCount => wrappedMethodCount; - public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log) + public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log, string intermediateOutputDirectory) { this.log = log ?? throw new ArgumentNullException (nameof (log)); this.tdCache = tdCache ?? throw new ArgumentNullException (nameof (tdCache)); @@ -239,6 +241,17 @@ public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyR marshalMethods = new Dictionary> (StringComparer.Ordinal); assemblies = new HashSet (); typesWithDynamicallyRegisteredMethods = new HashSet (); + + var fs = File.Open (Path.Combine (intermediateOutputDirectory, "marshal-methods-ignored.txt"), FileMode.Create); + ignoredMethodsLog = new StreamWriter (fs, Files.UTF8withoutBOM); + } + + public void FlushAndCloseOutputs () + { + ignoredMethodsLog.WriteLine (); + ignoredMethodsLog.WriteLine ($"Marshal methods count: {MarshalMethods.Count}; Rejected methods count: {RejectedMethodCount}"); + ignoredMethodsLog.Flush (); + ignoredMethodsLog.Close (); } public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute) @@ -414,6 +427,15 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere return true; } + void LogIgnored (TypeDefinition type, MethodDefinition method, string message, bool logWarning = true) + { + if (logWarning) { + log.LogWarning (message); + } + + ignoredMethodsLog.WriteLine ($"{type.FullName}\t{method.FullName}\t{message}"); + } + bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) { const string HandlerNameStart = "Get"; @@ -438,23 +460,23 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD MethodDefinition connectorMethod = FindMethod (connectorDeclaringType, connectorName); if (connectorMethod == null) { - log.LogWarning ($"\tConnector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tConnector method '{connectorName}' not found in type '{connectorDeclaringType.FullName}'"); return false; } if (String.Compare ("System.Delegate", connectorMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"\tConnector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tConnector '{connectorName}' in type '{connectorDeclaringType.FullName}' has invalid return type, expected 'System.Delegate', found '{connectorMethod.ReturnType.FullName}'"); return false; } var ncbs = new NativeCallbackSignature (registeredMethod, log); MethodDefinition nativeCallbackMethod = FindMethod (connectorDeclaringType, nativeCallbackName, ncbs); if (nativeCallbackMethod == null) { - log.LogWarning ($"\tUnable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}')"); + LogIgnored (topType, registeredMethod, $"\tUnable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}')"); return false; } - if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod, out bool needsBlittableWorkaround)) { + if (!EnsureIsValidUnmanagedCallersOnlyTarget (topType, registeredMethod, nativeCallbackMethod, out bool needsBlittableWorkaround)) { return false; } @@ -463,7 +485,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD FieldDefinition delegateField = FindField (nativeCallbackMethod.DeclaringType, delegateFieldName); if (delegateField != null) { if (String.Compare ("System.Delegate", delegateField.FieldType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"\tdelegate field '{delegateFieldName}' in type '{nativeCallbackMethod.DeclaringType.FullName}' has invalid type, expected 'System.Delegate', found '{delegateField.FieldType.FullName}'"); + LogIgnored (topType, registeredMethod, $"\tdelegate field '{delegateFieldName}' in type '{nativeCallbackMethod.DeclaringType.FullName}' has invalid type, expected 'System.Delegate', found '{delegateField.FieldType.FullName}'"); return false; } } @@ -523,23 +545,23 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return true; } - bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool needsBlittableWorkaround) + bool EnsureIsValidUnmanagedCallersOnlyTarget (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition nativeCallbackMethod, out bool needsBlittableWorkaround) { needsBlittableWorkaround = false; // Requirements: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0#remarks - if (!method.IsStatic) { + if (!nativeCallbackMethod.IsStatic) { return LogReasonWhyAndReturnFailure ($"is not static"); } - if (method.HasGenericParameters) { + if (nativeCallbackMethod.HasGenericParameters) { return LogReasonWhyAndReturnFailure ($"has generic parameters"); } TypeReference type; bool needsWrapper = false; - if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { - type = GetRealType (method.ReturnType); + if (String.Compare ("System.Void", nativeCallbackMethod.ReturnType.FullName, StringComparison.Ordinal) != 0) { + type = GetRealType (nativeCallbackMethod.ReturnType); if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a non-blittable return type '{type.FullName}'"); @@ -547,15 +569,15 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } } - if (method.DeclaringType.HasGenericParameters) { + if (nativeCallbackMethod.DeclaringType.HasGenericParameters) { return LogReasonWhyAndReturnFailure ($"is declared in a type with generic parameters"); } - if (!method.HasParameters) { + if (!nativeCallbackMethod.HasParameters) { return UpdateWrappedCountAndReturn (true); } - foreach (ParameterDefinition pdef in method.Parameters) { + foreach (ParameterDefinition pdef in nativeCallbackMethod.Parameters) { type = GetRealType (pdef.ParameterType); if (!IsAcceptable (type)) { @@ -599,14 +621,14 @@ TypeReference GetRealType (TypeReference type) bool LogReasonWhyAndReturnFailure (string why) { - log.LogWarning ($"Method '{method.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); + LogIgnored (topType, registeredMethod, $"Method '{nativeCallbackMethod.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); return false; } void WarnWhy (string why) { // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers - log.LogDebugMessage ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); + log.LogDebugMessage ($"Method '{nativeCallbackMethod.FullName}' {why}. A workaround is required, this may make the application slower"); } } From 35be48935028649004c0ba40d86ab0ae55d68f18 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 17 Apr 2023 23:12:18 +0200 Subject: [PATCH 15/60] A small TODO --- src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 506f9589f93..ca8535046de 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -336,6 +336,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. false true True + + True False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False From 4288baffc3df875197036420f84e5cb51906679f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 24 Apr 2023 11:59:11 +0200 Subject: [PATCH 16/60] Make sure invariant culture is used --- .../Utilities/LlvmIrGenerator/LlvmIrGenerator.cs | 2 +- .../Utilities/LlvmIrGenerator/LlvmIrStringManager.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index bd8eab5c002..61ceb5c3d09 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -1316,7 +1316,7 @@ public string WriteString (string symbolName, string value, LlvmIrVariableOption WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true); Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (PointerSize, stringSize)); + Output.WriteLine (GetAggregateAlignment (PointerSize, stringSize).ToString (CultureInfo.InvariantCulture)); return symbolName; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs index d7d4e7e6fc2..de7b107af24 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; namespace Xamarin.Android.Tasks.LLVMIR; @@ -103,11 +104,11 @@ public void Flush (LlvmIrGenerator generator) foreach (StringSymbolInfo info in group.Strings) { generator.WriteGlobalSymbolStart (info.SymbolName, LlvmIrVariableOptions.LocalConstexprString); output.Write ('['); - output.Write (info.Size); + output.Write (info.Size.ToString (CultureInfo.InvariantCulture)); output.Write (" x i8] c"); output.Write (info.Value); output.Write (", align "); - output.WriteLine (generator.GetAggregateAlignment (1, info.Size)); + output.WriteLine (generator.GetAggregateAlignment (1, info.Size).ToString (CultureInfo.InvariantCulture)); } } } From caae9e9e97808a036c29c28c6460f7c5ea2b1685 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 27 Apr 2023 23:35:05 +0200 Subject: [PATCH 17/60] Add some Java tracing + Java.Interop tracing Also, add a parameterless constructor to InputStreamAdapter, which wasn't activated in the "broken" case because its JCW lacked constructor with a call to TypeManager.Activate --- java.interop-tracing.diff | 165 ++++++++++++++++++ .../Android.Runtime/InputStreamAdapter.cs | 2 + .../java/mono/android/TypeManager.java | 12 +- .../Tasks/CreateTypeManagerJava.cs | 45 ++++- .../LlvmIrGenerator/LlvmIrGenerator.cs | 4 +- .../Xamarin.Android.Common.targets | 11 +- 6 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 java.interop-tracing.diff diff --git a/java.interop-tracing.diff b/java.interop-tracing.diff new file mode 100644 index 00000000000..39db7f8b1a5 --- /dev/null +++ b/java.interop-tracing.diff @@ -0,0 +1,165 @@ +diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +index 76f2bfc0..33cefdbf 100644 +--- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs ++++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +@@ -133,6 +133,7 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + JavaCallableWrapperGenerator (TypeDefinition type, string? outerType, Action log, IMetadataResolver resolver, JavaCallableMethodClassifier? methodClassifier = null) + { ++ Console.WriteLine ($"JCWG: processing {type}"); + this.methodClassifier = methodClassifier; + this.type = type; + this.log = log; +@@ -224,6 +225,8 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + curCtors = new List (); + AddConstructors (ctorTypes [i], outerType, baseCtors, curCtors, false); + } ++ ++ Console.WriteLine ($"JCWG: done processing {type}"); + } + + static void ExtractJavaNames (string jniName, out string package, out string type) +@@ -285,8 +288,10 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void AddConstructor (MethodDefinition ctor, TypeDefinition type, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors, bool skipParameterCheck) + { ++ Console.WriteLine ($" JCWG: AddConstructor ({ctor}, {type}, ..., onlyRegisteredOrExportedCtors: {onlyRegisteredOrExportedCtors}, skipParameterCheck: {skipParameterCheck});"); + string managedParameters = GetManagedParameters (ctor, outerType); + if (!skipParameterCheck && (managedParameters == null || ctors.Any (c => c.ManagedParameters == managedParameters))) { ++ Console.WriteLine (" JCWG: skip #1"); + return; + } + +@@ -297,28 +302,38 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + } + ctors.Add (new Signature (ctor, eattr, cache)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #1"); + return; + } + + RegisterAttribute rattr = GetMethodRegistrationAttributes (ctor).FirstOrDefault (); + if (rattr != null) { +- if (ctors.Any (c => c.JniSignature == rattr.Signature)) ++ if (ctors.Any (c => c.JniSignature == rattr.Signature)) { ++ Console.WriteLine (" JCWG: skip #2"); + return; ++ } + ctors.Add (new Signature (ctor, rattr, managedParameters, outerType, cache)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #2"); + return; + } + +- if (onlyRegisteredOrExportedCtors) ++ if (onlyRegisteredOrExportedCtors) { ++ Console.WriteLine (" JCWG: skip #1"); + return; ++ } + + string? jniSignature = GetJniSignature (ctor, cache); + +- if (jniSignature == null) ++ if (jniSignature == null) { ++ Console.WriteLine (" JCWG: skip #3"); + return; ++ } + +- if (ctors.Any (c => c.JniSignature == jniSignature)) ++ if (ctors.Any (c => c.JniSignature == jniSignature)) { ++ Console.WriteLine (" JCWG: skip #4"); + return; ++ } + + if (baseCtors == null) { + throw new InvalidOperationException ("`baseCtors` should not be null!"); +@@ -327,11 +342,13 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + if (baseCtors.Any (m => m.Parameters.AreParametersCompatibleWith (ctor.Parameters, cache))) { + ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, null)); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #3"); + return; + } + if (baseCtors.Any (m => !m.HasParameters)) { + ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, "")); + curCtors.Add (ctor); ++ Console.WriteLine (" JCWG: add #4"); + return; + } + } +@@ -687,9 +704,12 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateBody (TextWriter sw) + { ++ Console.WriteLine ($" JCWG: GenerateBody for {name} (ctors size: {ctors.Count})"); + foreach (Signature ctor in ctors) { +- if (string.IsNullOrEmpty (ctor.Params) && JavaNativeTypeManager.IsApplication (type, cache)) ++ if (string.IsNullOrEmpty (ctor.Params) && JavaNativeTypeManager.IsApplication (type, cache)) { ++ Console.WriteLine ($" JCWG: skipping constructor generation; params empty? {string.IsNullOrEmpty (ctor.Params)}; is application? {JavaNativeTypeManager.IsApplication (type, cache)}"); + continue; ++ } + GenerateConstructor (ctor, sw); + } + +@@ -818,9 +838,13 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + return jniName.Replace ('/', '.').Replace ('$', '.'); + } + +- bool CannotRegisterInStaticConstructor (TypeDefinition type) ++ bool CannotRegisterInStaticConstructor (TypeDefinition type, bool dolog = false) + { +- return JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache); ++ Console.WriteLine ($" JCWG: CannotRegisterInStaticConstructor ({type})"); ++ bool ret = JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache); ++ Console.WriteLine ($" JCWG: CannotRegisterInStaticConstructor for type {type}; ret == {ret}; IsApplication: {JavaNativeTypeManager.IsApplication (type, cache)}; IsInstrumentation: {JavaNativeTypeManager.IsInstrumentation (type, cache)}"); ++ ++ return ret; + } + + class Signature { +@@ -941,6 +965,8 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateConstructor (Signature ctor, TextWriter sw) + { ++ Console.WriteLine ($" JCWG: GenerateConstructor for {name}"); ++ + // TODO: we only generate constructors so that Android types w/ no + // default constructor can be subclasses by our generated code. + // +diff --git a/tools/java-source-utils/.classpath b/tools/java-source-utils/.classpath +index 30230c1c..cbded110 100644 +--- a/tools/java-source-utils/.classpath ++++ b/tools/java-source-utils/.classpath +@@ -20,6 +20,12 @@ + + + ++ ++ ++ ++ ++ ++ + + + +diff --git a/tools/java-source-utils/.project b/tools/java-source-utils/.project +index 8337a1c5..613733c8 100644 +--- a/tools/java-source-utils/.project ++++ b/tools/java-source-utils/.project +@@ -20,4 +20,15 @@ + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + ++ ++ ++ 1682588570543 ++ ++ 30 ++ ++ org.eclipse.core.resources.regexFilterMatcher ++ node_modules|.metadata|archetype-resources|META-INF/maven|__CREATED_BY_JAVA_LANGUAGE_SERVER__ ++ ++ ++ + diff --git a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs index 5eb3b04bd87..7b66fce5bca 100644 --- a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs +++ b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs @@ -8,6 +8,8 @@ public sealed class InputStreamAdapter : Java.IO.InputStream { public Stream BaseStream {get; private set;} + InputStreamAdapter () {} + public InputStreamAdapter (System.IO.Stream stream) : base ( JNIEnv.StartCreateInstance ("mono/android/runtime/InputStreamAdapter", "()V"), diff --git a/src/Mono.Android/java/mono/android/TypeManager.java b/src/Mono.Android/java/mono/android/TypeManager.java index bf64780bf9e..1d668b5e638 100644 --- a/src/Mono.Android/java/mono/android/TypeManager.java +++ b/src/Mono.Android/java/mono/android/TypeManager.java @@ -1,20 +1,26 @@ package mono.android; +//#NOTE: make sure the `#FEATURE=*:START` and `#FEATURE=*:END` lines for different values of FEATURE don't overlap or interlace. +//#NOTE: Each FEATURE block should be self-contained, with no other FEATURE blocks nested. +//#NOTE: This is because the code that skips over those FEATURE blocks is VERY primitive (see the `CreateTypeManagerJava` task in XABT) public class TypeManager { public static void Activate (String typeName, String sig, Object instance, Object[] parameterList) { +//#FEATURE=CALL_TRACING:START - do not remove or modify this line, it is required during application build + android.util.Log.i ("monodroid-trace", String.format ("java: activating type: '%s' [%s]", typeName, sig)); +//#FEATURE=CALL_TRACING:END - do not remove or modify this line, it is required during application build n_activate (typeName, sig, instance, parameterList); } private static native void n_activate (String typeName, String sig, Object instance, Object[] parameterList); -//#MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build +//#FEATURE=MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build static { - String methods = + String methods = "n_activate:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V:GetActivateHandler\n" + ""; mono.android.Runtime.register ("Java.Interop.TypeManager+JavaTypeManager, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", TypeManager.class, methods); } -//#MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build +//#FEATURE=MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs index 37872f31927..87309b463d9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs @@ -15,6 +15,9 @@ public class CreateTypeManagerJava : AndroidTask [Required] public string ResourceName { get; set; } + public bool CallTracingEnabled { get; set; } + public bool MarshalMethodsEnabled { get; set; } + [Required] public string OutputFilePath { get; set; } @@ -31,12 +34,18 @@ public override bool RunTask () var result = new StringBuilder (); bool ignoring = false; foreach (string line in content.Split ('\n')) { + if (SkipNoteLine (line)) { + continue; + } + if (!ignoring) { - if (ignoring = line.StartsWith ("//#MARSHAL_METHODS:START", StringComparison.Ordinal)) { + ignoring = StartIgnoring (line, out bool skipLine); + if (ignoring || skipLine) { continue; } + result.AppendLine (line); - } else if (line.StartsWith ("//#MARSHAL_METHODS:END", StringComparison.Ordinal)) { + } else if (EndIgnoring (line)) { ignoring = false; } } @@ -62,6 +71,38 @@ public override bool RunTask () return !Log.HasLoggedErrors; } + bool SkipNoteLine (string l) => l.Trim ().StartsWith ("//#NOTE:"); + + bool StartIgnoring (string l, out bool skipLine) + { + string line = l.Trim (); + skipLine = true; + if (MarshalMethodsEnabled && line.StartsWith ("//#FEATURE=MARSHAL_METHODS:START", StringComparison.Ordinal)) { + return true; + } + + if (!CallTracingEnabled && line.StartsWith ("//#FEATURE=CALL_TRACING:START", StringComparison.Ordinal)) { + return true; + } + + skipLine = line.StartsWith ("//#FEATURE", StringComparison.Ordinal); + return false; + } + + bool EndIgnoring (string l) + { + string line = l.Trim (); + if (MarshalMethodsEnabled && line.StartsWith ("//#FEATURE=MARSHAL_METHODS:END", StringComparison.Ordinal)) { + return true; + } + + if (!CallTracingEnabled && line.StartsWith ("//#FEATURE=CALL_TRACING:END", StringComparison.Ordinal)) { + return true; + } + + return false; + } + string? ReadResource (string resourceName) { using (var from = ExecutingAssembly.GetManifestResourceStream (resourceName)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 61ceb5c3d09..bdb9a92adcf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -618,8 +618,7 @@ public void WriteArray (IList values, string symbolName, string? initial WriteEOL (); WriteEOL (initialComment ?? symbolName); - var strings = new List (); - + var strings = new List (); foreach (string s in values) { StringSymbolInfo symbol = StringManager.Add (s, groupName: symbolName); strings.Add (symbol); @@ -1162,7 +1161,6 @@ void WriteGetBufferPointer (string? variableName, string irType, ulong size, boo } string sizeStr = size.ToString (CultureInfo.InvariantCulture); - output.Write (irType); output.Write (" getelementptr inbounds (["); output.Write (sizeStr); output.Write (" x "); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 66af7e552a5..7ecdb7a2582 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1401,6 +1401,11 @@ because xbuild doesn't support framework reference assemblies. Inputs="$(MonoPlatformJarPath);$(_AndroidBuildPropertiesCache)" Outputs="$(_AndroidStaticResourcesFlag)" DependsOnTargets="_CollectRuntimeJarFilenames;$(_BeforeAddStaticResources);_GetMonoPlatformJarPath"> + + <_UseStockTypeManager Condition=" '$(_UseNativeStackTraces)' == 'True' And '$(_AndroidUseMarshalMethods)' != 'True' ">true + <_UseStockTypeManager Condition=" '$(_UseStockTypeManager)' == '' ">false + + From 72abfa3c8d702ec09a124874704e70463491bade Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 28 Apr 2023 21:01:31 +0200 Subject: [PATCH 18/60] More tracing, perhaps getting closer It **might** be a bug in Mono native-to-managed wrappers generated for UCO methods, but we'll see - consulting with the runtime team. --- java.interop-tracing.diff | 50 ++++++++++++++++++- .../Android.Runtime/InputStreamAdapter.cs | 8 ++- src/Mono.Android/Java.Lang/Thread.cs | 11 +++- .../java/mono/android/TypeManager.java | 4 +- .../Tasks/GenerateJavaStubs.cs | 6 +-- 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/java.interop-tracing.diff b/java.interop-tracing.diff index 39db7f8b1a5..bb00e359fa4 100644 --- a/java.interop-tracing.diff +++ b/java.interop-tracing.diff @@ -1,5 +1,5 @@ diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs -index 76f2bfc0..33cefdbf 100644 +index 76f2bfc0..7a57f6b2 100644 --- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs @@ -133,6 +133,7 @@ namespace Java.Interop.Tools.JavaCallableWrappers { @@ -87,6 +87,41 @@ index 76f2bfc0..33cefdbf 100644 return; } } +@@ -549,17 +566,17 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + GenerateHeader (writer); + + bool needCtor = false; +- if (HasDynamicallyRegisteredMethods) { ++// if (HasDynamicallyRegisteredMethods) { + needCtor = true; + writer.WriteLine ("/** @hide */"); + writer.WriteLine ("\tpublic static final String __md_methods;"); +- } ++// } + + if (children != null) { + for (int i = 0; i < children.Count; i++) { +- if (!children[i].HasDynamicallyRegisteredMethods) { +- continue; +- } ++ // if (!children[i].HasDynamicallyRegisteredMethods) { ++ // continue; ++ // } + needCtor = true; + writer.Write ("\tstatic final String __md_"); + writer.Write (i + 1); +@@ -570,9 +587,9 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + if (needCtor) { + writer.WriteLine ("\tstatic {"); + +- if (HasDynamicallyRegisteredMethods) { ++// if (HasDynamicallyRegisteredMethods) { + GenerateRegisterType (writer, this, "__md_methods"); +- } ++// } + + if (children != null) { + for (int i = 0; i < children.Count; ++i) { @@ -687,9 +704,12 @@ namespace Java.Interop.Tools.JavaCallableWrappers { void GenerateBody (TextWriter sw) @@ -101,6 +136,19 @@ index 76f2bfc0..33cefdbf 100644 GenerateConstructor (ctor, sw); } +@@ -745,9 +765,9 @@ namespace Java.Interop.Tools.JavaCallableWrappers { + + void GenerateRegisterType (TextWriter sw, JavaCallableWrapperGenerator self, string field) + { +- if (!self.HasDynamicallyRegisteredMethods) { +- return; +- } ++ // if (!self.HasDynamicallyRegisteredMethods) { ++ // return; ++ // } + + sw.Write ("\t\t"); + sw.Write (field); @@ -818,9 +838,13 @@ namespace Java.Interop.Tools.JavaCallableWrappers { return jniName.Replace ('/', '.').Replace ('$', '.'); } diff --git a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs index 7b66fce5bca..795ed57d217 100644 --- a/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs +++ b/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs @@ -8,13 +8,18 @@ public sealed class InputStreamAdapter : Java.IO.InputStream { public Stream BaseStream {get; private set;} - InputStreamAdapter () {} + InputStreamAdapter () { + Console.WriteLine ("InputStreamAdapter () invoked"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); + } public InputStreamAdapter (System.IO.Stream stream) : base ( JNIEnv.StartCreateInstance ("mono/android/runtime/InputStreamAdapter", "()V"), JniHandleOwnership.TransferLocalRef) { + Console.WriteLine ("InputStreamAdapter (System.IO.Stream) invoked"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); JNIEnv.FinishCreateInstance (Handle, "()V"); this.BaseStream = stream; @@ -56,4 +61,3 @@ public static IntPtr ToLocalJniHandle (Stream? value) } } } - diff --git a/src/Mono.Android/Java.Lang/Thread.cs b/src/Mono.Android/Java.Lang/Thread.cs index 8c6155f41d3..a17aeebc16f 100644 --- a/src/Mono.Android/Java.Lang/Thread.cs +++ b/src/Mono.Android/Java.Lang/Thread.cs @@ -41,7 +41,15 @@ public void Run () Dispose (); } - static Dictionary instances = new Dictionary (); + static Dictionary instances;// = new Dictionary (); + + static RunnableImplementor () + { + Console.WriteLine ("RunnableImplementor.cctor (): begin"); + Console.WriteLine (new System.Diagnostics.StackTrace(true)); + instances = new (); + Console.WriteLine ("RunnableImplementor.cctor (): end"); + } public static RunnableImplementor Remove (Action handler) { @@ -65,4 +73,3 @@ public Thread (Action runHandler, string threadName) : this (new RunnableImpleme public Thread (ThreadGroup group, Action runHandler, string threadName, long stackSize) : this (group, new RunnableImplementor (runHandler), threadName, stackSize) {} } } - diff --git a/src/Mono.Android/java/mono/android/TypeManager.java b/src/Mono.Android/java/mono/android/TypeManager.java index 1d668b5e638..ab3c1beeef9 100644 --- a/src/Mono.Android/java/mono/android/TypeManager.java +++ b/src/Mono.Android/java/mono/android/TypeManager.java @@ -15,12 +15,12 @@ public static void Activate (String typeName, String sig, Object instance, Objec private static native void n_activate (String typeName, String sig, Object instance, Object[] parameterList); -//#FEATURE=MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build static { String methods = +//#FEATURE=MARSHAL_METHODS:START - do not remove or modify this line, it is required during application build "n_activate:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V:GetActivateHandler\n" + +//#FEATURE=MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build ""; mono.android.Runtime.register ("Java.Interop.TypeManager+JavaTypeManager, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", TypeManager.class, methods); } -//#FEATURE=MARSHAL_METHODS:END - do not remove or modify this line, it is required during application build } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 575d535074f..b2a96f0edd0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -387,9 +387,9 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first."); foreach (var type in javaTypes) { if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) { - if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { - continue; - } + // if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { + // continue; + // } string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", From 48f4b6a93520dacdef5e5fa7ed13806d58afa273 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 4 May 2023 21:47:42 +0200 Subject: [PATCH 19/60] Base code for parameter and return value logging After poring over the logs, it seems that we might be seeing a problem with some WebView callbacks not returning valid values when [UCO] wrappers are used. --- src/monodroid/jni/marshal-methods-tracing.cc | 185 +++++++++++++++++++ src/monodroid/jni/marshal-methods-tracing.hh | 51 +++++ 2 files changed, 236 insertions(+) diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 7f2b9ea03fc..11bb6c1a108 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -1,9 +1,12 @@ #include +#include +#include #include #include #include #include #include +#include #include @@ -19,6 +22,11 @@ using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; constexpr char LEAD[] = "MM: "; +struct MethodParams +{ + std::string buffer; +}; + void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept { @@ -81,3 +89,180 @@ void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_imag constexpr char LEAVE[] = "LEAVE"; _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); } + +template +[[gnu::always_inline]] +static void append_param (std::string& buffer, TVal value) noexcept +{ + if (!buffer.empty ()) { + buffer.append (", "); + } + + buffer.append (value); +} + +template +concept AcceptableInteger = std::is_integral_v || std::is_pointer_v; + +template +[[gnu::always_inline]] +static void append_hex_integer (std::string& buffer, TVal value) noexcept +{ + const char *format; + + if constexpr (std::is_same_v || std::is_same_v) { + format = "0x%lx"; + } else if constexpr (std::is_pointer_v) { + format = "%p"; + } else { + format = "0x%x"; + } + + // Enough room for maximum decimal representation of 64-bit unsigned value, or a minimum signed one with the sign. + std::array::digits10 + 2> data_buf; + std::snprintf (data_buf.data (), data_buf.size (), format, value); + append_param (buffer, data_buf.data ()); +} + +[[gnu::always_inline]] +static void append_pointer (std::string& buffer, void *pointer) noexcept +{ + append_hex_integer (buffer, pointer); +} + +MethodParams* _mm_trace_method_params_new (JNIEnv *env, jclass klass) noexcept +{ + auto ret = new MethodParams (); + + append_pointer (ret->buffer, env); + append_pointer (ret->buffer, klass); + + return ret; +} + +void _mm_trace_method_params_destroy (MethodParams *v) noexcept +{ + if (v == nullptr) { + return; + } + + delete v; +} + +void _mm_trace_param_append_bool (MethodParams *state, bool v) noexcept +{ + if (state == nullptr) { + return; + } + + append_param (state->buffer, v ? "true" : "false"); +} + +template +[[gnu::always_inline]] +static void _mm_trace_param_append_hex_integer (MethodParams *state, TVal v) noexcept +{ + if (state == nullptr) { + return; + } + + append_hex_integer (state->buffer, v); +} + +void _mm_trace_param_append_byte (MethodParams *state, uint8_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_sbyte (MethodParams *state, int8_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_char (MethodParams *state, char v) noexcept +{ + if (state == nullptr) { + return; + } + + state->buffer.append ("'"); + state->buffer.append (1, v); + state->buffer.append ("'"); +} + +void _mm_trace_param_append_short (MethodParams *state, int16_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_ushort (MethodParams *state, uint16_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_int (MethodParams *state, int32_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_uint (MethodParams *state, uint32_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_long (MethodParams *state, int64_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_ulong (MethodParams *state, uint64_t v) noexcept +{ + _mm_trace_param_append_hex_integer (state, v); +} + +void _mm_trace_param_append_float (MethodParams *state, float v) noexcept +{ + if (state == nullptr) { + return; + } + + state->buffer.append (std::to_string (v)); +} + +void _mm_trace_param_append_double (MethodParams *state, double v) noexcept +{ + if (state == nullptr) { + return; + } + + state->buffer.append (std::to_string (v)); +} + +void _mm_trace_param_append_string (MethodParams *state, JNIEnv *env, jstring v) noexcept +{ + if (state == nullptr) { + return; + } + + if (env == nullptr) { + state->buffer.append ("\"\""); + return; + } + + const char *s = env->GetStringUTFChars (v, nullptr); + + state->buffer.append ("\""); + state->buffer.append (s); + state->buffer.append ("\""); + + env->ReleaseStringUTFChars (v, s); +} + +void _mm_trace_param_append_pointer (MethodParams *state, void* v) noexcept +{ + if (state == nullptr) { + return; + } + + append_pointer (state->buffer, v); +} diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index 31f21100da3..aa1bef6129a 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -1,3 +1,4 @@ +#include #if !defined (__MARSHAL_METHODS_TRACING_HH) #define __MARSHAL_METHODS_TRACING_HH @@ -12,6 +13,8 @@ inline constexpr int32_t TracingModeBasic = 0x01; inline constexpr int32_t TracingModeFull = 0x02; extern "C" { + struct MethodParams; + [[gnu::visibility("hidden")]] void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; @@ -20,6 +23,54 @@ extern "C" { [[gnu::visibility("hidden")]] void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + + [[gnu::visibility("hidden")]] + MethodParams* _mm_trace_method_params_new (JNIEnv *env, jclass klass) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_method_params_destroy (MethodParams *v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_bool (MethodParams *state, bool v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_byte (MethodParams *state, uint8_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_sbyte (MethodParams *state, int8_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_char (MethodParams *state, char v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_short (MethodParams *state, int16_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_ushort (MethodParams *state, uint16_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_int (MethodParams *state, int32_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_uint (MethodParams *state, uint32_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_long (MethodParams *state, int64_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_ulong (MethodParams *state, uint64_t v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_float (MethodParams *state, float v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_double (MethodParams *state, double v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_string (MethodParams *state, JNIEnv *env, jstring v) noexcept; + + [[gnu::visibility("hidden")]] + void _mm_trace_param_append_pointer (MethodParams *state, void* v) noexcept; } #endif // ndef __MARSHAL_METHODS_TRACING_HH From 212f6efea3399ca0045ef452dfbb27e6a1cd8535 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 5 May 2023 22:56:06 +0200 Subject: [PATCH 20/60] Different approach to printing params More complicated from the code generator's pov, but more efficient at runtime (and much less verbose). --- .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 1 + .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 3 +- .../LlvmIrGenerator/LlvmIrComposer.cs | 1 + .../LlvmIrGenerator/LlvmIrFunction.cs | 44 +++- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 189 +++++++++++++++++- .../LlvmIrGenerator/X64LlvmIrGenerator.cs | 3 +- .../LlvmIrGenerator/X86LlvmIrGenerator.cs | 1 + .../MarshalMethodsNativeAssemblyGenerator.cs | 53 ++++- src/monodroid/jni/marshal-methods-tracing.cc | 179 +---------------- src/monodroid/jni/marshal-methods-tracing.hh | 50 +---- 10 files changed, 283 insertions(+), 241 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs index 5f60214ea33..81ed76a17c5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs @@ -38,6 +38,7 @@ protected override void InitFunctionAttributes () FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs index 68ca5fd19e8..3752b7eb5f4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs @@ -19,7 +19,7 @@ class Arm64LlvmIrGenerator : LlvmIrGenerator static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { new FramePointerFunctionAttribute ("non-leaf"), new TargetCpuFunctionAttribute ("generic"), - new TargetFeaturesFunctionAttribute ("+neon,+outline-atomics"), + new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics"), }; public Arm64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) @@ -42,6 +42,7 @@ protected override void InitFunctionAttributes () FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 374a432e76a..790b8d30668 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -26,6 +26,7 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) generator.WriteFileTop (); generator.WriteStructureDeclarations (); Write (generator); + generator.WriteFunctionDeclarations (); generator.WriteFileEnd (); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index dbf354c9a32..3dcc61e9eef 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -27,7 +27,24 @@ public LlvmIrFunctionLocalVariable (LlvmIrVariable variable, string? name = null class LlvmIrFunctionParameter : LlvmIrFunctionLocalVariable { + public bool Immarg { get; set; } public bool IsCplusPlusReference { get; } + public bool IsVarargs { get; set; } + public bool NoCapture { get; set; } + public bool NoUndef { get; set; } + + public LlvmIrFunctionParameter (LlvmIrFunctionParameter other, string? name = null) + : this (other.Type, name, other.IsNativePointer, other.IsCplusPlusReference) + { + CopyProperties (other); + } + + // This is most decidedly weird... poor API design ;) + public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, LlvmIrFunctionParameter otherParam, string? name = null) + : this (nativeFunction, name, otherParam.IsNativePointer, otherParam.IsCplusPlusReference) + { + CopyProperties (otherParam); + } public LlvmIrFunctionParameter (Type type, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) : base (type, name, isNativePointer) @@ -40,12 +57,21 @@ public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, stri { IsCplusPlusReference = isCplusPlusReference; } + + void CopyProperties (LlvmIrFunctionParameter other) + { + Immarg = other.Immarg; + IsVarargs = other.IsVarargs; + NoCapture = other.NoCapture; + NoUndef = other.NoUndef; + } } class LlvmIrFunctionArgument { public object Value { get; } public Type Type { get; } + public bool NonNull { get; set; } public LlvmIrFunctionArgument (Type type, object? value = null) { @@ -89,7 +115,7 @@ class LlvmIrFunction uint localSlot = 0; uint indentLevel = 1; - public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null) + public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null, bool skipParameterNames = false) { if (String.IsNullOrEmpty (name)) { throw new ArgumentException ("must not be null or empty", nameof (name)); @@ -111,34 +137,38 @@ LlvmIrFunctionParameter EnsureParameterName (LlvmIrFunctionParameter parameter) throw new InvalidOperationException ("null parameters aren't allowed"); } + if (skipParameterNames) { + return parameter; + } + if (!String.IsNullOrEmpty (parameter.Name)) { return parameter; } string name = GetNextSlotName (); if (parameter.NativeFunction != null) { - return new LlvmIrFunctionParameter (parameter.NativeFunction, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); + return new LlvmIrFunctionParameter (parameter.NativeFunction, parameter, name); } - return new LlvmIrFunctionParameter (parameter.Type, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); + return new LlvmIrFunctionParameter (parameter, name); } } - public LlvmIrFunctionLocalVariable MakeLocalVariable (Type type, string? name = null) + public LlvmIrFunctionLocalVariable MakeLocalVariable (Type type, string? name = null, bool isNativePointer = false) { if (String.IsNullOrEmpty (name)) { name = GetNextSlotName (); } - return new LlvmIrFunctionLocalVariable (type, name); + return new LlvmIrFunctionLocalVariable (type, name, isNativePointer: isNativePointer); } - public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, string? name = null) + public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) { if (String.IsNullOrEmpty (name)) { name = GetNextSlotName (); } - return new LlvmIrFunctionLocalVariable (variable, name); + return new LlvmIrFunctionLocalVariable (variable, name, isNativePointer: isNativePointer); } public void IncreaseIndent () diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 5eb8bd8460d..9e11f158697 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -19,10 +19,40 @@ abstract partial class LlvmIrGenerator public const int FunctionAttributesXamarinAppInit = 0; public const int FunctionAttributesJniMethods = 1; public const int FunctionAttributesCall = 2; + public const int FunctionAttributesLlvmLifetime = 3; + public const int FunctionAttributesLibcFree = 4; protected readonly Dictionary FunctionAttributes = new Dictionary (); bool codeOutputInitialized = false; + List? externalFunctions = null; + LlvmIrVariableReference? llvm_lifetime_start_p0i8_ref; + LlvmIrVariableReference? llvm_lifetime_end_p0i8_ref; + + public void WriteFunctionDeclarations () + { + if (externalFunctions == null || externalFunctions.Count == 0) { + return; + } + + WriteEOL (); + WriteCommentLine (); + WriteCommentLine ("External functions"); + WriteCommentLine (); + + foreach (LlvmIrFunction func in externalFunctions) { + WriteFunctionForwardDeclaration (func); + } + } + + public void AddExternalFunction (LlvmIrFunction func) + { + if (externalFunctions == null) { + externalFunctions = new List (); + } + + externalFunctions.Add (func); + } void ValidateFunction (LlvmIrFunction function, out LlvmFunctionAttributeSet? attributes) { @@ -36,7 +66,7 @@ void ValidateFunction (LlvmIrFunction function, out LlvmFunctionAttributeSet? at } } - void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmFunctionAttributeSet? attributes, string? comment) + void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmFunctionAttributeSet? attributes, string? comment, bool writeParameterNames = true) { if (!String.IsNullOrEmpty (comment)) { foreach (string line in comment.Split ('\n')) { @@ -49,7 +79,7 @@ void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeywor } Output.Write ($"{statementKindKeyword} {GetKnownIRType (function.ReturnType)} @{function.Name} ("); - WriteFunctionParameters (function.Parameters, writeNames: true); + WriteFunctionParameters (function.Parameters, writeNames: writeParameterNames); Output.Write(") local_unnamed_addr "); if (attributes != null) { Output.Write ($"#{function.AttributeSetID.ToString (CultureInfo.InvariantCulture)}"); @@ -64,7 +94,7 @@ public void WriteFunctionForwardDeclaration (LlvmIrFunction function, string? co ValidateFunction (function, out LlvmFunctionAttributeSet? attributes); Output.WriteLine (); - WriteFunctionSignature (function, "declare", attributes, comment); + WriteFunctionSignature (function, "declare", attributes, comment, writeParameterNames: false); Output.WriteLine (";"); } @@ -92,11 +122,37 @@ void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null) } string extraPointer = variable.IsNativePointer ? "*" : String.Empty; - string irType = $"{GetKnownIRType (variable.Type)}{extraPointer}"; - if (builder == null) { - Output.Write (irType); - } else { - builder.Append (irType); + StringBuilder? flags = null; + if (variable is LlvmIrFunctionParameter fparam) { + if (fparam.IsVarargs) { + DoWrite ("..."); + return; + } + + flags = new StringBuilder (); + if (fparam.NoCapture) { + flags.Append (" nocapture"); + } + + if (fparam.Immarg) { + flags.Append (" immarg"); + } + + if (fparam.NoUndef) { + flags.Append (" noundef"); + } + } + + string irType = $"{GetKnownIRType (variable.Type)}{extraPointer}{flags?.ToString() ?? String.Empty}"; + DoWrite (irType); + + void DoWrite (string s) + { + if (builder == null) { + Output.Write (s); + } else { + builder.Append (s); + } } } @@ -128,6 +184,10 @@ public string RenderFunctionParameters (IList? paramete CodeRenderType (p, sb); if (writeNames) { + if (String.IsNullOrEmpty (p.Name)) { + throw new InvalidOperationException ("Parameter name is required"); + } + sb.Append ($" %{p.Name}"); } } @@ -394,6 +454,10 @@ public void EmitLabel (LlvmIrFunction function, string labelName) string paramType = $"{GetKnownIRType (parameter.Type)}{extra}"; Output.Write ($"{paramType} "); + if (argument.NonNull) { + Output.Write ("nonnull "); + } + if (argument.Value is LlvmIrFunctionLocalVariable variable) { Output.Write ($"%{variable.Name}"); } else if (parameter.Type.IsNativePointer () || parameter.IsNativePointer) { @@ -425,7 +489,7 @@ public void EmitLabel (LlvmIrFunction function, string labelName) StringSymbolInfo info = StringManager.Add (str); WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true, skipPointerType: true); } else { - Output.Write (argument.Value.ToString ()); + Output.Write (MonoAndroidHelper.CultureInvariantToString (argument.Value)); } } } @@ -503,6 +567,93 @@ public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, return result; } + void RegisterLlvmLifetimeTrackerFunctions () + { + if (llvm_lifetime_start_p0i8_ref != null && llvm_lifetime_end_p0i8_ref != null) { + return; + } + + const string llvm_lifetime_start_p0i8_name = "llvm.lifetime.start.p0i8"; + const string llvm_lifetime_end_p0i8_name = "llvm.lifetime.end.p0i8"; + + var llvm_lifetime_start_end_params = new List { + new LlvmIrFunctionParameter (typeof(long)) { + Immarg = true, + }, + new LlvmIrFunctionParameter (typeof(sbyte), isNativePointer: true) { + NoCapture = true, + }, + }; + + if (llvm_lifetime_start_p0i8_ref == null) { + var llvm_lifetime_start_p0i8_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: llvm_lifetime_start_end_params + ); + + llvm_lifetime_start_p0i8_ref = new LlvmIrVariableReference (llvm_lifetime_start_p0i8_sig, llvm_lifetime_start_p0i8_name, isGlobal: true); + AddFunctionDeclaration (llvm_lifetime_start_p0i8_name, llvm_lifetime_start_p0i8_sig, LlvmIrGenerator.FunctionAttributesLlvmLifetime); + } + + if (llvm_lifetime_end_p0i8_ref == null) { + var llvm_lifetime_end_p0i8_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: llvm_lifetime_start_end_params + ); + + llvm_lifetime_end_p0i8_ref = new LlvmIrVariableReference (llvm_lifetime_end_p0i8_sig, llvm_lifetime_end_p0i8_name, isGlobal: true); + AddFunctionDeclaration (llvm_lifetime_end_p0i8_name, llvm_lifetime_end_p0i8_sig, LlvmIrGenerator.FunctionAttributesLlvmLifetime); + } + + void AddFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) + { + var func = new LlvmIrFunction ( + name: name, + returnType: sig.ReturnType, + attributeSetID: attributeSetID, + parameters: sig.Parameters + ); + AddExternalFunction (func); + } + } + + public (LlvmIrFunctionLocalVariable variable, LlvmIrFunctionLocalVariable lifetimeTracker) EmitAllocStackVariable (LlvmIrFunction function, Type type, string? name = null) + { + RegisterLlvmLifetimeTrackerFunctions (); + + string alignment = PointerSize.ToString (CultureInfo.InvariantCulture); + LlvmIrFunctionLocalVariable localVariable = function.MakeLocalVariable (type, name); + LlvmIrFunctionLocalVariable lifetimeTracker = function.MakeLocalVariable (localVariable.Type); + + string localVariableTypeName = GetKnownIRType (localVariable.Type); + Output.WriteLine ($"{function.Indent}%{localVariable.Name} = alloca {localVariableTypeName}, align {alignment}"); + Output.WriteLine ($"{function.Indent}%{lifetimeTracker.Name} = bitcast {GetKnownIRType (lifetimeTracker.Type)}* %{localVariable.Name} to {localVariableTypeName}"); + + var lifetimeStartArgs = new List { + new LlvmIrFunctionArgument (typeof(long), (long)PointerSize), + new LlvmIrFunctionArgument (lifetimeTracker) { + NonNull = true, + }, + }; + + EmitCall (function, llvm_lifetime_start_p0i8_ref, lifetimeStartArgs, marker: LlvmIrCallMarker.None, AttributeSetID: FunctionAttributesLlvmLifetime); + return (localVariable, lifetimeTracker); + } + + public void EmitDeallocStackVariable (LlvmIrFunction function, LlvmIrFunctionLocalVariable lifetimeTracker) + { + RegisterLlvmLifetimeTrackerFunctions (); + + var lifetimeEndArgs = new List { + new LlvmIrFunctionArgument (typeof(long), (long)PointerSize), + new LlvmIrFunctionArgument (lifetimeTracker) { + NonNull = true, + }, + }; + + EmitCall (function, llvm_lifetime_end_p0i8_ref, lifetimeEndArgs, marker: LlvmIrCallMarker.None, AttributeSetID: FunctionAttributesLlvmLifetime); + } + public void InitCodeOutput () { if (codeOutputInitialized) { @@ -549,6 +700,24 @@ protected virtual void InitFunctionAttributes () FunctionAttributes[FunctionAttributesCall] = new LlvmFunctionAttributeSet { new NounwindFunctionAttribute (), }; + + FunctionAttributes[FunctionAttributesLlvmLifetime] = new LlvmFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; + + FunctionAttributes[FunctionAttributesLibcFree] = new LlvmFunctionAttributeSet { + new InaccessiblememOrArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; + + FunctionAttributes[FunctionAttributesLibcFree].Add (FunctionAttributes[FunctionAttributesJniMethods]); } void WriteAttributeSets () @@ -560,6 +729,8 @@ void WriteAttributeSets () WriteSet (FunctionAttributesXamarinAppInit, Output); WriteSet (FunctionAttributesJniMethods, Output); WriteSet (FunctionAttributesCall, Output); + WriteSet (FunctionAttributesLlvmLifetime, Output); + WriteSet (FunctionAttributesLibcFree, Output); Output.WriteLine (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs index 81aa5dfdb9a..d685e3f6685 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs @@ -19,7 +19,7 @@ class X64LlvmIrGenerator : LlvmIrGenerator static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { new FramePointerFunctionAttribute ("none"), new TargetCpuFunctionAttribute ("x86-64"), - new TargetFeaturesFunctionAttribute ("+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), + new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), new TuneCpuFunctionAttribute ("generic"), }; @@ -47,6 +47,7 @@ protected override void InitFunctionAttributes () FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs index d779bf25bb2..df33d17e9b7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs @@ -40,6 +40,7 @@ protected override void InitFunctionAttributes () FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 42b0232e4bc..56ad024f4d5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -176,6 +176,8 @@ sealed class MarshalMethodName const string mm_trace_func_enter_name = "_mm_trace_func_enter"; const string mm_trace_func_leave_name = "_mm_trace_func_leave"; + const string asprintf_name = "asprintf"; + const string free_name = "free"; ICollection uniqueAssemblyNames; int numberOfAssembliesInApk; @@ -208,6 +210,8 @@ sealed class MarshalMethodName // Tracing LlvmIrVariableReference? mm_trace_func_enter_ref; LlvmIrVariableReference? mm_trace_func_leave_ref; + LlvmIrVariableReference? asprintf_ref; + LlvmIrVariableReference? free_ref; readonly bool generateEmptyCode; readonly MarshalMethodsTracingMode tracingMode; @@ -692,18 +696,47 @@ void WriteInitTracing (LlvmIrGenerator generator) ); mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); - WriteTraceDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig.ReturnType, mm_trace_func_enter_sig.Parameters); - WriteTraceDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig.ReturnType, mm_trace_func_leave_sig.Parameters); + var asprintf_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(int), + parameters: new List { + new LlvmIrFunctionParameter (typeof(string), isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(string)) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(void)) { + IsVarargs = true, + } + } + ); + asprintf_ref = new LlvmIrVariableReference (asprintf_sig, asprintf_name, isGlobal: true); + + var free_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: new List { + new LlvmIrFunctionParameter (typeof(string)) { + NoCapture = true, + NoUndef = true, + }, + } + ); + free_ref = new LlvmIrVariableReference (free_sig, free_name, isGlobal: true); + + AddTraceFunctionDeclaration (asprintf_name, asprintf_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (free_name, free_sig, LlvmIrGenerator.FunctionAttributesLibcFree); + AddTraceFunctionDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - void WriteTraceDeclaration (string name, Type returnType, IList parameters) + void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) { var func = new LlvmIrFunction ( name: name, - returnType: returnType, - attributeSetID: LlvmIrGenerator.FunctionAttributesJniMethods, - parameters: parameters + returnType: sig.ReturnType, + attributeSetID: attributeSetID, + parameters: sig.Parameters ); - generator.WriteFunctionForwardDeclaration (func); + generator.AddExternalFunction (func); } } @@ -753,8 +786,13 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); List? trace_enter_leave_args = null; + LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; if (tracingMode != MarshalMethodsTracingMode.None) { + const string paramsLocalVarName = "func_params"; + + (LlvmIrFunctionLocalVariable variable, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); + trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env new LlvmIrFunctionArgument (typeof(int), (int)tracingMode), @@ -821,6 +859,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll if (tracingMode != MarshalMethodsTracingMode.None) { generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args); + generator.EmitDeallocStackVariable (func, tracingParamsStringLifetimeTracker); } if (result != null) { diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 11bb6c1a108..793feafbc44 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -21,11 +21,9 @@ using namespace xamarin::android::internal; constexpr int PRIORITY = ANDROID_LOG_INFO; constexpr char LEAD[] = "MM: "; - -struct MethodParams -{ - std::string buffer; -}; +constexpr char BOOL_TRUE[] = "true"; +constexpr char BOOL_FALSE[] = "false"; +constexpr char MISSING_ENV[] = ""; void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept { @@ -90,179 +88,20 @@ void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_imag _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); } -template -[[gnu::always_inline]] -static void append_param (std::string& buffer, TVal value) noexcept -{ - if (!buffer.empty ()) { - buffer.append (", "); - } - - buffer.append (value); -} - -template -concept AcceptableInteger = std::is_integral_v || std::is_pointer_v; - -template -[[gnu::always_inline]] -static void append_hex_integer (std::string& buffer, TVal value) noexcept -{ - const char *format; - - if constexpr (std::is_same_v || std::is_same_v) { - format = "0x%lx"; - } else if constexpr (std::is_pointer_v) { - format = "%p"; - } else { - format = "0x%x"; - } - - // Enough room for maximum decimal representation of 64-bit unsigned value, or a minimum signed one with the sign. - std::array::digits10 + 2> data_buf; - std::snprintf (data_buf.data (), data_buf.size (), format, value); - append_param (buffer, data_buf.data ()); -} - -[[gnu::always_inline]] -static void append_pointer (std::string& buffer, void *pointer) noexcept -{ - append_hex_integer (buffer, pointer); -} - -MethodParams* _mm_trace_method_params_new (JNIEnv *env, jclass klass) noexcept -{ - auto ret = new MethodParams (); - - append_pointer (ret->buffer, env); - append_pointer (ret->buffer, klass); - - return ret; -} - -void _mm_trace_method_params_destroy (MethodParams *v) noexcept -{ - if (v == nullptr) { - return; - } - - delete v; -} - -void _mm_trace_param_append_bool (MethodParams *state, bool v) noexcept -{ - if (state == nullptr) { - return; - } - - append_param (state->buffer, v ? "true" : "false"); -} - -template -[[gnu::always_inline]] -static void _mm_trace_param_append_hex_integer (MethodParams *state, TVal v) noexcept -{ - if (state == nullptr) { - return; - } - - append_hex_integer (state->buffer, v); -} - -void _mm_trace_param_append_byte (MethodParams *state, uint8_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_sbyte (MethodParams *state, int8_t v) noexcept +const char* _mm_trace_render_bool (bool v) noexcept { - _mm_trace_param_append_hex_integer (state, v); + return v ? BOOL_TRUE : BOOL_FALSE; } -void _mm_trace_param_append_char (MethodParams *state, char v) noexcept +const char* _mm_trace_render_java_string (JNIEnv *env, jstring v) noexcept { - if (state == nullptr) { - return; - } - - state->buffer.append ("'"); - state->buffer.append (1, v); - state->buffer.append ("'"); -} - -void _mm_trace_param_append_short (MethodParams *state, int16_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_ushort (MethodParams *state, uint16_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_int (MethodParams *state, int32_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_uint (MethodParams *state, uint32_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_long (MethodParams *state, int64_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_ulong (MethodParams *state, uint64_t v) noexcept -{ - _mm_trace_param_append_hex_integer (state, v); -} - -void _mm_trace_param_append_float (MethodParams *state, float v) noexcept -{ - if (state == nullptr) { - return; - } - - state->buffer.append (std::to_string (v)); -} - -void _mm_trace_param_append_double (MethodParams *state, double v) noexcept -{ - if (state == nullptr) { - return; - } - - state->buffer.append (std::to_string (v)); -} - -void _mm_trace_param_append_string (MethodParams *state, JNIEnv *env, jstring v) noexcept -{ - if (state == nullptr) { - return; - } - if (env == nullptr) { - state->buffer.append ("\"\""); - return; + return strdup (MISSING_ENV); } const char *s = env->GetStringUTFChars (v, nullptr); - - state->buffer.append ("\""); - state->buffer.append (s); - state->buffer.append ("\""); - + const char *ret = strdup (s); env->ReleaseStringUTFChars (v, s); -} -void _mm_trace_param_append_pointer (MethodParams *state, void* v) noexcept -{ - if (state == nullptr) { - return; - } - - append_pointer (state->buffer, v); + return ret; } diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index aa1bef6129a..187056116bb 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -13,8 +13,6 @@ inline constexpr int32_t TracingModeBasic = 0x01; inline constexpr int32_t TracingModeFull = 0x02; extern "C" { - struct MethodParams; - [[gnu::visibility("hidden")]] void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; @@ -24,53 +22,13 @@ extern "C" { [[gnu::visibility("hidden")]] void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + // Returns pointer to a constant string, must not be freed [[gnu::visibility("hidden")]] - MethodParams* _mm_trace_method_params_new (JNIEnv *env, jclass klass) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_method_params_destroy (MethodParams *v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_bool (MethodParams *state, bool v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_byte (MethodParams *state, uint8_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_sbyte (MethodParams *state, int8_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_char (MethodParams *state, char v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_short (MethodParams *state, int16_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_ushort (MethodParams *state, uint16_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_int (MethodParams *state, int32_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_uint (MethodParams *state, uint32_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_long (MethodParams *state, int64_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_ulong (MethodParams *state, uint64_t v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_float (MethodParams *state, float v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_double (MethodParams *state, double v) noexcept; - - [[gnu::visibility("hidden")]] - void _mm_trace_param_append_string (MethodParams *state, JNIEnv *env, jstring v) noexcept; + const char* _mm_trace_render_bool (bool v) noexcept; + // Returns pointer to a dynamically allocated string, must be freed [[gnu::visibility("hidden")]] - void _mm_trace_param_append_pointer (MethodParams *state, void* v) noexcept; + const char* _mm_trace_render_java_string (JNIEnv *env, jstring v) noexcept; } #endif // ndef __MARSHAL_METHODS_TRACING_HH From bc5b0f83714007019a39b756e40780f2e14813ee Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 8 May 2023 22:22:00 +0200 Subject: [PATCH 21/60] Progress towards supporting variadic functions Code won't work right now, need to unify LlvmIrFunctionParameter with LlvmIrFunctionArgument tomorrow, it's currently very awkward to render **argument** types when handling variadic arguments for which there are no matching **parameters** --- TheoriesToTest.txt | 4 + .../LlvmIrGenerator/LlvmIrFunction.cs | 23 +++- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 101 +++++++++++++++--- .../LlvmIrGenerator/LlvmIrGenerator.cs | 26 ++++- .../LlvmNativeFunctionSignature.cs | 15 +++ .../MarshalMethodsNativeAssemblyGenerator.cs | 67 ++++++++---- 6 files changed, 196 insertions(+), 40 deletions(-) create mode 100644 TheoriesToTest.txt diff --git a/TheoriesToTest.txt b/TheoriesToTest.txt new file mode 100644 index 00000000000..7517af47f8a --- /dev/null +++ b/TheoriesToTest.txt @@ -0,0 +1,4 @@ +* See if it's a threading issue - add full locking to `get_function_pointer` instead of atomics that we use now +* See if it's an ALC issue - call `get_function_pointer` every time and check the returned pointer value +* Implement Jon's idea to always have the "old" dynamic registration stuff in JCW and only use it when some flag + is set and marshal methods are active. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 3dcc61e9eef..35a93fadf15 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -71,24 +71,39 @@ class LlvmIrFunctionArgument { public object Value { get; } public Type Type { get; } + public bool IsNativePointer { get; } public bool NonNull { get; set; } + public bool NoUndef { get; set; } - public LlvmIrFunctionArgument (Type type, object? value = null) + public LlvmIrFunctionArgument (LlvmIrFunctionParameter parameter, object? value = null) { - Type = type ?? throw new ArgumentNullException (nameof (type)); + Type = parameter?.Type ?? throw new ArgumentNullException (nameof (parameter)); + IsNativePointer = parameter.IsNativePointer; - if (value != null && value.GetType () != type) { - throw new ArgumentException ($"value type '{value.GetType ()}' does not match the argument type '{type}'"); + if (value != null && value.GetType () != Type) { + throw new ArgumentException ($"value type '{value.GetType ()}' does not match the argument type '{Type}'"); } Value = value; } + public LlvmIrFunctionArgument (LlvmIrGenerator.StringSymbolInfo symbol) + { + Type = typeof(LlvmIrGenerator.StringSymbolInfo); + Value = symbol; + } + public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable) { Type = typeof(LlvmIrFunctionLocalVariable); Value = variable; } + + public LlvmIrFunctionArgument (LlvmIrVariableReference variable) + { + Type = typeof(LlvmIrVariableReference); + Value = variable; + } } /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 9e11f158697..c065b812188 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -28,6 +28,7 @@ abstract partial class LlvmIrGenerator List? externalFunctions = null; LlvmIrVariableReference? llvm_lifetime_start_p0i8_ref; LlvmIrVariableReference? llvm_lifetime_end_p0i8_ref; + List? llvm_lifetime_start_end_params; public void WriteFunctionDeclarations () { @@ -269,6 +270,34 @@ public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVa Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); } + public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrVariableReference destination, T? value) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + if (typeof(T) != destination.Type) { + throw new InvalidOperationException ($"Destination variable is of type '{destination.Type}', it cannot be assigned a value of type {typeof(T)}"); + } + + if (value == null && typeof(T) != typeof(string) && typeof(T) != typeof(IntPtr) && !destination.IsNativePointer) { + throw new InvalidOperationException ($"Cannot assign a NULL pointer value to variable of non-pointer type '{destination.Type}'"); + } + + var dataTypeSB = new StringBuilder (); + CodeRenderType (destination, dataTypeSB); // Types are identical, we can use destination to render the type here + string dataType = dataTypeSB.ToString (); + + Output.Write ($"{function.Indent}store {dataType} "); + if (value == null) { + Output.Write ("null"); + } else { + Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + } + Output.Write ($", {dataType}"); + Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); + } + /// /// Emits the load instruction (https://llvm.org/docs/LangRef.html#load-instruction) /// @@ -403,8 +432,8 @@ public void EmitLabel (LlvmIrFunction function, string labelName) throw new ArgumentNullException (nameof (arguments)); } - if (targetSignature.Parameters.Count != arguments.Count) { - throw new ArgumentException ($"number of passed parameters ({arguments.Count}) does not match number of parameters in function signature ({targetSignature.Parameters.Count})", nameof (arguments)); + if ((targetSignature.IsVariadic && targetSignature.NumberOfRequiredArguments > arguments.Count) || (!targetSignature.IsVariadic && targetSignature.NumberOfRequiredArguments != arguments.Count)) { + throw new ArgumentException ($"number of passed parameters ({arguments.Count}) does not match number of required parameters in function signature ({targetSignature.NumberOfRequiredArguments})", nameof (arguments)); } } @@ -437,31 +466,64 @@ public void EmitLabel (LlvmIrFunction function, string labelName) throw new InvalidOperationException ($"Unsupported call marker '{marker}'"); } - Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} {targetRef.Reference} ("); + Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} "); + if (targetSignature.IsVariadic) { + Output.Write ('('); + for (int i = 0; i < targetSignature.NumberOfRequiredArguments; i++) { + if (i > 0) { + Output.Write (", "); + } + LlvmIrFunctionParameter argument = targetSignature.Parameters[i]; + Output.Write (GetParameterType (argument)); + } + Output.Write (", ...) "); // We know the variadic parameter is last + } + Output.Write ($"{targetRef.Reference} ("); if (haveParameters) { + bool variadicCountry = false; for (int i = 0; i < targetSignature.Parameters.Count; i++) { - LlvmIrFunctionParameter parameter = targetSignature.Parameters[i]; - LlvmIrFunctionArgument argument = arguments[i]; + LlvmIrFunctionParameter? parameter = null; + + if (!variadicCountry) { + parameter = targetSignature.Parameters[i]; - AssertValidType (i, parameter, argument); + if (parameter.IsVarargs) { + variadicCountry = true; + } + } if (i > 0) { Output.Write (", "); } - string extra = parameter.IsNativePointer ? "*" : String.Empty; - string paramType = $"{GetKnownIRType (parameter.Type)}{extra}"; + LlvmIrFunctionArgument argument = arguments[i]; + + string paramType; + if (!variadicCountry) { + AssertValidType (i, parameter, argument); + + paramType = GetParameterType (parameter); + } else { + paramType = String.Empty; + } + Output.Write ($"{paramType} "); if (argument.NonNull) { Output.Write ("nonnull "); } + if (argument.NoUndef) { + Output.Write ("noundef "); + } + if (argument.Value is LlvmIrFunctionLocalVariable variable) { Output.Write ($"%{variable.Name}"); - } else if (parameter.Type.IsNativePointer () || parameter.IsNativePointer) { - if (parameter.IsCplusPlusReference) { + } else if (argument.Value is StringSymbolInfo stringSymbol) { + WriteGetStringPointer (stringSymbol.SymbolName, stringSymbol.Size, indent: false, detectBitness: true, skipPointerType: true); + } else if (argument.Type.IsNativePointer () || argument.IsNativePointer) { + if (parameter != null && parameter.IsCplusPlusReference) { Output.Write ("nonnull "); } @@ -469,7 +531,7 @@ public void EmitLabel (LlvmIrFunction function, string labelName) Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) "); if (argument.Value is LlvmIrVariableReference variableRef) { - bool needBitcast = parameter.Type != argument.Type; + bool needBitcast = parameter == null ? false : parameter.Type != argument.Type; if (needBitcast) { Output.Write ("bitcast ("); @@ -506,6 +568,12 @@ public void EmitLabel (LlvmIrFunction function, string labelName) return result; + string GetParameterType (LlvmIrFunctionParameter parameter) + { + string extra = parameter.IsNativePointer ? "*" : String.Empty; + return $"{GetKnownIRType (parameter.Type)}{extra}"; + } + static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) { if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { @@ -513,6 +581,11 @@ static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmI } if (parameter.Type != typeof(IntPtr)) { + if (argument.Type == typeof(StringSymbolInfo) && parameter.Type == typeof (string)) { + // Fine, we want to pass a pointer to string + return; + } + if (argument.Type != parameter.Type) { ThrowException (); } @@ -576,7 +649,7 @@ void RegisterLlvmLifetimeTrackerFunctions () const string llvm_lifetime_start_p0i8_name = "llvm.lifetime.start.p0i8"; const string llvm_lifetime_end_p0i8_name = "llvm.lifetime.end.p0i8"; - var llvm_lifetime_start_end_params = new List { + llvm_lifetime_start_end_params = new List { new LlvmIrFunctionParameter (typeof(long)) { Immarg = true, }, @@ -630,7 +703,7 @@ void AddFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int a Output.WriteLine ($"{function.Indent}%{lifetimeTracker.Name} = bitcast {GetKnownIRType (lifetimeTracker.Type)}* %{localVariable.Name} to {localVariableTypeName}"); var lifetimeStartArgs = new List { - new LlvmIrFunctionArgument (typeof(long), (long)PointerSize), + new LlvmIrFunctionArgument (llvm_lifetime_start_end_params[0], (long)PointerSize), new LlvmIrFunctionArgument (lifetimeTracker) { NonNull = true, }, @@ -645,7 +718,7 @@ public void EmitDeallocStackVariable (LlvmIrFunction function, LlvmIrFunctionLoc RegisterLlvmLifetimeTrackerFunctions (); var lifetimeEndArgs = new List { - new LlvmIrFunctionArgument (typeof(long), (long)PointerSize), + new LlvmIrFunctionArgument (llvm_lifetime_start_end_params[0], (long)PointerSize), new LlvmIrFunctionArgument (lifetimeTracker) { NonNull = true, }, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index bdb9a92adcf..b99b9067a74 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -1291,7 +1291,13 @@ public string WriteString (string symbolName, string value) } /// + /// /// Writes a string with symbol options (writeability, visibility) options specified in the parameter. + /// + /// + /// If symbol is local, as per , then is used as a common prefix for a group of strings, with unique symbol + /// names assigned to each string added to the group. + /// /// public string WriteString (string symbolName, string value, LlvmIrVariableOptions options) { @@ -1299,15 +1305,22 @@ public string WriteString (string symbolName, string value, LlvmIrVariableOption } /// + /// /// Writes a string with specified , and symbol options (writeability, visibility etc) specified in the - /// parameter. Returns string size (in bytes) in + /// parameter. Places string size (in bytes) in . + /// + /// + /// If symbol is local, as per , then is used as a common prefix for a group of strings, with unique symbol + /// names assigned to each string added to the group. + /// /// + /// Name of the native symbol which refers to the written string. public string WriteString (string symbolName, string value, LlvmIrVariableOptions options, out ulong stringSize) { - StringSymbolInfo info = StringManager.Add (value, groupName: symbolName); + StringSymbolInfo info = AddString (value, groupName: symbolName); stringSize = info.Size; if (!options.IsGlobal) { - return symbolName; + return info.SymbolName; } string indexType = Is64Bit ? "i64" : "i32"; @@ -1319,7 +1332,12 @@ public string WriteString (string symbolName, string value, LlvmIrVariableOption return symbolName; } - public virtual void WriteFileTop () + public StringSymbolInfo AddString (string value, string? groupName = null) + { + return StringManager.Add (value, groupName: groupName); + } + + public void WriteFileTop () { WriteCommentLine ($"ModuleID = '{fileName}'"); WriteDirective ("source_filename", QuoteStringNoEscape (fileName)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs index 01471c8199a..5a92931d5da 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs @@ -12,12 +12,17 @@ namespace Xamarin.Android.Tasks.LLVMIR /// class LlvmNativeFunctionSignature { + int requiredArgsCount; + public Type ReturnType { get; } public IList? Parameters { get; } public object? FieldValue { get; set; } + public bool IsVariadic { get; set; } + public int NumberOfRequiredArguments => requiredArgsCount; public LlvmNativeFunctionSignature (Type returnType, List? parameters = null) { + requiredArgsCount = 0; ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); Parameters = parameters?.Select (p => EnsureValidParameter (p))?.ToList ()?.AsReadOnly (); @@ -27,6 +32,16 @@ LlvmIrFunctionParameter EnsureValidParameter (LlvmIrFunctionParameter parameter) throw new InvalidOperationException ("null parameters aren't allowed"); } + if (IsVariadic) { + throw new InvalidOperationException ("Variadic argument must be the last one"); + } + + if (parameter.IsVarargs) { + IsVariadic = true; + } else { + requiredArgsCount++; + } + return parameter; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 56ad024f4d5..13da8ea417a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -208,6 +208,8 @@ sealed class MarshalMethodName List> classes = new List> (); // Tracing + List? mm_trace_func_enter_or_leave_params; + List? get_function_pointer_params; LlvmIrVariableReference? mm_trace_func_enter_ref; LlvmIrVariableReference? mm_trace_func_leave_ref; LlvmIrVariableReference? asprintf_ref; @@ -674,7 +676,7 @@ void WriteInitTracing (LlvmIrGenerator generator) } // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - var mm_trace_func_enter_or_leave_params = new List { + mm_trace_func_enter_or_leave_params = new List { new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), @@ -758,6 +760,14 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm } } + string GetPrintfFormatForFunctionParams (LlvmIrFunction func) + { + var ret = new StringBuilder ('('); + + ret.Append (')'); + return ret.ToString (); + } + void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) { var backingFieldSignature = new LlvmNativeFunctionSignature ( @@ -789,17 +799,36 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; if (tracingMode != MarshalMethodsTracingMode.None) { - const string paramsLocalVarName = "func_params"; + const string paramsLocalVarName = "func_params_render"; + + (LlvmIrFunctionLocalVariable paramsRenderVariable, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); + var paramsRenderVariableRef = new LlvmIrVariableReference (paramsRenderVariable, isGlobal: false); + generator.EmitStoreInstruction (func, paramsRenderVariableRef, null); + string asprintfFormat = GetPrintfFormatForFunctionParams (func); + LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (asprintfFormat, $"asprintf_fmt_{func.Name}"); - (LlvmIrFunctionLocalVariable variable, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); + var asprintf_args = new List { + new LlvmIrFunctionArgument (paramsRenderVariable) { + NonNull = true, + NoUndef = true, + }, + new LlvmIrFunctionArgument (asprintfFormatSym) { + NoUndef = true, + }, + }; + + foreach (LlvmIrFunctionLocalVariable lfv in func.ParameterVariables) { + asprintf_args.Add (new LlvmIrFunctionArgument (lfv)); + } + generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None); trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env - new LlvmIrFunctionArgument (typeof(int), (int)tracingMode), - new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (typeof(string), method.NativeSymbolName), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[1], (int)tracingMode), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[2], method.AssemblyCacheIndex), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[3], method.ClassCacheIndex), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[4], nativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[5], method.NativeSymbolName), }; generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args); @@ -825,10 +854,10 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll func, getFunctionPtrRef, new List { - new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (typeof(LlvmIrVariableReference), backingFieldRef), + new LlvmIrFunctionArgument (get_function_pointer_params[0], method.AssemblyCacheIndex), + new LlvmIrFunctionArgument (get_function_pointer_params[1], method.ClassCacheIndex), + new LlvmIrFunctionArgument (get_function_pointer_params[2], nativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (backingFieldRef), } ); @@ -871,14 +900,16 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) { + get_function_pointer_params = new List { + new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), + new LlvmIrFunctionParameter (typeof(uint), "class_index"), + new LlvmIrFunctionParameter (typeof(uint), "method_token"), + new LlvmIrFunctionParameter (typeof(IntPtr), "target_ptr", isNativePointer: true, isCplusPlusReference: true) + }; + var get_function_pointer_sig = new LlvmNativeFunctionSignature ( returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), - new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token"), - new LlvmIrFunctionParameter (typeof(IntPtr), "target_ptr", isNativePointer: true, isCplusPlusReference: true) - } + parameters: get_function_pointer_params ) { FieldValue = "null", }; From 7600af18fda4cf4e37f70d282c41b7c98df29bc3 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 8 May 2023 23:02:50 +0200 Subject: [PATCH 22/60] Well, it generates correct code now --- .../LlvmIrGenerator/LlvmIrFunction.cs | 4 +++- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 24 +++++++++++++++++-- .../MarshalMethodsNativeAssemblyGenerator.cs | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 35a93fadf15..d1c7bd121bb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -97,12 +97,14 @@ public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable) { Type = typeof(LlvmIrFunctionLocalVariable); Value = variable; + IsNativePointer = variable.IsNativePointer; } public LlvmIrFunctionArgument (LlvmIrVariableReference variable) { Type = typeof(LlvmIrVariableReference); Value = variable; + IsNativePointer = variable.IsNativePointer; } } @@ -139,7 +141,7 @@ public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList EnsureParameterName (p))?.ToList ()?.AsReadOnly (); - ParameterVariables = Parameters?.Select (p => new LlvmIrFunctionLocalVariable (p.Type, p.Name))?.ToList ()?.AsReadOnly (); + ParameterVariables = Parameters?.Select (p => new LlvmIrFunctionLocalVariable (p.Type, p.Name, isNativePointer: p.IsNativePointer))?.ToList ()?.AsReadOnly (); // Unnamed local variables need to start from the value which equals [number_of_unnamed_parameters] + 1, // since there's an implicit label created for the top of the function whose name is `[number_of_unnamed_parameters]` diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index c065b812188..215b69949f8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -482,10 +482,14 @@ public void EmitLabel (LlvmIrFunction function, string labelName) if (haveParameters) { bool variadicCountry = false; - for (int i = 0; i < targetSignature.Parameters.Count; i++) { + for (int i = 0; i < arguments.Count; i++) { LlvmIrFunctionParameter? parameter = null; if (!variadicCountry) { + if (i >= targetSignature.Parameters.Count) { + throw new InvalidOperationException ("Internal error: Exceeded number of declared parameters, expected a trailing variadic parameter at this point"); + } + parameter = targetSignature.Parameters[i]; if (parameter.IsVarargs) { @@ -505,7 +509,7 @@ public void EmitLabel (LlvmIrFunction function, string labelName) paramType = GetParameterType (parameter); } else { - paramType = String.Empty; + paramType = GetArgumentType (argument); } Output.Write ($"{paramType} "); @@ -574,6 +578,22 @@ string GetParameterType (LlvmIrFunctionParameter parameter) return $"{GetKnownIRType (parameter.Type)}{extra}"; } + string GetArgumentType (LlvmIrFunctionArgument argument) + { + string extra = argument.IsNativePointer ? "*" : String.Empty; + + Type type; + if (argument.Value is LlvmIrFunctionLocalVariable variable) { + type = variable.Type; + } else if (argument.Value is StringSymbolInfo stringSymbol) { + type = typeof(string); + } else { + type = argument.Type; + } + + return $"{GetKnownIRType (type)}{extra}"; + } + static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) { if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 13da8ea417a..67bd6d2e5f2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -779,7 +779,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll CecilMethodDefinition nativeCallback = method.Method.NativeCallback; string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; - var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); + var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true, isNativePointer: true); if (!usedBackingFields.Contains (backingFieldName)) { generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); From 19e79a28b967d453fdafde28e5aae2009706577e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 10 May 2023 00:07:38 +0200 Subject: [PATCH 23/60] Generated code not complete, won't compile Continue to make generator more flexible and capable to add parameter tracing --- .../LlvmIrGenerator/LlvmIrFunction.cs | 36 +++++++++ .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 58 +++++++++------ .../LlvmIrVariableReference.cs | 2 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 73 ++++++++++++++----- 4 files changed, 128 insertions(+), 41 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index d1c7bd121bb..d1f74c2c97c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -132,6 +132,33 @@ class LlvmIrFunction uint localSlot = 0; uint indentLevel = 1; + // This is a hack to work around a requirement in LLVM IR compiler that we cannot meet with a forward-only, single-pass generator like ours. Namely: + // LLVM IR compiler uses a monotonically increasing counter for all the unnamed function parameters, local variables and labels and it expects them to be + // used in strict sequence in the generated code. However, branch instructions need to know their target labels, so in a generator like ours we'd have to allocate + // their names before outputting the branch instruction and the blocks that we refer to. However, if those blocks use unnamed (counted) variables themselves, then + // they would be allocated numbers out of sequence, resulting in code similar to: + // + // br i1 %7, label %8, label %9 + // 8: + // %11 = load i8*, i8** %func_params_render, align 8 + // br label %10 + // + // 9: + // store i8* null, i8** %func_params_render, align 8 + // br label %10 + // + // 10: + // + // In this instance, the LLVM IR compiler would complain about the line after `8:` as follows: + // + // error: instruction expected to be numbered '%9' + // + // Since we have no time to rewrite the generator in some manner that would support this scenario (e.g. two-pass generator with an AST and label/parameter/variable + // placeholders), we need to employ a different technique: named labels. They won't be subject to the samme restrictions, but they pose another problem - if a + // given block of code is output more than once and generates the same label names, we'd have another error on our hands. Thus this counter variable, which will + // generate a unique label name by appending a number to some prefix + uint labelCounter = 0; + public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null, bool skipParameterNames = false) { if (String.IsNullOrEmpty (name)) { @@ -188,6 +215,15 @@ public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, s return new LlvmIrFunctionLocalVariable (variable, name, isNativePointer: isNativePointer); } + /// + /// Return name of a local label with unique name. + /// + public string MakeUniqueLabel (string? prefix = null) + { + string name = String.IsNullOrEmpty (prefix) ? "ll" : prefix; + return $"{name}{labelCounter++}"; + } + public void IncreaseIndent () { indentLevel++; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 215b69949f8..a41ada474e8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -111,18 +111,25 @@ public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) Output.WriteLine ("{"); } - void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null) + void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null, bool ignoreNativePointer = false) { + string extraPointer = !ignoreNativePointer && variable.IsNativePointer ? "*" : String.Empty; + if (variable.NativeFunction != null) { if (builder == null) { WriteFunctionSignature (variable.NativeFunction); + if (extraPointer.Length > 0) { + Output.Write (extraPointer); + } } else { builder.Append (RenderFunctionSignature (variable.NativeFunction)); + if (extraPointer.Length > 0) { + builder.Append (extraPointer); + } } return; } - string extraPointer = variable.IsNativePointer ? "*" : String.Empty; StringBuilder? flags = null; if (variable is LlvmIrFunctionParameter fparam) { if (fparam.IsVarargs) { @@ -284,18 +291,17 @@ public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrVariableRefe throw new InvalidOperationException ($"Cannot assign a NULL pointer value to variable of non-pointer type '{destination.Type}'"); } - var dataTypeSB = new StringBuilder (); - CodeRenderType (destination, dataTypeSB); // Types are identical, we can use destination to render the type here - string dataType = dataTypeSB.ToString (); - - Output.Write ($"{function.Indent}store {dataType} "); + Output.Write ($"{function.Indent}store "); + CodeRenderType (destination, ignoreNativePointer: true); + Output.Write (' '); if (value == null) { Output.Write ("null"); } else { Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); } - Output.Write ($", {dataType}"); - Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); + Output.Write (", "); + CodeRenderType (destination); + Output.WriteLine ($" {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); } /// @@ -307,12 +313,16 @@ public LlvmIrFunctionLocalVariable EmitLoadInstruction (LlvmIrFunction function, throw new ArgumentNullException (nameof (function)); } - var sb = new StringBuilder (); - CodeRenderType (source, sb); - - string variableType = sb.ToString (); LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (source, resultVariableName); - Output.WriteLine ($"{function.Indent}%{result.Name} = load {variableType}, {variableType}* @{source.Name}, align {PointerSize.ToString (CultureInfo.InvariantCulture)}"); + Output.Write ($"{function.Indent}%{result.Name} = load "); + CodeRenderType (source, ignoreNativePointer: true); + Output.Write (", "); + CodeRenderType (source); + if (!source.IsNativePointer) { + Output.Write ('*'); + } + + Output.WriteLine ($" {source.Reference}, align {PointerSize.ToString (CultureInfo.InvariantCulture)}"); return result; } @@ -401,12 +411,16 @@ public void EmitBrInstruction (LlvmIrFunction function, string label) Output.WriteLine ($"{function.Indent}br label %{label}"); } - public void EmitLabel (LlvmIrFunction function, string labelName) + public void EmitLabel (LlvmIrFunction function, string labelName, bool insertNewlineBefore = true) { if (function == null) { throw new ArgumentNullException (nameof (function)); } + if (insertNewlineBefore) { + Output.WriteLine (); + } + Output.WriteLine ($"{labelName}:"); } @@ -531,16 +545,14 @@ public void EmitLabel (LlvmIrFunction function, string labelName) Output.Write ("nonnull "); } - string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); - Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) "); - if (argument.Value is LlvmIrVariableReference variableRef) { - bool needBitcast = parameter == null ? false : parameter.Type != argument.Type; + bool needBitcast = parameter == null ? false : parameter.Type != variableRef.Type; if (needBitcast) { - Output.Write ("bitcast ("); + string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); + Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) bitcast ("); CodeRenderType (variableRef); - Output.Write ("* "); + Output.Write (' '); } Output.Write (variableRef.Reference); @@ -642,7 +654,7 @@ public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (target, resultVariableName); Output.Write ($"{function.Indent}%{result.Name} = phi "); - CodeRenderType (target); + CodeRenderType (target, ignoreNativePointer: true); bool first = true; foreach ((LlvmIrVariableReference variableRef, string label) in pairs) { @@ -715,7 +727,7 @@ void AddFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int a RegisterLlvmLifetimeTrackerFunctions (); string alignment = PointerSize.ToString (CultureInfo.InvariantCulture); - LlvmIrFunctionLocalVariable localVariable = function.MakeLocalVariable (type, name); + LlvmIrFunctionLocalVariable localVariable = function.MakeLocalVariable (type, name, isNativePointer: true); LlvmIrFunctionLocalVariable lifetimeTracker = function.MakeLocalVariable (localVariable.Type); string localVariableTypeName = GetKnownIRType (localVariable.Type); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs index 628014eeb2a..e6838807877 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs @@ -33,7 +33,7 @@ public LlvmIrVariableReference (LlvmNativeFunctionSignature signature, string na } public LlvmIrVariableReference (LlvmIrVariable variable, bool isGlobal, bool isNativePointer = false) - : base (variable, variable?.Name, isNativePointer) + : base (variable, variable?.Name, isNativePointer || variable.IsNativePointer) { if (String.IsNullOrEmpty (variable?.Name)) { throw new ArgumentException ("variable name must not be null or empty", nameof (variable)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 67bd6d2e5f2..4cdf4110c6a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -768,6 +768,48 @@ string GetPrintfFormatForFunctionParams (LlvmIrFunction func) return ret.ToString (); } + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + { + LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); + + var asprintf_args = new List { + new LlvmIrFunctionArgument (allocatedStringVarRef) { + NonNull = true, + NoUndef = true, + }, + new LlvmIrFunctionArgument (asprintfFormatSym) { + NoUndef = true, + }, + }; + + asprintf_args.AddRange (variadicArgs); + + LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None, AttributeSetID: -1); + LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); + + // Check whether asprintf returned a negative value + LlvmIrFunctionLocalVariable asprintfResultVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.SignedLessThan, resultRef, "0"); + var asprintfResultVariableRef = new LlvmIrVariableReference (asprintfResultVariable, isGlobal: false); + + string asprintfFailedLabel = func.MakeUniqueLabel (); + string asprintfSucceededLabel = func.MakeUniqueLabel (); + string ifElseDoneLabel = func.MakeUniqueLabel (); + + generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfFailedLabel, asprintfSucceededLabel); + + generator.EmitLabel (func, asprintfFailedLabel); + LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); + generator.EmitBrInstruction (func, ifElseDoneLabel); + + generator.EmitLabel (func, asprintfSucceededLabel); + generator.EmitStoreInstruction (func, allocatedStringVarRef, null); + generator.EmitBrInstruction (func, ifElseDoneLabel); + + generator.EmitLabel (func, ifElseDoneLabel); + + return null; + } + void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) { var backingFieldSignature = new LlvmNativeFunctionSignature ( @@ -797,30 +839,27 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll List? trace_enter_leave_args = null; LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; + List? asprintfVariadicArgs = null; + LlvmIrVariableReference? asprintfAllocatedStringAccessorRef = null; + LlvmIrVariableReference? asprintfAllocatedStringVarRef = null; if (tracingMode != MarshalMethodsTracingMode.None) { const string paramsLocalVarName = "func_params_render"; - (LlvmIrFunctionLocalVariable paramsRenderVariable, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); - var paramsRenderVariableRef = new LlvmIrVariableReference (paramsRenderVariable, isGlobal: false); - generator.EmitStoreInstruction (func, paramsRenderVariableRef, null); - string asprintfFormat = GetPrintfFormatForFunctionParams (func); - LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (asprintfFormat, $"asprintf_fmt_{func.Name}"); - - var asprintf_args = new List { - new LlvmIrFunctionArgument (paramsRenderVariable) { - NonNull = true, - NoUndef = true, - }, - new LlvmIrFunctionArgument (asprintfFormatSym) { - NoUndef = true, - }, - }; + (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); + asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); + generator.EmitStoreInstruction (func, asprintfAllocatedStringVarRef, null); + asprintfVariadicArgs = new List (); foreach (LlvmIrFunctionLocalVariable lfv in func.ParameterVariables) { - asprintf_args.Add (new LlvmIrFunctionArgument (lfv)); + asprintfVariadicArgs.Add ( + new LlvmIrFunctionArgument (lfv) { + NoUndef = true, + } + ); } - generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None); + + asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, GetPrintfFormatForFunctionParams (func), asprintfVariadicArgs, asprintfAllocatedStringVarRef); trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env From cae0bd8652992dc81eb85414c2bafe80d6d1fa6b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 10 May 2023 22:06:37 +0200 Subject: [PATCH 24/60] Way too much time spent on this... ...but LLVM IR conditional blocks finally work and IR quirks worked around. The main quirk is that in LLVM IR all unnamed parameters, local variables **and** labels must use sequential numbers as their name. However, a branch instruction needs to be (obviously) generated before the labels it refers to (two of them, for true and false conditions) are output. This poses a problem if code generation is done in one, forward-only, pass - like in our case. There's no way to go back to the br instruction and update label names (which would be easy if an AST was used), so instead of using anonymous labels we now use named ones but whose names have a sequential integer value appended to guarantee uniqueness should two labels of the same name be requested. --- .../LlvmIrGenerator/LlvmIrFunction.cs | 30 ++++++++++++++- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 7 ++-- .../MarshalMethodsNativeAssemblyGenerator.cs | 37 +++++++++++-------- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index d1f74c2c97c..8e292b540b5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -124,6 +124,8 @@ class LlvmIrFunction public IList? Parameters { get; } public string ImplicitFuncTopLabel { get; } public IList? ParameterVariables { get; } + public string PreviousBlockStartLabel => previousBlockStartLabel; + public string PreviousBlockEndLabel => previousBlockEndLabel; // Function writing state public string Indent { get; private set; } = LlvmIrGenerator.Indent; @@ -159,6 +161,16 @@ class LlvmIrFunction // generate a unique label name by appending a number to some prefix uint labelCounter = 0; + // Names of the previous basic code block's start and end delimiters (labels), needed when the `phi` instruction is emitted. The instruction needs to refer to the + // code block preceding the current one, which will be the two labels preceding the current one (including the implicit label pointing to the beginning of the + // function, before any code. + // + // See also https://llvm.org/docs/LangRef.html#phi-instruction + // + string previousBlockStartLabel; + string previousBlockEndLabel; + string currentBlockStartLabel; + public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null, bool skipParameterNames = false) { if (String.IsNullOrEmpty (name)) { @@ -172,7 +184,7 @@ public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? arguments = null, @@ -646,7 +646,7 @@ void ThrowException () /// /// Emits the phi instruction (https://llvm.org/docs/LangRef.html#phi-instruction) for a function pointer type /// - public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrVariableReference target, List<(LlvmIrVariableReference variableRef, string label)> pairs, string? resultVariableName = null) + public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrVariableReference target, List<(LlvmIrVariableReference? variableRef, string label)> pairs, string? resultVariableName = null) { if (function == null) { throw new ArgumentNullException (nameof (function)); @@ -665,7 +665,8 @@ public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, Output.Write (", "); } - Output.Write ($"[{variableRef.Reference}, %{label}]"); + string value = variableRef?.Reference ?? "null"; + Output.Write ($"[{value}, %{label}]"); } Output.WriteLine (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 4cdf4110c6a..2e5d98bb148 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -787,25 +787,35 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None, AttributeSetID: -1); LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); - // Check whether asprintf returned a negative value + // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) LlvmIrFunctionLocalVariable asprintfResultVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.SignedLessThan, resultRef, "0"); var asprintfResultVariableRef = new LlvmIrVariableReference (asprintfResultVariable, isGlobal: false); - string asprintfFailedLabel = func.MakeUniqueLabel (); - string asprintfSucceededLabel = func.MakeUniqueLabel (); - string ifElseDoneLabel = func.MakeUniqueLabel (); + string asprintfIfThenLabel = func.MakeUniqueLabel ("if.then"); + string asprintfIfElseLabel = func.MakeUniqueLabel ("if.else"); + string ifElseDoneLabel = func.MakeUniqueLabel ("if.done"); - generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfFailedLabel, asprintfSucceededLabel); + generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfIfThenLabel, asprintfIfElseLabel); - generator.EmitLabel (func, asprintfFailedLabel); - LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); + // Condition is true if asprintf **failed** + generator.EmitLabel (func, asprintfIfThenLabel); + generator.EmitStoreInstruction (func, allocatedStringVarRef, null); generator.EmitBrInstruction (func, ifElseDoneLabel); - generator.EmitLabel (func, asprintfSucceededLabel); - generator.EmitStoreInstruction (func, allocatedStringVarRef, null); + generator.EmitLabel (func, asprintfIfElseLabel); + LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); + LlvmIrVariableReference bufferPointerVarRef = new LlvmIrVariableReference (bufferPointerVar, isGlobal: false); generator.EmitBrInstruction (func, ifElseDoneLabel); generator.EmitLabel (func, ifElseDoneLabel); + LlvmIrFunctionLocalVariable allocatedStringValueVar = generator.EmitPhiInstruction ( + func, + allocatedStringVarRef, + new List<(LlvmIrVariableReference? variableRef, string label)> { + (null, func.PreviousBlockStartLabel), + (bufferPointerVarRef, func.PreviousBlockEndLabel), + } + ); return null; } @@ -883,9 +893,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll const string callbackLoadedLabel = "callbackLoaded"; generator.EmitBrInstruction (func, isNullVariableRef, loadCallbackLabel, callbackLoadedLabel); - - generator.WriteEOL (); generator.EmitLabel (func, loadCallbackLabel); + LlvmIrFunctionLocalVariable getFunctionPointerVariable = generator.EmitLoadInstruction (func, get_function_pointer_ref, "get_func_ptr"); var getFunctionPtrRef = new LlvmIrVariableReference (getFunctionPointerVariable, isGlobal: false); @@ -904,16 +913,14 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll var callbackVariable2Ref = new LlvmIrVariableReference (callbackVariable2, isGlobal: false); generator.EmitBrInstruction (func, callbackLoadedLabel); - - generator.WriteEOL (); generator.EmitLabel (func, callbackLoadedLabel); LlvmIrFunctionLocalVariable fnVariable = generator.EmitPhiInstruction ( func, backingFieldRef, new List<(LlvmIrVariableReference variableRef, string label)> { - (callbackVariable1Ref, func.ImplicitFuncTopLabel), - (callbackVariable2Ref, loadCallbackLabel), + (callbackVariable1Ref, func.PreviousBlockStartLabel), + (callbackVariable2Ref, func.PreviousBlockEndLabel), }, resultVariableName: "fn" ); From fd3eefd3c99d4e27e30eafdce90e241e8b3c3e59 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 11 May 2023 23:07:06 +0200 Subject: [PATCH 25/60] Found and fixed an issue with marshal methods Wrong size was used for `char` to native translation. The type's 16-bits wide in Java, we used 8 bits. Also added support for System.Char blittable type. Furthermore, added generation of the asprintf format string, added support for upcasting narrower integers to 32-bits and float to double before passing them to asprintf. --- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 31 ++++++ .../LlvmIrGenerator/LlvmIrGenerator.cs | 4 +- .../MarshalMethodsAssemblyRewriter.cs | 25 +++++ .../MarshalMethodsNativeAssemblyGenerator.cs | 103 +++++++++++++++++- 4 files changed, 155 insertions(+), 8 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 0350d0fdbd3..eb9e4abede7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -424,6 +424,37 @@ public void EmitLabel (LlvmIrFunction function, string labelName, bool insertNew Output.WriteLine ($"{function.MakeLabel (labelName)}:"); } + public LlvmIrFunctionLocalVariable EmitUpcast (LlvmIrFunction function, LlvmIrVariableReference sourceRef, Type targetType) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + if (sourceRef == null) { + throw new ArgumentNullException (nameof (sourceRef)); + } + + if (targetType == null) { + throw new ArgumentNullException (nameof (targetType)); + } + + string extendOp; + if (targetType == typeof(double)) { + extendOp = "fp"; + } else if (targetType == typeof(int)) { + extendOp = "s"; + } else if (targetType == typeof(uint)) { + extendOp = "z"; + } else { + throw new InvalidOperationException ($"Unsupported target type for upcasting: {targetType}"); + } + + LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (targetType); + Output.WriteLine ($"{function.Indent}%{result.Name} = {extendOp}ext {GetKnownIRType (sourceRef.Type)} {sourceRef.Reference} to {GetKnownIRType (targetType)}"); + + return result; + } + public LlvmIrFunctionLocalVariable? EmitCall (LlvmIrFunction function, LlvmIrVariableReference targetRef, List? arguments = null, string? resultVariableName = null, LlvmIrCallMarker marker = LlvmIrCallMarker.Tail, int AttributeSetID = FunctionAttributesCall) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index b99b9067a74..139e079c4e7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -56,7 +56,7 @@ public PackedStructureMember (StructureMemberInfo memberInfo, object? value, static readonly Dictionary typeMap = new Dictionary { { typeof (bool), "i8" }, { typeof (byte), "i8" }, - { typeof (char), "i8" }, + { typeof (char), "i16" }, { typeof (sbyte), "i8" }, { typeof (short), "i16" }, { typeof (ushort), "i16" }, @@ -75,7 +75,7 @@ public PackedStructureMember (StructureMemberInfo memberInfo, object? value, static readonly Dictionary typeSizes = new Dictionary { { typeof (bool), 1 }, { typeof (byte), 1 }, - { typeof (char), 1 }, + { typeof (char), 2 }, { typeof (sbyte), 1 }, { typeof (short), 2 }, { typeof (ushort), 2 }, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index acd69f2d7ea..f8a6175711c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -313,6 +313,12 @@ void GenerateNonBlittableConversion (TypeReference sourceType, TypeReference tar return; } + if (IsCharConversion (sourceType, targetType)) { + // No need to generate any code. Java's `char` is unsigned 16-bit type, the same as + // .NET's + return; + } + ThrowUnsupportedType (sourceType); } @@ -346,6 +352,19 @@ bool IsBooleanConversion (TypeReference sourceType, TypeReference targetType) return false; } + bool IsCharConversion (TypeReference sourceType, TypeReference targetType) + { + if (String.Compare ("System.Char", sourceType.FullName, StringComparison.Ordinal) == 0) { + if (String.Compare ("System.UInt16", targetType.FullName, StringComparison.Ordinal) != 0) { + throw new InvalidOperationException ($"Unexpected conversion from '{sourceType.FullName}' to '{targetType.FullName}'"); + } + + return true; + } + + return false; + } + void ThrowUnsupportedType (TypeReference type) { throw new InvalidOperationException ($"Unsupported non-blittable type '{type.FullName}'"); @@ -450,6 +469,12 @@ TypeReference MapToBlittableTypeIfNecessary (TypeReference type, out bool typeMa return ReturnValid (typeof(byte)); } + if (String.Compare ("System.Char", type.FullName, StringComparison.Ordinal) == 0) { + // Maps to Java JNI's jchar which is an unsigned 16-bit type + typeMapped = true; + return ReturnValid (typeof(ushort)); + } + throw new NotSupportedException ($"Cannot map unsupported blittable type '{type.FullName}'"); TypeReference ReturnValid (Type typeToLookUp) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 2e5d98bb148..e2d0c1dae8b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -760,16 +760,70 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm } } - string GetPrintfFormatForFunctionParams (LlvmIrFunction func) + (string asprintfFormat, List paramUpcast) GetPrintfFormatForFunctionParams (LlvmIrFunction func) { - var ret = new StringBuilder ('('); + var ret = new StringBuilder ("("); + bool first = true; + + var upcast = new List (); + foreach (LlvmIrFunctionParameter parameter in func.Parameters) { + if (!first) { + ret.Append (", "); + } else { + first = false; + } + + string format; + if (parameter.Type == typeof(string)) { + format = "\"%s\""; + upcast.Add (null); + } else if (parameter.Type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (parameter.Type) || parameter.Type == typeof(_JNIEnv)) { + format = "%p"; + upcast.Add (null); + } else if (parameter.Type == typeof(bool) || parameter.Type == typeof(byte) || parameter.Type == typeof(ushort)) { + format = "%u"; + upcast.Add (typeof(uint)); + } else if (parameter.Type == typeof(sbyte) || parameter.Type == typeof(short)) { + format = "%d"; + upcast.Add (typeof(int)); + } else if (parameter.Type == typeof(char)) { + format = "'\\%x'"; + upcast.Add (typeof(uint)); + } else if (parameter.Type == typeof(int)) { + format = "%d"; + upcast.Add (null); + } else if (parameter.Type == typeof(uint)) { + format = "%u"; + upcast.Add (null); + } else if (parameter.Type == typeof(long)) { + format = "%ld"; + upcast.Add (null); + } else if (parameter.Type == typeof(ulong)) { + format = "%lu"; + upcast.Add (null); + } else if (parameter.Type == typeof(float)) { + format = "%g"; + upcast.Add (typeof(double)); + } else if (parameter.Type == typeof(double)) { + format = "%g"; + upcast.Add (null); + } else { + throw new InvalidOperationException ($"Unsupported type '{parameter.Type}'"); + }; + + ret.Append (format); + } ret.Append (')'); - return ret.ToString (); + return (ret.ToString (), upcast); } - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterUpcasts, LlvmIrVariableReference allocatedStringVarRef) { + if (variadicArgs.Count != parameterUpcasts.Count) { + throw new ArgumentException (nameof (parameterUpcasts), $"Number of upcasts ({parameterUpcasts.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); + } + LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); var asprintf_args = new List { @@ -782,8 +836,26 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc }, }; + // TODO: add upcasts code here and update args accordingly + for (int i = 0; i < variadicArgs.Count; i++) { + if (parameterUpcasts[i] == null) { + continue; + } + + LlvmIrVariableReference paramRef; + if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { + paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); + } else { + throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); + } + + LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, parameterUpcasts[i]); + } + asprintf_args.AddRange (variadicArgs); + generator.WriteEOL (); + generator.WriteCommentLine ($"Format: {format}", indent: true); LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None, AttributeSetID: -1); LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); @@ -817,7 +889,7 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc } ); - return null; + return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false); } void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) @@ -856,6 +928,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll if (tracingMode != MarshalMethodsTracingMode.None) { const string paramsLocalVarName = "func_params_render"; + generator.WriteCommentLine ("Tracing code start", indent: true); (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); generator.EmitStoreInstruction (func, asprintfAllocatedStringVarRef, null); @@ -869,7 +942,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll ); } - asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, GetPrintfFormatForFunctionParams (func), asprintfVariadicArgs, asprintfAllocatedStringVarRef); + (string asprintfFormat, List upcasts) = GetPrintfFormatForFunctionParams (func); + asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, asprintfVariadicArgs, upcasts, asprintfAllocatedStringVarRef); trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env @@ -881,6 +955,19 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll }; generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args); + asprintfAllocatedStringVar = generator.EmitLoadInstruction (func, asprintfAllocatedStringVarRef); + + generator.EmitCall ( + func, + free_ref, + new List { + new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { + NoUndef = true, + }, + } + ); + generator.WriteCommentLine ("Tracing code end", indent: true); + generator.WriteEOL (); } LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); @@ -933,8 +1020,12 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll ); if (tracingMode != MarshalMethodsTracingMode.None) { + generator.WriteCommentLine ("Tracing code start", indent: true); + generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args); generator.EmitDeallocStackVariable (func, tracingParamsStringLifetimeTracker); + + generator.WriteCommentLine ("Tracing code end", indent: true); } if (result != null) { From fde3078b9229edb0b0d3477bb093377065707fc8 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 12 May 2023 22:56:54 +0200 Subject: [PATCH 26/60] asprintf call & co is complete Return value rendering is still missing. It requires a bit more code if we want to see all the values (e.g. arrays of chars) --- .../MarshalMethodsNativeAssemblyGenerator.cs | 33 +++++++++++++------ src/monodroid/jni/marshal-methods-tracing.cc | 17 ++++++---- src/monodroid/jni/marshal-methods-tracing.hh | 4 +-- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index e2d0c1dae8b..995e44a9cb3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -215,6 +215,8 @@ sealed class MarshalMethodName LlvmIrVariableReference? asprintf_ref; LlvmIrVariableReference? free_ref; + LlvmIrCallMarker defaultCallMarker; + readonly bool generateEmptyCode; readonly MarshalMethodsTracingMode tracingMode; @@ -226,6 +228,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.numberOfAssembliesInApk = numberOfAssembliesInApk; this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); generateEmptyCode = true; + defaultCallMarker = LlvmIrCallMarker.Tail; } /// @@ -240,6 +243,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl generateEmptyCode = false; this.tracingMode = tracingMode; + defaultCallMarker = tracingMode != MarshalMethodsTracingMode.None ? LlvmIrCallMarker.None : LlvmIrCallMarker.Tail; } public override void Init () @@ -683,6 +687,7 @@ void WriteInitTracing (LlvmIrGenerator generator) new LlvmIrFunctionParameter (typeof(uint), "class_index"), new LlvmIrFunctionParameter (typeof(uint), "method_token"), new LlvmIrFunctionParameter (typeof(string), "native_method_name"), + new LlvmIrFunctionParameter (typeof(string), "method_extra_info"), }; var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( @@ -836,9 +841,9 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc }, }; - // TODO: add upcasts code here and update args accordingly for (int i = 0; i < variadicArgs.Count; i++) { if (parameterUpcasts[i] == null) { + asprintf_args.Add (variadicArgs[i]); continue; } @@ -850,13 +855,16 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc } LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, parameterUpcasts[i]); + asprintf_args.Add ( + new LlvmIrFunctionArgument (upcastVar) { + NoUndef = true, + } + ); } - asprintf_args.AddRange (variadicArgs); - generator.WriteEOL (); generator.WriteCommentLine ($"Format: {format}", indent: true); - LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: LlvmIrCallMarker.None, AttributeSetID: -1); + LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: defaultCallMarker, AttributeSetID: -1); LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) @@ -889,7 +897,7 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc } ); - return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false); + return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); } void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) @@ -952,9 +960,10 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[3], method.ClassCacheIndex), new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[4], nativeCallback.MetadataToken.ToUInt32 ()), new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[5], method.NativeSymbolName), + new LlvmIrFunctionArgument (asprintfAllocatedStringAccessorRef), }; - generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args); + generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args, marker: defaultCallMarker); asprintfAllocatedStringVar = generator.EmitLoadInstruction (func, asprintfAllocatedStringVarRef); generator.EmitCall ( @@ -964,7 +973,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { NoUndef = true, }, - } + }, + marker: defaultCallMarker ); generator.WriteCommentLine ("Tracing code end", indent: true); generator.WriteEOL (); @@ -993,7 +1003,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll new LlvmIrFunctionArgument (get_function_pointer_params[1], method.ClassCacheIndex), new LlvmIrFunctionArgument (get_function_pointer_params[2], nativeCallback.MetadataToken.ToUInt32 ()), new LlvmIrFunctionArgument (backingFieldRef), - } + }, + marker: defaultCallMarker ); LlvmIrFunctionLocalVariable callbackVariable2 = generator.EmitLoadInstruction (func, backingFieldRef, "cb2"); @@ -1016,13 +1027,15 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll LlvmIrFunctionLocalVariable? result = generator.EmitCall ( func, fnVariableRef, - func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList () + func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList (), + marker: defaultCallMarker ); if (tracingMode != MarshalMethodsTracingMode.None) { generator.WriteCommentLine ("Tracing code start", indent: true); - generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args); + // TODO: replace last argumetn with asprintf-allocated string + generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args, marker: defaultCallMarker); generator.EmitDeallocStackVariable (func, tracingParamsStringLifetimeTracker); generator.WriteCommentLine ("Tracing code end", indent: true); diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 793feafbc44..548672d9fe7 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -31,7 +31,7 @@ void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, ui } static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, - const char* which, const char* native_method_name, bool need_trace) noexcept + const char* which, const char* native_method_name, bool need_trace, const char* method_extra) noexcept { uint64_t method_id = MarshalMethodsUtilities::get_method_id (mono_image_index, method_token); const char *managed_method_name = MarshalMethodsUtilities::get_method_name (method_id); @@ -42,9 +42,12 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 trace.append (which); trace.append (": "); trace.append (native_method_name); - trace.append (" ("); + if (method_extra != nullptr) { + trace.append (method_extra); + } + trace.append (" ["); trace.append (managed_method_name); - trace.append (") in class "); + trace.append ("] in class "); trace.append (class_name); trace.append ("\n Native stack trace:\n"); @@ -76,16 +79,16 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 } } -void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept +void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept { constexpr char ENTER[] = "ENTER"; - _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */); + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, ENTER, native_method_name, true /* need_trace */, method_params); } -void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept +void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_return_value) noexcept { constexpr char LEAVE[] = "LEAVE"; - _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */); + _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */, method_return_value); } const char* _mm_trace_render_bool (bool v) noexcept diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index 187056116bb..813136bcc6d 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -17,10 +17,10 @@ extern "C" { void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; [[gnu::visibility("hidden")]] - void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; [[gnu::visibility("hidden")]] - void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name) noexcept; + void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; // Returns pointer to a constant string, must not be freed [[gnu::visibility("hidden")]] From d942663a03ca592ae3dcc014021ed1d720d35c32 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 16 May 2023 22:19:36 +0200 Subject: [PATCH 27/60] Function parameter and return value tracing works Output for the function parameters is mostly extremely boring, since we don't (yet) translate Java strings or characters, so it's mostly hexadecimal pointer values. Candidates for "unwrapping": * strings * characters * character arrays * class name for the `klass` parameter --- .../LlvmIrGenerator/LlvmIrFunction.cs | 8 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 288 ++++++++++-------- .../MarshalMethodsNativeAssemblyGenerator.cs | 214 ++++++++----- src/monodroid/jni/marshal-methods-tracing.cc | 9 +- 4 files changed, 308 insertions(+), 211 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 8e292b540b5..dd16f0547a8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -93,17 +93,17 @@ public LlvmIrFunctionArgument (LlvmIrGenerator.StringSymbolInfo symbol) Value = symbol; } - public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable) + public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable, bool isNull = false) { Type = typeof(LlvmIrFunctionLocalVariable); - Value = variable; + Value = isNull ? null : variable; IsNativePointer = variable.IsNativePointer; } - public LlvmIrFunctionArgument (LlvmIrVariableReference variable) + public LlvmIrFunctionArgument (LlvmIrVariableReference variable, bool isNull = false) { Type = typeof(LlvmIrVariableReference); - Value = variable; + Value = isNull ? null : variable; IsNativePointer = variable.IsNativePointer; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index eb9e4abede7..784b1a08b05 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -455,6 +455,156 @@ public LlvmIrFunctionLocalVariable EmitUpcast (LlvmIrFunction function, LlvmIrVa return result; } + void WriteCallArgument (LlvmIrFunction function, LlvmIrFunctionArgument argument, LlvmIrFunctionParameter? parameter, bool variadicCountry, int argumentIndex) + { + string paramType; + if (!variadicCountry) { + AssertValidType (argumentIndex, parameter, argument); + + paramType = GetParameterType (parameter); + } else { + paramType = GetArgumentType (argument); + } + + Output.Write ($"{paramType} "); + + if (argument.NonNull) { + Output.Write ("nonnull "); + } + + if (argument.NoUndef) { + Output.Write ("noundef "); + } + + if (argument.Value is LlvmIrFunctionLocalVariable variable) { + Output.Write ($"%{variable.Name}"); + return; + } + + if (argument.Value is StringSymbolInfo stringSymbol) { + WriteGetStringPointer (stringSymbol.SymbolName, stringSymbol.Size, indent: false, detectBitness: true, skipPointerType: true); + return; + } + + if (argument.Type.IsNativePointer () || argument.IsNativePointer) { + if (parameter != null && parameter.IsCplusPlusReference) { + Output.Write ("nonnull "); + } + + if (argument.Type == typeof(LlvmIrVariableReference)) { + var variableRef = argument.Value as LlvmIrVariableReference; + bool needBitcast = parameter == null || variableRef == null ? false : parameter.Type != variableRef.Type; + + if (needBitcast) { + string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); + Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) bitcast ("); + CodeRenderType (variableRef); + Output.Write (' '); + } + + if (variableRef != null) { + Output.Write (variableRef.Reference); + } else { + Output.Write ("null"); + } + + if (needBitcast) { + Output.Write ($" to {paramType})"); + } + } else { + throw new InvalidOperationException ($"Unexpected pointer type in argument {argumentIndex}, '{argument.Type}'"); + } + return; + } + + if (argument.Value is string str) { + StringSymbolInfo info = StringManager.Add (str); + WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true, skipPointerType: true); + return; + + } + + Output.Write (MonoAndroidHelper.CultureInvariantToString (argument.Value)); + + string GetArgumentType (LlvmIrFunctionArgument argument) + { + string extra = argument.IsNativePointer ? "*" : String.Empty; + + Type type; + if (argument.Value is LlvmIrFunctionLocalVariable variable) { + type = variable.Type; + } else if (argument.Value is StringSymbolInfo stringSymbol) { + type = typeof(string); + } else { + type = argument.Type; + } + + return $"{GetKnownIRType (type)}{extra}"; + } + + static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) + { + if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { + return; + } + + if (parameter.Type != typeof(IntPtr)) { + if (argument.Type == typeof(StringSymbolInfo) && parameter.Type == typeof (string)) { + // Fine, we want to pass a pointer to string + return; + } + + if (argument.Type != parameter.Type) { + ThrowException (); + } + return; + } + + if (argument.Type.IsNativePointer ()) { + return; + } + + if (typeof(LlvmIrVariable).IsAssignableFrom (argument.Type) && + argument.Value is LlvmIrVariable variable && + (variable.IsNativePointer || variable.NativeFunction != null)) { + return; + } + + ThrowException (); + + void ThrowException () + { + throw new InvalidOperationException ($"Argument {index} type '{argument.Type}' does not match the expected function parameter type '{parameter.Type}'"); + } + } + } + + void WriteCallArguments (LlvmIrFunction function, LlvmNativeFunctionSignature targetSignature, List arguments) + { + bool variadicCountry = false; + for (int i = 0; i < arguments.Count; i++) { + LlvmIrFunctionParameter? parameter = null; + + if (!variadicCountry) { + if (i >= targetSignature.Parameters.Count) { + throw new InvalidOperationException ("Internal error: Exceeded number of declared parameters, expected a trailing variadic parameter at this point"); + } + + parameter = targetSignature.Parameters[i]; + + if (parameter.IsVarargs) { + variadicCountry = true; + } + } + + if (i > 0) { + Output.Write (", "); + } + + WriteCallArgument (function, arguments[i], parameter, variadicCountry, i); + } + } + public LlvmIrFunctionLocalVariable? EmitCall (LlvmIrFunction function, LlvmIrVariableReference targetRef, List? arguments = null, string? resultVariableName = null, LlvmIrCallMarker marker = LlvmIrCallMarker.Tail, int AttributeSetID = FunctionAttributesCall) { @@ -526,81 +676,7 @@ public LlvmIrFunctionLocalVariable EmitUpcast (LlvmIrFunction function, LlvmIrVa Output.Write ($"{targetRef.Reference} ("); if (haveParameters) { - bool variadicCountry = false; - for (int i = 0; i < arguments.Count; i++) { - LlvmIrFunctionParameter? parameter = null; - - if (!variadicCountry) { - if (i >= targetSignature.Parameters.Count) { - throw new InvalidOperationException ("Internal error: Exceeded number of declared parameters, expected a trailing variadic parameter at this point"); - } - - parameter = targetSignature.Parameters[i]; - - if (parameter.IsVarargs) { - variadicCountry = true; - } - } - - if (i > 0) { - Output.Write (", "); - } - - LlvmIrFunctionArgument argument = arguments[i]; - - string paramType; - if (!variadicCountry) { - AssertValidType (i, parameter, argument); - - paramType = GetParameterType (parameter); - } else { - paramType = GetArgumentType (argument); - } - - Output.Write ($"{paramType} "); - - if (argument.NonNull) { - Output.Write ("nonnull "); - } - - if (argument.NoUndef) { - Output.Write ("noundef "); - } - - if (argument.Value is LlvmIrFunctionLocalVariable variable) { - Output.Write ($"%{variable.Name}"); - } else if (argument.Value is StringSymbolInfo stringSymbol) { - WriteGetStringPointer (stringSymbol.SymbolName, stringSymbol.Size, indent: false, detectBitness: true, skipPointerType: true); - } else if (argument.Type.IsNativePointer () || argument.IsNativePointer) { - if (parameter != null && parameter.IsCplusPlusReference) { - Output.Write ("nonnull "); - } - - if (argument.Value is LlvmIrVariableReference variableRef) { - bool needBitcast = parameter == null ? false : parameter.Type != variableRef.Type; - - if (needBitcast) { - string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); - Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) bitcast ("); - CodeRenderType (variableRef); - Output.Write (' '); - } - - Output.Write (variableRef.Reference); - - if (needBitcast) { - Output.Write ($" to {paramType})"); - } - } else { - throw new InvalidOperationException ($"Unexpected pointer type in argument {i}, '{argument.Type}'"); - } - } else if (argument.Value is string str) { - StringSymbolInfo info = StringManager.Add (str); - WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true, skipPointerType: true); - } else { - Output.Write (MonoAndroidHelper.CultureInvariantToString (argument.Value)); - } - } + WriteCallArguments (function, targetSignature, arguments); } Output.Write (")"); @@ -614,64 +690,12 @@ public LlvmIrFunctionLocalVariable EmitUpcast (LlvmIrFunction function, LlvmIrVa Output.WriteLine (); return result; + } - string GetParameterType (LlvmIrFunctionParameter parameter) - { - string extra = parameter.IsNativePointer ? "*" : String.Empty; - return $"{GetKnownIRType (parameter.Type)}{extra}"; - } - - string GetArgumentType (LlvmIrFunctionArgument argument) - { - string extra = argument.IsNativePointer ? "*" : String.Empty; - - Type type; - if (argument.Value is LlvmIrFunctionLocalVariable variable) { - type = variable.Type; - } else if (argument.Value is StringSymbolInfo stringSymbol) { - type = typeof(string); - } else { - type = argument.Type; - } - - return $"{GetKnownIRType (type)}{extra}"; - } - - static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) - { - if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { - return; - } - - if (parameter.Type != typeof(IntPtr)) { - if (argument.Type == typeof(StringSymbolInfo) && parameter.Type == typeof (string)) { - // Fine, we want to pass a pointer to string - return; - } - - if (argument.Type != parameter.Type) { - ThrowException (); - } - return; - } - - if (argument.Type.IsNativePointer ()) { - return; - } - - if (typeof(LlvmIrVariable).IsAssignableFrom (argument.Type) && - argument.Value is LlvmIrVariable variable && - (variable.IsNativePointer || variable.NativeFunction != null)) { - return; - } - - ThrowException (); - - void ThrowException () - { - throw new InvalidOperationException ($"Argument {index} type '{argument.Type}' does not match the expected function parameter type '{parameter.Type}'"); - } - } + string GetParameterType (LlvmIrFunctionParameter parameter) + { + string extra = parameter.IsNativePointer ? "*" : String.Empty; + return $"{GetKnownIRType (parameter.Type)}{extra}"; } /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 995e44a9cb3..e11cee57c6a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -209,6 +209,7 @@ sealed class MarshalMethodName // Tracing List? mm_trace_func_enter_or_leave_params; + int mm_trace_func_enter_leave_extra_info_param_index = -1; List? get_function_pointer_params; LlvmIrVariableReference? mm_trace_func_enter_ref; LlvmIrVariableReference? mm_trace_func_leave_ref; @@ -689,6 +690,7 @@ void WriteInitTracing (LlvmIrGenerator generator) new LlvmIrFunctionParameter (typeof(string), "native_method_name"), new LlvmIrFunctionParameter (typeof(string), "method_extra_info"), }; + mm_trace_func_enter_leave_extra_info_param_index = mm_trace_func_enter_or_leave_params.Count - 1; var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( returnType: typeof(void), @@ -765,12 +767,65 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm } } + void AddPrintfFormatForType (StringBuilder sb, Type type, List upcast) + { + string format; + if (type == typeof(string)) { + format = "\"%s\""; + upcast.Add (null); + } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { + format = "%p"; + upcast.Add (null); + } else if (type == typeof(bool) || type == typeof(byte) || type == typeof(ushort)) { + format = "%u"; + upcast.Add (typeof(uint)); + } else if (type == typeof(sbyte) || type == typeof(short)) { + format = "%d"; + upcast.Add (typeof(int)); + } else if (type == typeof(char)) { + format = "'\\%x'"; + upcast.Add (typeof(uint)); + } else if (type == typeof(int)) { + format = "%d"; + upcast.Add (null); + } else if (type == typeof(uint)) { + format = "%u"; + upcast.Add (null); + } else if (type == typeof(long)) { + format = "%ld"; + upcast.Add (null); + } else if (type == typeof(ulong)) { + format = "%lu"; + upcast.Add (null); + } else if (type == typeof(float)) { + format = "%g"; + upcast.Add (typeof(double)); + } else if (type == typeof(double)) { + format = "%g"; + upcast.Add (null); + } else { + throw new InvalidOperationException ($"Unsupported type '{type}'"); + }; + + sb.Append (format); + } + + (StringBuilder sb, List upcasts) InitPrintfFormat (string startChars = "(") + { + return (new StringBuilder (startChars), new List ()); + } + + (string asprintfFormat, List paramUpcast) FinishPrintfFormat (StringBuilder sb, List upcasts, string endChars = ")") + { + sb.Append (endChars); + return (sb.ToString (), upcasts); + } + (string asprintfFormat, List paramUpcast) GetPrintfFormatForFunctionParams (LlvmIrFunction func) { - var ret = new StringBuilder ("("); + (StringBuilder ret, List upcasts) = InitPrintfFormat (); bool first = true; - var upcast = new List (); foreach (LlvmIrFunctionParameter parameter in func.Parameters) { if (!first) { ret.Append (", "); @@ -778,60 +833,26 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm first = false; } - string format; - if (parameter.Type == typeof(string)) { - format = "\"%s\""; - upcast.Add (null); - } else if (parameter.Type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (parameter.Type) || parameter.Type == typeof(_JNIEnv)) { - format = "%p"; - upcast.Add (null); - } else if (parameter.Type == typeof(bool) || parameter.Type == typeof(byte) || parameter.Type == typeof(ushort)) { - format = "%u"; - upcast.Add (typeof(uint)); - } else if (parameter.Type == typeof(sbyte) || parameter.Type == typeof(short)) { - format = "%d"; - upcast.Add (typeof(int)); - } else if (parameter.Type == typeof(char)) { - format = "'\\%x'"; - upcast.Add (typeof(uint)); - } else if (parameter.Type == typeof(int)) { - format = "%d"; - upcast.Add (null); - } else if (parameter.Type == typeof(uint)) { - format = "%u"; - upcast.Add (null); - } else if (parameter.Type == typeof(long)) { - format = "%ld"; - upcast.Add (null); - } else if (parameter.Type == typeof(ulong)) { - format = "%lu"; - upcast.Add (null); - } else if (parameter.Type == typeof(float)) { - format = "%g"; - upcast.Add (typeof(double)); - } else if (parameter.Type == typeof(double)) { - format = "%g"; - upcast.Add (null); - } else { - throw new InvalidOperationException ($"Unsupported type '{parameter.Type}'"); - }; - - ret.Append (format); + AddPrintfFormatForType (ret, parameter.Type, upcasts); } - ret.Append (')'); - return (ret.ToString (), upcast); + return FinishPrintfFormat (ret, upcasts); } - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterUpcasts, LlvmIrVariableReference allocatedStringVarRef) + (string asprintfFormat, List paramUpcast) GetPrintfFormatForReturnValue (LlvmIrFunctionLocalVariable localVariable) { - if (variadicArgs.Count != parameterUpcasts.Count) { - throw new ArgumentException (nameof (parameterUpcasts), $"Number of upcasts ({parameterUpcasts.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); - } + (StringBuilder ret, List upcasts) = InitPrintfFormat ("=>["); + AddPrintfFormatForType (ret, localVariable.Type, upcasts); + + return FinishPrintfFormat (ret, upcasts, "]"); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + { LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); - var asprintf_args = new List { + var asprintfArgs = new List { new LlvmIrFunctionArgument (allocatedStringVarRef) { NonNull = true, NoUndef = true, @@ -841,30 +862,11 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc }, }; - for (int i = 0; i < variadicArgs.Count; i++) { - if (parameterUpcasts[i] == null) { - asprintf_args.Add (variadicArgs[i]); - continue; - } - - LlvmIrVariableReference paramRef; - if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { - paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); - } else { - throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); - } - - LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, parameterUpcasts[i]); - asprintf_args.Add ( - new LlvmIrFunctionArgument (upcastVar) { - NoUndef = true, - } - ); - } + asprintfArgs.AddRange (variadicArgs); generator.WriteEOL (); generator.WriteCommentLine ($"Format: {format}", indent: true); - LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintf_args, marker: defaultCallMarker, AttributeSetID: -1); + LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintfArgs, marker: defaultCallMarker, AttributeSetID: -1); LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) @@ -900,6 +902,55 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); } + void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, Type? upcast, LlvmIrFunctionLocalVariable paramVar) + { + if (upcast == null) { + asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); + return; + } + + LlvmIrVariableReference paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); + LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, upcast); + asprintfArgs.Add ( + new LlvmIrFunctionArgument (upcastVar) { + NoUndef = true, + } + ); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterUpcasts, LlvmIrVariableReference allocatedStringVarRef) + { + if (variadicArgs.Count != parameterUpcasts.Count) { + throw new ArgumentException (nameof (parameterUpcasts), $"Number of upcasts ({parameterUpcasts.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); + } + + var asprintfArgs = new List (); + + for (int i = 0; i < variadicArgs.Count; i++) { + if (parameterUpcasts[i] == null) { + asprintfArgs.Add (variadicArgs[i]); + continue; + } + + if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { + AddAsprintfArgument (generator, func, asprintfArgs, parameterUpcasts[i], paramVar); + continue; + } + + throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); + } + + return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, LlvmIrFunctionLocalVariable retVal, Type? retValUpcast, LlvmIrVariableReference allocatedStringVarRef) + { + var asprintfArgs = new List (); + AddAsprintfArgument (generator, func, asprintfArgs, retValUpcast, retVal); + + return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + } + void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) { var backingFieldSignature = new LlvmNativeFunctionSignature ( @@ -932,6 +983,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll List? asprintfVariadicArgs = null; LlvmIrVariableReference? asprintfAllocatedStringAccessorRef = null; LlvmIrVariableReference? asprintfAllocatedStringVarRef = null; + string? asprintfFormat = null; + List asprintfUpcasts = null; if (tracingMode != MarshalMethodsTracingMode.None) { const string paramsLocalVarName = "func_params_render"; @@ -950,8 +1003,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll ); } - (string asprintfFormat, List upcasts) = GetPrintfFormatForFunctionParams (func); - asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, asprintfVariadicArgs, upcasts, asprintfAllocatedStringVarRef); + (asprintfFormat, asprintfUpcasts) = GetPrintfFormatForFunctionParams (func); + asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, asprintfVariadicArgs, asprintfUpcasts, asprintfAllocatedStringVarRef); trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env @@ -1034,7 +1087,24 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll if (tracingMode != MarshalMethodsTracingMode.None) { generator.WriteCommentLine ("Tracing code start", indent: true); - // TODO: replace last argumetn with asprintf-allocated string + LlvmIrFunctionArgument extraInfoArg; + if (result != null) { + (asprintfFormat, asprintfUpcasts) = GetPrintfFormatForReturnValue (result); + asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, result, asprintfUpcasts[0], asprintfAllocatedStringVarRef); + extraInfoArg = new LlvmIrFunctionArgument (asprintfAllocatedStringAccessorRef) { + NoUndef = true, + }; + } else { + extraInfoArg = new LlvmIrFunctionArgument (asprintfAllocatedStringVarRef, isNull: true) { + NoUndef = true, + }; + } + + if (mm_trace_func_enter_leave_extra_info_param_index < 0) { + throw new InvalidOperationException ("Internal error: index of the extra info parameter is unknown"); + } + trace_enter_leave_args[mm_trace_func_enter_leave_extra_info_param_index] = extraInfoArg; + generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args, marker: defaultCallMarker); generator.EmitDeallocStackVariable (func, tracingParamsStringLifetimeTracker); diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index 548672d9fe7..d9f32f40bd2 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -43,11 +43,12 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 trace.append (": "); trace.append (native_method_name); if (method_extra != nullptr) { + trace.append (" "); trace.append (method_extra); } - trace.append (" ["); + trace.append (" {"); trace.append (managed_method_name); - trace.append ("] in class "); + trace.append ("} in class "); trace.append (class_name); trace.append ("\n Native stack trace:\n"); @@ -69,10 +70,12 @@ static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint3 __android_log_print ( PRIORITY, SharedConstants::LOG_CATEGORY_NAME_MONODROID_ASSEMBLY, - "%s%s: %s (%s) in class %s", + "%s%s: %s%s%s {%s} in class %s", LEAD, which, native_method_name, + method_extra == nullptr ? "" : " ", + method_extra == nullptr ? "" : method_extra, managed_method_name, class_name ); From a74882f99433df6116f67beac18e0e7aa8d38d31 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 17 May 2023 20:22:07 +0200 Subject: [PATCH 28/60] Preparations for better argument logging Prepare infrastructure for converting jclass, jobject, jstring and jboolean to human-readable strings in trace logs. --- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 529 ++++++++++++++++++ .../MarshalMethodsNativeAssemblyGenerator.cs | 365 +----------- src/monodroid/jni/marshal-methods-tracing.cc | 78 ++- src/monodroid/jni/marshal-methods-tracing.hh | 16 +- src/monodroid/jni/native-tracing.cc | 17 - src/monodroid/jni/native-tracing.hh | 15 + 6 files changed, 637 insertions(+), 383 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs new file mode 100644 index 00000000000..97393d88b07 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -0,0 +1,529 @@ +using System; +using System.Text; +using System.Collections.Generic; + +using Xamarin.Android.Tasks.LLVMIR; + +namespace Xamarin.Android.Tasks +{ + using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; + using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; + + partial class MarshalMethodsNativeAssemblyGenerator + { + const string mm_trace_init_name = "_mm_trace_init"; + const string mm_trace_func_enter_name = "_mm_trace_func_enter"; + const string mm_trace_func_leave_name = "_mm_trace_func_leave"; + const string mm_trace_get_class_name_name = "_mm_trace_get_class_name"; + const string mm_trace_get_object_class_name_name = "_mm_trace_get_object_class_name"; + const string mm_trace_get_c_string_name = "_mm_trace_get_c_string"; + const string mm_trace_get_boolean_string_name = "_mm_trace_get_boolean_string"; + const string asprintf_name = "asprintf"; + const string free_name = "free"; + + enum TracingRenderArgumentFunction + { + None, + GetClassName, + GetObjectClassname, + GetCString, + GetBooleanString, + } + + sealed class AsprintfParameterOperation + { + public readonly Type? Upcast; + public readonly TracingRenderArgumentFunction RenderFunction = TracingRenderArgumentFunction.None; + + public AsprintfParameterOperation (Type? upcast = null, TracingRenderArgumentFunction renderFunction = TracingRenderArgumentFunction.None) + { + Upcast = upcast; + RenderFunction = renderFunction; + } + } + + sealed class AsprintfCallState + { + public readonly string Format; + public readonly List ParameterOps; + + public AsprintfCallState (string format, List parameterOps) + { + Format = format; + ParameterOps = parameterOps; + } + } + + sealed class TracingState + { + public List? trace_enter_leave_args = null; + public LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; + public List? asprintfVariadicArgs = null; + public LlvmIrVariableReference? asprintfAllocatedStringAccessorRef = null; + public LlvmIrVariableReference? asprintfAllocatedStringVarRef = null; + } + + List? mm_trace_func_enter_or_leave_params; + int mm_trace_func_enter_leave_extra_info_param_index = -1; + List? get_function_pointer_params; + LlvmIrVariableReference? mm_trace_init_ref; + LlvmIrVariableReference? mm_trace_func_enter_ref; + LlvmIrVariableReference? mm_trace_func_leave_ref; + LlvmIrVariableReference? mm_trace_get_class_name_ref; + LlvmIrVariableReference? mm_trace_get_object_class_name_ref; + LlvmIrVariableReference? mm_trace_get_c_string_ref; + LlvmIrVariableReference? mm_trace_get_boolean_string_ref; + LlvmIrVariableReference? asprintf_ref; + LlvmIrVariableReference? free_ref; + + void InitializeTracing (LlvmIrGenerator generator) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return; + } + + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + mm_trace_func_enter_or_leave_params = new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env + new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), + new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), + new LlvmIrFunctionParameter (typeof(uint), "class_index"), + new LlvmIrFunctionParameter (typeof(uint), "method_token"), + new LlvmIrFunctionParameter (typeof(string), "native_method_name"), + new LlvmIrFunctionParameter (typeof(string), "method_extra_info"), + }; + mm_trace_func_enter_leave_extra_info_param_index = mm_trace_func_enter_or_leave_params.Count - 1; + + var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + + ); + mm_trace_func_enter_ref = new LlvmIrVariableReference (mm_trace_func_enter_sig, mm_trace_func_enter_name, isGlobal: true); + + var mm_trace_func_leave_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + ); + mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); + + var asprintf_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(int), + parameters: new List { + new LlvmIrFunctionParameter (typeof(string), isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(string)) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(void)) { + IsVarargs = true, + } + } + ); + asprintf_ref = new LlvmIrVariableReference (asprintf_sig, asprintf_name, isGlobal: true); + + var free_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: new List { + new LlvmIrFunctionParameter (typeof(string)) { + NoCapture = true, + NoUndef = true, + }, + } + ); + free_ref = new LlvmIrVariableReference (free_sig, free_name, isGlobal: true); + + var mm_trace_init_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { + NoUndef = true, + }, + } + ); + mm_trace_init_ref = new LlvmIrVariableReference (mm_trace_init_sig, mm_trace_init_name, isGlobal: true); + + var mm_trace_get_class_name_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(string), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(_jclass), "v") { + NoUndef = true, + }, + } + ); + mm_trace_get_class_name_ref = new LlvmIrVariableReference (mm_trace_get_class_name_sig, mm_trace_get_class_name_name, isGlobal: true); + + var mm_trace_get_object_class_name_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(string), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(_jobject), "v") { + NoUndef = true, + }, + } + ); + mm_trace_get_object_class_name_ref = new LlvmIrVariableReference (mm_trace_get_object_class_name_sig, mm_trace_get_object_class_name_name, isGlobal: true); + + var mm_trace_get_c_string_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(string), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(_jstring), "v") { + NoUndef = true, + }, + } + ); + mm_trace_get_c_string_ref = new LlvmIrVariableReference (mm_trace_get_c_string_sig, mm_trace_get_c_string_name, isGlobal: true); + + var mm_trace_get_boolean_string_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(string), + parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { + NoUndef = true, + }, + new LlvmIrFunctionParameter (typeof(byte), "v") { + NoUndef = true, + }, + } + ); + mm_trace_get_boolean_string_ref = new LlvmIrVariableReference (mm_trace_get_boolean_string_sig, mm_trace_get_boolean_string_name, isGlobal: true); + + AddTraceFunctionDeclaration (asprintf_name, asprintf_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (free_name, free_sig, LlvmIrGenerator.FunctionAttributesLibcFree); + AddTraceFunctionDeclaration (mm_trace_init_name, mm_trace_init_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_get_class_name_name, mm_trace_get_class_name_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_get_object_class_name_name, mm_trace_get_object_class_name_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_get_c_string_name, mm_trace_get_c_string_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + AddTraceFunctionDeclaration (mm_trace_get_boolean_string_name, mm_trace_get_boolean_string_sig, LlvmIrGenerator.FunctionAttributesJniMethods); + + void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) + { + var func = new LlvmIrFunction ( + name: name, + returnType: sig.ReturnType, + attributeSetID: attributeSetID, + parameters: sig.Parameters + ); + generator.AddExternalFunction (func); + } + } + + void AddPrintfFormatForType (StringBuilder sb, Type type, List parameterOps) + { + string format; + if (type == typeof(string)) { + format = "\"%s\""; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { + format = "%p"; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(bool) || type == typeof(byte) || type == typeof(ushort)) { + format = "%u"; + parameterOps.Add (new AsprintfParameterOperation (typeof(uint))); + } else if (type == typeof(sbyte) || type == typeof(short)) { + format = "%d"; + parameterOps.Add (new AsprintfParameterOperation (typeof(int))); + } else if (type == typeof(char)) { + format = "'\\%x'"; + parameterOps.Add (new AsprintfParameterOperation (typeof(uint))); + } else if (type == typeof(int)) { + format = "%d"; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(uint)) { + format = "%u"; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(long)) { + format = "%ld"; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(ulong)) { + format = "%lu"; + parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(float)) { + format = "%g"; + parameterOps.Add (new AsprintfParameterOperation (typeof(double))); + } else if (type == typeof(double)) { + format = "%g"; + parameterOps.Add (null); + } else { + throw new InvalidOperationException ($"Unsupported type '{type}'"); + }; + + sb.Append (format); + } + + (StringBuilder sb, List parameterOps) InitPrintfFormat (string startChars = "(") + { + return (new StringBuilder (startChars), new List ()); + } + + AsprintfCallState FinishPrintfFormat (StringBuilder sb, List parameterOps, string endChars = ")") + { + sb.Append (endChars); + return new AsprintfCallState (sb.ToString (), parameterOps); + } + + AsprintfCallState GetPrintfFormatForFunctionParams (MarshalMethodInfo method) + { + (StringBuilder ret, List parameterOps) = InitPrintfFormat (); + bool first = true; + + List nativeMethodParameters = method.Parameters; + Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters; + + int expectedRegisteredParamCount = nativeMethodParameters.Count - 2; + if (managedMethodParameters != null && managedMethodParameters.Count != expectedRegisteredParamCount) { + throw new InvalidOperationException ($"Internal error: unexpected number of registered method parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); + } + + for (int i = 0; i < nativeMethodParameters.Count; i++) { + LlvmIrFunctionParameter parameter = nativeMethodParameters[i]; + if (!first) { + ret.Append (", "); + } else { + first = false; + } + + AddPrintfFormatForType (ret, parameter.Type, parameterOps); + } + + return FinishPrintfFormat (ret, parameterOps); + + Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) + { + if (managedParameter == null) { + return nativeParameter.Type; + } + + return nativeParameter.Type; + } + } + + AsprintfCallState GetPrintfFormatForReturnValue (LlvmIrFunctionLocalVariable localVariable) + { + (StringBuilder ret, List parameterOps) = InitPrintfFormat ("=>["); + + AddPrintfFormatForType (ret, localVariable.Type, parameterOps); + + return FinishPrintfFormat (ret, parameterOps, "]"); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + { + LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); + + var asprintfArgs = new List { + new LlvmIrFunctionArgument (allocatedStringVarRef) { + NonNull = true, + NoUndef = true, + }, + new LlvmIrFunctionArgument (asprintfFormatSym) { + NoUndef = true, + }, + }; + + asprintfArgs.AddRange (variadicArgs); + + generator.WriteEOL (); + generator.WriteCommentLine ($"Format: {format}", indent: true); + LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintfArgs, marker: defaultCallMarker, AttributeSetID: -1); + LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); + + // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) + LlvmIrFunctionLocalVariable asprintfResultVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.SignedLessThan, resultRef, "0"); + var asprintfResultVariableRef = new LlvmIrVariableReference (asprintfResultVariable, isGlobal: false); + + string asprintfIfThenLabel = func.MakeUniqueLabel ("if.then"); + string asprintfIfElseLabel = func.MakeUniqueLabel ("if.else"); + string ifElseDoneLabel = func.MakeUniqueLabel ("if.done"); + + generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfIfThenLabel, asprintfIfElseLabel); + + // Condition is true if asprintf **failed** + generator.EmitLabel (func, asprintfIfThenLabel); + generator.EmitStoreInstruction (func, allocatedStringVarRef, null); + generator.EmitBrInstruction (func, ifElseDoneLabel); + + generator.EmitLabel (func, asprintfIfElseLabel); + LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); + LlvmIrVariableReference bufferPointerVarRef = new LlvmIrVariableReference (bufferPointerVar, isGlobal: false); + generator.EmitBrInstruction (func, ifElseDoneLabel); + + generator.EmitLabel (func, ifElseDoneLabel); + LlvmIrFunctionLocalVariable allocatedStringValueVar = generator.EmitPhiInstruction ( + func, + allocatedStringVarRef, + new List<(LlvmIrVariableReference? variableRef, string label)> { + (null, func.PreviousBlockStartLabel), + (bufferPointerVarRef, func.PreviousBlockEndLabel), + } + ); + + return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); + } + + void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, AsprintfParameterOperation paramOp, LlvmIrFunctionLocalVariable paramVar) + { + if (paramOp.Upcast == null) { + asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); + return; + } + + LlvmIrVariableReference paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); + LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, paramOp.Upcast); + asprintfArgs.Add ( + new LlvmIrFunctionArgument (upcastVar) { + NoUndef = true, + } + ); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterOps, LlvmIrVariableReference allocatedStringVarRef) + { + if (variadicArgs.Count != parameterOps.Count) { + throw new ArgumentException (nameof (parameterOps), $"Number of upcasts ({parameterOps.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); + } + + var asprintfArgs = new List (); + + for (int i = 0; i < variadicArgs.Count; i++) { + if (parameterOps[i] == null) { + asprintfArgs.Add (variadicArgs[i]); + continue; + } + + if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { + AddAsprintfArgument (generator, func, asprintfArgs, parameterOps[i], paramVar); + continue; + } + + throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); + } + + return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + } + + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, LlvmIrFunctionLocalVariable retVal, AsprintfParameterOperation retValOp, LlvmIrVariableReference allocatedStringVarRef) + { + var asprintfArgs = new List (); + AddAsprintfArgument (generator, func, asprintfArgs, retValOp, retVal); + + return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + } + + TracingState? WriteMarshalMethodTracingTop (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrFunction func) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return null; + } + + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + var state = new TracingState (); + + const string paramsLocalVarName = "func_params_render"; + + generator.WriteCommentLine ("Tracing code start", indent: true); + (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, state.tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); + state.asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); + generator.EmitStoreInstruction (func, state.asprintfAllocatedStringVarRef, null); + + state.asprintfVariadicArgs = new List (); + foreach (LlvmIrFunctionLocalVariable lfv in func.ParameterVariables) { + state.asprintfVariadicArgs.Add ( + new LlvmIrFunctionArgument (lfv) { + NoUndef = true, + } + ); + } + + AsprintfCallState asprintfState = GetPrintfFormatForFunctionParams (method); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, state.asprintfVariadicArgs, asprintfState.ParameterOps, state.asprintfAllocatedStringVarRef); + + state.trace_enter_leave_args = new List { + new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[1], (int)tracingMode), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[2], method.AssemblyCacheIndex), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[3], method.ClassCacheIndex), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[4], nativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[5], method.NativeSymbolName), + new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef), + }; + + generator.EmitCall (func, mm_trace_func_enter_ref, state.trace_enter_leave_args, marker: defaultCallMarker); + asprintfAllocatedStringVar = generator.EmitLoadInstruction (func, state.asprintfAllocatedStringVarRef); + + generator.EmitCall ( + func, + free_ref, + new List { + new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { + NoUndef = true, + }, + }, + marker: defaultCallMarker + ); + generator.WriteCommentLine ("Tracing code end", indent: true); + generator.WriteEOL (); + + return state; + } + + void WriteMarshalMethodTracingBottom (TracingState? state, LlvmIrGenerator generator, LlvmIrFunction func, LlvmIrFunctionLocalVariable? result) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return; + } + + if (state == null) { + throw new InvalidOperationException ("Internal error: tracing state is required."); + } + + generator.WriteCommentLine ("Tracing code start", indent: true); + + LlvmIrFunctionArgument extraInfoArg; + if (result != null) { + AsprintfCallState asprintfState = GetPrintfFormatForReturnValue (result); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, result, asprintfState.ParameterOps[0], state.asprintfAllocatedStringVarRef); + extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef) { + NoUndef = true, + }; + } else { + extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringVarRef, isNull: true) { + NoUndef = true, + }; + } + + if (mm_trace_func_enter_leave_extra_info_param_index < 0) { + throw new InvalidOperationException ("Internal error: index of the extra info parameter is unknown"); + } + state.trace_enter_leave_args[mm_trace_func_enter_leave_extra_info_param_index] = extraInfoArg; + + generator.EmitCall (func, mm_trace_func_leave_ref, state.trace_enter_leave_args, marker: defaultCallMarker); + generator.EmitDeallocStackVariable (func, state.tracingParamsStringLifetimeTracker); + + generator.WriteCommentLine ("Tracing code end", indent: true); + } + + void WriteTracingInit (LlvmIrGenerator generator, LlvmIrFunction func) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return; + } + + var trace_init_args = new List { + new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env + }; + + generator.EmitCall (func, mm_trace_init_ref, trace_init_args, marker: defaultCallMarker); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index e11cee57c6a..87ef7814af1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -17,7 +17,7 @@ // why Blazor hangs. namespace Xamarin.Android.Tasks { - class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer + partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { // This is here only to generate strongly-typed IR internal sealed class MonoClass @@ -174,11 +174,6 @@ sealed class MarshalMethodName { 'L', typeof(_jobjectArray) }, }; - const string mm_trace_func_enter_name = "_mm_trace_func_enter"; - const string mm_trace_func_leave_name = "_mm_trace_func_leave"; - const string asprintf_name = "asprintf"; - const string free_name = "free"; - ICollection uniqueAssemblyNames; int numberOfAssembliesInApk; IDictionary> marshalMethods; @@ -207,15 +202,6 @@ sealed class MarshalMethodName List methods; List> classes = new List> (); - // Tracing - List? mm_trace_func_enter_or_leave_params; - int mm_trace_func_enter_leave_extra_info_param_index = -1; - List? get_function_pointer_params; - LlvmIrVariableReference? mm_trace_func_enter_ref; - LlvmIrVariableReference? mm_trace_func_leave_ref; - LlvmIrVariableReference? asprintf_ref; - LlvmIrVariableReference? free_ref; - LlvmIrCallMarker defaultCallMarker; readonly bool generateEmptyCode; @@ -600,7 +586,7 @@ protected override void Write (LlvmIrGenerator generator) { WriteAssemblyImageCache (generator, out Dictionary asmNameToIndex); WriteClassCache (generator); - WriteInitTracing (generator); + InitializeTracing (generator); LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); @@ -674,81 +660,6 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) } } - void WriteInitTracing (LlvmIrGenerator generator) - { - if (tracingMode == MarshalMethodsTracingMode.None) { - return; - } - - // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - mm_trace_func_enter_or_leave_params = new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), - new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), - new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token"), - new LlvmIrFunctionParameter (typeof(string), "native_method_name"), - new LlvmIrFunctionParameter (typeof(string), "method_extra_info"), - }; - mm_trace_func_enter_leave_extra_info_param_index = mm_trace_func_enter_or_leave_params.Count - 1; - - var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - - ); - mm_trace_func_enter_ref = new LlvmIrVariableReference (mm_trace_func_enter_sig, mm_trace_func_enter_name, isGlobal: true); - - var mm_trace_func_leave_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - ); - mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); - - var asprintf_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(int), - parameters: new List { - new LlvmIrFunctionParameter (typeof(string), isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(string)) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(void)) { - IsVarargs = true, - } - } - ); - asprintf_ref = new LlvmIrVariableReference (asprintf_sig, asprintf_name, isGlobal: true); - - var free_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(string)) { - NoCapture = true, - NoUndef = true, - }, - } - ); - free_ref = new LlvmIrVariableReference (free_sig, free_name, isGlobal: true); - - AddTraceFunctionDeclaration (asprintf_name, asprintf_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (free_name, free_sig, LlvmIrGenerator.FunctionAttributesLibcFree); - AddTraceFunctionDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - - void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) - { - var func = new LlvmIrFunction ( - name: name, - returnType: sig.ReturnType, - attributeSetID: attributeSetID, - parameters: sig.Parameters - ); - generator.AddExternalFunction (func); - } - } - void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) { if (generateEmptyCode || methods == null || methods.Count == 0) { @@ -767,190 +678,6 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm } } - void AddPrintfFormatForType (StringBuilder sb, Type type, List upcast) - { - string format; - if (type == typeof(string)) { - format = "\"%s\""; - upcast.Add (null); - } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { - format = "%p"; - upcast.Add (null); - } else if (type == typeof(bool) || type == typeof(byte) || type == typeof(ushort)) { - format = "%u"; - upcast.Add (typeof(uint)); - } else if (type == typeof(sbyte) || type == typeof(short)) { - format = "%d"; - upcast.Add (typeof(int)); - } else if (type == typeof(char)) { - format = "'\\%x'"; - upcast.Add (typeof(uint)); - } else if (type == typeof(int)) { - format = "%d"; - upcast.Add (null); - } else if (type == typeof(uint)) { - format = "%u"; - upcast.Add (null); - } else if (type == typeof(long)) { - format = "%ld"; - upcast.Add (null); - } else if (type == typeof(ulong)) { - format = "%lu"; - upcast.Add (null); - } else if (type == typeof(float)) { - format = "%g"; - upcast.Add (typeof(double)); - } else if (type == typeof(double)) { - format = "%g"; - upcast.Add (null); - } else { - throw new InvalidOperationException ($"Unsupported type '{type}'"); - }; - - sb.Append (format); - } - - (StringBuilder sb, List upcasts) InitPrintfFormat (string startChars = "(") - { - return (new StringBuilder (startChars), new List ()); - } - - (string asprintfFormat, List paramUpcast) FinishPrintfFormat (StringBuilder sb, List upcasts, string endChars = ")") - { - sb.Append (endChars); - return (sb.ToString (), upcasts); - } - - (string asprintfFormat, List paramUpcast) GetPrintfFormatForFunctionParams (LlvmIrFunction func) - { - (StringBuilder ret, List upcasts) = InitPrintfFormat (); - bool first = true; - - foreach (LlvmIrFunctionParameter parameter in func.Parameters) { - if (!first) { - ret.Append (", "); - } else { - first = false; - } - - AddPrintfFormatForType (ret, parameter.Type, upcasts); - } - - return FinishPrintfFormat (ret, upcasts); - } - - (string asprintfFormat, List paramUpcast) GetPrintfFormatForReturnValue (LlvmIrFunctionLocalVariable localVariable) - { - (StringBuilder ret, List upcasts) = InitPrintfFormat ("=>["); - - AddPrintfFormatForType (ret, localVariable.Type, upcasts); - - return FinishPrintfFormat (ret, upcasts, "]"); - } - - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) - { - LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); - - var asprintfArgs = new List { - new LlvmIrFunctionArgument (allocatedStringVarRef) { - NonNull = true, - NoUndef = true, - }, - new LlvmIrFunctionArgument (asprintfFormatSym) { - NoUndef = true, - }, - }; - - asprintfArgs.AddRange (variadicArgs); - - generator.WriteEOL (); - generator.WriteCommentLine ($"Format: {format}", indent: true); - LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintfArgs, marker: defaultCallMarker, AttributeSetID: -1); - LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); - - // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) - LlvmIrFunctionLocalVariable asprintfResultVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.SignedLessThan, resultRef, "0"); - var asprintfResultVariableRef = new LlvmIrVariableReference (asprintfResultVariable, isGlobal: false); - - string asprintfIfThenLabel = func.MakeUniqueLabel ("if.then"); - string asprintfIfElseLabel = func.MakeUniqueLabel ("if.else"); - string ifElseDoneLabel = func.MakeUniqueLabel ("if.done"); - - generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfIfThenLabel, asprintfIfElseLabel); - - // Condition is true if asprintf **failed** - generator.EmitLabel (func, asprintfIfThenLabel); - generator.EmitStoreInstruction (func, allocatedStringVarRef, null); - generator.EmitBrInstruction (func, ifElseDoneLabel); - - generator.EmitLabel (func, asprintfIfElseLabel); - LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); - LlvmIrVariableReference bufferPointerVarRef = new LlvmIrVariableReference (bufferPointerVar, isGlobal: false); - generator.EmitBrInstruction (func, ifElseDoneLabel); - - generator.EmitLabel (func, ifElseDoneLabel); - LlvmIrFunctionLocalVariable allocatedStringValueVar = generator.EmitPhiInstruction ( - func, - allocatedStringVarRef, - new List<(LlvmIrVariableReference? variableRef, string label)> { - (null, func.PreviousBlockStartLabel), - (bufferPointerVarRef, func.PreviousBlockEndLabel), - } - ); - - return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); - } - - void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, Type? upcast, LlvmIrFunctionLocalVariable paramVar) - { - if (upcast == null) { - asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); - return; - } - - LlvmIrVariableReference paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); - LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, upcast); - asprintfArgs.Add ( - new LlvmIrFunctionArgument (upcastVar) { - NoUndef = true, - } - ); - } - - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterUpcasts, LlvmIrVariableReference allocatedStringVarRef) - { - if (variadicArgs.Count != parameterUpcasts.Count) { - throw new ArgumentException (nameof (parameterUpcasts), $"Number of upcasts ({parameterUpcasts.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); - } - - var asprintfArgs = new List (); - - for (int i = 0; i < variadicArgs.Count; i++) { - if (parameterUpcasts[i] == null) { - asprintfArgs.Add (variadicArgs[i]); - continue; - } - - if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { - AddAsprintfArgument (generator, func, asprintfArgs, parameterUpcasts[i], paramVar); - continue; - } - - throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); - } - - return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); - } - - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, LlvmIrFunctionLocalVariable retVal, Type? retValUpcast, LlvmIrVariableReference allocatedStringVarRef) - { - var asprintfArgs = new List (); - AddAsprintfArgument (generator, func, asprintfArgs, retValUpcast, retVal); - - return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); - } - void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) { var backingFieldSignature = new LlvmNativeFunctionSignature ( @@ -976,63 +703,9 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll parameters: method.Parameters ); - generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); - - List? trace_enter_leave_args = null; - LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; - List? asprintfVariadicArgs = null; - LlvmIrVariableReference? asprintfAllocatedStringAccessorRef = null; - LlvmIrVariableReference? asprintfAllocatedStringVarRef = null; - string? asprintfFormat = null; - List asprintfUpcasts = null; - - if (tracingMode != MarshalMethodsTracingMode.None) { - const string paramsLocalVarName = "func_params_render"; - - generator.WriteCommentLine ("Tracing code start", indent: true); - (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); - asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); - generator.EmitStoreInstruction (func, asprintfAllocatedStringVarRef, null); - - asprintfVariadicArgs = new List (); - foreach (LlvmIrFunctionLocalVariable lfv in func.ParameterVariables) { - asprintfVariadicArgs.Add ( - new LlvmIrFunctionArgument (lfv) { - NoUndef = true, - } - ); - } - - (asprintfFormat, asprintfUpcasts) = GetPrintfFormatForFunctionParams (func); - asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, asprintfVariadicArgs, asprintfUpcasts, asprintfAllocatedStringVarRef); - - trace_enter_leave_args = new List { - new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[1], (int)tracingMode), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[2], method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[3], method.ClassCacheIndex), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[4], nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[5], method.NativeSymbolName), - new LlvmIrFunctionArgument (asprintfAllocatedStringAccessorRef), - }; - - generator.EmitCall (func, mm_trace_func_enter_ref, trace_enter_leave_args, marker: defaultCallMarker); - asprintfAllocatedStringVar = generator.EmitLoadInstruction (func, asprintfAllocatedStringVarRef); - - generator.EmitCall ( - func, - free_ref, - new List { - new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { - NoUndef = true, - }, - }, - marker: defaultCallMarker - ); - generator.WriteCommentLine ("Tracing code end", indent: true); - generator.WriteEOL (); - } + generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}\nRegistered: {method.Method.RegisteredMethod?.FullName}"); + TracingState? tracingState = WriteMarshalMethodTracingTop (generator, method, func); LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); @@ -1084,33 +757,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll marker: defaultCallMarker ); - if (tracingMode != MarshalMethodsTracingMode.None) { - generator.WriteCommentLine ("Tracing code start", indent: true); - - LlvmIrFunctionArgument extraInfoArg; - if (result != null) { - (asprintfFormat, asprintfUpcasts) = GetPrintfFormatForReturnValue (result); - asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfFormat, result, asprintfUpcasts[0], asprintfAllocatedStringVarRef); - extraInfoArg = new LlvmIrFunctionArgument (asprintfAllocatedStringAccessorRef) { - NoUndef = true, - }; - } else { - extraInfoArg = new LlvmIrFunctionArgument (asprintfAllocatedStringVarRef, isNull: true) { - NoUndef = true, - }; - } - - if (mm_trace_func_enter_leave_extra_info_param_index < 0) { - throw new InvalidOperationException ("Internal error: index of the extra info parameter is unknown"); - } - trace_enter_leave_args[mm_trace_func_enter_leave_extra_info_param_index] = extraInfoArg; - - generator.EmitCall (func, mm_trace_func_leave_ref, trace_enter_leave_args, marker: defaultCallMarker); - generator.EmitDeallocStackVariable (func, tracingParamsStringLifetimeTracker); - - generator.WriteCommentLine ("Tracing code end", indent: true); - } - + WriteMarshalMethodTracingBottom (tracingState, generator, func, result); if (result != null) { generator.EmitReturnInstruction (func, result); } @@ -1151,6 +798,8 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) generator.WriteFunctionStart (func); generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); + WriteTracingInit (generator, func); + generator.WriteFunctionEnd (func); return new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true); diff --git a/src/monodroid/jni/marshal-methods-tracing.cc b/src/monodroid/jni/marshal-methods-tracing.cc index d9f32f40bd2..49a319b7bf7 100644 --- a/src/monodroid/jni/marshal-methods-tracing.cc +++ b/src/monodroid/jni/marshal-methods-tracing.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -24,10 +25,34 @@ constexpr char LEAD[] = "MM: "; constexpr char BOOL_TRUE[] = "true"; constexpr char BOOL_FALSE[] = "false"; constexpr char MISSING_ENV[] = ""; +constexpr char NULL_PARAM[] = ""; +constexpr char INTERNAL_ERROR[] = ""; +constexpr char UNKNOWN[] = ""; -void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char *message) noexcept +static jclass java_lang_Class; +static jmethodID java_lang_Class_getName; + +void _mm_trace_init (JNIEnv *env) noexcept { + if (env == nullptr || java_lang_Class != nullptr) { + return; + } + + java_lang_Class = to_gref (env, env->FindClass ("java/lang/Class")); + java_lang_Class_getName = env->GetMethodID (java_lang_Class, "getName", "()Ljava/lang/String"); + + if (env->ExceptionOccurred ()) { + env->ExceptionDescribe (); + env->ExceptionClear (); + xamarin::android::Helpers::abort_application (); + } + bool all_found = assert_valid_jni_pointer (java_lang_Class, "class", "java.lang.Class"); + all_found &= assert_valid_jni_pointer (java_lang_Class_getName, "method", "java.lang.Class.getName ()"); + + if (!all_found) { + xamarin::android::Helpers::abort_application (); + } } static void _mm_trace_func_leave_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, @@ -94,20 +119,65 @@ void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_imag _mm_trace_func_leave_enter (env, tracing_mode, mono_image_index, class_index, method_token, LEAVE, native_method_name, false /* need_trace */, method_return_value); } -const char* _mm_trace_render_bool (bool v) noexcept +const char* _mm_trace_get_boolean_string (uint8_t v) noexcept { return v ? BOOL_TRUE : BOOL_FALSE; } -const char* _mm_trace_render_java_string (JNIEnv *env, jstring v) noexcept +char* _mm_trace_get_c_string (JNIEnv *env, jstring v) noexcept { if (env == nullptr) { return strdup (MISSING_ENV); } + if (v == nullptr) { + return strdup (NULL_PARAM); + } + const char *s = env->GetStringUTFChars (v, nullptr); - const char *ret = strdup (s); + char *ret = strdup (s); env->ReleaseStringUTFChars (v, s); return ret; } + +[[gnu::always_inline]] +static char* get_class_name (JNIEnv *env, jclass klass) noexcept +{ + if (java_lang_Class == nullptr || java_lang_Class_getName == nullptr) { + return strdup (INTERNAL_ERROR); + } + + auto className = static_cast(env->CallObjectMethod (klass, java_lang_Class_getName)); + if (className == nullptr) { + return strdup (UNKNOWN); + } + + return _mm_trace_get_c_string (env, className); +} + +char* _mm_trace_get_class_name (JNIEnv *env, jclass v) noexcept +{ + if (env == nullptr) { + return strdup (MISSING_ENV); + } + + if (v == nullptr) { + return strdup (NULL_PARAM); + } + + return get_class_name (env, v); +} + +char* _mm_trace_get_object_class_name (JNIEnv *env, jobject v) noexcept +{ + if (env == nullptr) { + return strdup (MISSING_ENV); + } + + if (v == nullptr) { + return strdup (NULL_PARAM); + } + + return get_class_name (env, env->GetObjectClass (v)); +} diff --git a/src/monodroid/jni/marshal-methods-tracing.hh b/src/monodroid/jni/marshal-methods-tracing.hh index 813136bcc6d..aa725eae783 100644 --- a/src/monodroid/jni/marshal-methods-tracing.hh +++ b/src/monodroid/jni/marshal-methods-tracing.hh @@ -14,7 +14,7 @@ inline constexpr int32_t TracingModeFull = 0x02; extern "C" { [[gnu::visibility("hidden")]] - void _mm_trace (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* method_name, const char* message) noexcept; + void _mm_trace_init (JNIEnv *env) noexcept; [[gnu::visibility("hidden")]] void _mm_trace_func_enter (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; @@ -22,13 +22,21 @@ extern "C" { [[gnu::visibility("hidden")]] void _mm_trace_func_leave (JNIEnv *env, int32_t tracing_mode, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, const char* native_method_name, const char* method_params) noexcept; - // Returns pointer to a constant string, must not be freed + // Returns pointer to a dynamically allocated string, must be freed + [[gnu::visibility("hidden")]] + char* _mm_trace_get_class_name (JNIEnv *env, jclass v) noexcept; + + // Returns pointer to a dynamically allocated string, must be freed [[gnu::visibility("hidden")]] - const char* _mm_trace_render_bool (bool v) noexcept; + char* _mm_trace_get_object_class_name (JNIEnv *env, jobject v) noexcept; // Returns pointer to a dynamically allocated string, must be freed [[gnu::visibility("hidden")]] - const char* _mm_trace_render_java_string (JNIEnv *env, jstring v) noexcept; + char* _mm_trace_get_c_string (JNIEnv *env, jstring v) noexcept; + + // Returns pointer to a constant string, must not be freed + [[gnu::visibility("hidden")]] + const char* _mm_trace_get_boolean_string (uint8_t v) noexcept; } #endif // ndef __MARSHAL_METHODS_TRACING_HH diff --git a/src/monodroid/jni/native-tracing.cc b/src/monodroid/jni/native-tracing.cc index 7db29ef5069..1111c1adeca 100644 --- a/src/monodroid/jni/native-tracing.cc +++ b/src/monodroid/jni/native-tracing.cc @@ -16,10 +16,6 @@ constexpr int PRIORITY = ANDROID_LOG_INFO; static void append_frame_number (std::string &trace, size_t count) noexcept; static unw_word_t adjust_address (unw_word_t addr) noexcept; static void init_jni (JNIEnv *env) noexcept; -static bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; - -template -static TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept; // java.lang.Thread static jclass java_lang_Thread; @@ -288,19 +284,6 @@ void append_frame_number (std::string &trace, size_t count) noexcept trace.append (num_buf.data ()); } -template -[[gnu::always_inline]] -TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept -{ - if (lref == nullptr) { - return nullptr; - } - - auto ret = static_cast (env->NewGlobalRef (lref)); - env->DeleteLocalRef (lref); - return ret; -} - void init_jni (JNIEnv *env) noexcept { // We might be called more than once, ignore all but the first call diff --git a/src/monodroid/jni/native-tracing.hh b/src/monodroid/jni/native-tracing.hh index 4bb19988bde..6f08e7273b3 100644 --- a/src/monodroid/jni/native-tracing.hh +++ b/src/monodroid/jni/native-tracing.hh @@ -28,4 +28,19 @@ extern "C" { [[gnu::visibility("default")]] const char* xa_get_interesting_signal_handlers () noexcept; } + +template +[[gnu::always_inline]] +inline TJavaPointer to_gref (JNIEnv *env, TJavaPointer lref) noexcept +{ + if (lref == nullptr) { + return nullptr; + } + + auto ret = static_cast (env->NewGlobalRef (lref)); + env->DeleteLocalRef (lref); + return ret; +} + +bool assert_valid_jni_pointer (void *o, const char *missing_kind, const char *missing_name) noexcept; #endif // ndef __NATIVE_TRACING_HH From 37b16d3f7f912deff260b4aa3e37dd04a30a62b1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 18 May 2023 23:44:37 +0200 Subject: [PATCH 29/60] Third iteration... done for today Just flushing current state, doesn't build. TBC tomorrow. --- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 191 +++++++++++++----- 1 file changed, 140 insertions(+), 51 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 97393d88b07..210a77a918a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Xamarin.Android.Tasks.LLVMIR; +using System.Collections; namespace Xamarin.Android.Tasks { @@ -34,23 +35,60 @@ sealed class AsprintfParameterOperation { public readonly Type? Upcast; public readonly TracingRenderArgumentFunction RenderFunction = TracingRenderArgumentFunction.None; + public readonly bool MustBeFreed; - public AsprintfParameterOperation (Type? upcast = null, TracingRenderArgumentFunction renderFunction = TracingRenderArgumentFunction.None) + public AsprintfParameterOperation (Type? upcast, TracingRenderArgumentFunction renderFunction) { Upcast = upcast; RenderFunction = renderFunction; } + + public AsprintfParameterOperation (Type upcast) + : this (upcast, TracingRenderArgumentFunction.None) + {} + + public AsprintfParameterOperation (TracingRenderArgumentFunction renderFunction, bool mustBeFreed) + : this (null, renderFunction) + { + MustBeFreed = mustBeFreed; + } + + public AsprintfParameterOperation () + : this (null, TracingRenderArgumentFunction.None) + {} + } + + sealed class AsprintfParameterTransform : IEnumerable + { + public List Operations { get; } = new List (); + + public void Add (AsprintfParameterOperation parameterOp) + { + Operations.Add (parameterOp); + } + + public IEnumerator GetEnumerator () + { + return ((IEnumerable)Operations).GetEnumerator (); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return ((IEnumerable)Operations).GetEnumerator (); + } } sealed class AsprintfCallState { public readonly string Format; - public readonly List ParameterOps; + public readonly List ParameterTransforms; + public readonly List VariablesToFree = new List (); + public readonly List VariadicArgs = new List (); - public AsprintfCallState (string format, List parameterOps) + public AsprintfCallState (string format, List parameterTransforms) { Format = format; - ParameterOps = parameterOps; + ParameterTransforms = parameterTransforms; } } @@ -218,63 +256,90 @@ void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, } } - void AddPrintfFormatForType (StringBuilder sb, Type type, List parameterOps) + void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms) { string format; + AsprintfParameterTransform? transform = null; + if (type == typeof(string)) { format = "\"%s\""; - parameterOps.Add (new AsprintfParameterOperation ()); + } else if (type == typeof(_jclass)) { + format = "%s @%p"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetClassName, mustBeFreed: true), + new AsprintfParameterOperation (), + }; + } else if (type == typeof(_jobject)) { + format = "%s @%p"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetObjectClassname, mustBeFreed: true), + new AsprintfParameterOperation (), + }; + } else if (type == typeof(_jstring)) { + format = "\"%s\""; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetCString, mustBeFreed: true), + new AsprintfParameterOperation (), + }; } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { format = "%p"; - parameterOps.Add (new AsprintfParameterOperation ()); - } else if (type == typeof(bool) || type == typeof(byte) || type == typeof(ushort)) { + } else if (type == typeof(bool)) { + format = "%s"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetBooleanString, mustBeFreed: false), + }; + } else if (type == typeof(byte) || type == typeof(ushort)) { format = "%u"; - parameterOps.Add (new AsprintfParameterOperation (typeof(uint))); + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(uint)), + }; } else if (type == typeof(sbyte) || type == typeof(short)) { format = "%d"; - parameterOps.Add (new AsprintfParameterOperation (typeof(int))); + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(int)), + }; } else if (type == typeof(char)) { format = "'\\%x'"; - parameterOps.Add (new AsprintfParameterOperation (typeof(uint))); + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(uint)), + }; } else if (type == typeof(int)) { format = "%d"; - parameterOps.Add (new AsprintfParameterOperation ()); } else if (type == typeof(uint)) { format = "%u"; - parameterOps.Add (new AsprintfParameterOperation ()); } else if (type == typeof(long)) { format = "%ld"; - parameterOps.Add (new AsprintfParameterOperation ()); } else if (type == typeof(ulong)) { format = "%lu"; - parameterOps.Add (new AsprintfParameterOperation ()); } else if (type == typeof(float)) { format = "%g"; - parameterOps.Add (new AsprintfParameterOperation (typeof(double))); + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(double)), + }; } else if (type == typeof(double)) { format = "%g"; - parameterOps.Add (null); } else { throw new InvalidOperationException ($"Unsupported type '{type}'"); }; + parameterTransforms.Add (transform); sb.Append (format); } - (StringBuilder sb, List parameterOps) InitPrintfFormat (string startChars = "(") + (StringBuilder sb, List parameterOps) InitPrintfState (string startChars = "(") { - return (new StringBuilder (startChars), new List ()); + return (new StringBuilder (startChars), new List ()); } - AsprintfCallState FinishPrintfFormat (StringBuilder sb, List parameterOps, string endChars = ")") + AsprintfCallState FinishPrintfState (StringBuilder sb, List parameterTransforms, string endChars = ")") { sb.Append (endChars); - return new AsprintfCallState (sb.ToString (), parameterOps); + return new AsprintfCallState (sb.ToString (), parameterTransforms); } - AsprintfCallState GetPrintfFormatForFunctionParams (MarshalMethodInfo method) + AsprintfCallState GetPrintfStateForFunctionParams (MarshalMethodInfo method, LlvmIrFunction func) { - (StringBuilder ret, List parameterOps) = InitPrintfFormat (); + (StringBuilder ret, List parameterOps) = InitPrintfState (); bool first = true; List nativeMethodParameters = method.Parameters; @@ -285,6 +350,11 @@ AsprintfCallState GetPrintfFormatForFunctionParams (MarshalMethodInfo method) throw new InvalidOperationException ($"Internal error: unexpected number of registered method parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); } + if (nativeMethodParameters.Count != func.Parameters.Count) { + throw new InvalidOperationException ($"Internal error: number of native method parameter ({nativeMethodParameters.Count}) doesn't match the number of marshal method parameters ({func.Parameters.Count})"); + } + + var variadicArgs = new List (); for (int i = 0; i < nativeMethodParameters.Count; i++) { LlvmIrFunctionParameter parameter = nativeMethodParameters[i]; if (!first) { @@ -293,10 +363,13 @@ AsprintfCallState GetPrintfFormatForFunctionParams (MarshalMethodInfo method) first = false; } - AddPrintfFormatForType (ret, parameter.Type, parameterOps); + // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two + // parameters, so we start looking at the managed parameters only once the first two are out of the way + AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, i >= 2 ? managedMethodParameters[i - 2] : null), parameterOps); + variadicArgs.Add (new LlvmIrVariableReference (func.ParameterVariables[i], isGlobal: false)); } - return FinishPrintfFormat (ret, parameterOps); + return FinishPrintfState (ret, parameterOps); Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) { @@ -304,20 +377,25 @@ Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, C return nativeParameter.Type; } + if (nativeParameter.Type == typeof(byte) && String.Compare ("System.Boolean", managedParameter.Name, StringComparison.Ordinal) == 0) { + // `bool`, as a non-blittable type, is mapped to `byte` by the marshal method rewriter + return typeof(bool); + } + return nativeParameter.Type; } } - AsprintfCallState GetPrintfFormatForReturnValue (LlvmIrFunctionLocalVariable localVariable) + AsprintfCallState GetPrintfStateForReturnValue (LlvmIrFunctionLocalVariable localVariable) { - (StringBuilder ret, List parameterOps) = InitPrintfFormat ("=>["); + (StringBuilder ret, List parameterTransforms) = InitPrintfState ("=>["); - AddPrintfFormatForType (ret, localVariable.Type, parameterOps); + AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms); - return FinishPrintfFormat (ret, parameterOps, "]"); + return FinishPrintfState (ret, parameterTransforms, "]"); } - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) { LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); @@ -371,53 +449,64 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); } - void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, AsprintfParameterOperation paramOp, LlvmIrFunctionLocalVariable paramVar) + + void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, List? paramOps, LlvmIrFunctionLocalVariable paramVar) { - if (paramOp.Upcast == null) { + if (paramOps == null || paramOps.Count == 0) { asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); return; } - LlvmIrVariableReference paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); - LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, paramOp.Upcast); - asprintfArgs.Add ( - new LlvmIrFunctionArgument (upcastVar) { - NoUndef = true, + foreach (AsprintfParameterOperation paramOp in paramOps) { + LlvmIrVariableReference? paramRef = null; + + if (paramOp.RenderFunction != TracingRenderArgumentFunction.None) { } - ); + + if (paramOp.Upcast != null) { + } + + paramRef = new LlvmIrVariableReference (paramVar, isGlobal: false); + LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef, paramOps.Upcast); + asprintfArgs.Add ( + new LlvmIrFunctionArgument (upcastVar) { + NoUndef = true, + } + ); + } } - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterOps, LlvmIrVariableReference allocatedStringVarRef) + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, List parameterTransforms, LlvmIrVariableReference allocatedStringVarRef) { - if (variadicArgs.Count != parameterOps.Count) { - throw new ArgumentException (nameof (parameterOps), $"Number of upcasts ({parameterOps.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); + if (variadicArgs.Count != parameterTransforms.Count) { + throw new ArgumentException (nameof (parameterTransforms), $"Number of transforms ({parameterTransforms.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); } var asprintfArgs = new List (); for (int i = 0; i < variadicArgs.Count; i++) { - if (parameterOps[i] == null) { + if (parameterTransforms[i] == null) { asprintfArgs.Add (variadicArgs[i]); continue; } if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { - AddAsprintfArgument (generator, func, asprintfArgs, parameterOps[i], paramVar); + AddAsprintfArgument (generator, func, asprintfArgs, parameterTransforms[i]?.Operations, paramVar); continue; } throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); } - return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + return DoWriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); } LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, LlvmIrFunctionLocalVariable retVal, AsprintfParameterOperation retValOp, LlvmIrVariableReference allocatedStringVarRef) { var asprintfArgs = new List (); - AddAsprintfArgument (generator, func, asprintfArgs, retValOp, retVal); + AddAsprintfArgument (generator, func, asprintfArgs, new List { retValOp }, retVal); - return WriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + return DoWriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); } TracingState? WriteMarshalMethodTracingTop (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrFunction func) @@ -441,12 +530,12 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc state.asprintfVariadicArgs.Add ( new LlvmIrFunctionArgument (lfv) { NoUndef = true, - } + } ); } - AsprintfCallState asprintfState = GetPrintfFormatForFunctionParams (method); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, state.asprintfVariadicArgs, asprintfState.ParameterOps, state.asprintfAllocatedStringVarRef); + AsprintfCallState asprintfState = GetPrintfStateForFunctionParams (method, func); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state.asprintfVariadicArgs, state.asprintfAllocatedStringVarRef); state.trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env @@ -491,8 +580,8 @@ void WriteMarshalMethodTracingBottom (TracingState? state, LlvmIrGenerator gener LlvmIrFunctionArgument extraInfoArg; if (result != null) { - AsprintfCallState asprintfState = GetPrintfFormatForReturnValue (result); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, result, asprintfState.ParameterOps[0], state.asprintfAllocatedStringVarRef); + AsprintfCallState asprintfState = GetPrintfStateForReturnValue (result); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, result, asprintfState.ParameterTransforms[0], state.asprintfAllocatedStringVarRef); extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef) { NoUndef = true, }; From 8f86bc738eb1bd6d29d8c1794785d6aa0d09ca32 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 May 2023 22:08:56 +0200 Subject: [PATCH 30/60] Cul-de-sac, dead end, wrong way, doh Well, time to take a few steps back and start anew. Next week. --- .../LlvmIrGenerator/LlvmIrFunction.cs | 2 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 2 + ...lMethodsNativeAssemblyGenerator.Tracing.cs | 140 +++++++++++------- 3 files changed, 93 insertions(+), 51 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index dd16f0547a8..c5f95fb3748 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -21,7 +21,7 @@ public LlvmIrFunctionLocalVariable (LlvmNativeFunctionSignature nativeFunction, } public LlvmIrFunctionLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) - : base (variable, name, isNativePointer) + : base (variable, name, variable.IsNativePointer || isNativePointer) {} } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 784b1a08b05..8110a804489 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -581,8 +581,10 @@ void ThrowException () void WriteCallArguments (LlvmIrFunction function, LlvmNativeFunctionSignature targetSignature, List arguments) { + Console.WriteLine ($"WCA for '{function.Name}'"); bool variadicCountry = false; for (int i = 0; i < arguments.Count; i++) { + Console.WriteLine ($" WCA: arg #{i}"); LlvmIrFunctionParameter? parameter = null; if (!variadicCountry) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 210a77a918a..962c5db27bd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -83,7 +83,7 @@ sealed class AsprintfCallState public readonly string Format; public readonly List ParameterTransforms; public readonly List VariablesToFree = new List (); - public readonly List VariadicArgs = new List (); + public readonly List VariadicArgsVariables = new List (); public AsprintfCallState (string format, List parameterTransforms) { @@ -256,38 +256,45 @@ void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, } } - void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms) + void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms, out bool isNativePointer) { string format; AsprintfParameterTransform? transform = null; + isNativePointer = false; if (type == typeof(string)) { format = "\"%s\""; + isNativePointer = true; } else if (type == typeof(_jclass)) { format = "%s @%p"; transform = new AsprintfParameterTransform { new AsprintfParameterOperation (TracingRenderArgumentFunction.GetClassName, mustBeFreed: true), new AsprintfParameterOperation (), }; + isNativePointer = true; } else if (type == typeof(_jobject)) { format = "%s @%p"; transform = new AsprintfParameterTransform { new AsprintfParameterOperation (TracingRenderArgumentFunction.GetObjectClassname, mustBeFreed: true), new AsprintfParameterOperation (), }; + isNativePointer = true; } else if (type == typeof(_jstring)) { format = "\"%s\""; transform = new AsprintfParameterTransform { new AsprintfParameterOperation (TracingRenderArgumentFunction.GetCString, mustBeFreed: true), new AsprintfParameterOperation (), }; + isNativePointer = true; } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { format = "%p"; + isNativePointer = true; } else if (type == typeof(bool)) { format = "%s"; transform = new AsprintfParameterTransform { new AsprintfParameterOperation (TracingRenderArgumentFunction.GetBooleanString, mustBeFreed: false), }; + isNativePointer = true; } else if (type == typeof(byte) || type == typeof(ushort)) { format = "%u"; transform = new AsprintfParameterTransform { @@ -365,11 +372,13 @@ AsprintfCallState GetPrintfStateForFunctionParams (MarshalMethodInfo method, Llv // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two // parameters, so we start looking at the managed parameters only once the first two are out of the way - AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, i >= 2 ? managedMethodParameters[i - 2] : null), parameterOps); - variadicArgs.Add (new LlvmIrVariableReference (func.ParameterVariables[i], isGlobal: false)); + AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, i >= 2 ? managedMethodParameters[i - 2] : null), parameterOps, out bool isNativePointer); + variadicArgs.Add (new LlvmIrVariableReference (func.ParameterVariables[i], isGlobal: false, isNativePointer: isNativePointer)); } - return FinishPrintfState (ret, parameterOps); + AsprintfCallState state = FinishPrintfState (ret, parameterOps); + state.VariadicArgsVariables.AddRange (variadicArgs); + return state; Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) { @@ -390,17 +399,17 @@ AsprintfCallState GetPrintfStateForReturnValue (LlvmIrFunctionLocalVariable loca { (StringBuilder ret, List parameterTransforms) = InitPrintfState ("=>["); - AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms); + AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms, out bool isNativePointer); return FinishPrintfState (ret, parameterTransforms, "]"); } - LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, List variadicArgs, LlvmIrVariableReference allocatedStringVarRef) + LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, List variadicArgs, AsprintfCallState asprintfState, TracingState tracingState) { - LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (format, $"asprintf_fmt_{func.Name}"); + LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (asprintfState.Format, $"asprintf_fmt_{func.Name}"); var asprintfArgs = new List { - new LlvmIrFunctionArgument (allocatedStringVarRef) { + new LlvmIrFunctionArgument (tracingState.asprintfAllocatedStringVarRef) { NonNull = true, NoUndef = true, }, @@ -408,11 +417,10 @@ LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFu NoUndef = true, }, }; - asprintfArgs.AddRange (variadicArgs); generator.WriteEOL (); - generator.WriteCommentLine ($"Format: {format}", indent: true); + generator.WriteCommentLine ($"Format: {asprintfState.Format}", indent: true); LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintfArgs, marker: defaultCallMarker, AttributeSetID: -1); LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); @@ -428,18 +436,18 @@ LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFu // Condition is true if asprintf **failed** generator.EmitLabel (func, asprintfIfThenLabel); - generator.EmitStoreInstruction (func, allocatedStringVarRef, null); + generator.EmitStoreInstruction (func, tracingState.asprintfAllocatedStringVarRef, null); generator.EmitBrInstruction (func, ifElseDoneLabel); generator.EmitLabel (func, asprintfIfElseLabel); - LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, allocatedStringVarRef); + LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, tracingState.asprintfAllocatedStringVarRef); LlvmIrVariableReference bufferPointerVarRef = new LlvmIrVariableReference (bufferPointerVar, isGlobal: false); generator.EmitBrInstruction (func, ifElseDoneLabel); generator.EmitLabel (func, ifElseDoneLabel); LlvmIrFunctionLocalVariable allocatedStringValueVar = generator.EmitPhiInstruction ( func, - allocatedStringVarRef, + tracingState.asprintfAllocatedStringVarRef, new List<(LlvmIrVariableReference? variableRef, string label)> { (null, func.PreviousBlockStartLabel), (bufferPointerVarRef, func.PreviousBlockEndLabel), @@ -449,8 +457,58 @@ LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFu return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); } + LlvmIrVariableReference? WriteTransformFunctionCall (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, AsprintfParameterOperation paramOp, LlvmIrVariableReference paramVar) + { + if (paramOp.RenderFunction == TracingRenderArgumentFunction.None) { + return null; + } + + var transformerArgs = new List (); + LlvmIrVariableReference transformerFunc; + + switch (paramOp.RenderFunction) { + case TracingRenderArgumentFunction.GetClassName: + transformerFunc = mm_trace_get_class_name_ref; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetObjectClassname: + transformerFunc = mm_trace_get_object_class_name_ref; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetCString: + transformerFunc = mm_trace_get_c_string_ref; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetBooleanString: + transformerFunc = mm_trace_get_boolean_string_ref; + break; - void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List asprintfArgs, List? paramOps, LlvmIrFunctionLocalVariable paramVar) + default: + throw new InvalidOperationException ($"Internal error: unsupported transformer function {paramOp.RenderFunction}"); + }; + transformerArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); + + LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, transformerFunc, transformerArgs, marker: defaultCallMarker, AttributeSetID: -1); + if (result == null) { + return null; + } + + if (paramOp.MustBeFreed) { + asprintfState.VariablesToFree.Add (new LlvmIrVariableReference (result, isGlobal: false)); + } + + return new LlvmIrVariableReference (result, isGlobal: false, isNativePointer: true); + + void AddJNIEnvArgument () + { + transformerArgs.Add (new LlvmIrFunctionArgument (func.ParameterVariables[0]) { NoUndef = true }); + } + } + + void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, List asprintfArgs, List? paramOps, LlvmIrVariableReference paramVar) { if (paramOps == null || paramOps.Count == 0) { asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); @@ -458,55 +516,38 @@ void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, List variadicArgs, List parameterTransforms, LlvmIrVariableReference allocatedStringVarRef) + LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, TracingState tracingState) { - if (variadicArgs.Count != parameterTransforms.Count) { - throw new ArgumentException (nameof (parameterTransforms), $"Number of transforms ({parameterTransforms.Count}) is not equal to the number of variadic arguments ({variadicArgs.Count})"); + if (asprintfState.VariadicArgsVariables.Count != asprintfState.ParameterTransforms.Count) { + throw new ArgumentException (nameof (asprintfState), $"Number of transforms ({asprintfState.ParameterTransforms.Count}) is not equal to the number of variadic arguments ({asprintfState.VariadicArgsVariables.Count})"); } var asprintfArgs = new List (); - for (int i = 0; i < variadicArgs.Count; i++) { - if (parameterTransforms[i] == null) { - asprintfArgs.Add (variadicArgs[i]); - continue; - } - - if (variadicArgs[i].Value is LlvmIrFunctionLocalVariable paramVar) { - AddAsprintfArgument (generator, func, asprintfArgs, parameterTransforms[i]?.Operations, paramVar); - continue; - } - - throw new InvalidOperationException ($"Unexpected argument type {variadicArgs[i].Type}"); + for (int i = 0; i < asprintfState.VariadicArgsVariables.Count; i++) { + AddAsprintfArgument (generator, func, asprintfState, asprintfArgs, asprintfState.ParameterTransforms[i]?.Operations, asprintfState.VariadicArgsVariables[i]); } - return DoWriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); - } - - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, string format, LlvmIrFunctionLocalVariable retVal, AsprintfParameterOperation retValOp, LlvmIrVariableReference allocatedStringVarRef) - { - var asprintfArgs = new List (); - AddAsprintfArgument (generator, func, asprintfArgs, new List { retValOp }, retVal); - - return DoWriteAsprintfCall (generator, func, format, asprintfArgs, allocatedStringVarRef); + return DoWriteAsprintfCall (generator, func, asprintfArgs, asprintfState, tracingState); } TracingState? WriteMarshalMethodTracingTop (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrFunction func) @@ -516,10 +557,9 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc } CecilMethodDefinition nativeCallback = method.Method.NativeCallback; - var state = new TracingState (); - const string paramsLocalVarName = "func_params_render"; + var state = new TracingState (); generator.WriteCommentLine ("Tracing code start", indent: true); (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, state.tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); state.asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); @@ -535,7 +575,7 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc } AsprintfCallState asprintfState = GetPrintfStateForFunctionParams (method, func); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state.asprintfVariadicArgs, state.asprintfAllocatedStringVarRef); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state); state.trace_enter_leave_args = new List { new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env @@ -556,7 +596,7 @@ LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunc new List { new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { NoUndef = true, - }, + }, }, marker: defaultCallMarker ); @@ -581,7 +621,7 @@ void WriteMarshalMethodTracingBottom (TracingState? state, LlvmIrGenerator gener LlvmIrFunctionArgument extraInfoArg; if (result != null) { AsprintfCallState asprintfState = GetPrintfStateForReturnValue (result); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState.Format, result, asprintfState.ParameterTransforms[0], state.asprintfAllocatedStringVarRef); + state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state); extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef) { NoUndef = true, }; From 135744cee89e2fc488b619b4f29c94b259cf9739 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 May 2023 23:16:06 +0200 Subject: [PATCH 31/60] New approach to LLVM IR generation New code will be built around the notion of LLVM IR module - a collection of data, code, attributes etc. As many operations as possible will be implemented by adding abstract nodes (a'la CodeDOM or AST) instead of generating LLVM IR code directly. This will simplify "client" (e.g. `MarshalMethodsNativeAssemblyGenerator`) implementation. Code will be generated from the AST at the end. --- .../LlvmIrGenerator/FunctionAttributes.cs | 345 ++++++++++++++++- .../LlvmFunctionAttributeSet.cs | 58 ++- .../LlvmIrGenerator/LlvmIrComposer.cs | 8 + .../LlvmIrGenerator/LlvmIrDataLayout.cs | 354 ++++++++++++++++++ .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 81 ++++ .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 36 ++ .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 44 +++ .../LlvmIrGenerator/LlvmIrModuleX64.cs | 49 +++ .../LlvmIrGenerator/LlvmIrModuleX86.cs | 47 +++ 9 files changed, 1017 insertions(+), 5 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index d0bbc9cc7cc..e9e1fc87231 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -6,7 +6,7 @@ namespace Xamarin.Android.Tasks.LLVMIR { // Not all attributes are currently used throughout the code, but we define them call for potential future use. // Documentation can be found here: https://llvm.org/docs/LangRef.html#function-attributes - abstract class LLVMFunctionAttribute + abstract class LLVMFunctionAttribute : IComparable, IComparable, IEquatable { public string Name { get; } public bool Quoted { get; } @@ -89,6 +89,83 @@ protected string EnsureNonEmptyParameter (string name, string value) return value; } + + public int CompareTo (object obj) + { + var attr = obj as LLVMFunctionAttribute; + if (obj == null) { + return 1; + } + + return CompareTo (attr); + } + + public int CompareTo (LLVMFunctionAttribute other) + { + return Name.CompareTo (other?.Name); + } + + public override int GetHashCode() + { + int hc = 0; + if (Name != null) { + hc ^= Name.GetHashCode (); + } + + return + hc ^ + Quoted.GetHashCode () ^ + SupportsParams.GetHashCode () ^ + ParamsAreOptional.GetHashCode () ^ + HasValueAsignment.GetHashCode (); + } + + public override bool Equals (object obj) + { + var attr = obj as LLVMFunctionAttribute; + if (attr == null) { + return false; + } + + return Equals (attr); + } + + public virtual bool Equals (LLVMFunctionAttribute other) + { + if (other == null) { + return false; + } + + if (String.Compare (Name, other.Name, StringComparison.Ordinal) != 0) { + return false; + } + + return + Quoted == other.Quoted && + SupportsParams == other.SupportsParams && + ParamsAreOptional == other.ParamsAreOptional && + HasValueAsignment == other.HasValueAsignment; + } + + public static bool operator > (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + { + return a.CompareTo (b) > 0; + } + + public static bool operator < (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + { + return a.CompareTo (b) < 0; + } + + public static bool operator >= (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + { + return a.CompareTo (b) >= 0; + } + + public static bool operator <= (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + { + return a.CompareTo (b) <= 0; + } } abstract class LLVMFlagFunctionAttribute : LLVMFunctionAttribute @@ -116,6 +193,25 @@ protected override void RenderParams (StringBuilder sb) { sb.Append (alignment.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AlignstackFunctionAttribute; + if (attr == null) { + return false; + } + + return alignment == attr.alignment; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ alignment.GetHashCode (); + } } class AllocFamilyFunctionAttribute : LLVMFunctionAttribute @@ -132,6 +228,25 @@ protected override void RenderAssignedValue (StringBuilder sb) { sb.Append (family); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllocFamilyFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (family, attr.family, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (family?.GetHashCode () ?? 0); + } } class AllockindFunctionAttribute : LLVMFunctionAttribute @@ -150,6 +265,25 @@ protected override void RenderParams (StringBuilder sb) sb.Append (kind); sb.Append ('"'); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllockindFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (kind, attr.kind, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (kind?.GetHashCode () ?? 0); + } } class AllocsizeFunctionAttribute : LLVMFunctionAttribute @@ -174,6 +308,25 @@ protected override void RenderParams (StringBuilder sb) sb.Append (", "); sb.Append (numberOfElements.Value.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as AllocsizeFunctionAttribute; + if (attr == null) { + return false; + } + + return elementSize == attr.elementSize && numberOfElements == attr.numberOfElements; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ elementSize.GetHashCode () ^ (numberOfElements?.GetHashCode () ?? 0); + } } class AlwaysinlineFunctionAttribute : LLVMFlagFunctionAttribute @@ -252,6 +405,25 @@ public FramePointerFunctionAttribute (string fpMode = "none") } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (fpMode); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as FramePointerFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (fpMode, attr.fpMode, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (fpMode?.GetHashCode () ?? 0); + } } class HotFunctionAttribute : LLVMFlagFunctionAttribute @@ -652,6 +824,25 @@ protected override void RenderParams (StringBuilder sb) sb.Append (isSync.Value ? "sync" : "async"); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as UwtableFunctionAttribute; + if (attr == null) { + return false; + } + + return isSync == attr.isSync; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (isSync?.GetHashCode () ?? 0); + } } class NocfCheckFunctionAttribute : LLVMFlagFunctionAttribute @@ -686,6 +877,25 @@ public WarnStackSizeFunctionAttribute (uint threshold) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (threshold); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as WarnStackSizeFunctionAttribute; + if (attr == null) { + return false; + } + + return threshold == attr.threshold; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ threshold.GetHashCode (); + } } class VscaleRangeFunctionAttribute : LLVMFunctionAttribute @@ -710,6 +920,25 @@ protected override void RenderParams (StringBuilder sb) sb.Append (", "); sb.Append (max.Value.ToString (CultureInfo.InvariantCulture)); } + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as VscaleRangeFunctionAttribute; + if (attr == null) { + return false; + } + + return min == attr.min && max == attr.max; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ min.GetHashCode () ^ (max?.GetHashCode () ?? 0); + } } class MinLegalVectorWidthFunctionAttribute : LLVMFunctionAttribute @@ -723,6 +952,25 @@ public MinLegalVectorWidthFunctionAttribute (uint size) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as MinLegalVectorWidthFunctionAttribute; + if (attr == null) { + return false; + } + + return size == attr.size; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ size.GetHashCode (); + } } class StackProtectorBufferSizeFunctionAttribute : LLVMFunctionAttribute @@ -736,6 +984,25 @@ public StackProtectorBufferSizeFunctionAttribute (uint size) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as StackProtectorBufferSizeFunctionAttribute; + if (attr == null) { + return false; + } + + return size == attr.size; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ size.GetHashCode (); + } } class TargetCpuFunctionAttribute : LLVMFunctionAttribute @@ -749,6 +1016,25 @@ public TargetCpuFunctionAttribute (string cpu) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TargetCpuFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (cpu, attr.cpu, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (cpu?.GetHashCode () ?? 0); + } } class TuneCpuFunctionAttribute : LLVMFunctionAttribute @@ -762,6 +1048,25 @@ public TuneCpuFunctionAttribute (string cpu) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TuneCpuFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (cpu, attr.cpu, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (cpu?.GetHashCode () ?? 0); + } } class TargetFeaturesFunctionAttribute : LLVMFunctionAttribute @@ -775,6 +1080,25 @@ public TargetFeaturesFunctionAttribute (string features) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (features); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as TargetFeaturesFunctionAttribute; + if (attr == null) { + return false; + } + + return String.Compare (features, attr.features, StringComparison.Ordinal) == 0; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ (features?.GetHashCode () ?? 0); + } } class NoTrappingMathFunctionAttribute : LLVMFunctionAttribute @@ -788,6 +1112,25 @@ public NoTrappingMathFunctionAttribute (bool yesno) } protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (yesno.ToString ().ToLowerInvariant ()); + + public override bool Equals (LLVMFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as NoTrappingMathFunctionAttribute; + if (attr == null) { + return false; + } + + return yesno == attr.yesno; + } + + public override int GetHashCode () + { + return base.GetHashCode () ^ yesno.GetHashCode (); + } } class StackrealignFunctionAttribute : LLVMFlagFunctionAttribute diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs index 4ba4ed9be75..b416e7c5cee 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs @@ -5,13 +5,22 @@ namespace Xamarin.Android.Tasks.LLVMIR { - class LlvmFunctionAttributeSet : IEnumerable + class LlvmFunctionAttributeSet : IEnumerable, IEquatable { + static readonly object counterLock = new object (); + static uint counter = 0; + + public uint Number { get; } + HashSet attributes; public LlvmFunctionAttributeSet () { attributes = new HashSet (); + + lock (counterLock) { + Number = counter++; + } } public void Add (LLVMFunctionAttribute attr) @@ -20,8 +29,9 @@ public void Add (LLVMFunctionAttribute attr) throw new ArgumentNullException (nameof (attr)); } - // TODO: implement uniqueness checks - attributes.Add (attr); + if (!attributes.Contains (attr)) { + attributes.Add (attr); + } } public void Add (LlvmFunctionAttributeSet sourceSet) @@ -46,5 +56,45 @@ public string Render () public IEnumerator GetEnumerator () => attributes.GetEnumerator (); IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - } + + public bool Equals (LlvmFunctionAttributeSet other) + { + if (other == null) { + return false; + } + + if (attributes.Count != other.attributes.Count) { + return false; + } + + foreach (LLVMFunctionAttribute attr in attributes) { + if (!other.attributes.Contains (attr)) { + return false; + } + } + + return true; + } + + public override bool Equals (object obj) + { + var attrSet = obj as LlvmFunctionAttributeSet; + if (attrSet == null) { + return false; + } + + return Equals (attrSet); + } + + public override int GetHashCode() + { + int hc = 0; + + foreach (LLVMFunctionAttribute attr in attributes) { + hc ^= attr?.GetHashCode () ?? 0; + } + + return hc; + } + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 790b8d30668..e164b344a26 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -21,6 +21,11 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) { LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); + string newFileName = Path.Combine (Path.GetDirectoryName (fileName), $"new-{Path.GetFileName(fileName)}"); + using var newOutput = new StreamWriter (File.Create (newFileName), new UTF8Encoding (false)); + + LlvmIrModule module = LlvmIrModule.Create (arch, output, newFileName); + InitGenerator (generator); MapStructures (generator); generator.WriteFileTop (); @@ -28,6 +33,9 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) Write (generator); generator.WriteFunctionDeclarations (); generator.WriteFileEnd (); + + module.Generate (newOutput); + newOutput.Flush (); } protected static string GetAbiName (AndroidTargetArch arch) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs new file mode 100644 index 00000000000..d4107e5073a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + abstract class LlvmIrDataLayoutField + { + public const char Separator = ':'; + + public string Id { get; } + + protected LlvmIrDataLayoutField (string id) + { + if (String.IsNullOrEmpty (id)) { + throw new ArgumentException (nameof (id), "must not be null or empty"); + } + + Id = id; + } + + public virtual void Render (StringBuilder sb) + { + sb.Append (Id); + } + + public static string ConvertToString (uint v) + { + return v.ToString (CultureInfo.InvariantCulture); + } + + protected void Append (StringBuilder sb, uint v, bool needSeparator = true) + { + if (needSeparator) { + sb.Append (Separator); + } + + sb.Append (ConvertToString (v)); + } + + protected void Append (StringBuilder sb, uint? v, bool needSeparator = true) + { + if (!v.HasValue) { + return; + } + + Append (sb, v.Value, needSeparator); + } + } + + class LlvmIrDataLayoutPointerSize : LlvmIrDataLayoutField + { + public uint? AddressSpace { get; set; } + public uint Abi { get; } + public uint Size { get; } + public uint? Pref { get; set; } + public uint? Idx { get; set; } + + public LlvmIrDataLayoutPointerSize (uint size, uint abi) + : base ("p") + { + Size = size; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + if (AddressSpace.HasValue && AddressSpace.Value > 0) { + Append (sb, AddressSpace.Value, needSeparator: false); + } + Append (sb, Size); + Append (sb, Abi); + Append (sb, Pref); + Append (sb, Idx); + } + } + + abstract class LlvmIrDataLayoutTypeAlignment : LlvmIrDataLayoutField + { + public uint Size { get; } + public uint Abi { get; } + public uint? Pref { get; set; } + + protected LlvmIrDataLayoutTypeAlignment (string id, uint size, uint abi) + : base (id) + { + Size = size; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + Append (sb, Size, needSeparator: false); + Append (sb, Abi); + Append (sb, Pref); + } + } + + class LlvmIrDataLayoutIntegerAlignment : LlvmIrDataLayoutTypeAlignment + { + public LlvmIrDataLayoutIntegerAlignment (uint size, uint abi, uint? pref = null) + : base ("i", size, abi) + { + if (size == 8 && abi != 8) { + throw new ArgumentOutOfRangeException (nameof (abi), "Must equal 8 for i8"); + } + + Pref = pref; + } + } + + class LlvmIrDataLayoutVectorAlignment : LlvmIrDataLayoutTypeAlignment + { + public LlvmIrDataLayoutVectorAlignment (uint size, uint abi, uint? pref = null) + : base ("v", size, abi) + { + Pref = pref; + } + } + + class LlvmIrDataLayoutFloatAlignment : LlvmIrDataLayoutTypeAlignment + { + public LlvmIrDataLayoutFloatAlignment (uint size, uint abi, uint? pref = null) + : base ("f", size, abi) + { + Pref = pref; + } + } + + class LlvmIrDataLayoutAggregateObjectAlignment : LlvmIrDataLayoutField + { + public uint Abi { get; } + public uint? Pref { get; set; } + + public LlvmIrDataLayoutAggregateObjectAlignment (uint abi, uint? pref = null) + : base ("a") + { + Abi = abi; + Pref = pref; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + Append (sb, Abi); + Append (sb, Pref); + } + } + + enum LlvmIrDataLayoutFunctionPointerAlignmentType + { + Independent, + Multiple, + } + + class LlvmIrDataLayoutFunctionPointerAlignment : LlvmIrDataLayoutField + { + public uint Abi { get; } + public LlvmIrDataLayoutFunctionPointerAlignmentType Type { get; } + + public LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType type, uint abi) + : base ("F") + { + Type = type; + Abi = abi; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + char type = Type switch { + LlvmIrDataLayoutFunctionPointerAlignmentType.Independent => 'i', + LlvmIrDataLayoutFunctionPointerAlignmentType.Multiple => 'n', + _ => throw new InvalidOperationException ($"Unsupported function pointer alignment type '{Type}'") + }; + sb.Append (type); + Append (sb, Abi, needSeparator: false); + } + } + + enum LlvmIrDataLayoutManglingOption + { + ELF, + GOFF, + MIPS, + MachO, + WindowsX86COFF, + WindowsCOFF, + XCOFF + } + + class LlvmIrDataLayoutMangling : LlvmIrDataLayoutField + { + public LlvmIrDataLayoutManglingOption Option { get; } + + public LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption option) + : base ("m") + { + Option = option; + } + + public override void Render (StringBuilder sb) + { + base.Render (sb); + + sb.Append (Separator); + + char opt = Option switch { + LlvmIrDataLayoutManglingOption.ELF => 'e', + LlvmIrDataLayoutManglingOption.GOFF => 'l', + LlvmIrDataLayoutManglingOption.MIPS => 'm', + LlvmIrDataLayoutManglingOption.MachO => 'o', + LlvmIrDataLayoutManglingOption.WindowsX86COFF => 'x', + LlvmIrDataLayoutManglingOption.WindowsCOFF => 'w', + LlvmIrDataLayoutManglingOption.XCOFF => 'a', + _ => throw new InvalidOperationException ($"Unsupported mangling option '{Option}'") + }; + + sb.Append (opt); + } + } + + // See: https://llvm.org/docs/LangRef.html#data-layout + class LlvmIrDataLayout + { + bool bigEndian; + bool littleEndian = true; + + public bool BigEndian { + get => bigEndian; + set { + bigEndian = value; + littleEndian = !bigEndian; + } + } + + public bool LittleEndian { + get => littleEndian; + set { + littleEndian = value; + bigEndian = !littleEndian; + } + } + + public uint? AllocaAddressSpaceId { get; set; } + public uint? GlobalsAddressSpaceId { get; set; } + public LlvmIrDataLayoutMangling? Mangling { get; set; } + public uint? ProgramAddressSpaceId { get; set; } + public uint? StackAlignment { get; set; } + + public LlvmIrDataLayoutAggregateObjectAlignment? AggregateObjectAlignment { get; set; } + public List? FloatAlignment { get; set; } + public LlvmIrDataLayoutFunctionPointerAlignment? FunctionPointerAlignment { get; set; } + public List? IntegerAlignment { get; set; } + public List? VectorAlignment { get; set; } + public List? PointerSize { get; set; } + + public List? NativeIntegerWidths { get; set; } + public List? NonIntegralPointerTypeAddressSpaces { get; set; } + + public string Render () + { + var sb = new StringBuilder (); + + sb.Append ("target datalayout = \""); + + sb.Append (LittleEndian ? 'e' : 'E'); + + if (Mangling != null) { + sb.Append ('-'); + Mangling.Render (sb); + } + + AppendFieldList (PointerSize); + + if (FunctionPointerAlignment != null) { + sb.Append ('-'); + FunctionPointerAlignment.Render (sb); + } + + AppendFieldList (IntegerAlignment); + AppendFieldList (FloatAlignment); + AppendFieldList (VectorAlignment); + + Append ('P', ProgramAddressSpaceId); + Append ('G', GlobalsAddressSpaceId); + Append ('A', AllocaAddressSpaceId); + + if (AggregateObjectAlignment != null) { + sb.Append ('-'); + AggregateObjectAlignment.Render (sb); + } + + AppendList ("n", NativeIntegerWidths); + AppendList ("ni", NonIntegralPointerTypeAddressSpaces); + Append ('S', StackAlignment); + + sb.Append ('"'); + + return sb.ToString (); + + void AppendFieldList (List? list) where T: LlvmIrDataLayoutField + { + if (list == null || list.Count == 0) { + return; + } + + foreach (LlvmIrDataLayoutField field in list) { + sb.Append ('-'); + field.Render (sb); + } + } + + void AppendList (string id, List? list) + { + if (list == null || list.Count == 0) { + return; + } + + sb.Append ('-'); + sb.Append (id); + + bool first = true; + foreach (uint v in list) { + if (first) { + first = false; + } else { + sb.Append (LlvmIrDataLayoutField.Separator); + } + + sb.Append (LlvmIrDataLayoutField.ConvertToString (v)); + } + } + + void Append (char id, uint? v) + { + if (!v.HasValue) { + return; + } + + sb.Append ('-'); + sb.Append (id); + sb.Append (LlvmIrDataLayoutField.ConvertToString (v.Value)); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs new file mode 100644 index 00000000000..57f2d30bc0d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + abstract class LlvmIrModule + { + public abstract LlvmIrDataLayout DataLayout { get; } + public abstract string TargetTriple { get; } + public abstract AndroidTargetArch TargetArch { get; } + + public string FilePath { get; } + public string FileName { get; } + + HashSet? attributeSets; + + protected LlvmIrModule (string filePath) + { + FilePath = Path.GetFullPath (filePath); + FileName = Path.GetFileName (filePath); + } + + public static LlvmIrModule Create (AndroidTargetArch arch, StreamWriter output, string fileName) + { + return arch switch { + AndroidTargetArch.Arm => new LlvmIrModuleArmV7a (fileName), + AndroidTargetArch.Arm64 => new LlvmIrModuleAArch64 (fileName), + AndroidTargetArch.X86 => new LlvmIrModuleX86 (fileName), + AndroidTargetArch.X86_64 => new LlvmIrModuleX64 (fileName), + _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") + }; + } + + public void Generate (TextWriter writer) + { + if (!String.IsNullOrEmpty (FilePath)) { + WriteCommentLine (writer, $" ModuleID = '{FileName}'"); + writer.WriteLine ($"source_filename = \"{FileName}\""); + } + + writer.WriteLine (DataLayout.Render ()); + writer.WriteLine ($"target triple = \"{TargetTriple}\""); + writer.WriteLine (); + + + WriteAttributeSets (writer); + } + + void WriteAttributeSets (TextWriter writer) + { + if (attributeSets == null || attributeSets.Count == 0) { + return; + } + + List list = attributeSets.ToList (); + list.Sort ((LlvmFunctionAttributeSet a, LlvmFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); + + foreach (LlvmFunctionAttributeSet attrSet in attributeSets) { + writer.WriteLine ($"attributes #{attrSet.Number} {{ {attrSet.Render ()} }}"); + } + } + + void WriteComment (TextWriter writer, string comment) + { + writer.Write (';'); + writer.Write (comment); + } + + void WriteCommentLine (TextWriter writer, string comment) + { + WriteComment (writer, comment); + writer.WriteLine (); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs new file mode 100644 index 00000000000..86072d0f35c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrModuleAArch64 : LlvmIrModule + { + public override LlvmIrDataLayout DataLayout { get; } + public override string TargetTriple => "aarch64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; + + public LlvmIrModuleAArch64 (string fileName) + : base (fileName) + { + // + // As per Android NDK: + // target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 8, abi: 8, pref: 32), // i8 + new LlvmIrDataLayoutIntegerAlignment (size: 16, abi: 16, pref: 32), // i16 + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + new LlvmIrDataLayoutIntegerAlignment (size: 128, abi: 128), // i128 + }, + + NativeIntegerWidths = new List { 32, 64}, + StackAlignment = 128, + }; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs new file mode 100644 index 00000000000..f2a1956ff3a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrModuleArmV7a : LlvmIrModule + { + public override LlvmIrDataLayout DataLayout { get; } + public override string TargetTriple => "armv7-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; + + public LlvmIrModuleArmV7a (string fileName) + : base (fileName) + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + }, + + FunctionPointerAlignment = new LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType.Independent, abi: 8), + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, + + VectorAlignment = new List { + new LlvmIrDataLayoutVectorAlignment (size: 128, abi: 64, pref: 128), // v128 + }, + + AggregateObjectAlignment = new LlvmIrDataLayoutAggregateObjectAlignment (abi: 0, pref: 32), + NativeIntegerWidths = new List { 32 }, + StackAlignment = 64, + }; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs new file mode 100644 index 00000000000..9aa35a63b51 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrModuleX64 : LlvmIrModule + { + public override LlvmIrDataLayout DataLayout { get; } + public override string TargetTriple => "x86_64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; + + public LlvmIrModuleX64 (string fileName) + : base (fileName) + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, + }, + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, + }, + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, + }, + }, + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, + + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 128), // f80 + }, + + NativeIntegerWidths = new List { 8, 16, 32, 64 }, + StackAlignment = 128, + }; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs new file mode 100644 index 00000000000..a28f721d348 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrModuleX86 : LlvmIrModule + { + public override LlvmIrDataLayout DataLayout { get; } + public override string TargetTriple => "i686-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; + + public LlvmIrModuleX86 (string fileName) + : base (fileName) + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, + }, + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, + }, + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, + }, + }, + + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 64, abi: 32, pref: 64), // f64 + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 32), // f80 + }, + + NativeIntegerWidths = new List { 8, 16, 32 }, + StackAlignment = 128, + }; + } + } +} From ef27fe5e4e9f05e0689c1f9de1e20aa9c709f758 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 23 May 2023 23:04:31 +0200 Subject: [PATCH 32/60] More steps towards new LLVM IR generator There will be a lot of code duplication for a while --- ...cationConfigNativeAssemblyGenerator.New.cs | 11 + ...pplicationConfigNativeAssemblyGenerator.cs | 2 +- ...edAssembliesNativeAssemblyGenerator.New.cs | 12 + ...ressedAssembliesNativeAssemblyGenerator.cs | 2 +- .../JniRemappingAssemblyGenerator.New.cs | 11 + .../JniRemappingAssemblyGenerator.cs | 2 +- .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 7 +- .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 7 +- .../LlvmIrGenerator/FunctionAttributes.cs | 214 +++++++++--------- .../LlvmIrGenerator/LlvmIrComposer.cs | 4 + .../LlvmIrGenerator/LlvmIrDataLayout.cs | 2 +- .../LlvmIrGenerator/LlvmIrFunction.cs | 188 +++++++++++++++ ...teSet.cs => LlvmIrFunctionAttributeSet.cs} | 30 +-- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 38 +++- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 86 +++++-- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 16 +- .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 16 +- .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 15 ++ .../LlvmIrGenerator/LlvmIrModuleX64.cs | 16 +- .../LlvmIrGenerator/LlvmIrModuleX86.cs | 16 +- .../LlvmIrGenerator/LlvmIrVariable.cs | 113 +++++++++ .../LlvmIrGenerator/X64LlvmIrGenerator.cs | 8 +- .../LlvmIrGenerator/X86LlvmIrGenerator.cs | 9 +- ...rshalMethodsNativeAssemblyGenerator.New.cs | 53 +++++ .../MarshalMethodsNativeAssemblyGenerator.cs | 2 +- ...MappingDebugNativeAssemblyGenerator.New.cs | 11 + ...TypeMappingDebugNativeAssemblyGenerator.cs | 2 +- ...ppingReleaseNativeAssemblyGenerator.New.cs | 11 + ...peMappingReleaseNativeAssemblyGenerator.cs | 2 +- 29 files changed, 720 insertions(+), 186 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs rename src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/{LlvmFunctionAttributeSet.cs => LlvmIrFunctionAttributeSet.cs} (56%) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs new file mode 100644 index 00000000000..cd7ee9e0a67 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -0,0 +1,11 @@ +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks +{ + partial class ApplicationConfigNativeAssemblyGenerator + { + protected override void Write (LlvmIrModule module) + { + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index c240e5aefa2..675e1b5abb9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -21,7 +21,7 @@ enum MonoComponent Tracing = 0x04, } - class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer + partial class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer { sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextDataProvider { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs new file mode 100644 index 00000000000..d4822c7b688 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Android.Tasks +{ + // TODO: remove once migration to LLVM.IR is done + using LlvmIrModule = Xamarin.Android.Tasks.LLVM.IR.LlvmIrModule; + + partial class CompressedAssembliesNativeAssemblyGenerator + { + protected override void Write (LlvmIrModule module) + { + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs index 39ce70a4d73..31e714e8625 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs @@ -5,7 +5,7 @@ namespace Xamarin.Android.Tasks { - class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer + partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer { const string DescriptorsArraySymbolName = "compressed_assembly_descriptors"; const string CompressedAssembliesSymbolName = "compressed_assemblies"; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs new file mode 100644 index 00000000000..4b8ec40bd34 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs @@ -0,0 +1,11 @@ +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks +{ + partial class JniRemappingAssemblyGenerator + { + protected override void Write (LlvmIrModule module) + { + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs index 486fd2f4c7e..1fefb8ced46 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs @@ -42,7 +42,7 @@ public JniRemappingMethodReplacement (string sourceType, string sourceMethod, st } } - class JniRemappingAssemblyGenerator : LlvmIrComposer + partial class JniRemappingAssemblyGenerator : LlvmIrComposer { sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs index 81ed76a17c5..0f21891c92e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs @@ -4,6 +4,11 @@ using Xamarin.Android.Tools; +using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; +using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; +using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; +using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; + namespace Xamarin.Android.Tasks.LLVMIR { class Arm32LlvmIrGenerator : LlvmIrGenerator @@ -16,7 +21,7 @@ class Arm32LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 4; protected override string Triple => "armv7-unknown-linux-android"; // NDK appends API level, we don't need that - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { new FramePointerFunctionAttribute ("all"), new TargetCpuFunctionAttribute ("generic"), new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+thumb-mode,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs index 3752b7eb5f4..1dfed4f0a1b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs @@ -4,6 +4,11 @@ using Xamarin.Android.Tools; +using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; +using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; +using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; +using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; + namespace Xamarin.Android.Tasks.LLVMIR { class Arm64LlvmIrGenerator : LlvmIrGenerator @@ -16,7 +21,7 @@ class Arm64LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 8; protected override string Triple => "aarch64-unknown-linux-android"; // NDK appends API level, we don't need that - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { new FramePointerFunctionAttribute ("non-leaf"), new TargetCpuFunctionAttribute ("generic"), new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics"), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index e9e1fc87231..ccde20dd4cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -2,11 +2,11 @@ using System.Text; using System.Globalization; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { // Not all attributes are currently used throughout the code, but we define them call for potential future use. // Documentation can be found here: https://llvm.org/docs/LangRef.html#function-attributes - abstract class LLVMFunctionAttribute : IComparable, IComparable, IEquatable + abstract class LlvmIrFunctionAttribute : IComparable, IComparable, IEquatable { public string Name { get; } public bool Quoted { get; } @@ -14,7 +14,7 @@ abstract class LLVMFunctionAttribute : IComparable, IComparable (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + public static bool operator > (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) { return a.CompareTo (b) > 0; } - public static bool operator < (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + public static bool operator < (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) { return a.CompareTo (b) < 0; } - public static bool operator >= (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + public static bool operator >= (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) { return a.CompareTo (b) >= 0; } - public static bool operator <= (LLVMFunctionAttribute a, LLVMFunctionAttribute b) + public static bool operator <= (LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) { return a.CompareTo (b) <= 0; } } - abstract class LLVMFlagFunctionAttribute : LLVMFunctionAttribute + abstract class LlvmIrFlagFunctionAttribute : LlvmIrFunctionAttribute { - protected LLVMFlagFunctionAttribute (string name, bool quoted = false) + protected LlvmIrFlagFunctionAttribute (string name, bool quoted = false) : base (name, quoted, supportsParams: false, optionalParams: false, hasValueAssignment: false) {} } - class AlignstackFunctionAttribute : LLVMFunctionAttribute + class AlignstackFunctionAttribute : LlvmIrFunctionAttribute { uint alignment; @@ -194,7 +194,7 @@ protected override void RenderParams (StringBuilder sb) sb.Append (alignment.ToString (CultureInfo.InvariantCulture)); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -214,7 +214,7 @@ public override int GetHashCode () } } - class AllocFamilyFunctionAttribute : LLVMFunctionAttribute + class AllocFamilyFunctionAttribute : LlvmIrFunctionAttribute { string family; @@ -229,7 +229,7 @@ protected override void RenderAssignedValue (StringBuilder sb) sb.Append (family); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -249,7 +249,7 @@ public override int GetHashCode () } } - class AllockindFunctionAttribute : LLVMFunctionAttribute + class AllockindFunctionAttribute : LlvmIrFunctionAttribute { string kind; @@ -266,7 +266,7 @@ protected override void RenderParams (StringBuilder sb) sb.Append ('"'); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -286,7 +286,7 @@ public override int GetHashCode () } } - class AllocsizeFunctionAttribute : LLVMFunctionAttribute + class AllocsizeFunctionAttribute : LlvmIrFunctionAttribute { uint elementSize; uint? numberOfElements; @@ -309,7 +309,7 @@ protected override void RenderParams (StringBuilder sb) sb.Append (numberOfElements.Value.ToString (CultureInfo.InvariantCulture)); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -329,63 +329,63 @@ public override int GetHashCode () } } - class AlwaysinlineFunctionAttribute : LLVMFlagFunctionAttribute + class AlwaysinlineFunctionAttribute : LlvmIrFlagFunctionAttribute { public AlwaysinlineFunctionAttribute () : base ("alwaysinline") {} } - class ArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + class ArgmemonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public ArgmemonlyFunctionAttribute () : base ("argmemonly") {} } - class BuiltinFunctionAttribute : LLVMFlagFunctionAttribute + class BuiltinFunctionAttribute : LlvmIrFlagFunctionAttribute { public BuiltinFunctionAttribute () : base ("builtin") {} } - class ColdFunctionAttribute : LLVMFlagFunctionAttribute + class ColdFunctionAttribute : LlvmIrFlagFunctionAttribute { public ColdFunctionAttribute () : base ("cold") {} } - class ConvergentFunctionAttribute : LLVMFlagFunctionAttribute + class ConvergentFunctionAttribute : LlvmIrFlagFunctionAttribute { public ConvergentFunctionAttribute () : base ("convergent") {} } - class DisableSanitizerInstrumentationFunctionAttribute : LLVMFlagFunctionAttribute + class DisableSanitizerInstrumentationFunctionAttribute : LlvmIrFlagFunctionAttribute { public DisableSanitizerInstrumentationFunctionAttribute () : base ("disable_sanitizer_instrumentation") {} } - class DontcallErrorFunctionAttribute : LLVMFlagFunctionAttribute + class DontcallErrorFunctionAttribute : LlvmIrFlagFunctionAttribute { public DontcallErrorFunctionAttribute () : base ("dontcall-error", quoted: true) {} } - class DontcallWarnFunctionAttribute : LLVMFlagFunctionAttribute + class DontcallWarnFunctionAttribute : LlvmIrFlagFunctionAttribute { public DontcallWarnFunctionAttribute () : base ("dontcall-warn", quoted: true) {} } - class FramePointerFunctionAttribute : LLVMFunctionAttribute + class FramePointerFunctionAttribute : LlvmIrFunctionAttribute { string fpMode; @@ -406,7 +406,7 @@ public FramePointerFunctionAttribute (string fpMode = "none") protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (fpMode); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -426,385 +426,385 @@ public override int GetHashCode () } } - class HotFunctionAttribute : LLVMFlagFunctionAttribute + class HotFunctionAttribute : LlvmIrFlagFunctionAttribute { public HotFunctionAttribute () : base ("hot") {} } - class InaccessiblememonlyFunctionAttribute : LLVMFlagFunctionAttribute + class InaccessiblememonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public InaccessiblememonlyFunctionAttribute () : base ("inaccessiblememonly") {} } - class InaccessiblememOrArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + class InaccessiblememOrArgmemonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public InaccessiblememOrArgmemonlyFunctionAttribute () : base ("inaccessiblemem_or_argmemonly") {} } - class InlinehintFunctionAttribute : LLVMFlagFunctionAttribute + class InlinehintFunctionAttribute : LlvmIrFlagFunctionAttribute { public InlinehintFunctionAttribute () : base ("inlinehint") {} } - class JumptableFunctionAttribute : LLVMFlagFunctionAttribute + class JumptableFunctionAttribute : LlvmIrFlagFunctionAttribute { public JumptableFunctionAttribute () : base ("jumptable") {} } - class MinsizeFunctionAttribute : LLVMFlagFunctionAttribute + class MinsizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public MinsizeFunctionAttribute () : base ("minsize") {} } - class NakedFunctionAttribute : LLVMFlagFunctionAttribute + class NakedFunctionAttribute : LlvmIrFlagFunctionAttribute { public NakedFunctionAttribute () : base ("naked") {} } - class NoInlineLineTablesFunctionAttribute : LLVMFlagFunctionAttribute + class NoInlineLineTablesFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoInlineLineTablesFunctionAttribute () : base ("no-inline-line-tables", quoted: true) {} } - class NoJumpTablesFunctionAttribute : LLVMFlagFunctionAttribute + class NoJumpTablesFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoJumpTablesFunctionAttribute () : base ("no-jump-tables") {} } - class NobuiltinFunctionAttribute : LLVMFlagFunctionAttribute + class NobuiltinFunctionAttribute : LlvmIrFlagFunctionAttribute { public NobuiltinFunctionAttribute () : base ("nobuiltin") {} } - class NoduplicateFunctionAttribute : LLVMFlagFunctionAttribute + class NoduplicateFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoduplicateFunctionAttribute () : base ("noduplicate") {} } - class NofreeFunctionAttribute : LLVMFlagFunctionAttribute + class NofreeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NofreeFunctionAttribute () : base ("nofree") {} } - class NoimplicitfloatFunctionAttribute : LLVMFlagFunctionAttribute + class NoimplicitfloatFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoimplicitfloatFunctionAttribute () : base ("noimplicitfloat") {} } - class NoinlineFunctionAttribute : LLVMFlagFunctionAttribute + class NoinlineFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoinlineFunctionAttribute () : base ("noinline") {} } - class NomergeFunctionAttribute : LLVMFlagFunctionAttribute + class NomergeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NomergeFunctionAttribute () : base ("nomerge") {} } - class NonlazybindFunctionAttribute : LLVMFlagFunctionAttribute + class NonlazybindFunctionAttribute : LlvmIrFlagFunctionAttribute { public NonlazybindFunctionAttribute () : base ("nonlazybind") {} } - class NoprofileFunctionAttribute : LLVMFlagFunctionAttribute + class NoprofileFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoprofileFunctionAttribute () : base ("noprofile") {} } - class NoredzoneFunctionAttribute : LLVMFlagFunctionAttribute + class NoredzoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoredzoneFunctionAttribute () : base ("noredzone") {} } - class IndirectTlsSegRefsFunctionAttribute : LLVMFlagFunctionAttribute + class IndirectTlsSegRefsFunctionAttribute : LlvmIrFlagFunctionAttribute { public IndirectTlsSegRefsFunctionAttribute () : base ("indirect-tls-seg-refs") {} } - class NoreturnFunctionAttribute : LLVMFlagFunctionAttribute + class NoreturnFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoreturnFunctionAttribute () : base ("noreturn") {} } - class NorecurseFunctionAttribute : LLVMFlagFunctionAttribute + class NorecurseFunctionAttribute : LlvmIrFlagFunctionAttribute { public NorecurseFunctionAttribute () : base ("norecurse") {} } - class WillreturnFunctionAttribute : LLVMFlagFunctionAttribute + class WillreturnFunctionAttribute : LlvmIrFlagFunctionAttribute { public WillreturnFunctionAttribute () : base ("willreturn") {} } - class NosyncFunctionAttribute : LLVMFlagFunctionAttribute + class NosyncFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosyncFunctionAttribute () : base ("nosync") {} } - class NounwindFunctionAttribute : LLVMFlagFunctionAttribute + class NounwindFunctionAttribute : LlvmIrFlagFunctionAttribute { public NounwindFunctionAttribute () : base ("nounwind") {} } - class NosanitizeBoundsFunctionAttribute : LLVMFlagFunctionAttribute + class NosanitizeBoundsFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosanitizeBoundsFunctionAttribute () : base ("nosanitize_bounds") {} } - class NosanitizeCoverageFunctionAttribute : LLVMFlagFunctionAttribute + class NosanitizeCoverageFunctionAttribute : LlvmIrFlagFunctionAttribute { public NosanitizeCoverageFunctionAttribute () : base ("nosanitize_coverage") {} } - class NullPointerIsValidFunctionAttribute : LLVMFlagFunctionAttribute + class NullPointerIsValidFunctionAttribute : LlvmIrFlagFunctionAttribute { public NullPointerIsValidFunctionAttribute () : base ("null_pointer_is_valid") {} } - class OptforfuzzingFunctionAttribute : LLVMFlagFunctionAttribute + class OptforfuzzingFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptforfuzzingFunctionAttribute () : base ("optforfuzzing") {} } - class OptnoneFunctionAttribute : LLVMFlagFunctionAttribute + class OptnoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptnoneFunctionAttribute () : base ("optnone") {} } - class OptsizeFunctionAttribute : LLVMFlagFunctionAttribute + class OptsizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public OptsizeFunctionAttribute () : base ("optsize") {} } - class PatchableFunctionFunctionAttribute : LLVMFlagFunctionAttribute + class PatchableFunctionFunctionAttribute : LlvmIrFlagFunctionAttribute { public PatchableFunctionFunctionAttribute () : base ("patchable-function", quoted: true) {} } - class ProbeStackFunctionAttribute : LLVMFlagFunctionAttribute + class ProbeStackFunctionAttribute : LlvmIrFlagFunctionAttribute { public ProbeStackFunctionAttribute () : base ("probe-stack") {} } - class ReadnoneFunctionAttribute : LLVMFlagFunctionAttribute + class ReadnoneFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReadnoneFunctionAttribute () : base ("readnone") {} } - class ReadonlyFunctionAttribute : LLVMFlagFunctionAttribute + class ReadonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReadonlyFunctionAttribute () : base ("readonly") {} } - class StackProbeSizeFunctionAttribute : LLVMFlagFunctionAttribute + class StackProbeSizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public StackProbeSizeFunctionAttribute () : base ("stack-probe-size", quoted: true) {} } - class NoStackArgProbeFunctionAttribute : LLVMFlagFunctionAttribute + class NoStackArgProbeFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoStackArgProbeFunctionAttribute () : base ("no-stack-arg-probe") {} } - class WriteonlyFunctionAttribute : LLVMFlagFunctionAttribute + class WriteonlyFunctionAttribute : LlvmIrFlagFunctionAttribute { public WriteonlyFunctionAttribute () : base ("writeonly") {} } - class ReturnsTwiceFunctionAttribute : LLVMFlagFunctionAttribute + class ReturnsTwiceFunctionAttribute : LlvmIrFlagFunctionAttribute { public ReturnsTwiceFunctionAttribute () : base ("returns_twice") {} } - class SafestackFunctionAttribute : LLVMFlagFunctionAttribute + class SafestackFunctionAttribute : LlvmIrFlagFunctionAttribute { public SafestackFunctionAttribute () : base ("safestack") {} } - class SanitizeAddressFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeAddressFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeAddressFunctionAttribute () : base ("sanitize_address") {} } - class SanitizeMemoryFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeMemoryFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeMemoryFunctionAttribute () : base ("sanitize_memory") {} } - class SanitizeThreadFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeThreadFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeThreadFunctionAttribute () : base ("sanitize_thread") {} } - class SanitizeHwaddressFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeHwaddressFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeHwaddressFunctionAttribute () : base ("sanitize_hwaddress") {} } - class SanitizeMemtagFunctionAttribute : LLVMFlagFunctionAttribute + class SanitizeMemtagFunctionAttribute : LlvmIrFlagFunctionAttribute { public SanitizeMemtagFunctionAttribute () : base ("sanitize_memtag") {} } - class SpeculativeLoadHardeningFunctionAttribute : LLVMFlagFunctionAttribute + class SpeculativeLoadHardeningFunctionAttribute : LlvmIrFlagFunctionAttribute { public SpeculativeLoadHardeningFunctionAttribute () : base ("speculative_load_hardening") {} } - class SpeculatableFunctionAttribute : LLVMFlagFunctionAttribute + class SpeculatableFunctionAttribute : LlvmIrFlagFunctionAttribute { public SpeculatableFunctionAttribute () : base ("speculatable") {} } - class SspFunctionAttribute : LLVMFlagFunctionAttribute + class SspFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspFunctionAttribute () : base ("ssp") {} } - class SspstrongFunctionAttribute : LLVMFlagFunctionAttribute + class SspstrongFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspstrongFunctionAttribute () : base ("sspstrong") {} } - class SspreqFunctionAttribute : LLVMFlagFunctionAttribute + class SspreqFunctionAttribute : LlvmIrFlagFunctionAttribute { public SspreqFunctionAttribute () : base ("sspreq") {} } - class StrictfpFunctionAttribute : LLVMFlagFunctionAttribute + class StrictfpFunctionAttribute : LlvmIrFlagFunctionAttribute { public StrictfpFunctionAttribute () : base ("strictfp") {} } - class DenormalFpMathFunctionAttribute : LLVMFlagFunctionAttribute + class DenormalFpMathFunctionAttribute : LlvmIrFlagFunctionAttribute { public DenormalFpMathFunctionAttribute () : base ("denormal-fp-math", quoted: true) {} } - class DenormalFpMathF32FunctionAttribute : LLVMFlagFunctionAttribute + class DenormalFpMathF32FunctionAttribute : LlvmIrFlagFunctionAttribute { public DenormalFpMathF32FunctionAttribute () : base ("denormal-fp-math-f32", quoted: true) {} } - class ThunkFunctionAttribute : LLVMFlagFunctionAttribute + class ThunkFunctionAttribute : LlvmIrFlagFunctionAttribute { public ThunkFunctionAttribute () : base ("thunk", quoted: true) {} } - class TlsLoadHoistFunctionAttribute : LLVMFlagFunctionAttribute + class TlsLoadHoistFunctionAttribute : LlvmIrFlagFunctionAttribute { public TlsLoadHoistFunctionAttribute () : base ("tls-load-hoist") {} } - class UwtableFunctionAttribute : LLVMFunctionAttribute + class UwtableFunctionAttribute : LlvmIrFunctionAttribute { bool? isSync; @@ -825,7 +825,7 @@ protected override void RenderParams (StringBuilder sb) sb.Append (isSync.Value ? "sync" : "async"); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -845,28 +845,28 @@ public override int GetHashCode () } } - class NocfCheckFunctionAttribute : LLVMFlagFunctionAttribute + class NocfCheckFunctionAttribute : LlvmIrFlagFunctionAttribute { public NocfCheckFunctionAttribute () : base ("nocf_check") {} } - class ShadowcallstackFunctionAttribute : LLVMFlagFunctionAttribute + class ShadowcallstackFunctionAttribute : LlvmIrFlagFunctionAttribute { public ShadowcallstackFunctionAttribute () : base ("shadowcallstack") {} } - class MustprogressFunctionAttribute : LLVMFlagFunctionAttribute + class MustprogressFunctionAttribute : LlvmIrFlagFunctionAttribute { public MustprogressFunctionAttribute () : base ("mustprogress") {} } - class WarnStackSizeFunctionAttribute : LLVMFunctionAttribute + class WarnStackSizeFunctionAttribute : LlvmIrFunctionAttribute { uint threshold; @@ -878,7 +878,7 @@ public WarnStackSizeFunctionAttribute (uint threshold) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (threshold); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -898,7 +898,7 @@ public override int GetHashCode () } } - class VscaleRangeFunctionAttribute : LLVMFunctionAttribute + class VscaleRangeFunctionAttribute : LlvmIrFunctionAttribute { uint min; uint? max; @@ -921,7 +921,7 @@ protected override void RenderParams (StringBuilder sb) sb.Append (max.Value.ToString (CultureInfo.InvariantCulture)); } - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -941,7 +941,7 @@ public override int GetHashCode () } } - class MinLegalVectorWidthFunctionAttribute : LLVMFunctionAttribute + class MinLegalVectorWidthFunctionAttribute : LlvmIrFunctionAttribute { uint size; @@ -953,7 +953,7 @@ public MinLegalVectorWidthFunctionAttribute (uint size) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -973,7 +973,7 @@ public override int GetHashCode () } } - class StackProtectorBufferSizeFunctionAttribute : LLVMFunctionAttribute + class StackProtectorBufferSizeFunctionAttribute : LlvmIrFunctionAttribute { uint size; @@ -985,7 +985,7 @@ public StackProtectorBufferSizeFunctionAttribute (uint size) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size.ToString (CultureInfo.InvariantCulture)); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -1005,7 +1005,7 @@ public override int GetHashCode () } } - class TargetCpuFunctionAttribute : LLVMFunctionAttribute + class TargetCpuFunctionAttribute : LlvmIrFunctionAttribute { string cpu; @@ -1017,7 +1017,7 @@ public TargetCpuFunctionAttribute (string cpu) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -1037,7 +1037,7 @@ public override int GetHashCode () } } - class TuneCpuFunctionAttribute : LLVMFunctionAttribute + class TuneCpuFunctionAttribute : LlvmIrFunctionAttribute { string cpu; @@ -1049,7 +1049,7 @@ public TuneCpuFunctionAttribute (string cpu) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -1069,7 +1069,7 @@ public override int GetHashCode () } } - class TargetFeaturesFunctionAttribute : LLVMFunctionAttribute + class TargetFeaturesFunctionAttribute : LlvmIrFunctionAttribute { string features; @@ -1081,7 +1081,7 @@ public TargetFeaturesFunctionAttribute (string features) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (features); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -1101,7 +1101,7 @@ public override int GetHashCode () } } - class NoTrappingMathFunctionAttribute : LLVMFunctionAttribute + class NoTrappingMathFunctionAttribute : LlvmIrFunctionAttribute { bool yesno; @@ -1113,7 +1113,7 @@ public NoTrappingMathFunctionAttribute (bool yesno) protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (yesno.ToString ().ToLowerInvariant ()); - public override bool Equals (LLVMFunctionAttribute other) + public override bool Equals (LlvmIrFunctionAttribute other) { if (!base.Equals (other)) { return false; @@ -1133,7 +1133,7 @@ public override int GetHashCode () } } - class StackrealignFunctionAttribute : LLVMFlagFunctionAttribute + class StackrealignFunctionAttribute : LlvmIrFlagFunctionAttribute { public StackrealignFunctionAttribute () : base ("stackrealign", quoted: true) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index e164b344a26..0be1cca9857 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -5,6 +5,8 @@ using Xamarin.Android.Tools; +using LlvmIrModule = Xamarin.Android.Tasks.LLVM.IR.LlvmIrModule; + namespace Xamarin.Android.Tasks.LLVMIR { /// @@ -31,6 +33,7 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) generator.WriteFileTop (); generator.WriteStructureDeclarations (); Write (generator); + Write (module); generator.WriteFunctionDeclarations (); generator.WriteFileEnd (); @@ -85,5 +88,6 @@ protected virtual void InitGenerator (LlvmIrGenerator generator) /// native pointer size). /// protected abstract void Write (LlvmIrGenerator generator); + protected abstract void Write (LlvmIrModule module); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs index d4107e5073a..f62d23332e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs @@ -3,7 +3,7 @@ using System.Globalization; using System.Text; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { abstract class LlvmIrDataLayoutField { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index c5f95fb3748..e65e2e52bb8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -4,6 +4,194 @@ using System.Linq; using System.Text; +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = Xamarin.Android.Tasks.LLVMIR.LlvmIrAddressSignificance; + + class LlvmIrFunctionParameter : LlvmIrLocalVariable + { + // To save on time, we declare only attributes that are actually used in our generated code. More will be added, as needed. + + /// + /// align(n) attribute, see + /// + public uint? Align { get; set; } + + /// + /// allocptr attribute, see + /// + public bool AllocPtr { get; set; } + + /// + /// dereferenceable(n) attribute, see + /// + public uint? Dereferenceable { get; set; } + + /// + /// immarg attribute, see + /// + public bool ImmArg { get; set; } + + /// + /// nocapture attribute, see + /// + public bool NoCapture { get; set; } + + /// + /// nonnull attribute, see + /// + public bool NonNull { get; set; } + + /// + /// noundef attribute, see + /// + public bool NoUndef { get; set; } + + /// + /// noundef attribute, see + /// + public bool ReadNone { get; set; } + + /// + /// zeroext attribute, see + /// + public bool ZeroExt { get; set; } + + public LlvmIrFunctionParameter (Type type, string? name = null) + : base (type, name) + { + NameMatters = false; + } + } + + class LlvmIrFunctionSignature : IEquatable + { + public string Name { get; } + public Type ReturnType { get; } + public IList Parameters { get; } + public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } + + public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null, LlvmIrFunctionAttributeSet? attributeSet = null) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Name = name; + ReturnType = returnType; + Parameters = parameters ?? new List (); + AttributeSet = attributeSet; + } + + /// + /// Create new signature using data from the one, with the exception of name. + /// Useful when there are several functions with different names but identical parameters and return types. + /// + public LlvmIrFunctionSignature (string name, LlvmIrFunctionSignature templateSignature) + : this (name, templateSignature.ReturnType, templateSignature.Parameters, templateSignature.AttributeSet) + {} + + public override int GetHashCode () + { + int hc = + Name.GetHashCode () ^ + Parameters.GetHashCode () ^ + ReturnType.GetHashCode () ^ + (AttributeSet?.GetHashCode () ?? 0); + + foreach (LlvmIrFunctionParameter p in Parameters) { + hc ^= p.GetHashCode (); + } + + return hc; + } + + public override bool Equals (object obj) + { + var sig = obj as LlvmIrFunctionSignature; + if (sig == null) { + return false; + } + + return Equals (sig); + } + + public bool Equals (LlvmIrFunctionSignature other) + { + if (other == null) { + return false; + } + + if (Parameters.Count != other.Parameters.Count || + ReturnType != other.ReturnType || + String.Compare (Name, other.Name, StringComparison.Ordinal) != 0 + ) { + return false; + } + + for (int i = 0; i < Parameters.Count; i++) { + if (Parameters[i] != other.Parameters[i]) { + return false; + } + } + + return true; + } + } + + /// + /// Describes a native function to be emitted or declared and keeps code emitting state between calls to various generator. + /// methods. + /// + class LlvmIrFunction : IEquatable + { + public LlvmIrFunctionSignature Signature { get; } + public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.LocalUnnamed; + + public LlvmIrFunction (LlvmIrFunctionSignature signature) + { + Signature = signature; + } + + /// + /// Create new function using data from the signature, with the exception of name. + /// Useful when there are several functions with different names but identical parameters and return types. + /// + public LlvmIrFunction (string name, LlvmIrFunctionSignature templateSignature) + : this (new LlvmIrFunctionSignature (name, templateSignature)) + {} + + public LlvmIrFunction (string name, Type returnType, List? parameters = null, LlvmIrFunctionAttributeSet? attributeSet = null) + : this (new LlvmIrFunctionSignature (name, returnType, parameters, attributeSet)) + {} + + public override int GetHashCode () + { + return Signature.GetHashCode (); + } + + public override bool Equals (object obj) + { + var func = obj as LlvmIrFunction; + if (func == null) { + return false; + } + + return Equals (func); + } + + public bool Equals (LlvmIrFunction other) + { + if (other == null) { + return false; + } + + return Signature == other.Signature; + } + } +} + namespace Xamarin.Android.Tasks.LLVMIR { class LlvmIrFunctionLocalVariable : LlvmIrVariable diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs similarity index 56% rename from src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs rename to src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index b416e7c5cee..8d4f72ab42f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -3,27 +3,27 @@ using System.Collections.Generic; using System.Linq; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmFunctionAttributeSet : IEnumerable, IEquatable + class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable { static readonly object counterLock = new object (); static uint counter = 0; public uint Number { get; } - HashSet attributes; + HashSet attributes; - public LlvmFunctionAttributeSet () + public LlvmIrFunctionAttributeSet () { - attributes = new HashSet (); + attributes = new HashSet (); lock (counterLock) { Number = counter++; } } - public void Add (LLVMFunctionAttribute attr) + public void Add (LlvmIrFunctionAttribute attr) { if (attr == null) { throw new ArgumentNullException (nameof (attr)); @@ -34,30 +34,30 @@ public void Add (LLVMFunctionAttribute attr) } } - public void Add (LlvmFunctionAttributeSet sourceSet) + public void Add (LlvmIrFunctionAttributeSet sourceSet) { if (sourceSet == null) { throw new ArgumentNullException (nameof (sourceSet)); } - foreach (LLVMFunctionAttribute attr in sourceSet) { + foreach (LlvmIrFunctionAttribute attr in sourceSet) { Add (attr); } } public string Render () { - List list = attributes.ToList (); - list.Sort ((LLVMFunctionAttribute a, LLVMFunctionAttribute b) => a.Name.CompareTo (b.Name)); + List list = attributes.ToList (); + list.Sort ((LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) => a.Name.CompareTo (b.Name)); return String.Join (" ", list.Select (a => a.Render ())); } - public IEnumerator GetEnumerator () => attributes.GetEnumerator (); + public IEnumerator GetEnumerator () => attributes.GetEnumerator (); IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - public bool Equals (LlvmFunctionAttributeSet other) + public bool Equals (LlvmIrFunctionAttributeSet other) { if (other == null) { return false; @@ -67,7 +67,7 @@ public bool Equals (LlvmFunctionAttributeSet other) return false; } - foreach (LLVMFunctionAttribute attr in attributes) { + foreach (LlvmIrFunctionAttribute attr in attributes) { if (!other.attributes.Contains (attr)) { return false; } @@ -78,7 +78,7 @@ public bool Equals (LlvmFunctionAttributeSet other) public override bool Equals (object obj) { - var attrSet = obj as LlvmFunctionAttributeSet; + var attrSet = obj as LlvmIrFunctionAttributeSet; if (attrSet == null) { return false; } @@ -90,7 +90,7 @@ public override int GetHashCode() { int hc = 0; - foreach (LLVMFunctionAttribute attr in attributes) { + foreach (LlvmIrFunctionAttribute attr in attributes) { hc ^= attr?.GetHashCode () ?? 0; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 8110a804489..8c6e09ad3bd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -4,6 +4,22 @@ using System.IO; using System.Text; +using LlvmIrFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttribute; +using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; +using ArgmemonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.ArgmemonlyFunctionAttribute; +using InaccessiblememOrArgmemonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.InaccessiblememOrArgmemonlyFunctionAttribute; +using MinLegalVectorWidthFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.MinLegalVectorWidthFunctionAttribute; +using MustprogressFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.MustprogressFunctionAttribute; +using NoTrappingMathFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NoTrappingMathFunctionAttribute; +using NofreeFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NofreeFunctionAttribute; +using NorecurseFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NorecurseFunctionAttribute; +using NosyncFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NosyncFunctionAttribute; +using NounwindFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NounwindFunctionAttribute; +using SspstrongFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.SspstrongFunctionAttribute; +using StackProtectorBufferSizeFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.StackProtectorBufferSizeFunctionAttribute; +using WillreturnFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.WillreturnFunctionAttribute; +using WriteonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.WriteonlyFunctionAttribute; + namespace Xamarin.Android.Tasks.LLVMIR { abstract partial class LlvmIrGenerator @@ -22,7 +38,7 @@ abstract partial class LlvmIrGenerator public const int FunctionAttributesLlvmLifetime = 3; public const int FunctionAttributesLibcFree = 4; - protected readonly Dictionary FunctionAttributes = new Dictionary (); + protected readonly Dictionary FunctionAttributes = new Dictionary (); bool codeOutputInitialized = false; List? externalFunctions = null; @@ -55,7 +71,7 @@ public void AddExternalFunction (LlvmIrFunction func) externalFunctions.Add (func); } - void ValidateFunction (LlvmIrFunction function, out LlvmFunctionAttributeSet? attributes) + void ValidateFunction (LlvmIrFunction function, out LlvmIrFunctionAttributeSet? attributes) { if (function == null) { throw new ArgumentNullException (nameof (function)); @@ -67,7 +83,7 @@ void ValidateFunction (LlvmIrFunction function, out LlvmFunctionAttributeSet? at } } - void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmFunctionAttributeSet? attributes, string? comment, bool writeParameterNames = true) + void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmIrFunctionAttributeSet? attributes, string? comment, bool writeParameterNames = true) { if (!String.IsNullOrEmpty (comment)) { foreach (string line in comment.Split ('\n')) { @@ -92,7 +108,7 @@ void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeywor /// public void WriteFunctionForwardDeclaration (LlvmIrFunction function, string? comment = null) { - ValidateFunction (function, out LlvmFunctionAttributeSet? attributes); + ValidateFunction (function, out LlvmIrFunctionAttributeSet? attributes); Output.WriteLine (); WriteFunctionSignature (function, "declare", attributes, comment, writeParameterNames: false); @@ -104,7 +120,7 @@ public void WriteFunctionForwardDeclaration (LlvmIrFunction function, string? co /// public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) { - ValidateFunction (function, out LlvmFunctionAttributeSet? attributes); + ValidateFunction (function, out LlvmIrFunctionAttributeSet? attributes); Output.WriteLine (); WriteFunctionSignature (function, "define", attributes, comment); Output.WriteLine (); @@ -835,7 +851,7 @@ protected virtual void InitCodeMetadata () protected virtual void InitFunctionAttributes () { - FunctionAttributes[FunctionAttributesXamarinAppInit] = new LlvmFunctionAttributeSet { + FunctionAttributes[FunctionAttributesXamarinAppInit] = new LlvmIrFunctionAttributeSet { new MinLegalVectorWidthFunctionAttribute (0), new MustprogressFunctionAttribute (), new NofreeFunctionAttribute (), @@ -850,7 +866,7 @@ protected virtual void InitFunctionAttributes () new WriteonlyFunctionAttribute (), }; - FunctionAttributes[FunctionAttributesJniMethods] = new LlvmFunctionAttributeSet { + FunctionAttributes[FunctionAttributesJniMethods] = new LlvmIrFunctionAttributeSet { new MinLegalVectorWidthFunctionAttribute (0), new MustprogressFunctionAttribute (), new NoTrappingMathFunctionAttribute (true), @@ -860,11 +876,11 @@ protected virtual void InitFunctionAttributes () // new UwtableFunctionAttribute (), }; - FunctionAttributes[FunctionAttributesCall] = new LlvmFunctionAttributeSet { + FunctionAttributes[FunctionAttributesCall] = new LlvmIrFunctionAttributeSet { new NounwindFunctionAttribute (), }; - FunctionAttributes[FunctionAttributesLlvmLifetime] = new LlvmFunctionAttributeSet { + FunctionAttributes[FunctionAttributesLlvmLifetime] = new LlvmIrFunctionAttributeSet { new ArgmemonlyFunctionAttribute (), new MustprogressFunctionAttribute (), new NofreeFunctionAttribute (), @@ -873,7 +889,7 @@ protected virtual void InitFunctionAttributes () new WillreturnFunctionAttribute (), }; - FunctionAttributes[FunctionAttributesLibcFree] = new LlvmFunctionAttributeSet { + FunctionAttributes[FunctionAttributesLibcFree] = new LlvmIrFunctionAttributeSet { new InaccessiblememOrArgmemonlyFunctionAttribute (), new MustprogressFunctionAttribute (), new NounwindFunctionAttribute (), @@ -900,7 +916,7 @@ void WriteAttributeSets () void WriteSet (int id, TextWriter output) { output.Write ($"attributes #{id.ToString (CultureInfo.InvariantCulture)} = {{ "); - foreach (LLVMFunctionAttribute attr in FunctionAttributes[id]) { + foreach (LlvmIrFunctionAttribute attr in FunctionAttributes[id]) { output.Write (attr.Render ()); output.Write (' '); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 57f2d30bc0d..ff2a2ab42a3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -1,42 +1,77 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; -using System.Text; using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - abstract class LlvmIrModule + class LlvmIrModule { - public abstract LlvmIrDataLayout DataLayout { get; } - public abstract string TargetTriple { get; } - public abstract AndroidTargetArch TargetArch { get; } + public string FilePath { get; } + public string FileName { get; } + public LlvmIrModuleTarget Target { get; } - public string FilePath { get; } - public string FileName { get; } + Dictionary? attributeSets; + Dictionary? externalFunctions; - HashSet? attributeSets; - - protected LlvmIrModule (string filePath) + protected LlvmIrModule (string filePath, LlvmIrModuleTarget target) { FilePath = Path.GetFullPath (filePath); FileName = Path.GetFileName (filePath); + Target = target; } public static LlvmIrModule Create (AndroidTargetArch arch, StreamWriter output, string fileName) { return arch switch { - AndroidTargetArch.Arm => new LlvmIrModuleArmV7a (fileName), - AndroidTargetArch.Arm64 => new LlvmIrModuleAArch64 (fileName), - AndroidTargetArch.X86 => new LlvmIrModuleX86 (fileName), - AndroidTargetArch.X86_64 => new LlvmIrModuleX64 (fileName), + AndroidTargetArch.Arm => new LlvmIrModule (fileName, new LlvmIrModuleArmV7a ()), + AndroidTargetArch.Arm64 => new LlvmIrModule (fileName, new LlvmIrModuleAArch64 ()), + AndroidTargetArch.X86 => new LlvmIrModule (fileName, new LlvmIrModuleX86 ()), + AndroidTargetArch.X86_64 => new LlvmIrModule (fileName, new LlvmIrModuleX64 ()), _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") }; } + /// + /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed + /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to + /// the new one. + /// + public LlvmIrFunctionAttributeSet AddAttributeSet (LlvmIrFunctionAttributeSet attrSet) + { + if (attributeSets == null) { + attributeSets = new Dictionary (); + } + + if (attributeSets.TryGetValue (attrSet, out LlvmIrFunctionAttributeSet existingSet)) { + return existingSet; + } + attributeSets.Add (attrSet, attrSet); + + return attrSet; + } + + /// + /// Add a new external function declaration. The caller MUST use the returned value to refer to the function, instead + /// of the one passed as parameter, since this function de-duplicates function declarations and may return a previously + /// added one that's identical to the new one. + /// + public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) + { + if (externalFunctions == null) { + externalFunctions = new Dictionary (); + } + + if (externalFunctions.TryGetValue (func, out LlvmIrFunction existingFunc)) { + return existingFunc; + } + externalFunctions.Add (func, func); + + return func; + } + public void Generate (TextWriter writer) { if (!String.IsNullOrEmpty (FilePath)) { @@ -44,24 +79,31 @@ public void Generate (TextWriter writer) writer.WriteLine ($"source_filename = \"{FileName}\""); } - writer.WriteLine (DataLayout.Render ()); - writer.WriteLine ($"target triple = \"{TargetTriple}\""); + writer.WriteLine (Target.DataLayout.Render ()); + writer.WriteLine ($"target triple = \"{Target.Triple}\""); writer.WriteLine (); - + // Bottom of file WriteAttributeSets (writer); } + void WriteExternalFunctionDeclarations (TextWriter writer) + { + if (externalFunctions == null || externalFunctions.Count == 0) { + return; + } + } + void WriteAttributeSets (TextWriter writer) { if (attributeSets == null || attributeSets.Count == 0) { return; } - List list = attributeSets.ToList (); - list.Sort ((LlvmFunctionAttributeSet a, LlvmFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); + List list = attributeSets.Keys.ToList (); + list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); - foreach (LlvmFunctionAttributeSet attrSet in attributeSets) { + foreach (LlvmIrFunctionAttributeSet attrSet in list) { writer.WriteLine ($"attributes #{attrSet.Number} {{ {attrSet.Render ()} }}"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index 86072d0f35c..3ce80520576 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -1,17 +1,18 @@ +using System; using System.Collections.Generic; using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmIrModuleAArch64 : LlvmIrModule + class LlvmIrModuleAArch64 : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } - public override string TargetTriple => "aarch64-unknown-linux-android21"; + public override string Triple => "aarch64-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; + public override uint NativePointerSize => 8; - public LlvmIrModuleAArch64 (string fileName) - : base (fileName) + public LlvmIrModuleAArch64 () { // // As per Android NDK: @@ -32,5 +33,10 @@ public LlvmIrModuleAArch64 (string fileName) StackAlignment = 128, }; } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + throw new NotImplementedException (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index f2a1956ff3a..594e8cc12aa 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -1,17 +1,18 @@ +using System; using System.Collections.Generic; using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmIrModuleArmV7a : LlvmIrModule + class LlvmIrModuleArmV7a : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } - public override string TargetTriple => "armv7-unknown-linux-android21"; + public override string Triple => "armv7-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; + public override uint NativePointerSize => 4; - public LlvmIrModuleArmV7a (string fileName) - : base (fileName) + public LlvmIrModuleArmV7a () { // // As per Android NDK: @@ -40,5 +41,10 @@ public LlvmIrModuleArmV7a (string fileName) StackAlignment = 64, }; } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + throw new NotImplementedException (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs new file mode 100644 index 00000000000..7d4a760382d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -0,0 +1,15 @@ +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + abstract class LlvmIrModuleTarget + { + public abstract LlvmIrDataLayout DataLayout { get; } + public abstract string Triple { get; } + public abstract AndroidTargetArch TargetArch { get; } + public abstract uint NativePointerSize { get; } + + public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + {} + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index 9aa35a63b51..11f3a4a78f5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -1,17 +1,18 @@ +using System; using System.Collections.Generic; using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmIrModuleX64 : LlvmIrModule + class LlvmIrModuleX64 : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } - public override string TargetTriple => "x86_64-unknown-linux-android21"; + public override string Triple => "x86_64-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; + public override uint NativePointerSize => 8; - public LlvmIrModuleX64 (string fileName) - : base (fileName) + public LlvmIrModuleX64 () { // // As per Android NDK: @@ -45,5 +46,10 @@ public LlvmIrModuleX64 (string fileName) StackAlignment = 128, }; } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + throw new NotImplementedException (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index a28f721d348..877979209d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -1,17 +1,18 @@ +using System; using System.Collections.Generic; using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmIrModuleX86 : LlvmIrModule + class LlvmIrModuleX86 : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } - public override string TargetTriple => "i686-unknown-linux-android21"; + public override string Triple => "i686-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; + public override uint NativePointerSize => 4; - public LlvmIrModuleX86 (string fileName) - : base (fileName) + public LlvmIrModuleX86 () { // // As per Android NDK: @@ -43,5 +44,10 @@ public LlvmIrModuleX86 (string fileName) StackAlignment = 128, }; } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + throw new NotImplementedException (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 0abda63bfdd..c39973fd166 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -1,4 +1,117 @@ using System; +using System.Globalization; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + abstract class LlvmIrVariable : IEquatable + { + public abstract bool Global { get; } + public abstract string NamePrefix { get; } + + public string? Name { get; protected set; } + public Type Type { get; } + + /// + /// Both global and local variables will want their names to matter in equality checks, but function + /// parameters must not take it into account, thus this property. If set to false, + /// will ignore name when checking for equality. + protected bool NameMatters { get; set; } = true; + + /// + /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global + /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are + /// referenced. + /// + public string Reference { + get { + if (String.IsNullOrEmpty (Name)) { + throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); + } + + return $"{NamePrefix}{Name}"; + } + } + + /// + /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. + /// + protected LlvmIrVariable (Type type, string? name = null) + { + Type = type; + Name = name; + } + + public override int GetHashCode () + { + return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); + } + + public override bool Equals (object obj) + { + var irVar = obj as LlvmIrVariable; + if (irVar == null) { + return false; + } + + return Equals (irVar); + } + + public virtual bool Equals (LlvmIrVariable other) + { + if (other == null) { + return false; + } + + return + Global == other.Global && + Type == other.Type && + String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && + (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); + } + } + + class LlvmIrLocalVariable : LlvmIrVariable + { + public override bool Global => false; + public override string NamePrefix => "%"; + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in + /// which case they will be assigned a sequential number when function code is generated. + /// + public LlvmIrLocalVariable (Type type, string? name = null) + : base (type, name) + {} + + public void AssignNumber (uint n) + { + Name = n.ToString (CultureInfo.InvariantCulture); + } + } + + class LlvmIrGlobalVariable : LlvmIrVariable + { + public override bool Global => true; + public override string NamePrefix => "@"; + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is required because global variables must not be unnamed. + /// + public LlvmIrGlobalVariable (Type type, string name) + : base (type, name) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + } + } +} namespace Xamarin.Android.Tasks.LLVMIR { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs index d685e3f6685..039b231cecd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs @@ -4,6 +4,12 @@ using Xamarin.Android.Tools; +using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; +using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; +using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; +using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; +using TuneCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TuneCpuFunctionAttribute; + namespace Xamarin.Android.Tasks.LLVMIR { class X64LlvmIrGenerator : LlvmIrGenerator @@ -16,7 +22,7 @@ class X64LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 8; protected override string Triple => "x86_64-unknown-linux-android"; // NDK appends API level, we don't need that - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { new FramePointerFunctionAttribute ("none"), new TargetCpuFunctionAttribute ("x86-64"), new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs index df33d17e9b7..284314fdb5a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs @@ -4,6 +4,13 @@ using Xamarin.Android.Tools; +using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; +using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; +using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; +using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; +using TuneCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TuneCpuFunctionAttribute; +using StackrealignFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.StackrealignFunctionAttribute; + namespace Xamarin.Android.Tasks.LLVMIR { class X86LlvmIrGenerator : LlvmIrGenerator @@ -16,7 +23,7 @@ class X86LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 4; protected override string Triple => "i686-unknown-linux-android"; // NDK appends API level, we don't need that - static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { new FramePointerFunctionAttribute ("none"), new TargetCpuFunctionAttribute ("i686"), new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87"), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs new file mode 100644 index 00000000000..f4a656d3ea9 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks +{ + partial class MarshalMethodsNativeAssemblyGenerator + { + LlvmIrFunction? mm_trace_func_enter; + LlvmIrFunction? mm_trace_func_leave; + + protected override void Write (LlvmIrModule module) + { + InitTracing (module); + } + + void InitTracing (LlvmIrModule module) + { + // TODO: fill + var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_func_enter_or_leave_params = new List { + new (typeof(IntPtr), "env"), // JNIEnv *env + new (typeof(int), "tracing_mode"), + new (typeof(uint), "mono_image_index"), + new (typeof(uint), "class_index"), + new (typeof(uint), "method_token"), + new (typeof(string), "native_method_name"), + new (typeof(string), "method_extra_info"), + }; + + var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( + name: mm_trace_func_enter_name, + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params, + attributeSet: traceFunctionsAttributeSet + ); + + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_leave_name, mm_trace_func_enter_leave_sig)); + } + + LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) + { + // TODO: fill with defaults + var ret = new LlvmIrFunctionAttributeSet (); + module.Target.AddTargetSpecificAttributes (ret); + return ret; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 87ef7814af1..d0f031b59ca 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -13,7 +13,7 @@ using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; // TODO: generate code to check for pending Java exceptions (maybe?) -// TODO: check whether delegates not converted to marshale methods work correctly. It's possible something isn't called when it should be and that's +// TODO: check whether delegates not converted to marshal methods work correctly. It's possible something isn't called when it should be and that's // why Blazor hangs. namespace Xamarin.Android.Tasks { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs new file mode 100644 index 00000000000..1945bf8d58c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs @@ -0,0 +1,11 @@ +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks +{ + partial class TypeMappingDebugNativeAssemblyGenerator + { + protected override void Write (LlvmIrModule module) + { + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs index 3f1917d791b..9e0246ed06c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -5,7 +5,7 @@ namespace Xamarin.Android.Tasks { - class TypeMappingDebugNativeAssemblyGenerator : TypeMappingAssemblyGenerator + partial class TypeMappingDebugNativeAssemblyGenerator : TypeMappingAssemblyGenerator { const string JavaToManagedSymbol = "map_java_to_managed"; const string ManagedToJavaSymbol = "map_managed_to_java"; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs new file mode 100644 index 00000000000..1a3a738ac59 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs @@ -0,0 +1,11 @@ +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks +{ + partial class TypeMappingReleaseNativeAssemblyGenerator + { + protected override void Write (LlvmIrModule module) + { + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index bc407b478aa..7618962ac5e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -8,7 +8,7 @@ namespace Xamarin.Android.Tasks { - class TypeMappingReleaseNativeAssemblyGenerator : TypeMappingAssemblyGenerator + partial class TypeMappingReleaseNativeAssemblyGenerator : TypeMappingAssemblyGenerator { sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextDataProvider { From 12b8a619d37f37ea2a7f195d14e4d6f3f3b9a354 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 24 May 2023 22:34:45 +0200 Subject: [PATCH 33/60] Moving forward with the new LLVM IR generator Function declarations and definitions are done. --- .../LlvmIrGenerator/FunctionAttributes.cs | 7 + .../LlvmIrGenerator/LlvmIrFunction.cs | 61 +++-- .../LlvmIrFunctionAttributeSet.cs | 9 +- .../LlvmIrGenerator/LlvmIrModule.Constants.cs | 50 +++++ .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 211 +++++++++++++++++- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 3 +- .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 9 +- .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 40 ++++ .../LlvmIrGenerator/LlvmIrModuleX64.cs | 10 +- .../LlvmIrGenerator/LlvmIrModuleX86.cs | 11 +- ...rshalMethodsNativeAssemblyGenerator.New.cs | 76 ++++++- src/monodroid/jni/application_dso_stub.cc | 68 +++++- 12 files changed, 513 insertions(+), 42 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index ccde20dd4cd..e413e534bfc 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -496,6 +496,13 @@ public NobuiltinFunctionAttribute () {} } + class NocallbackFunctionAttribute : LlvmIrFlagFunctionAttribute + { + public NocallbackFunctionAttribute () + : base ("nocallback") + {} + } + class NoduplicateFunctionAttribute : LlvmIrFlagFunctionAttribute { public NoduplicateFunctionAttribute () diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index e65e2e52bb8..ef9e6294222 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -7,7 +7,10 @@ namespace Xamarin.Android.Tasks.LLVM.IR { // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = Xamarin.Android.Tasks.LLVMIR.LlvmIrAddressSignificance; + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; + using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; + using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; class LlvmIrFunctionParameter : LlvmIrLocalVariable { @@ -21,7 +24,7 @@ class LlvmIrFunctionParameter : LlvmIrLocalVariable /// /// allocptr attribute, see /// - public bool AllocPtr { get; set; } + public bool? AllocPtr { get; set; } /// /// dereferenceable(n) attribute, see @@ -31,32 +34,37 @@ class LlvmIrFunctionParameter : LlvmIrLocalVariable /// /// immarg attribute, see /// - public bool ImmArg { get; set; } + public bool? ImmArg { get; set; } /// /// nocapture attribute, see /// - public bool NoCapture { get; set; } + public bool? NoCapture { get; set; } /// /// nonnull attribute, see /// - public bool NonNull { get; set; } + public bool? NonNull { get; set; } /// /// noundef attribute, see /// - public bool NoUndef { get; set; } + public bool? NoUndef { get; set; } /// /// noundef attribute, see /// - public bool ReadNone { get; set; } + public bool? ReadNone { get; set; } + + /// + /// zeroext attribute, see + /// + public bool? SignExt { get; set; } /// /// zeroext attribute, see /// - public bool ZeroExt { get; set; } + public bool? ZeroExt { get; set; } public LlvmIrFunctionParameter (Type type, string? name = null) : base (type, name) @@ -70,9 +78,8 @@ class LlvmIrFunctionSignature : IEquatable public string Name { get; } public Type ReturnType { get; } public IList Parameters { get; } - public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } - public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null, LlvmIrFunctionAttributeSet? attributeSet = null) + public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null) { if (String.IsNullOrEmpty (name)) { throw new ArgumentException ("must not be null or empty", nameof (name)); @@ -81,7 +88,6 @@ public LlvmIrFunctionSignature (string name, Type returnType, IList (); - AttributeSet = attributeSet; } /// @@ -89,7 +95,7 @@ public LlvmIrFunctionSignature (string name, Type returnType, IList public LlvmIrFunctionSignature (string name, LlvmIrFunctionSignature templateSignature) - : this (name, templateSignature.ReturnType, templateSignature.Parameters, templateSignature.AttributeSet) + : this (name, templateSignature.ReturnType, templateSignature.Parameters) {} public override int GetHashCode () @@ -97,8 +103,7 @@ public override int GetHashCode () int hc = Name.GetHashCode () ^ Parameters.GetHashCode () ^ - ReturnType.GetHashCode () ^ - (AttributeSet?.GetHashCode () ?? 0); + ReturnType.GetHashCode (); foreach (LlvmIrFunctionParameter p in Parameters) { hc ^= p.GetHashCode (); @@ -148,22 +153,42 @@ class LlvmIrFunction : IEquatable { public LlvmIrFunctionSignature Signature { get; } public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.LocalUnnamed; + public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } + public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; + public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; + public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; + + // Counter shared by unnamed local variables (including function parameters) and unnamed labels. + uint unnamedTemporaryCounter = 0; - public LlvmIrFunction (LlvmIrFunctionSignature signature) + // Implicit unnamed label at the start of the function + readonly uint startingBlockNumber; + + public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) { Signature = signature; + AttributeSet = attributeSet; + + foreach (LlvmIrFunctionParameter parameter in signature.Parameters) { + if (!String.IsNullOrEmpty (parameter.Name)) { + continue; + } + + parameter.AssignNumber (unnamedTemporaryCounter++); + } + startingBlockNumber = unnamedTemporaryCounter++; } /// /// Create new function using data from the signature, with the exception of name. /// Useful when there are several functions with different names but identical parameters and return types. /// - public LlvmIrFunction (string name, LlvmIrFunctionSignature templateSignature) - : this (new LlvmIrFunctionSignature (name, templateSignature)) + public LlvmIrFunction (string name, LlvmIrFunctionSignature templateSignature, LlvmIrFunctionAttributeSet? attributeSet = null) + : this (new LlvmIrFunctionSignature (name, templateSignature), attributeSet) {} public LlvmIrFunction (string name, Type returnType, List? parameters = null, LlvmIrFunctionAttributeSet? attributeSet = null) - : this (new LlvmIrFunctionSignature (name, returnType, parameters, attributeSet)) + : this (new LlvmIrFunctionSignature (name, returnType, parameters), attributeSet) {} public override int GetHashCode () diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index 8d4f72ab42f..c1a4ced6f5a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -7,20 +7,13 @@ namespace Xamarin.Android.Tasks.LLVM.IR { class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable { - static readonly object counterLock = new object (); - static uint counter = 0; - - public uint Number { get; } + public uint Number { get; set; } = 0; HashSet attributes; public LlvmIrFunctionAttributeSet () { attributes = new HashSet (); - - lock (counterLock) { - Number = counter++; - } } public void Add (LlvmIrFunctionAttribute attr) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs new file mode 100644 index 00000000000..c95525b902b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; + using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; + using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + + partial class LlvmIrModule + { + // 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#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" }, + }; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index ff2a2ab42a3..da680d128f8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -7,8 +7,44 @@ namespace Xamarin.Android.Tasks.LLVM.IR { - class LlvmIrModule + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; + using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; + using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + + partial class LlvmIrModule { + sealed class BasicType + { + public readonly string Name; + public readonly ulong Size; + + public BasicType (string name, ulong size) + { + Name = name; + Size = size; + } + } + + const string IRPointerType = "ptr"; + + static readonly Dictionary basicTypeMap = new Dictionary { + { typeof (bool), new ("i8", 1) }, + { typeof (byte), new ("i8", 1) }, + { typeof (char), new ("i16", 2) }, + { typeof (sbyte), new ("i8", 1) }, + { typeof (short), new ("i16", 2) }, + { typeof (ushort), new ("i16", 2) }, + { typeof (int), new ("i32", 4) }, + { typeof (uint), new ("i32", 4) }, + { typeof (long), new ("i64", 8) }, + { typeof (ulong), new ("i64", 8) }, + { typeof (float), new ("float", 4) }, + { typeof (double), new ("double", 8) }, + { typeof (void), new ("void", 0) }, + }; + public string FilePath { get; } public string FileName { get; } public LlvmIrModuleTarget Target { get; } @@ -48,6 +84,7 @@ public LlvmIrFunctionAttributeSet AddAttributeSet (LlvmIrFunctionAttributeSet at if (attributeSets.TryGetValue (attrSet, out LlvmIrFunctionAttributeSet existingSet)) { return existingSet; } + attrSet.Number = (uint)attributeSets.Count; attributeSets.Add (attrSet, attrSet); return attrSet; @@ -67,6 +104,10 @@ public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) if (externalFunctions.TryGetValue (func, out LlvmIrFunction existingFunc)) { return existingFunc; } + + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + Target.SetParameterFlags (parameter); + } externalFunctions.Add (func, func); return func; @@ -82,16 +123,173 @@ public void Generate (TextWriter writer) writer.WriteLine (Target.DataLayout.Render ()); writer.WriteLine ($"target triple = \"{Target.Triple}\""); writer.WriteLine (); + WriteExternalFunctionDeclarations (writer); // Bottom of file WriteAttributeSets (writer); } + // + // Functions syntax: https://llvm.org/docs/LangRef.html#functions + // void WriteExternalFunctionDeclarations (TextWriter writer) { if (externalFunctions == null || externalFunctions.Count == 0) { return; } + + List list = externalFunctions.Values.ToList (); + list.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); + + foreach (LlvmIrFunction func in list) { + WriteFunctionAttributesComment (writer, func); + writer.Write ("declare "); + WriteFunctionDeclarationLeadingDecorations (writer, func); + WriteFunctionSignature (writer, func, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (writer, func); + writer.WriteLine (); + } + } + + void WriteFunctionAttributesComment (TextWriter writer, LlvmIrFunction func) + { + if (func.AttributeSet == null) { + return; + } + + writer.WriteLine (); + WriteCommentLine (writer, $"Function attributes: {func.AttributeSet.Render ()}"); + } + + void WriteFunctionDeclarationLeadingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionLeadingDecorations (writer, func, declaration: true); + } + + void WriteFunctionDefinitionLeadingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionLeadingDecorations (writer, func, declaration: false); + } + + void WriteFunctionLeadingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + { + if (func.Linkage != LlvmIrLinkage.Default) { + writer.Write (llvmLinkage[func.Linkage]); + writer.Write (' '); + } + + if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { + writer.Write (llvmRuntimePreemption[func.RuntimePreemption]); + writer.Write (' '); + } + + if (func.Visibility != LlvmIrVisibility.Default) { + writer.Write (llvmVisibility[func.Visibility]); + writer.Write (' '); + } + } + + void WriteFunctionDeclarationTrailingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionTrailingDecorations (writer, func, declaration: true); + } + + void WriteFunctionDefinitionTrailingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionTrailingDecorations (writer, func, declaration: false); + } + + void WriteFunctionTrailingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + { + if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { + writer.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); + } + + if (func.AttributeSet != null) { + writer.Write ($" #{func.AttributeSet.Number}"); + } + } + + void WriteFunctionSignature (TextWriter writer, LlvmIrFunction func, bool writeParameterNames) + { + writer.Write (MapToIRType (func.Signature.ReturnType)); + writer.Write (" @"); + writer.Write (func.Signature.Name); + writer.Write ('('); + + bool first = true; + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + if (!first) { + writer.Write (", "); + } else { + first = false; + } + + writer.Write (MapToIRType (parameter.Type)); + WriteParameterAttributes (writer, parameter); + if (writeParameterNames) { + if (String.IsNullOrEmpty (parameter.Name)) { + throw new InvalidOperationException ($"Internal error: parameter must have a name"); + } + writer.Write (" %"); // Function arguments are always local variables + writer.Write (parameter.Name); + } + } + + writer.Write (')'); + } + + void WriteParameterAttributes (TextWriter writer, LlvmIrFunctionParameter parameter) + { + var attributes = new List (); + if (AttributeIsSet (parameter.ImmArg)) { + attributes.Add ("immarg"); + } + + if (AttributeIsSet (parameter.AllocPtr)) { + attributes.Add ("allocptr"); + } + + if (AttributeIsSet (parameter.NoCapture)) { + attributes.Add ("nocapture"); + } + + if (AttributeIsSet (parameter.NonNull)) { + attributes.Add ("nonnull"); + } + + if (AttributeIsSet (parameter.NoUndef)) { + attributes.Add ("noundef"); + } + + if (AttributeIsSet (parameter.ReadNone)) { + attributes.Add ("readnone"); + } + + if (AttributeIsSet (parameter.SignExt)) { + attributes.Add ("signext"); + } + + if (AttributeIsSet (parameter.ZeroExt)) { + attributes.Add ("zeroext"); + } + + if (parameter.Align.HasValue) { + attributes.Add ($"align({parameter.Align.Value})"); + } + + if (parameter.Dereferenceable.HasValue) { + attributes.Add ($"dereferenceable({parameter.Dereferenceable.Value})"); + } + + if (attributes.Count == 0) { + return; + } + + writer.Write (' '); + writer.Write (String.Join (" ", attributes)); + + bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; } void WriteAttributeSets (TextWriter writer) @@ -100,6 +298,7 @@ void WriteAttributeSets (TextWriter writer) return; } + writer.WriteLine (); List list = attributeSets.Keys.ToList (); list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); @@ -119,5 +318,15 @@ void WriteCommentLine (TextWriter writer, string comment) WriteComment (writer, comment); writer.WriteLine (); } + + string MapToIRType (Type type) + { + if (basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + return typeDesc.Name; + } + + // if it's not a basic type, then it's an opaque pointer + return IRPointerType; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index 3ce80520576..cacb6f1ada8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -36,7 +36,8 @@ public LlvmIrModuleAArch64 () public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) { - throw new NotImplementedException (); + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics,+v8a")); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index 594e8cc12aa..b536dee422c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -44,7 +44,14 @@ public LlvmIrModuleArmV7a () public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) { - throw new NotImplementedException (); + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp")); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs index 7d4a760382d..c85e25fbf6c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -9,7 +9,47 @@ abstract class LlvmIrModuleTarget public abstract AndroidTargetArch TargetArch { get; } public abstract uint NativePointerSize { get; } + /// + /// Adds target-specific attributes which are common to many attribute sets. Usually this specifies CPU type, tuning and + /// features. + /// public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) {} + + public virtual void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + if (!parameter.NoUndef.HasValue) { + parameter.NoUndef = true; + } + } + + /// + /// Sets the zeroext or signext attributes on the parameter, if not set previously and if + /// the parameter is a small integral type. Out of our supported architectures, all except AArch64 set + /// the flags, thus the reason to put this method in the base class. + /// + protected void SetIntegerParameterUpcastFlags (LlvmIrFunctionParameter parameter) + { + if (parameter.Type == typeof(bool) || + parameter.Type == typeof(byte) || + parameter.Type == typeof(char) || + parameter.Type == typeof(ushort)) + { + if (!parameter.ZeroExt.HasValue) { + parameter.ZeroExt = true; + parameter.SignExt = false; + } + return; + } + + if (parameter.Type == typeof(sbyte) || + parameter.Type == typeof(short)) + { + if (!parameter.SignExt.HasValue) { + parameter.SignExt = true; + parameter.ZeroExt = false; + } + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index 11f3a4a78f5..db148a41214 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -49,7 +49,15 @@ public LlvmIrModuleX64 () public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) { - throw new NotImplementedException (); + attrSet.Add (new TargetCpuFunctionAttribute ("x86-64")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index 877979209d2..0d11bd5ba08 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -47,7 +47,16 @@ public LlvmIrModuleX86 () public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) { - throw new NotImplementedException (); + attrSet.Add (new TargetCpuFunctionAttribute ("i686")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + attrSet.Add (new StackrealignFunctionAttribute ()); + } + + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index f4a656d3ea9..71836bab57f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; +using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks { + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + partial class MarshalMethodsNativeAssemblyGenerator { LlvmIrFunction? mm_trace_func_enter; LlvmIrFunction? mm_trace_func_leave; + LlvmIrFunction? llvm_lifetime_start; + LlvmIrFunction? llvm_lifetime_end; protected override void Write (LlvmIrModule module) { @@ -17,9 +23,31 @@ protected override void Write (LlvmIrModule module) void InitTracing (LlvmIrModule module) { - // TODO: fill + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + var llvm_lifetime_params = new List { + new (typeof(ulong), "size"), + new (typeof(IntPtr), "pointer"), + }; + + var lifetime_sig = new LlvmIrFunctionSignature ( + name: "llvm.lifetime.start", + returnType: typeof(void), + parameters: llvm_lifetime_params + ); + + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh var mm_trace_func_enter_or_leave_params = new List { new (typeof(IntPtr), "env"), // JNIEnv *env @@ -34,18 +62,52 @@ void InitTracing (LlvmIrModule module) var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( name: mm_trace_func_enter_name, returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params, - attributeSet: traceFunctionsAttributeSet + parameters: mm_trace_func_enter_or_leave_params ); - mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_leave_name, mm_trace_func_enter_leave_sig)); + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_leave_name, mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + } + + LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) + { + return new LlvmIrFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NocallbackFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; } LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) { - // TODO: fill with defaults - var ret = new LlvmIrFunctionAttributeSet (); + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + switch (module.Target.TargetArch) { + case AndroidTargetArch.Arm64: + ret.Add (new FramePointerFunctionAttribute ("non-leaf")); + break; + + case AndroidTargetArch.Arm: + ret.Add (new FramePointerFunctionAttribute ("all")); + break; + + case AndroidTargetArch.X86: + case AndroidTargetArch.X86_64: + ret.Add (new FramePointerFunctionAttribute ("none")); + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported target architecture {module.Target.TargetArch}"); + } + module.Target.AddTargetSpecificAttributes (ret); return ret; } diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 97f554e4c25..a8ffcedb71a 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -22,13 +23,72 @@ const TypeMap type_map = { managed_to_java }; #else -const uint32_t map_module_count = 0; +const uint32_t map_module_count = 2; const uint32_t java_type_count = 0; const char* const java_type_names[] = {}; -TypeMapModule map_modules[] = {}; -const TypeMapJava map_java[] = {}; -const xamarin::android::hash_t map_java_hashes[] = {}; +static TypeMapModuleEntry module1[] = { + { + .type_token_id = 1111, + .java_map_index = 0 + } +}; + +static uint8_t module1_java_map[] = { 1, 2 }; + +static TypeMapModuleEntry module2[] = { + { + .type_token_id = 2222, + .java_map_index = 0 + } +}; + +static uint8_t module2_java_map[] = { 3, 4 }; + +TypeMapModule map_modules[] = { + { + .module_uuid = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, + .entry_count = 1, + .duplicate_count = 0, + .map = module1, + .duplicate_map = nullptr, + .assembly_name = "Mono.Android", + .image = nullptr, + .java_name_width = 111, + .java_map = module1_java_map, + }, + + { + .module_uuid = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }, + .entry_count = 1, + .duplicate_count = 0, + .map = module2, + .duplicate_map = nullptr, + .assembly_name = "System", + .image = nullptr, + .java_name_width = 222, + .java_map = module2_java_map, + }, +}; + +const TypeMapJava map_java[] = { + { + .module_index = 0, + .type_token_id = 1, + .java_name_index = 1, + }, + + { + .module_index = 1, + .type_token_id = 2, + .java_name_index = 2, + }, +}; + +const xamarin::android::hash_t map_java_hashes[] = { + 0x1, + 0x2, +}; #endif CompressedAssemblies compressed_assemblies = { From 49b7d6313131cb93808a1b9c446eb7a62f06025b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 25 May 2023 22:22:20 +0200 Subject: [PATCH 34/60] Rearchitecting things for simpler use --- ...cationConfigNativeAssemblyGenerator.New.cs | 61 +++- ...edAssembliesNativeAssemblyGenerator.New.cs | 14 +- .../JniRemappingAssemblyGenerator.New.cs | 9 +- .../LlvmIrGenerator/LlvmIrComposer.New.cs | 36 +++ .../LlvmIrGenerator/LlvmIrComposer.cs | 12 - .../LlvmIrGenerator/LlvmIrFunction.cs | 202 +++++++++++- .../LlvmIrFunctionAttributeSet.cs | 42 +++ ...ts.cs => LlvmIrGenerator.New.Constants.cs} | 2 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 302 ++++++++++++++++++ .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 279 +--------------- ...rshalMethodsNativeAssemblyGenerator.New.cs | 33 +- ...MappingDebugNativeAssemblyGenerator.New.cs | 9 +- ...ppingReleaseNativeAssemblyGenerator.New.cs | 9 +- 13 files changed, 659 insertions(+), 351 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs rename src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/{LlvmIrModule.Constants.cs => LlvmIrGenerator.New.Constants.cs} (98%) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index cd7ee9e0a67..f0dd447d99d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -1,11 +1,66 @@ +using System; +using System.IO; + using Xamarin.Android.Tasks.LLVM.IR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks.New { - partial class ApplicationConfigNativeAssemblyGenerator + using ApplicationConfig = Xamarin.Android.Tasks.ApplicationConfig; + + // Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh + [Flags] + enum MonoComponent + { + None = 0x00, + Debugger = 0x01, + HotReload = 0x02, + Tracing = 0x04, + } + + class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer { - protected override void Write (LlvmIrModule module) + sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextDataProvider { + public override string GetComment (object data, string fieldName) + { + var dso_entry = data as DSOCacheEntry; + if (dso_entry == null) { + throw new InvalidOperationException ("Invalid data type, expected an instance of DSOCacheEntry"); + } + + if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { + return $"hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; + } + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $"name: {dso_entry.name}"; + } + + return String.Empty; + } } + + // 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 + { + [NativeAssembler (Ignore = true)] + public string HashedName; + + [NativeAssembler (UsesDataProvider = true)] + public ulong hash; + public bool ignore; + + public string name; + public IntPtr handle = IntPtr.Zero; + } + + // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh + const ulong FORMAT_TAG = 0x015E6972616D58; + + protected override void Construct (LlvmIrModule module) + {} } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs index d4822c7b688..0bbc0f4b8dc 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs @@ -1,12 +1,10 @@ -namespace Xamarin.Android.Tasks -{ - // TODO: remove once migration to LLVM.IR is done - using LlvmIrModule = Xamarin.Android.Tasks.LLVM.IR.LlvmIrModule; +using Xamarin.Android.Tasks.LLVM.IR; - partial class CompressedAssembliesNativeAssemblyGenerator +namespace Xamarin.Android.Tasks.New +{ + partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer { - protected override void Write (LlvmIrModule module) - { - } + protected override void Construct (LlvmIrModule module) + {} } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs index 4b8ec40bd34..3b79b0eb358 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs @@ -1,11 +1,10 @@ using Xamarin.Android.Tasks.LLVM.IR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks.New { - partial class JniRemappingAssemblyGenerator + class JniRemappingAssemblyGenerator : LlvmIrComposer { - protected override void Write (LlvmIrModule module) - { - } + protected override void Construct (LlvmIrModule module) + {} } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs new file mode 100644 index 00000000000..f6dd7d0d8ad --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using System.IO.Hashing; +using System.Text; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + abstract class LlvmIrComposer + { + bool constructed; + + protected abstract void Construct (LlvmIrModule module); + + public LlvmIrModule Construct () + { + var module = new LlvmIrModule (); + Construct (module); + constructed = true; + + return module; + } + + public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter output, string fileName) + { + if (!constructed) { + throw new InvalidOperationException ($"Internal error: module not constructed yet. Was Constrict () called?"); + } + + LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName); + generator.Generate (output, module); + output.Flush (); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 0be1cca9857..790b8d30668 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -5,8 +5,6 @@ using Xamarin.Android.Tools; -using LlvmIrModule = Xamarin.Android.Tasks.LLVM.IR.LlvmIrModule; - namespace Xamarin.Android.Tasks.LLVMIR { /// @@ -23,22 +21,13 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) { LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); - string newFileName = Path.Combine (Path.GetDirectoryName (fileName), $"new-{Path.GetFileName(fileName)}"); - using var newOutput = new StreamWriter (File.Create (newFileName), new UTF8Encoding (false)); - - LlvmIrModule module = LlvmIrModule.Create (arch, output, newFileName); - InitGenerator (generator); MapStructures (generator); generator.WriteFileTop (); generator.WriteStructureDeclarations (); Write (generator); - Write (module); generator.WriteFunctionDeclarations (); generator.WriteFileEnd (); - - module.Generate (newOutput); - newOutput.Flush (); } protected static string GetAbiName (AndroidTargetArch arch) @@ -88,6 +77,5 @@ protected virtual void InitGenerator (LlvmIrGenerator generator) /// native pointer size). /// protected abstract void Write (LlvmIrGenerator generator); - protected abstract void Write (LlvmIrModule module); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index ef9e6294222..ceae057f451 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -12,69 +12,170 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + interface ILlvmIrFunctionParameterState {} + class LlvmIrFunctionParameter : LlvmIrLocalVariable { + sealed class ParameterState : ILlvmIrFunctionParameterState + { + public readonly LlvmIrFunctionParameter Owner; + + public uint? Align; + public bool? AllocPtr; + public uint? Dereferenceable; + public bool? ImmArg; + public bool? NoCapture; + public bool? NonNull; + public bool? NoUndef; + public bool? ReadNone; + public bool? SignExt; + public bool? ZeroExt; + + public ParameterState (LlvmIrFunctionParameter owner) + { + Owner = owner; + } + } + + ParameterState state; + // To save on time, we declare only attributes that are actually used in our generated code. More will be added, as needed. /// /// align(n) attribute, see /// - public uint? Align { get; set; } + public uint? Align { + get => state.Align; + set => state.Align = value; + } /// /// allocptr attribute, see /// - public bool? AllocPtr { get; set; } + public bool? AllocPtr { + get => state.AllocPtr; + set => state.AllocPtr = value; + } /// /// dereferenceable(n) attribute, see /// - public uint? Dereferenceable { get; set; } + public uint? Dereferenceable { + get => state.Dereferenceable; + set => state.Dereferenceable = value; + } /// /// immarg attribute, see /// - public bool? ImmArg { get; set; } + public bool? ImmArg { + get => state.ImmArg; + set => state.ImmArg = value; + } /// /// nocapture attribute, see /// - public bool? NoCapture { get; set; } + public bool? NoCapture { + get => state.NoCapture; + set => state.NoCapture = value; + } /// /// nonnull attribute, see /// - public bool? NonNull { get; set; } + public bool? NonNull { + get => state.NonNull; + set => state.NonNull = value; + } /// /// noundef attribute, see /// - public bool? NoUndef { get; set; } + public bool? NoUndef { + get => state.NoUndef; + set => state.NoUndef = value; + } /// /// noundef attribute, see /// - public bool? ReadNone { get; set; } + public bool? ReadNone { + get => state.ReadNone; + set => state.ReadNone = value; + } /// /// zeroext attribute, see /// - public bool? SignExt { get; set; } + public bool? SignExt { + get => state.SignExt; + set => state.SignExt = value; + } /// /// zeroext attribute, see /// - public bool? ZeroExt { get; set; } + public bool? ZeroExt { + get => state.ZeroExt; + set => state.ZeroExt = value; + } public LlvmIrFunctionParameter (Type type, string? name = null) : base (type, name) { NameMatters = false; + state = new ParameterState (this); + } + + /// + /// Save (opaque) parameter state. This is necessary because we generate code from the same model (module) for different + /// targets. At the same time, function, signature and parameter instances are shared between the different code generation + /// sessions, so we must sure the state as set by the model is properly preserved. NOTE: it does NOT make the code thread-safe! + /// Instances are **still** shared and thus different threads would step on each other's toes should they saved and restored + /// state without synchronization. + /// + public ILlvmIrFunctionParameterState SaveState () + { + ILlvmIrFunctionParameterState ret = state; + state = new ParameterState (this); + return ret; + } + + /// + /// Restore (opaque) state. for more info + /// + public void RestoreState (ILlvmIrFunctionParameterState savedState) + { + var oldState = savedState as ParameterState; + if (oldState == null) { + throw new InvalidOperationException ("Internal error: savedState not an instance of ParameterState"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); + } + + state = oldState; } } + interface ILlvmIrFunctionSignatureState {} + class LlvmIrFunctionSignature : IEquatable { + sealed class SignatureState : ILlvmIrFunctionSignatureState + { + public readonly LlvmIrFunctionSignature Owner; + public readonly IList ParameterStates; + + public SignatureState (LlvmIrFunctionSignature owner, IList parameterStates) + { + Owner = owner; + ParameterStates = parameterStates; + } + } + public string Name { get; } public Type ReturnType { get; } public IList Parameters { get; } @@ -98,6 +199,43 @@ public LlvmIrFunctionSignature (string name, LlvmIrFunctionSignature templateSig : this (name, templateSignature.ReturnType, templateSignature.Parameters) {} + /// + /// Save (opaque) signature state. This includes states of all the parameters. + /// for more information. + /// + public ILlvmIrFunctionSignatureState SaveState () + { + var list = new List (); + + foreach (LlvmIrFunctionParameter parameter in Parameters) { + list.Add (parameter.SaveState ()); + } + + return new SignatureState (this, list.AsReadOnly ()); + } + + /// + /// Restore (opaque) signature state. This includes states of all the parameters. + /// for more information. + /// + public void RestoreState (ILlvmIrFunctionSignatureState savedState) + { + var oldState = savedState as SignatureState; + if (oldState == null) { + throw new InvalidOperationException ("Internal error: savedState not an instance of {nameof(SignatureState)}"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); + } + + for (int i = 0; i < oldState.ParameterStates.Count; i++) { + ILlvmIrFunctionParameterState parameterState = oldState.ParameterStates[i]; + Parameters[i].RestoreState (parameterState); + + } + } + public override int GetHashCode () { int hc = @@ -112,7 +250,7 @@ public override int GetHashCode () return hc; } - public override bool Equals (object obj) + public override bool Equals (object obj) { var sig = obj as LlvmIrFunctionSignature; if (sig == null) { @@ -122,7 +260,7 @@ public override bool Equals (object obj) return Equals (sig); } - public bool Equals (LlvmIrFunctionSignature other) + public bool Equals (LlvmIrFunctionSignature other) { if (other == null) { return false; @@ -145,12 +283,25 @@ public bool Equals (LlvmIrFunctionSignature other) } } + interface ILlvmIrFunctionState {} + /// /// Describes a native function to be emitted or declared and keeps code emitting state between calls to various generator. /// methods. /// class LlvmIrFunction : IEquatable { + sealed class FunctionState : ILlvmIrFunctionState + { + public readonly LlvmIrFunction Owner; + public readonly ILlvmIrFunctionSignatureState SignatureState; + + public FunctionState (LlvmIrFunction owner, ILlvmIrFunctionSignatureState signatureState) + { + Owner = owner; + } + } + public LlvmIrFunctionSignature Signature { get; } public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.LocalUnnamed; public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } @@ -191,6 +342,33 @@ public LlvmIrFunction (string name, Type returnType, List + /// Save (opaque) function state. This includes signature state. + /// for more information. + /// + public ILlvmIrFunctionState SaveState () + { + return new FunctionState (this, Signature.SaveState ()); + } + + /// + /// Restore (opaque) function state. This includes signature state. + /// for more information. + /// + public void RestoreState (ILlvmIrFunctionState savedState) + { + var oldState = savedState as FunctionState; + if (oldState == null) { + throw new InvalidOperationException ("Internal error: savedState not an instance of {nameof(FunctionState)}"); + } + + if (oldState.Owner != this) { + throw new InvalidOperationException ("Internal error: savedState not saved by this instance"); + } + + Signature.RestoreState (oldState.SignatureState); + } + public override int GetHashCode () { return Signature.GetHashCode (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index c1a4ced6f5a..36ae6591424 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using Xamarin.Android.Tools; + namespace Xamarin.Android.Tasks.LLVM.IR { class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable @@ -10,12 +12,28 @@ class LlvmIrFunctionAttributeSet : IEnumerable, IEquata public uint Number { get; set; } = 0; HashSet attributes; + Dictionary>? privateTargetSpecificAttributes; public LlvmIrFunctionAttributeSet () { attributes = new HashSet (); } + public LlvmIrFunctionAttributeSet (LlvmIrFunctionAttributeSet other) + { + attributes = new HashSet (other); + Number = other.Number; + } + + public IList? GetPrivateTargetAttributes (AndroidTargetArch targetArch) + { + if (privateTargetSpecificAttributes == null || !privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { + return null; + } + + return list.AsReadOnly (); + } + public void Add (LlvmIrFunctionAttribute attr) { if (attr == null) { @@ -27,6 +45,30 @@ public void Add (LlvmIrFunctionAttribute attr) } } + public void Add (IList attrList) + { + foreach (LlvmIrFunctionAttribute attr in attrList) { + Add (attr); + } + } + + /// + /// Add architecture-specific attributes, private to the module generator (as opposed to arch-specific attributes which are common + /// between all attribute sets. + /// + public void Add (AndroidTargetArch targetArch, LlvmIrFunctionAttribute attr) + { + if (privateTargetSpecificAttributes == null) { + privateTargetSpecificAttributes = new Dictionary> (); + } + + if (!privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { + list = new List (); + } + + list.Add (attr); + } + public void Add (LlvmIrFunctionAttributeSet sourceSet) { if (sourceSet == null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs similarity index 98% rename from src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs rename to src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs index c95525b902b..9e089bcc77a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.Constants.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs @@ -9,7 +9,7 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - partial class LlvmIrModule + partial class LlvmIrGenerator { // https://llvm.org/docs/LangRef.html#global-variables static readonly Dictionary llvmAddressSignificance = new Dictionary { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs new file mode 100644 index 00000000000..a8576270f1a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; + using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; + using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + + partial class LlvmIrGenerator + { + sealed class BasicType + { + public readonly string Name; + public readonly ulong Size; + + public BasicType (string name, ulong size) + { + Name = name; + Size = size; + } + } + + const string IRPointerType = "ptr"; + + static readonly Dictionary basicTypeMap = new Dictionary { + { typeof (bool), new ("i8", 1) }, + { typeof (byte), new ("i8", 1) }, + { typeof (char), new ("i16", 2) }, + { typeof (sbyte), new ("i8", 1) }, + { typeof (short), new ("i16", 2) }, + { typeof (ushort), new ("i16", 2) }, + { typeof (int), new ("i32", 4) }, + { typeof (uint), new ("i32", 4) }, + { typeof (long), new ("i64", 8) }, + { typeof (ulong), new ("i64", 8) }, + { typeof (float), new ("float", 4) }, + { typeof (double), new ("double", 8) }, + { typeof (void), new ("void", 0) }, + }; + + public string FilePath { get; } + public string FileName { get; } + + LlvmIrModuleTarget target; + + protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) + { + FilePath = Path.GetFullPath (filePath); + FileName = Path.GetFileName (filePath); + this.target = target; + } + + public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) + { + return arch switch { + AndroidTargetArch.Arm => new LlvmIrGenerator (fileName, new LlvmIrModuleArmV7a ()), + AndroidTargetArch.Arm64 => new LlvmIrGenerator (fileName, new LlvmIrModuleAArch64 ()), + AndroidTargetArch.X86 => new LlvmIrGenerator (fileName, new LlvmIrModuleX86 ()), + AndroidTargetArch.X86_64 => new LlvmIrGenerator (fileName, new LlvmIrModuleX64 ()), + _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") + }; + } + + public void Generate (TextWriter writer, LlvmIrModule module) + { + if (!String.IsNullOrEmpty (FilePath)) { + WriteCommentLine (writer, $" ModuleID = '{FileName}'"); + writer.WriteLine ($"source_filename = \"{FileName}\""); + } + + writer.WriteLine (target.DataLayout.Render ()); + writer.WriteLine ($"target triple = \"{target.Triple}\""); + writer.WriteLine (); + WriteExternalFunctionDeclarations (writer, module); + + // Bottom of file + WriteAttributeSets (writer, module); + } + + // + // Functions syntax: https://llvm.org/docs/LangRef.html#functions + // + void WriteExternalFunctionDeclarations (TextWriter writer, LlvmIrModule module) + { + if (module.ExternalFunctions == null || module.ExternalFunctions.Count == 0) { + return; + } + + module.ExternalFunctions.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); + + foreach (LlvmIrFunction func in module.ExternalFunctions) { + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) + ILlvmIrFunctionState funcState = func.SaveState (); + + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + target.SetParameterFlags (parameter); + } + + WriteFunctionAttributesComment (writer, func); + writer.Write ("declare "); + WriteFunctionDeclarationLeadingDecorations (writer, func); + WriteFunctionSignature (writer, func, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (writer, func); + writer.WriteLine (); + + func.RestoreState (funcState); + } + } + + void WriteFunctionAttributesComment (TextWriter writer, LlvmIrFunction func) + { + if (func.AttributeSet == null) { + return; + } + + writer.WriteLine (); + WriteCommentLine (writer, $"Function attributes: {func.AttributeSet.Render ()}"); + } + + void WriteFunctionDeclarationLeadingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionLeadingDecorations (writer, func, declaration: true); + } + + void WriteFunctionDefinitionLeadingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionLeadingDecorations (writer, func, declaration: false); + } + + void WriteFunctionLeadingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + { + if (func.Linkage != LlvmIrLinkage.Default) { + writer.Write (llvmLinkage[func.Linkage]); + writer.Write (' '); + } + + if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { + writer.Write (llvmRuntimePreemption[func.RuntimePreemption]); + writer.Write (' '); + } + + if (func.Visibility != LlvmIrVisibility.Default) { + writer.Write (llvmVisibility[func.Visibility]); + writer.Write (' '); + } + } + + void WriteFunctionDeclarationTrailingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionTrailingDecorations (writer, func, declaration: true); + } + + void WriteFunctionDefinitionTrailingDecorations (TextWriter writer, LlvmIrFunction func) + { + WriteFunctionTrailingDecorations (writer, func, declaration: false); + } + + void WriteFunctionTrailingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + { + if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { + writer.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); + } + + if (func.AttributeSet != null) { + writer.Write ($" #{func.AttributeSet.Number}"); + } + } + + void WriteFunctionSignature (TextWriter writer, LlvmIrFunction func, bool writeParameterNames) + { + writer.Write (MapToIRType (func.Signature.ReturnType)); + writer.Write (" @"); + writer.Write (func.Signature.Name); + writer.Write ('('); + + bool first = true; + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + if (!first) { + writer.Write (", "); + } else { + first = false; + } + + writer.Write (MapToIRType (parameter.Type)); + WriteParameterAttributes (writer, parameter); + if (writeParameterNames) { + if (String.IsNullOrEmpty (parameter.Name)) { + throw new InvalidOperationException ($"Internal error: parameter must have a name"); + } + writer.Write (" %"); // Function arguments are always local variables + writer.Write (parameter.Name); + } + } + + writer.Write (')'); + } + + void WriteParameterAttributes (TextWriter writer, LlvmIrFunctionParameter parameter) + { + var attributes = new List (); + if (AttributeIsSet (parameter.ImmArg)) { + attributes.Add ("immarg"); + } + + if (AttributeIsSet (parameter.AllocPtr)) { + attributes.Add ("allocptr"); + } + + if (AttributeIsSet (parameter.NoCapture)) { + attributes.Add ("nocapture"); + } + + if (AttributeIsSet (parameter.NonNull)) { + attributes.Add ("nonnull"); + } + + if (AttributeIsSet (parameter.NoUndef)) { + attributes.Add ("noundef"); + } + + if (AttributeIsSet (parameter.ReadNone)) { + attributes.Add ("readnone"); + } + + if (AttributeIsSet (parameter.SignExt)) { + attributes.Add ("signext"); + } + + if (AttributeIsSet (parameter.ZeroExt)) { + attributes.Add ("zeroext"); + } + + if (parameter.Align.HasValue) { + attributes.Add ($"align({parameter.Align.Value})"); + } + + if (parameter.Dereferenceable.HasValue) { + attributes.Add ($"dereferenceable({parameter.Dereferenceable.Value})"); + } + + if (attributes.Count == 0) { + return; + } + + writer.Write (' '); + writer.Write (String.Join (" ", attributes)); + + bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; + } + + void WriteAttributeSets (TextWriter writer, LlvmIrModule module) + { + if (module.AttributeSets == null || module.AttributeSets.Count == 0) { + return; + } + + writer.WriteLine (); + module.AttributeSets.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); + + foreach (LlvmIrFunctionAttributeSet attrSet in module.AttributeSets) { + // Must not modify the original set, it is shared with other targets. + var targetSet = new LlvmIrFunctionAttributeSet (attrSet); + target.AddTargetSpecificAttributes (targetSet); + + IList? privateTargetSet = attrSet.GetPrivateTargetAttributes (target.TargetArch); + if (privateTargetSet != null) { + targetSet.Add (privateTargetSet); + } + + writer.WriteLine ($"attributes #{targetSet.Number} {{ {targetSet.Render ()} }}"); + } + } + + void WriteComment (TextWriter writer, string comment) + { + writer.Write (';'); + writer.Write (comment); + } + + void WriteCommentLine (TextWriter writer, string comment) + { + WriteComment (writer, comment); + writer.WriteLine (); + } + + string MapToIRType (Type type) + { + if (basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + return typeDesc.Name; + } + + // if it's not a basic type, then it's an opaque pointer + return IRPointerType; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index da680d128f8..050d55efee0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -7,69 +7,14 @@ namespace Xamarin.Android.Tasks.LLVM.IR { - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; - using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; - using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - partial class LlvmIrModule { - sealed class BasicType - { - public readonly string Name; - public readonly ulong Size; - - public BasicType (string name, ulong size) - { - Name = name; - Size = size; - } - } - - const string IRPointerType = "ptr"; - - static readonly Dictionary basicTypeMap = new Dictionary { - { typeof (bool), new ("i8", 1) }, - { typeof (byte), new ("i8", 1) }, - { typeof (char), new ("i16", 2) }, - { typeof (sbyte), new ("i8", 1) }, - { typeof (short), new ("i16", 2) }, - { typeof (ushort), new ("i16", 2) }, - { typeof (int), new ("i32", 4) }, - { typeof (uint), new ("i32", 4) }, - { typeof (long), new ("i64", 8) }, - { typeof (ulong), new ("i64", 8) }, - { typeof (float), new ("float", 4) }, - { typeof (double), new ("double", 8) }, - { typeof (void), new ("void", 0) }, - }; - - public string FilePath { get; } - public string FileName { get; } - public LlvmIrModuleTarget Target { get; } + public List? ExternalFunctions => externalFunctions?.Values.ToList (); + public List? AttributeSets => attributeSets?.Values.ToList (); Dictionary? attributeSets; Dictionary? externalFunctions; - protected LlvmIrModule (string filePath, LlvmIrModuleTarget target) - { - FilePath = Path.GetFullPath (filePath); - FileName = Path.GetFileName (filePath); - Target = target; - } - - public static LlvmIrModule Create (AndroidTargetArch arch, StreamWriter output, string fileName) - { - return arch switch { - AndroidTargetArch.Arm => new LlvmIrModule (fileName, new LlvmIrModuleArmV7a ()), - AndroidTargetArch.Arm64 => new LlvmIrModule (fileName, new LlvmIrModuleAArch64 ()), - AndroidTargetArch.X86 => new LlvmIrModule (fileName, new LlvmIrModuleX86 ()), - AndroidTargetArch.X86_64 => new LlvmIrModule (fileName, new LlvmIrModuleX64 ()), - _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") - }; - } - /// /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to @@ -105,228 +50,8 @@ public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) return existingFunc; } - foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { - Target.SetParameterFlags (parameter); - } externalFunctions.Add (func, func); - return func; } - - public void Generate (TextWriter writer) - { - if (!String.IsNullOrEmpty (FilePath)) { - WriteCommentLine (writer, $" ModuleID = '{FileName}'"); - writer.WriteLine ($"source_filename = \"{FileName}\""); - } - - writer.WriteLine (Target.DataLayout.Render ()); - writer.WriteLine ($"target triple = \"{Target.Triple}\""); - writer.WriteLine (); - WriteExternalFunctionDeclarations (writer); - - // Bottom of file - WriteAttributeSets (writer); - } - - // - // Functions syntax: https://llvm.org/docs/LangRef.html#functions - // - void WriteExternalFunctionDeclarations (TextWriter writer) - { - if (externalFunctions == null || externalFunctions.Count == 0) { - return; - } - - List list = externalFunctions.Values.ToList (); - list.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); - - foreach (LlvmIrFunction func in list) { - WriteFunctionAttributesComment (writer, func); - writer.Write ("declare "); - WriteFunctionDeclarationLeadingDecorations (writer, func); - WriteFunctionSignature (writer, func, writeParameterNames: false); - WriteFunctionDeclarationTrailingDecorations (writer, func); - writer.WriteLine (); - } - } - - void WriteFunctionAttributesComment (TextWriter writer, LlvmIrFunction func) - { - if (func.AttributeSet == null) { - return; - } - - writer.WriteLine (); - WriteCommentLine (writer, $"Function attributes: {func.AttributeSet.Render ()}"); - } - - void WriteFunctionDeclarationLeadingDecorations (TextWriter writer, LlvmIrFunction func) - { - WriteFunctionLeadingDecorations (writer, func, declaration: true); - } - - void WriteFunctionDefinitionLeadingDecorations (TextWriter writer, LlvmIrFunction func) - { - WriteFunctionLeadingDecorations (writer, func, declaration: false); - } - - void WriteFunctionLeadingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) - { - if (func.Linkage != LlvmIrLinkage.Default) { - writer.Write (llvmLinkage[func.Linkage]); - writer.Write (' '); - } - - if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { - writer.Write (llvmRuntimePreemption[func.RuntimePreemption]); - writer.Write (' '); - } - - if (func.Visibility != LlvmIrVisibility.Default) { - writer.Write (llvmVisibility[func.Visibility]); - writer.Write (' '); - } - } - - void WriteFunctionDeclarationTrailingDecorations (TextWriter writer, LlvmIrFunction func) - { - WriteFunctionTrailingDecorations (writer, func, declaration: true); - } - - void WriteFunctionDefinitionTrailingDecorations (TextWriter writer, LlvmIrFunction func) - { - WriteFunctionTrailingDecorations (writer, func, declaration: false); - } - - void WriteFunctionTrailingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) - { - if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { - writer.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); - } - - if (func.AttributeSet != null) { - writer.Write ($" #{func.AttributeSet.Number}"); - } - } - - void WriteFunctionSignature (TextWriter writer, LlvmIrFunction func, bool writeParameterNames) - { - writer.Write (MapToIRType (func.Signature.ReturnType)); - writer.Write (" @"); - writer.Write (func.Signature.Name); - writer.Write ('('); - - bool first = true; - foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { - if (!first) { - writer.Write (", "); - } else { - first = false; - } - - writer.Write (MapToIRType (parameter.Type)); - WriteParameterAttributes (writer, parameter); - if (writeParameterNames) { - if (String.IsNullOrEmpty (parameter.Name)) { - throw new InvalidOperationException ($"Internal error: parameter must have a name"); - } - writer.Write (" %"); // Function arguments are always local variables - writer.Write (parameter.Name); - } - } - - writer.Write (')'); - } - - void WriteParameterAttributes (TextWriter writer, LlvmIrFunctionParameter parameter) - { - var attributes = new List (); - if (AttributeIsSet (parameter.ImmArg)) { - attributes.Add ("immarg"); - } - - if (AttributeIsSet (parameter.AllocPtr)) { - attributes.Add ("allocptr"); - } - - if (AttributeIsSet (parameter.NoCapture)) { - attributes.Add ("nocapture"); - } - - if (AttributeIsSet (parameter.NonNull)) { - attributes.Add ("nonnull"); - } - - if (AttributeIsSet (parameter.NoUndef)) { - attributes.Add ("noundef"); - } - - if (AttributeIsSet (parameter.ReadNone)) { - attributes.Add ("readnone"); - } - - if (AttributeIsSet (parameter.SignExt)) { - attributes.Add ("signext"); - } - - if (AttributeIsSet (parameter.ZeroExt)) { - attributes.Add ("zeroext"); - } - - if (parameter.Align.HasValue) { - attributes.Add ($"align({parameter.Align.Value})"); - } - - if (parameter.Dereferenceable.HasValue) { - attributes.Add ($"dereferenceable({parameter.Dereferenceable.Value})"); - } - - if (attributes.Count == 0) { - return; - } - - writer.Write (' '); - writer.Write (String.Join (" ", attributes)); - - bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; - } - - void WriteAttributeSets (TextWriter writer) - { - if (attributeSets == null || attributeSets.Count == 0) { - return; - } - - writer.WriteLine (); - List list = attributeSets.Keys.ToList (); - list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); - - foreach (LlvmIrFunctionAttributeSet attrSet in list) { - writer.WriteLine ($"attributes #{attrSet.Number} {{ {attrSet.Render ()} }}"); - } - } - - void WriteComment (TextWriter writer, string comment) - { - writer.Write (';'); - writer.Write (comment); - } - - void WriteCommentLine (TextWriter writer, string comment) - { - WriteComment (writer, comment); - writer.WriteLine (); - } - - string MapToIRType (Type type) - { - if (basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { - return typeDesc.Name; - } - - // if it's not a basic type, then it's an opaque pointer - return IRPointerType; - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index 71836bab57f..69350e245c3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -1,22 +1,23 @@ using System; using System.Collections.Generic; +using System.IO; using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVM.IR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks.New { // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - partial class MarshalMethodsNativeAssemblyGenerator + partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { LlvmIrFunction? mm_trace_func_enter; LlvmIrFunction? mm_trace_func_leave; LlvmIrFunction? llvm_lifetime_start; LlvmIrFunction? llvm_lifetime_end; - protected override void Write (LlvmIrModule module) + protected override void Construct (LlvmIrModule module) { InitTracing (module); } @@ -60,13 +61,13 @@ void InitTracing (LlvmIrModule module) }; var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( - name: mm_trace_func_enter_name, + name: "_mm_trace_func_enter", returnType: typeof(void), parameters: mm_trace_func_enter_or_leave_params ); mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_leave_name, mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); } LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) @@ -90,25 +91,11 @@ LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) new StackProtectorBufferSizeFunctionAttribute (8), }; - switch (module.Target.TargetArch) { - case AndroidTargetArch.Arm64: - ret.Add (new FramePointerFunctionAttribute ("non-leaf")); - break; + ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); + ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); + ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); + ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); - case AndroidTargetArch.Arm: - ret.Add (new FramePointerFunctionAttribute ("all")); - break; - - case AndroidTargetArch.X86: - case AndroidTargetArch.X86_64: - ret.Add (new FramePointerFunctionAttribute ("none")); - break; - - default: - throw new InvalidOperationException ($"Internal error: unsupported target architecture {module.Target.TargetArch}"); - } - - module.Target.AddTargetSpecificAttributes (ret); return ret; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs index 1945bf8d58c..b9ca6216a39 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs @@ -1,11 +1,10 @@ using Xamarin.Android.Tasks.LLVM.IR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks.New { - partial class TypeMappingDebugNativeAssemblyGenerator + class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer { - protected override void Write (LlvmIrModule module) - { - } + protected override void Construct (LlvmIrModule module) + {} } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs index 1a3a738ac59..70b86f37b75 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs @@ -1,11 +1,10 @@ using Xamarin.Android.Tasks.LLVM.IR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks.New { - partial class TypeMappingReleaseNativeAssemblyGenerator + partial class TypeMappingReleaseNativeAssemblyGenerator : LlvmIrComposer { - protected override void Write (LlvmIrModule module) - { - } + protected override void Construct (LlvmIrModule module) + {} } } From 3d5e0733bf719cd6897fb1f9e3ba67f52079d0ca Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 26 May 2023 22:12:18 +0200 Subject: [PATCH 35/60] New generator is alive --- .../Tasks/GeneratePackageManagerJava.cs | 38 +++ ...cationConfigNativeAssemblyGenerator.New.cs | 53 +++- .../LlvmIrGenerator/IStructureInfo.New.cs | 16 + .../LlvmIrGenerator/LlvmIrComposer.New.cs | 3 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 2 +- .../LlvmIrGenerator.New.Constants.cs | 49 +-- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 298 +++++++++++++++++- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 96 +++++- .../LlvmIrGenerator/LlvmIrVariable.cs | 21 +- .../LlvmIrGenerator/LlvmIrVariableOptions.cs | 2 +- .../MemberInfoUtilities.New.cs | 60 ++++ .../LlvmIrGenerator/StructureInfo.New.cs | 71 +++++ .../StructureMemberInfo.New.cs | 105 ++++++ .../LlvmIrGenerator/TypeUtilities.New.cs | 75 +++++ 14 files changed, 844 insertions(+), 45 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 5a2496719f2..66451cc112c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -406,6 +406,37 @@ void AddEnvironment () }; appConfigAsmGen.Init (); + var appConfigAsmGenNew = new New.ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { + UsesMonoAOT = usesMonoAOT, + UsesMonoLLVM = EnableLLVM, + UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, + MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), + AotEnableLazyLoad = AndroidAotEnableLazyLoad, + AndroidPackageName = AndroidPackageName, + BrokenExceptionTransitions = environmentParser.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 = (New.MonoComponent)monoComponents, + NativeLibraries = uniqueNativeLibraries, + HaveAssemblyStore = UseAssemblyStore, + AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, + JNIEnvInitializeToken = jnienv_initialize_method_token, + JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, + JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, + JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, + MarshalMethodsEnabled = EnableMarshalMethods, + }; + LLVM.IR.LlvmIrModule appConfigModule = appConfigAsmGenNew.Construct (); + var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build); MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; @@ -436,6 +467,13 @@ void AddEnvironment () Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); } + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + string newEnvironmentLlFilePath = Path.Combine (Path.GetDirectoryName (environmentLlFilePath), $"new-{Path.GetFileName (environmentLlFilePath)}"); + appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, newEnvironmentLlFilePath); + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, newEnvironmentLlFilePath); + } + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); sw.Flush (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index f0dd447d99d..d9dd4aa1656 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; using System.IO; +using Java.Interop.Tools.TypeNameMappings; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks.New { + // TODO: remove these aliases once the refactoring is done using ApplicationConfig = Xamarin.Android.Tasks.ApplicationConfig; + using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; // Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh [Flags] @@ -60,7 +66,52 @@ sealed class DSOCacheEntry // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x015E6972616D58; + StructureInfo? applicationConfigStructureInfo; + StructureInfo? dsoCacheEntryStructureInfo; + + public bool UsesMonoAOT { get; set; } + public bool UsesMonoLLVM { get; set; } + public bool UsesAssemblyPreload { get; set; } + public string MonoAOTMode { get; set; } + public bool AotEnableLazyLoad { get; set; } + public string AndroidPackageName { get; set; } + public bool BrokenExceptionTransitions { get; set; } + public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; } + public bool InstantRunEnabled { get; set; } + public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } + public bool HaveRuntimeConfigBlob { get; set; } + public bool HaveAssemblyStore { get; set; } + public int NumberOfAssembliesInApk { get; set; } + public int NumberOfAssemblyStoresInApks { get; set; } + public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL + public int AndroidRuntimeJNIEnvToken { get; set; } + public int JNIEnvInitializeToken { get; set; } + public int JNIEnvRegisterJniNativesToken { get; set; } + public int JniRemappingReplacementTypeCount { get; set; } + public int JniRemappingReplacementMethodIndexEntryCount { get; set; } + public MonoComponent MonoComponents { get; set; } + public PackageNamingPolicy PackageNamingPolicy { get; set; } + public List NativeLibraries { get; set; } + public bool MarshalMethodsEnabled { get; set; } + + public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) + { + } + protected override void Construct (LlvmIrModule module) - {} + { + MapStructures (module); + + var format_tag = new LlvmIrGlobalVariable (FORMAT_TAG.GetType (), "format_tag") { + Value = FORMAT_TAG, + }; + module.Add (format_tag); + } + + void MapStructures (LlvmIrModule module) + { + applicationConfigStructureInfo = module.MapStructure (); + dsoCacheEntryStructureInfo = module.MapStructure (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs new file mode 100644 index 00000000000..5648815252a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + interface IStructureInfo + { + Type Type { get; } + ulong Size { get; } + int MaxFieldAlignment { get; } + string Name { get; } + string NativeTypeDesignator { get; } + IList Members { get; } + bool IsOpaque { get; } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs index f6dd7d0d8ad..75e6de91548 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.IO.Hashing; -using System.Text; using Xamarin.Android.Tools; @@ -17,6 +15,7 @@ public LlvmIrModule Construct () { var module = new LlvmIrModule (); Construct (module); + module.AfterConstruction (); constructed = true; return module; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index 8c6e09ad3bd..8dc3184fe10 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -399,7 +399,7 @@ public LlvmIrFunctionLocalVariable EmitIcmpInstruction (LlvmIrFunction function, } var sb = new StringBuilder (); - CodeRenderType (variable, sb); + CodeRenderType (variable, sb, ignoreNativePointer: true); string variableType = sb.ToString (); LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (variable.Type, resultVariableName); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs index 9e089bcc77a..d82dee21f60 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs @@ -8,43 +8,50 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + using LlvmIrWritability = LLVMIR.LlvmIrWritability; partial class LlvmIrGenerator { - // 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#linkage-types static readonly Dictionary llvmLinkage = new Dictionary { - { LlvmIrLinkage.Default, String.Empty }, - { LlvmIrLinkage.Private, "private" }, - { LlvmIrLinkage.Internal, "internal" }, + { 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" }, + { 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.Default, String.Empty }, { LlvmIrRuntimePreemption.DSOPreemptable, "dso_preemptable" }, - { LlvmIrRuntimePreemption.DSOLocal, "dso_local" }, + { 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.Default, "default" }, + { LlvmIrVisibility.Hidden, "hidden" }, { LlvmIrVisibility.Protected, "protected" }, }; + + // https://llvm.org/docs/LangRef.html#global-variables + static readonly Dictionary llvmAddressSignificance = new Dictionary { + { LlvmIrAddressSignificance.Default, String.Empty }, + { LlvmIrAddressSignificance.Unnamed, "unnamed_addr" }, + { LlvmIrAddressSignificance.LocalUnnamed, "local_unnamed_addr" }, + }; + + // https://llvm.org/docs/LangRef.html#global-variables + static readonly Dictionary llvmWritability = new Dictionary { + { LlvmIrWritability.Constant, "constant" }, + { LlvmIrWritability.Writable, "global" }, + }; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index a8576270f1a..ceba769737d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Globalization; using Xamarin.Android.Tools; @@ -11,25 +12,31 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; + using LlvmIrWritability = LLVMIR.LlvmIrWritability; + using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; partial class LlvmIrGenerator { + const char IndentChar = '\t'; + sealed class BasicType { public readonly string Name; public readonly ulong Size; + public readonly bool IsNumeric; - public BasicType (string name, ulong size) + public BasicType (string name, ulong size, bool isNumeric = true) { Name = name; Size = size; + IsNumeric = isNumeric; } } - const string IRPointerType = "ptr"; + public const string IRPointerType = "ptr"; static readonly Dictionary basicTypeMap = new Dictionary { - { typeof (bool), new ("i8", 1) }, + { typeof (bool), new ("i8", 1, isNumeric: false) }, { typeof (byte), new ("i8", 1) }, { typeof (char), new ("i16", 2) }, { typeof (sbyte), new ("i8", 1) }, @@ -41,13 +48,15 @@ public BasicType (string name, ulong size) { typeof (ulong), new ("i64", 8) }, { typeof (float), new ("float", 4) }, { typeof (double), new ("double", 8) }, - { typeof (void), new ("void", 0) }, + { typeof (void), new ("void", 0, isNumeric: false) }, }; public string FilePath { get; } public string FileName { get; } LlvmIrModuleTarget target; + int currentIndentLevel = 0; + string currentIndent = String.Empty; protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) { @@ -76,13 +85,212 @@ public void Generate (TextWriter writer, LlvmIrModule module) writer.WriteLine (target.DataLayout.Render ()); writer.WriteLine ($"target triple = \"{target.Triple}\""); - writer.WriteLine (); - WriteExternalFunctionDeclarations (writer, module); + WriteStructureDeclarations (writer, module); + WriteGlobalVariables (writer, module); // Bottom of file + WriteExternalFunctionDeclarations (writer, module); WriteAttributeSets (writer, module); } + void WriteGlobalVariables (TextWriter writer, LlvmIrModule module) + { + if (module.GlobalVariables == null || module.GlobalVariables.Count == 0) { + return; + } + + writer.WriteLine (); + foreach (LlvmIrGlobalVariable gv in module.GlobalVariables) { + writer.Write ('@'); + writer.Write (gv.Name); + writer.Write (" = "); + + LlvmIrVariableOptions options = gv.Options ?? LlvmIrGlobalVariable.DefaultOptions; + WriteLinkage (writer, options.Linkage); + WritePreemptionSpecifier (writer, options.RuntimePreemption); + WriteVisibility (writer, options.Visibility); + WriteAddressSignificance (writer, options.AddressSignificance); + WriteWritability (writer, options.Writability); + + WriteTypeAndValue (writer, gv, out ulong size, out bool isPointer); + writer.Write (", align "); + writer.Write ((isPointer ? target.NativePointerSize : size).ToString (CultureInfo.InvariantCulture)); + } + } + + void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer) + { + string irType = MapToIRType (variable.Type, out size, out isPointer); + writer.Write (irType); + writer.Write (' '); + + if (variable.Value == null) { + if (isPointer) { + writer.Write ("null"); + } + + throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); + } + + Type valueType = variable.Value.GetType (); + if (valueType != variable.Type) { + throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); + } + + WriteValue (writer, valueType, variable.Value); + } + + void WriteValue (TextWriter writer, Type valueType, object value) + { + if (IsNumeric (valueType)) { + writer.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } + + throw new NotSupportedException ($"Internal error: value type '{valueType}' is unsupported"); + } + + void WriteLinkage (TextWriter writer, LlvmIrLinkage linkage) + { + if (linkage == LlvmIrLinkage.Default) { + return; + } + + try { + WriteAttribute (writer, llvmLinkage[linkage]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); + } + } + + void WriteWritability (TextWriter writer, LlvmIrWritability writability) + { + try { + WriteAttribute (writer, llvmWritability[writability]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); + } + } + + void WriteAddressSignificance (TextWriter writer, LlvmIrAddressSignificance addressSignificance) + { + if (addressSignificance == LlvmIrAddressSignificance.Default) { + return; + } + + try { + WriteAttribute (writer, llvmAddressSignificance[addressSignificance]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported address significance '{addressSignificance}'", ex); + } + } + + void WriteVisibility (TextWriter writer, LlvmIrVisibility visibility) + { + if (visibility == LlvmIrVisibility.Default) { + return; + } + + try { + WriteAttribute (writer, llvmVisibility[visibility]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported visibility '{visibility}'", ex); + } + } + + void WritePreemptionSpecifier (TextWriter writer, LlvmIrRuntimePreemption preemptionSpecifier) + { + if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { + return; + } + + try { + WriteAttribute (writer, llvmRuntimePreemption[preemptionSpecifier]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported preemption specifier '{preemptionSpecifier}'", ex); + } + } + + /// + /// Write attribute named in followed by a single space + /// + void WriteAttribute (TextWriter writer, string attr) + { + writer.Write (attr); + writer.Write (' '); + } + + void WriteStructureDeclarations (TextWriter writer, LlvmIrModule module) + { + if (module.Structures == null || module.Structures.Count == 0) { + Console.WriteLine (" #1"); + return; + } + + foreach (IStructureInfo si in module.Structures) { + Console.WriteLine (" #2"); + writer.WriteLine (); + WriteStructureDeclaration (writer, si); + } + } + + void WriteStructureDeclaration (TextWriter writer, IStructureInfo si) + { + // $"%{typeDesignator}.{name} = type " + writer.Write ('%'); + writer.Write (si.NativeTypeDesignator); + writer.Write ('.'); + writer.Write (si.Name); + writer.Write (" = type "); + + if (si.IsOpaque) { + writer.WriteLine ("opaque"); + } else { + writer.WriteLine ('{'); + } + + if (si.IsOpaque) { + return; + } + + IncreaseIndent (); + for (int i = 0; i < si.Members.Count; i++) { + StructureMemberInfo info = si.Members[i]; + string nativeType = MapManagedTypeToNative (info.MemberType); + + // TODO: nativeType can be an array, update to indicate that (and get the size) + string arraySize; + if (info.IsNativeArray) { + arraySize = $"[{info.ArrayElements}]"; + } else { + arraySize = String.Empty; + } + + var comment = $" {nativeType} {info.Info.Name}{arraySize}"; + WriteStructureDeclarationField (info.IRType, comment, i == si.Members.Count - 1); + } + DecreaseIndent (); + + writer.WriteLine ('}'); + + void WriteStructureDeclarationField (string typeName, string comment, bool last) + { + writer.Write (currentIndent); + writer.Write (typeName); + if (!last) { + writer.Write (", "); + } else { + writer.Write (' '); + } + + if (!String.IsNullOrEmpty (comment)) { + WriteCommentLine (writer, comment); + } else { + writer.WriteLine (); + } + } + } + // // Functions syntax: https://llvm.org/docs/LangRef.html#functions // @@ -92,8 +300,7 @@ void WriteExternalFunctionDeclarations (TextWriter writer, LlvmIrModule module) return; } - module.ExternalFunctions.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); - + writer.WriteLine (); foreach (LlvmIrFunction func in module.ExternalFunctions) { // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) ILlvmIrFunctionState funcState = func.SaveState (); @@ -261,8 +468,6 @@ void WriteAttributeSets (TextWriter writer, LlvmIrModule module) } writer.WriteLine (); - module.AttributeSets.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); - foreach (LlvmIrFunctionAttributeSet attrSet in module.AttributeSets) { // Must not modify the original set, it is shared with other targets. var targetSet = new LlvmIrFunctionAttributeSet (attrSet); @@ -289,13 +494,82 @@ void WriteCommentLine (TextWriter writer, string comment) writer.WriteLine (); } - string MapToIRType (Type type) + void IncreaseIndent () + { + currentIndentLevel++; + currentIndent = MakeIndentString (); + } + + void DecreaseIndent () + { + if (currentIndentLevel > 0) { + currentIndentLevel--; + } + currentIndent = MakeIndentString (); + } + + string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; + + 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 a managed to its C++ counterpart. Only primitive types, + /// string and IntPtr are supported. + /// + 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 (); + } + + static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; + + public static string MapToIRType (Type type) + { + return MapToIRType (type, out _, out _); + } + + public static string MapToIRType (Type type, out ulong size) + { + return MapToIRType (type, out size, out _); + } + + public static string MapToIRType (Type type, out ulong size, out bool isPointer) { - if (basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + type = GetActualType (type); + if (!type.IsNativePointer () && basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + size = typeDesc.Size; + isPointer = false; return typeDesc.Name; } // if it's not a basic type, then it's an opaque pointer + size = 0; // Will be determined by the specific target architecture class + isPointer = true; return IRPointerType; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 050d55efee0..6033c552e16 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -1,19 +1,56 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using Xamarin.Android.Tools; - namespace Xamarin.Android.Tasks.LLVM.IR { partial class LlvmIrModule { - public List? ExternalFunctions => externalFunctions?.Values.ToList (); - public List? AttributeSets => attributeSets?.Values.ToList (); + public IList? ExternalFunctions { get; private set; } + public IList? AttributeSets { get; private set; } + public IList? Structures { get; private set; } + public IList? GlobalVariables { get; private set; } Dictionary? attributeSets; Dictionary? externalFunctions; + Dictionary? structures; + + List? globalVariables; + + /// + /// Perform any tasks that need to be done after construction is complete. + /// + public void AfterConstruction () + { + if (externalFunctions != null) { + List list = externalFunctions.Values.ToList (); + list.Sort ((LlvmIrFunction a, LlvmIrFunction b) => a.Signature.Name.CompareTo (b.Signature.Name)); + ExternalFunctions = list.AsReadOnly (); + } + + if (attributeSets != null) { + List list = attributeSets.Values.ToList (); + list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); + AttributeSets = list.AsReadOnly (); + } + + if (structures != null) { + List list = structures.Values.ToList (); + list.Sort ((IStructureInfo a, IStructureInfo b) => a.Name.CompareTo (b.Name)); + Structures = list.AsReadOnly (); + } + + GlobalVariables = globalVariables?.AsReadOnly (); + } + + public void Add (LlvmIrGlobalVariable variable) + { + if (globalVariables == null) { + globalVariables = new List (); + } + + globalVariables.Add (variable); + } /// /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed @@ -53,5 +90,54 @@ public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) externalFunctions.Add (func, func); return func; } + + /// + /// 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 () + { + Console.WriteLine ($"Mapping structure: {typeof(T)}"); + if (structures == null) { + structures = new Dictionary (); + } + + Type t = typeof(T); + if (!t.IsClass && !t.IsValueType) { + throw new InvalidOperationException ($"{t} must be a class or a struct"); + } + + // TODO: check if already there + if (structures.TryGetValue (t, out IStructureInfo sinfo)) { + return (StructureInfo)sinfo; + } + + var ret = new StructureInfo (this); + structures.Add (t, ret); + + return ret; + } + + internal IStructureInfo GetStructureInfo (Type type) + { + if (structures == null) { + throw new InvalidOperationException ($"Internal error: no structures have been mapped, cannot return info for {type}"); + } + + foreach (var kvp in structures) { + IStructureInfo si = kvp.Value; + if (si.Type != type) { + continue; + } + + return si; + } + + throw new InvalidOperationException ($"Unmapped structure {type}"); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index c39973fd166..7208f9fe2bb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -3,6 +3,9 @@ namespace Xamarin.Android.Tasks.LLVM.IR { + // TODO: remove these aliases once the refactoring is done + using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; + abstract class LlvmIrVariable : IEquatable { public abstract bool Global { get; } @@ -10,6 +13,7 @@ abstract class LlvmIrVariable : IEquatable public string? Name { get; protected set; } public Type Type { get; } + public object? Value { get; set; } /// /// Both global and local variables will want their names to matter in equality checks, but function @@ -95,20 +99,33 @@ public void AssignNumber (uint n) class LlvmIrGlobalVariable : LlvmIrVariable { + /// + /// By default a global variable is constant and exported. + /// + public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; + public override bool Global => true; public override string NamePrefix => "@"; + /// + /// Specify variable options. If omitted, it defaults to . + /// + /// + public LlvmIrVariableOptions? Options { get; set; } + /// /// Constructs a local variable. is translated to one of the LLVM IR first class types (see /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. is required because global variables must not be unnamed. + /// is treated as an opaque pointer type. is required because global variables must be named. /// - public LlvmIrGlobalVariable (Type type, string name) + public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) : base (type, name) { if (String.IsNullOrEmpty (name)) { throw new ArgumentException ("must not be null or empty", nameof (name)); } + + Options = options; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs index 0c0b3c121b4..80594e60203 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -83,7 +83,7 @@ class LlvmIrVariableOptions }; public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; - public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; + public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.DSOLocal; public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.Default; public LlvmIrWritability Writability { get; set; } = LlvmIrWritability.Writable; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs new file mode 100644 index 00000000000..65873d35648 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs @@ -0,0 +1,60 @@ +using System; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: remove these aliases once the refactoring is done + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + + 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 ShouldBeIgnored (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + return attr != null && attr.Ignore; + } + + 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/StructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs new file mode 100644 index 00000000000..2be6ce2d1af --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: add cache for members and data provider info + sealed class StructureInfo : IStructureInfo + { + Type type; + + public Type Type => type; + public string Name { get; } = String.Empty; + public ulong Size { get; } + public IList Members { get; } = new List (); + public NativeAssemblerStructContextDataProvider? DataProvider { get; } + public int MaxFieldAlignment { get; private set; } = 0; + public bool HasStrings { get; private set; } + public bool HasPreAllocatedBuffers { get; private set; } + + public bool IsOpaque => Members.Count == 0; + public string NativeTypeDesignator { get; } + + public StructureInfo (LlvmIrModule module) + { + type = typeof(T); + Name = type.GetShortName (); + Size = GatherMembers (type, module); + DataProvider = type.GetDataProvider (); + NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; + } + + ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) + { + ulong size = 0; + foreach (MemberInfo mi in type.GetMembers ()) { + if (mi.ShouldBeIgnored () || (!(mi is FieldInfo) && !(mi is PropertyInfo))) { + continue; + } + + var info = new StructureMemberInfo (mi, module); + if (storeMembers) { + Members.Add (info); + size += info.Size; + + if ((int)info.Alignment > MaxFieldAlignment) { + MaxFieldAlignment = (int)info.Alignment; + } + } + + if (!HasStrings && info.MemberType == typeof (string)) { + HasStrings = true; + } + + if (!HasPreAllocatedBuffers && info.Info.IsNativePointerToPreallocatedBuffer (out ulong _)) { + HasPreAllocatedBuffers = true; + } + + // If we encounter an embedded struct (as opposed to a pointer), we need to descend and check if that struct contains any strings or buffers, but we + // do NOT want to store any members while doing that, as the struct should have been mapped by the composer previously. + // The presence of strings/buffers is important at the generation time as it is used to decide whether we need separate stream writers for them and + // if the owning structure does **not** have any of those, the generated code would be invalid + if (info.IsIRStruct ()) { + GatherMembers (info.MemberType, module, storeMembers: false); + } + } + + return size; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs new file mode 100644 index 00000000000..0f4e215db79 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs @@ -0,0 +1,105 @@ +using System; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + 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 or a struct + /// + public ulong Size { get; } + public ulong Alignment { 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, LlvmIrModule module) + { + Info = mi; + + MemberType = mi switch { + FieldInfo fi => fi.FieldType, + PropertyInfo pi => pi.PropertyType, + _ => throw new InvalidOperationException ($"Unsupported member type {mi}") + }; + + ulong size = 0; + bool isPointer = false; + if (MemberType != typeof(string) && !MemberType.IsArray && (MemberType.IsStructure () || MemberType.IsClass)) { + IRType = $"%struct.{MemberType.GetShortName ()}"; + // TODO: figure out how to get structure size if it isn't a pointer + } else { + IRType = LlvmIrGenerator.MapToIRType (MemberType, out size, out isPointer); + } + IsNativePointer = isPointer; + + if (!IsNativePointer) { + IsNativePointer = mi.IsNativePointer (); + if (IsNativePointer) { + IRType = LlvmIrGenerator.IRPointerType; + } + } + + BaseTypeSize = size; + ArrayElements = 0; + IsInlineArray = false; + NeedsPadding = false; + Alignment = 0; + + if (IsNativePointer) { + size = 0; // Real size will be determined when code is generated and we know the target architecture + } else if (mi.IsInlineArray ()) { + IsInlineArray = true; + IsNativeArray = true; + NeedsPadding = mi.InlineArrayNeedsPadding (); + int arrayElements = mi.GetInlineArraySize (); + if (arrayElements < 0) { + arrayElements = GetArraySizeFromProvider (MemberType.GetDataProvider (), mi.Name); + } + + if (arrayElements < 0) { + throw new InvalidOperationException ($"Array cannot have negative size (got {arrayElements})"); + } + + IRType = $"[{arrayElements} x {IRType}]"; + ArrayElements = (ulong)arrayElements; + } else if (this.IsIRStruct ()) { + IStructureInfo si = module.GetStructureInfo (MemberType); + size = si.Size; + Alignment = (ulong)si.MaxFieldAlignment; + } + + if (MemberType.IsArray && !IsInlineArray) { + throw new InvalidOperationException ("Out of line arrays in structures aren't currently supported"); + } + + Size = size; + if (Alignment == 0) { + Alignment = size; + } + } + + 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/TypeUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs new file mode 100644 index 00000000000..8d1a7c93bd2 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs @@ -0,0 +1,75 @@ +using System; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + 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.IsArray && + type != typeof (decimal) && + type != typeof (DateTime) && + type != typeof (object); + } + + public static bool IsIRStruct (this StructureMemberInfo smi) + { + Type type = smi.MemberType; + + // type.IsStructure() handles checks for primitive types, enums etc + return + type != typeof(string) && + !smi.Info.IsInlineArray () && + !smi.Info.IsNativePointer () && + (type.IsStructure () || type.IsClass); + } + + public static NativeAssemblerStructContextDataProvider? GetDataProvider (this Type t) + { + var attr = t.GetCustomAttribute (); + if (attr == null) { + return null; + } + + return Activator.CreateInstance (attr.Type) as NativeAssemblerStructContextDataProvider; + } + + public static bool IsNativeClass (this Type t) + { + var attr = t.GetCustomAttribute (); + return attr != null; + } + } +} From ec099cf9fc8434c3d49d6a539b84f7ad9135c086 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 29 May 2023 23:23:37 +0200 Subject: [PATCH 36/60] Data generation progressing nicely. Added a way to generate code for "generic" arrays. Nested arrays probably won't work, but we don't use them so I'm not sweating it. --- .../Tasks/GeneratePackageManagerJava.cs | 11 +- ...cationConfigNativeAssemblyGenerator.New.cs | 124 +++++++- .../LlvmIrArrayVariableInfo.cs | 18 ++ .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 282 ++++++++++++++++-- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 166 +++++++++++ .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 5 + .../LlvmIrGenerator/LlvmIrModuleX64.cs | 14 + .../LlvmIrGenerator/LlvmIrStringGroup.cs | 16 + .../LlvmIrStringManager.New.cs | 63 ++++ .../LlvmIrGenerator/LlvmIrVariable.cs | 25 +- .../LlvmIrGenerator/LlvmIrVariableOptions.cs | 1 + .../LlvmIrGenerator/StructureInstance.New.cs | 40 +++ .../LlvmIrGenerator/TypeUtilities.New.cs | 27 ++ src/monodroid/jni/application_dso_stub.cc | 4 +- 14 files changed, 763 insertions(+), 33 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 66451cc112c..3af27dd41d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -469,9 +469,14 @@ void AddEnvironment () using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { string newEnvironmentLlFilePath = Path.Combine (Path.GetDirectoryName (environmentLlFilePath), $"new-{Path.GetFileName (environmentLlFilePath)}"); - appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, newEnvironmentLlFilePath); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, newEnvironmentLlFilePath); + try { + appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, newEnvironmentLlFilePath); + } catch { + throw; + } finally { + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, newEnvironmentLlFilePath); + } } using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index d9dd4aa1656..083a6fc9dc8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using Java.Interop.Tools.TypeNameMappings; @@ -11,7 +12,6 @@ namespace Xamarin.Android.Tasks.New { // TODO: remove these aliases once the refactoring is done using ApplicationConfig = Xamarin.Android.Tasks.ApplicationConfig; - using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; // Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh [Flags] @@ -66,6 +66,12 @@ sealed class DSOCacheEntry // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x015E6972616D58; + SortedDictionary ? environmentVariables; + SortedDictionary ? systemProperties; + TaskLoggingHelper log; + StructureInstance? application_config; + List? dsoCache; + StructureInfo? applicationConfigStructureInfo; StructureInfo? dsoCacheEntryStructureInfo; @@ -96,16 +102,126 @@ sealed class DSOCacheEntry public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) { + if (environmentVariables != null) { + this.environmentVariables = new SortedDictionary (environmentVariables, StringComparer.Ordinal); + } + + if (systemProperties != null) { + this.systemProperties = new SortedDictionary (systemProperties, StringComparer.Ordinal); + } + + this.log = log; } protected override void Construct (LlvmIrModule module) { MapStructures (module); - var format_tag = new LlvmIrGlobalVariable (FORMAT_TAG.GetType (), "format_tag") { - Value = FORMAT_TAG, + module.AddGlobalVariable (FORMAT_TAG.GetType (), "format_tag", FORMAT_TAG); + module.AddGlobalVariable (typeof(string), "mono_aot_mode_name", MonoAOTMode); + + var envVars = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_environment_variables") { + Value = environmentVariables, + }; + module.Add (envVars, "env", "Application environment variables"); + + var sysProps = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_system_properties") { + Value = systemProperties, }; - module.Add (format_tag); + module.Add (sysProps, "sysprop", "System properties defined by the application"); + + dsoCache = InitDSOCache (); + var app_cfg = new ApplicationConfig { + uses_mono_llvm = UsesMonoLLVM, + uses_mono_aot = UsesMonoAOT, + aot_lazy_load = AotEnableLazyLoad, + uses_assembly_preload = UsesAssemblyPreload, + broken_exception_transitions = BrokenExceptionTransitions, + instant_run_enabled = InstantRunEnabled, + jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, + have_runtime_config_blob = HaveRuntimeConfigBlob, + have_assemblies_blob = HaveAssemblyStore, + marshal_methods_enabled = MarshalMethodsEnabled, + bound_stream_io_exception_type = (byte)BoundExceptionType, + package_naming_policy = (uint)PackageNamingPolicy, + environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), + system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2), + 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 = (uint)dsoCache.Count, + android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, + jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, + jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, + jni_remapping_replacement_type_count = (uint)JniRemappingReplacementTypeCount, + jni_remapping_replacement_method_index_entry_count = (uint)JniRemappingReplacementMethodIndexEntryCount, + mono_components_mask = (uint)MonoComponents, + android_package_name = AndroidPackageName, + }; + application_config = new StructureInstance (app_cfg); + //module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); + } + + List InitDSOCache () + { + var dsos = new List<(string name, string nameLabel, bool ignore)> (); + var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); + + foreach (ITaskItem item in NativeLibraries) { + string? name = item.GetMetadata ("ArchiveFileName"); + if (String.IsNullOrEmpty (name)) { + name = item.ItemSpec; + } + name = Path.GetFileName (name); + + if (nameCache.Contains (name)) { + continue; + } + + dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (log, item.ItemSpec))); + } + + var dsoCache = new List (); + var nameMutations = new List (); + + for (int i = 0; i < dsos.Count; i++) { + string name = dsos[i].name; + nameMutations.Clear(); + AddNameMutations (name); + // All mutations point to the actual library name, but have hash of the mutated one + foreach (string entryName in nameMutations) { + 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)); + } + } + + return dsoCache; + + void AddNameMutations (string name) + { + nameMutations.Add (name); + if (name.EndsWith (".dll.so", StringComparison.OrdinalIgnoreCase)) { + nameMutations.Add (Path.GetFileNameWithoutExtension (Path.GetFileNameWithoutExtension (name))!); + } else { + nameMutations.Add (Path.GetFileNameWithoutExtension (name)!); + } + + const string aotPrefix = "libaot-"; + if (name.StartsWith (aotPrefix, StringComparison.OrdinalIgnoreCase)) { + AddNameMutations (name.Substring (aotPrefix.Length)); + } + + const string libPrefix = "lib"; + if (name.StartsWith (libPrefix, StringComparison.OrdinalIgnoreCase)) { + AddNameMutations (name.Substring (libPrefix.Length)); + } + } } void MapStructures (LlvmIrModule module) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs new file mode 100644 index 00000000000..8dae975a1b5 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +sealed class LlvmIrArrayVariableInfo +{ + public readonly Type ElementType; + public readonly IList Entries; + public readonly object OriginalVariableValue; + + public LlvmIrArrayVariableInfo (Type elementType, IList entries, object originalVariableValue) + { + ElementType = elementType; + Entries = entries; + OriginalVariableValue = originalVariableValue; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index ceba769737d..ba59f919a54 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -1,7 +1,10 @@ using System; +using System.Buffers; +using System.Collections; using System.Collections.Generic; -using System.IO; using System.Globalization; +using System.IO; +using System.Text; using Xamarin.Android.Tools; @@ -89,10 +92,41 @@ public void Generate (TextWriter writer, LlvmIrModule module) WriteGlobalVariables (writer, module); // Bottom of file + WriteStrings (writer, module); WriteExternalFunctionDeclarations (writer, module); WriteAttributeSets (writer, module); } + void WriteStrings (TextWriter writer, LlvmIrModule module) + { + if (module.Strings == null || module.Strings.Count == 0) { + return; + } + + writer.WriteLine (); + WriteComment (writer, " Strings"); + + foreach (LlvmIrStringGroup group in module.Strings) { + writer.WriteLine (); + + if (!String.IsNullOrEmpty (group.Comment)) { + WriteCommentLine (writer, group.Comment); + } + + foreach (LlvmIrStringVariable info in group.Strings) { + string s = QuoteString ((string)info.Value, out ulong size); + + WriteGlobalVariableStart (writer, info); + writer.Write ('['); + writer.Write (size.ToString (CultureInfo.InvariantCulture)); + writer.Write (" x i8] c"); + writer.Write (s); + writer.Write (", align "); + writer.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); + } + } + } + void WriteGlobalVariables (TextWriter writer, LlvmIrModule module) { if (module.GlobalVariables == null || module.GlobalVariables.Count == 0) { @@ -101,27 +135,46 @@ void WriteGlobalVariables (TextWriter writer, LlvmIrModule module) writer.WriteLine (); foreach (LlvmIrGlobalVariable gv in module.GlobalVariables) { - writer.Write ('@'); - writer.Write (gv.Name); - writer.Write (" = "); + WriteGlobalVariable (writer, gv); + } + } + + public void WriteGlobalVariableStart (TextWriter writer, LlvmIrGlobalVariable variable) + { + writer.Write ('@'); + writer.Write (variable.Name); + writer.Write (" = "); + + LlvmIrVariableOptions options = variable.Options ?? LlvmIrGlobalVariable.DefaultOptions; + WriteLinkage (writer, options.Linkage); + WritePreemptionSpecifier (writer, options.RuntimePreemption); + WriteVisibility (writer, options.Visibility); + WriteAddressSignificance (writer, options.AddressSignificance); + WriteWritability (writer, options.Writability); + } - LlvmIrVariableOptions options = gv.Options ?? LlvmIrGlobalVariable.DefaultOptions; - WriteLinkage (writer, options.Linkage); - WritePreemptionSpecifier (writer, options.RuntimePreemption); - WriteVisibility (writer, options.Visibility); - WriteAddressSignificance (writer, options.AddressSignificance); - WriteWritability (writer, options.Writability); + public void WriteGlobalVariable (TextWriter writer, LlvmIrGlobalVariable variable) + { + WriteGlobalVariableStart (writer, variable); + WriteTypeAndValue (writer, variable, out ulong typeSize, out bool isPointer, out bool isAggregate); + writer.Write (", align "); - WriteTypeAndValue (writer, gv, out ulong size, out bool isPointer); - writer.Write (", align "); - writer.Write ((isPointer ? target.NativePointerSize : size).ToString (CultureInfo.InvariantCulture)); + ulong alignment; + if (isAggregate) { + uint count = GetAggregateValueElementCount (variable); + alignment = (ulong)target.GetAggregateAlignment ((int)typeSize, count * typeSize); + } else if (isPointer) { + alignment = target.NativePointerSize; + } else { + alignment = typeSize; } + + writer.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); } - void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer) + void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer, out bool isAggregate) { - string irType = MapToIRType (variable.Type, out size, out isPointer); - writer.Write (irType); + WriteType (writer, variable, out size, out isPointer, out isAggregate); writer.Write (' '); if (variable.Value == null) { @@ -132,24 +185,135 @@ void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong si throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); } - Type valueType = variable.Value.GetType (); - if (valueType != variable.Type) { + Type valueType; + if (variable.Value is LlvmIrVariable referencedVariable) { + valueType = referencedVariable.Type; + } else { + valueType = variable.Value.GetType (); + } + + if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); } - WriteValue (writer, valueType, variable.Value); + WriteValue (writer, valueType, variable); } - void WriteValue (TextWriter writer, Type valueType, object value) + uint GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value); + + uint GetAggregateValueElementCount (Type type, object? value) { + if (!IsArray (type)) { + throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); + } + + if (value == null) { + return 0; + } + + var info = (LlvmIrArrayVariableInfo)value; + return (uint)info.Entries.Count; + } + + void WriteType (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer, out bool isAggregate) + { + WriteType (writer, variable.Type, variable.Value, out size, out isPointer, out isAggregate); + } + + void WriteType (TextWriter writer, Type type, object? value, out ulong size, out bool isPointer, out bool isAggregate) + { + string irType; + + if (IsArray (type)) { + isAggregate = true; + irType = GetIRType (type, out size, out isPointer); + + writer.Write ('['); + writer.Write (GetAggregateValueElementCount (type, value).ToString (CultureInfo.InvariantCulture)); + writer.Write (" x "); + writer.Write (irType); + writer.Write (']'); + return; + } + + isAggregate = false; + irType = GetIRType (type, out size, out isPointer); + writer.Write (irType); + } + + bool IsArray (Type t) => t == typeof(LlvmIrArrayVariableInfo); + + void WriteValue (TextWriter writer, Type valueType, LlvmIrVariable variable) + { + if (variable.Value is LlvmIrVariable variableRef) { + writer.Write (variableRef.Reference); + return; + } + + if (IsArray (variable.Type)) { + uint count = GetAggregateValueElementCount (variable); + if (count == 0) { + writer.Write ("zeroinitializer"); + return; + } + + WriteArray (writer, (LlvmIrArrayVariableInfo)variable.Value); + return; + } + if (IsNumeric (valueType)) { - writer.Write (MonoAndroidHelper.CultureInvariantToString (value)); + writer.Write (MonoAndroidHelper.CultureInvariantToString (variable.Value)); return; } throw new NotSupportedException ($"Internal error: value type '{valueType}' is unsupported"); } + void WriteValue (TextWriter writer, Type type, object? value) + { + if (value is LlvmIrVariable variableRef) { + writer.Write (variableRef.Reference); + return; + } + + if (IsNumeric (type)) { + writer.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } + + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); + } + + void WriteArray (TextWriter writer, LlvmIrArrayVariableInfo arrayInfo) + { + writer.WriteLine (" ["); + IncreaseIndent (); + + string irType; + if (arrayInfo.ElementType == typeof(LlvmIrStringVariable)) { + irType = MapToIRType (typeof(string)); + } else { + irType = MapToIRType (arrayInfo.ElementType); + } + + bool first = true; + foreach (object entry in arrayInfo.Entries) { + if (!first) { + writer.WriteLine (','); + } else { + first = false; + } + writer.Write (currentIndent); + WriteType (writer, arrayInfo.ElementType, entry, out _, out _, out _); + writer.Write (' '); + WriteValue (writer, arrayInfo.ElementType, entry); + } + writer.WriteLine (); + + DecreaseIndent (); + writer.Write (']'); + } + void WriteLinkage (TextWriter writer, LlvmIrLinkage linkage) { if (linkage == LlvmIrLinkage.Default) { @@ -159,7 +323,7 @@ void WriteLinkage (TextWriter writer, LlvmIrLinkage linkage) try { WriteAttribute (writer, llvmLinkage[linkage]); } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); + throw new InvalidOperationException ($"Internal error: unsupported writability '{linkage}'", ex); } } @@ -482,13 +646,13 @@ void WriteAttributeSets (TextWriter writer, LlvmIrModule module) } } - void WriteComment (TextWriter writer, string comment) + public void WriteComment (TextWriter writer, string comment) { writer.Write (';'); writer.Write (comment); } - void WriteCommentLine (TextWriter writer, string comment) + public void WriteCommentLine (TextWriter writer, string comment) { WriteComment (writer, comment); writer.WriteLine (); @@ -558,6 +722,13 @@ public static string MapToIRType (Type type, out ulong size) return MapToIRType (type, out size, out _); } + /// + /// Maps managed type to equivalent IR type. Puts type size in and whether or not the type + /// is a pointer in . When a type is determined to be a pointer, + /// will be set to 0, because this method doesn't have access to the generator target. In order to adjust pointer + /// size, the instance method must be called (private to the generator as other classes should not + /// have any need to know the pointer size). + /// public static string MapToIRType (Type type, out ulong size, out bool isPointer) { type = GetActualType (type); @@ -572,5 +743,68 @@ public static string MapToIRType (Type type, out ulong size, out bool isPointer) isPointer = true; return IRPointerType; } + + string GetIRType (Type type, out ulong size, out bool isPointer) + { + string ret = MapToIRType (type, out size, out isPointer); + if (isPointer && size == 0) { + size = target.NativePointerSize; + } + + return ret; + } + + 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, bytes.Length, out _, nullTerminated: false); + } + + public static string QuoteString (string value, out ulong stringSize, bool nullTerminated = true) + { + var encoding = Encoding.UTF8; + int byteCount = encoding.GetByteCount (value); + var bytes = ArrayPool.Shared.Rent (byteCount); + try { + encoding.GetBytes (value, 0, value.Length, bytes, 0); + return QuoteString (bytes, byteCount, out stringSize, nullTerminated); + } finally { + ArrayPool.Shared.Return (bytes); + } + } + + public static string QuoteString (byte[] bytes, int byteCount, out ulong stringSize, bool nullTerminated = true) + { + var sb = new StringBuilder (byteCount * 2); // rough estimate of capacity + + byte b; + for (int i = 0; i < byteCount; i++) { + b = bytes [i]; + if (b != '"' && b != '\\' && b >= 32 && b < 127) { + sb.Append ((char)b); + continue; + } + + sb.Append ('\\'); + sb.Append ($"{b:X2}"); + } + + stringSize = (ulong) byteCount; + if (nullTerminated) { + stringSize++; + sb.Append ("\\00"); + } + + return QuoteStringNoEscape (sb.ToString ()); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 6033c552e16..b1763f8be32 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -4,16 +4,27 @@ namespace Xamarin.Android.Tasks.LLVM.IR { + // TODO: remove these aliases once the refactoring is done + using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; + partial class LlvmIrModule { + /// + /// Global variable type to be used to output name:value string arrays. This is a notational shortcut, + /// do **NOT** change the type without understanding how it affects the rest of code. + /// + public static readonly Type NameValueArrayType = typeof(IDictionary); + public IList? ExternalFunctions { get; private set; } public IList? AttributeSets { get; private set; } public IList? Structures { get; private set; } public IList? GlobalVariables { get; private set; } + public IList? Strings { get; private set; } Dictionary? attributeSets; Dictionary? externalFunctions; Dictionary? structures; + LlvmIrStringManager? stringManager; List? globalVariables; @@ -40,10 +51,58 @@ public void AfterConstruction () Structures = list.AsReadOnly (); } + if (stringManager != null && stringManager.StringGroups.Count > 0) { + Strings = stringManager.StringGroups.AsReadOnly (); + } + GlobalVariables = globalVariables?.AsReadOnly (); } + /// + /// A shortcut way to add a global variable without first having to create an instance of first. + /// + public LlvmIrGlobalVariable AddGlobalVariable (Type type, string name, object? value, LlvmIrVariableOptions? options = null) + { + var ret = new LlvmIrGlobalVariable (type, name, options) { Value = value }; + Add (ret); + return ret; + } + + public void Add (LlvmIrGlobalVariable variable, string stringGroupName, string? stringGroupComment = null, string? symbolSuffix = null) + { + EnsureValidGlobalVariableType (variable); + + if (IsStringVariable (variable)) { + AddStringGlobalVariable (variable, stringGroupName, stringGroupComment, symbolSuffix); + return; + } + + if (IsStringArrayVariable (variable)) { + AddStringArrayGlobalVariable (variable, stringGroupName, stringGroupComment, symbolSuffix); + return; + } + + throw new InvalidOperationException ("Internal error: this overload is for adding ONLY string or array-of-string variables"); + } + public void Add (LlvmIrGlobalVariable variable) + { + EnsureValidGlobalVariableType (variable); + + if (IsStringVariable (variable)) { + AddStringGlobalVariable (variable); + return; + } + + if (IsStringArrayVariable (variable)) { + AddStringArrayGlobalVariable (variable); + return; + } + + AddStandardGlobalVariable (variable); + } + + void AddStandardGlobalVariable (LlvmIrGlobalVariable variable) { if (globalVariables == null) { globalVariables = new List (); @@ -52,6 +111,113 @@ public void Add (LlvmIrGlobalVariable variable) globalVariables.Add (variable); } + void AddStringGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + if (stringManager == null) { + stringManager = new LlvmIrStringManager (); + } + + LlvmIrStringVariable sv = RegisterString (variable, stringGroupName, stringGroupComment, symbolSuffix); + variable.Value = sv; + + AddStandardGlobalVariable (variable); + } + + LlvmIrStringVariable RegisterString (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + return RegisterString ((string)variable.Value, stringGroupName, stringGroupComment, symbolSuffix); + } + + LlvmIrStringVariable RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + LlvmIrStringVariable sv = stringManager.Add (value, stringGroupName, stringGroupComment, symbolSuffix); + return sv; + } + + void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + { + if (variable.Value == null) { + AddStandardGlobalVariable (variable); + return; + } + + List? entries = null; + if (NameValueArrayType.IsAssignableFrom (variable.Type)) { + entries = new List (); + var dict = (IDictionary)variable.Value; + foreach (var kvp in dict) { + entries.Add (kvp.Key); + entries.Add (kvp.Value); + } + } else if (typeof(ICollection).IsAssignableFrom (variable.Type)) { + entries = new List ((ICollection)variable.Value); + } else if (variable.Type == typeof(string[])) { + entries = new List ((string[])variable.Value); + } else { + throw new InvalidOperationException ($"Internal error: unsupported array string type `{variable.Type}'"); + } + + var strings = new List (); + foreach (string entry in entries) { + var sv = RegisterString (entry, stringGroupName, stringGroupComment, symbolSuffix); + strings.Add (sv); + } + + var arrayInfo = new LlvmIrArrayVariableInfo (typeof(LlvmIrStringVariable), strings, variable.Value); + variable.OverrideValue (typeof(LlvmIrArrayVariableInfo), arrayInfo); + AddStandardGlobalVariable (variable); + } + + bool IsStringArrayVariable (LlvmIrGlobalVariable variable) + { + if (NameValueArrayType.IsAssignableFrom (variable.Type)) { + if (variable.Value != null && !NameValueArrayType.IsAssignableFrom (variable.Value.GetType ())) { + throw new InvalidOperationException ($"Internal error: name:value array variable must have its value set to either `null` or `{NameValueArrayType}`"); + } + + return true; + } + + var ctype = typeof(ICollection); + if (ctype.IsAssignableFrom (variable.Type)) { + if (variable.Value != null && !ctype.IsAssignableFrom (variable.Value.GetType ())) { + throw new InvalidOperationException ($"Internal error: string array variable must have its value set to either `null` or implement `{ctype}`"); + } + + return true; + } + + if (variable.Type.IsArray && variable.Type.GetElementType () == typeof(string)) { + if (variable.Value != null && variable.Value.GetType () != typeof(string[])) { + throw new InvalidOperationException ($"Internal error: string array variable must have its value set to either `null` or be `{typeof(string[])}`"); + } + + return true; + } + + return false; + } + + bool IsStringVariable (LlvmIrGlobalVariable variable) + { + if (variable.Type != typeof(string)) { + return false; + } + + if (variable.Value != null && variable.Value.GetType () != typeof(string)) { + throw new InvalidOperationException ("Internal error: variable of string type must have its value set to either `null` or a string"); + } + + return true; + } + + void EnsureValidGlobalVariableType (LlvmIrGlobalVariable variable) + { + if (variable is LlvmIrStringVariable) { + throw new ArgumentException ("Internal error: do not add instances of LlvmIrStringVariable, simply set variable value to the desired string instead"); + } + } + /// /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs index c85e25fbf6c..402e5dd81df 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -51,5 +51,10 @@ protected void SetIntegerParameterUpcastFlags (LlvmIrFunctionParameter parameter } } } + + public virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + return maxFieldAlignment; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index db148a41214..6b862e5f994 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -59,5 +59,19 @@ public override void SetParameterFlags (LlvmIrFunctionParameter parameter) base.SetParameterFlags (parameter); SetIntegerParameterUpcastFlags (parameter); } + + public override int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + // System V ABI for x86_64 mandates that any aggregates 16 bytes or more long will + // be aligned at at least 16 bytes + // + // See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf (Section '3.1.2 Data Representation', "Aggregates and Unions") + // + if (dataSize >= 16 && maxFieldAlignment < 16) { + return 16; + } + + return maxFieldAlignment; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs new file mode 100644 index 00000000000..4d0bcdd8182 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +sealed class LlvmIrStringGroup +{ + public ulong Count; + public readonly string? Comment; + public readonly List Strings = new List (); + + public LlvmIrStringGroup (string? comment = null) + { + Comment = comment; + Count = 0; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs new file mode 100644 index 00000000000..0ef22078dd6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +partial class LlvmIrModule +{ + protected class LlvmIrStringManager + { + Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); + List stringGroups = new List (); + + LlvmIrStringGroup defaultGroup; + + public List StringGroups => stringGroups; + + public LlvmIrStringManager () + { + defaultGroup = new LlvmIrStringGroup (); + stringGroupCache.Add (String.Empty, defaultGroup); + stringGroups.Add (defaultGroup); + } + + public LlvmIrStringVariable Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) + { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + LlvmIrStringVariable? stringVar; + if (stringSymbolCache.TryGetValue (value, out stringVar) && stringVar != null) { + return stringVar; + } + + LlvmIrStringGroup? group; + string groupPrefix; + if (String.IsNullOrEmpty (groupName) || String.Compare ("str", groupName, StringComparison.Ordinal) == 0) { + group = defaultGroup; + groupPrefix = ".str"; + } else if (!stringGroupCache.TryGetValue (groupName, out group) || group == null) { + group = new LlvmIrStringGroup (groupComment ?? groupName); + stringGroups.Add (group); + stringGroupCache[groupName] = group; + groupPrefix = $".{groupName}"; + } else { + groupPrefix = $".{groupName}"; + } + + string symbolName = $"{groupPrefix}.{group.Count++}"; + if (!String.IsNullOrEmpty (symbolSuffix)) { + symbolName = $"{symbolName}_{symbolSuffix}"; + } + + stringVar = new LlvmIrStringVariable (symbolName, value); + group.Strings.Add (stringVar); + stringSymbolCache.Add (value, stringVar); + + return stringVar; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 7208f9fe2bb..dc93e6fd6cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -12,7 +12,7 @@ abstract class LlvmIrVariable : IEquatable public abstract string NamePrefix { get; } public string? Name { get; protected set; } - public Type Type { get; } + public Type Type { get; protected set; } public object? Value { get; set; } /// @@ -127,6 +127,29 @@ public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? opti Options = options; } + + /// + /// Supports instances where a variable value must be processed by (for instance for arrays). + /// Should **not** be used by code other than LlvmIrModule. + /// + public void OverrideValue (Type newType, object? newValue) + { + if (newValue != null && !newType.IsAssignableFrom (newValue.GetType ())) { + throw new ArgumentException ($"Must be exactly, or derived from, the '{newType}' type.", nameof (newValue)); + } + + Type = newType; + Value = newValue; + } + } + + class LlvmIrStringVariable : LlvmIrGlobalVariable + { + public LlvmIrStringVariable (string name, string value) + : base (typeof(string), name, LlvmIrVariableOptions.LocalString) + { + Value = value; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs index 80594e60203..db2f69b43e3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -57,6 +57,7 @@ class LlvmIrVariableOptions Linkage = LlvmIrLinkage.Private, Writability = LlvmIrWritability.Constant, AddressSignificance = LlvmIrAddressSignificance.Unnamed, + RuntimePreemption = LlvmIrRuntimePreemption.Default, }; /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs new file mode 100644 index 00000000000..aadc348e326 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + // TODO: remove these aliases once the refactoring is done + using StructurePointerData = LLVMIR.StructurePointerData; + + class StructureInstance + { + Dictionary? pointees; + + public object Obj { get; } + public Type Type { get; } + + public StructureInstance (object instance) + { + Obj = instance; + Type = instance.GetType (); + } + + public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) + { + if (pointees == null) { + pointees = new Dictionary (); + } + + 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/TypeUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs index 8d1a7c93bd2..666ca99b8d6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs @@ -71,5 +71,32 @@ public static bool IsNativeClass (this Type t) var attr = t.GetCustomAttribute (); return attr != null; } + + public static bool ImplementsInterface (this Type type, Type requiredIfaceType) + { + if (type == null || requiredIfaceType == null) { + return false; + } + + bool generic = requiredIfaceType.IsGenericType; + foreach (Type iface in type.GetInterfaces ()) { + if (generic) { + if (!iface.IsGenericType) { + continue; + } + + if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { + return true; + } + continue; + } + + if (iface == requiredIfaceType) { + return true; + } + } + + return false; + } } } diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index a8ffcedb71a..c8bc36c277a 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -129,7 +129,9 @@ const ApplicationConfig application_config = { }; const char* const mono_aot_mode_name = "normal"; -const char* const app_environment_variables[] = {}; +const char* const app_environment_variables[] = { + "name", "value", +}; const char* const app_system_properties[] = {}; static constexpr size_t AssemblyNameWidth = 128; From db5be2305dcfbbf694abb6fdd2f6dbf0ce7c43a6 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 30 May 2023 23:07:19 +0200 Subject: [PATCH 37/60] Structures written fine, onto structure arrays tomorrow --- ...cationConfigNativeAssemblyGenerator.New.cs | 55 +- .../LlvmIrGenerator/IStructureInfo.New.cs | 16 - .../LlvmIrGenerator/LlvmIrComposer.New.cs | 12 + .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 541 +++++++++++------- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 83 ++- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 1 + .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 1 + .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 1 + .../LlvmIrGenerator/LlvmIrModuleX64.cs | 1 + .../LlvmIrGenerator/LlvmIrModuleX86.cs | 1 + .../LlvmIrStringManager.New.cs | 9 + .../LlvmIrGenerator/LlvmIrVariable.New.cs | 161 ++++++ .../LlvmIrGenerator/LlvmIrVariable.cs | 153 ----- .../LlvmIrGenerator/StructureInfo.New.cs | 11 +- .../LlvmIrGenerator/StructureInstance.New.cs | 12 +- .../StructureMemberInfo.New.cs | 12 +- 16 files changed, 664 insertions(+), 406 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index 083a6fc9dc8..33474a54d8b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -29,11 +29,7 @@ sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextData { public override string GetComment (object data, string fieldName) { - var dso_entry = data as DSOCacheEntry; - if (dso_entry == null) { - throw new InvalidOperationException ("Invalid data type, expected an instance of DSOCacheEntry"); - } - + var dso_entry = EnsureType (data); if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { return $"hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; } @@ -72,8 +68,8 @@ sealed class DSOCacheEntry StructureInstance? application_config; List? dsoCache; - StructureInfo? applicationConfigStructureInfo; - StructureInfo? dsoCacheEntryStructureInfo; + StructureInfo? applicationConfigStructureInfo; + StructureInfo? dsoCacheEntryStructureInfo; public bool UsesMonoAOT { get; set; } public bool UsesMonoLLVM { get; set; } @@ -117,18 +113,20 @@ protected override void Construct (LlvmIrModule module) { MapStructures (module); - module.AddGlobalVariable (FORMAT_TAG.GetType (), "format_tag", FORMAT_TAG); + module.AddGlobalVariable (FORMAT_TAG.GetType (), "format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); module.AddGlobalVariable (typeof(string), "mono_aot_mode_name", MonoAOTMode); var envVars = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_environment_variables") { Value = environmentVariables, + Comment = " Application environment variables array, name:value", }; - module.Add (envVars, "env", "Application environment variables"); + module.Add (envVars, stringGroupName: "env", stringGroupComment: " Application environment variables name:value pairs"); var sysProps = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_system_properties") { Value = systemProperties, + Comment = " System properties defined by the application", }; - module.Add (sysProps, "sysprop", "System properties defined by the application"); + module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); dsoCache = InitDSOCache (); var app_cfg = new ApplicationConfig { @@ -158,8 +156,39 @@ protected override void Construct (LlvmIrModule module) mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; - application_config = new StructureInstance (app_cfg); - //module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); + application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); + module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); + + var dso_cache = new LlvmIrGlobalVariable (dsoCache.GetType (), "dso_cache", options: LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + Value = dsoCache, + Comment = " DSO cache entries", + BeforeWriteCallback = HashAndSortDSOCache, + }; + module.Add (dso_cache); + } + + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) + { + var cache = variable.Value as List; + if (cache == null) { + return; + } + + bool is64Bit = target.Is64Bit; + foreach (StructureInstance instance in cache) { + if (instance.Obj == null) { + throw new InvalidOperationException ("Internal error: DSO cache must not contain null entries"); + } + + var entry = instance.Obj as DSOCacheEntry; + if (entry == null) { + throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); + } + + entry.hash = GetXxHash (entry.HashedName, is64Bit); + } + + cache.Sort ((StructureInstance a, StructureInstance b) => ((DSOCacheEntry)a.Obj).hash.CompareTo (((DSOCacheEntry)b.Obj).hash)); } List InitDSOCache () @@ -197,7 +226,7 @@ List InitDSOCache () name = name, }; - dsoCache.Add (new StructureInstance (entry)); + dsoCache.Add (new StructureInstance (dsoCacheEntryStructureInfo, entry)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs deleted file mode 100644 index 5648815252a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.New.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - interface IStructureInfo - { - Type Type { get; } - ulong Size { get; } - int MaxFieldAlignment { get; } - string Name { get; } - string NativeTypeDesignator { get; } - IList Members { get; } - bool IsOpaque { get; } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs index 75e6de91548..a9c24576b8b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.IO.Hashing; +using System.Text; using Xamarin.Android.Tools; @@ -31,5 +33,15 @@ public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter generator.Generate (output, module); output.Flush (); } + + public static ulong GetXxHash (string str, bool is64Bit) + { + byte[] stringBytes = Encoding.UTF8.GetBytes (str); + if (is64Bit) { + return XxHash64.HashToUInt64 (stringBytes); + } + + return (ulong)XxHash32.HashToUInt32 (stringBytes); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index ba59f919a54..0262d622efe 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -22,6 +22,55 @@ partial class LlvmIrGenerator { const char IndentChar = '\t'; + sealed class LlvmTypeInfo + { + public readonly bool IsPointer; + public readonly bool IsAggregate; + public readonly bool IsStructure; + public readonly ulong Size; + public readonly ulong MaxFieldAlignment; + + public LlvmTypeInfo (bool isPointer, bool isAggregate, bool isStructure, ulong size, ulong maxFieldAlignment) + { + IsPointer = isPointer; + IsAggregate = isAggregate; + IsStructure = isStructure; + Size = size; + MaxFieldAlignment = maxFieldAlignment; + } + } + + sealed class WriteContext + { + int currentIndentLevel = 0; + + public readonly TextWriter Output; + public readonly LlvmIrModule Module; + public string CurrentIndent { get; private set; } = String.Empty; + + public WriteContext (TextWriter writer, LlvmIrModule module) + { + Output = writer; + Module = module; + } + + public void IncreaseIndent () + { + currentIndentLevel++; + CurrentIndent = MakeIndentString (); + } + + public void DecreaseIndent () + { + if (currentIndentLevel > 0) { + currentIndentLevel--; + } + CurrentIndent = MakeIndentString (); + } + + string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; + } + sealed class BasicType { public readonly string Name; @@ -58,8 +107,6 @@ public BasicType (string name, ulong size, bool isNumeric = true) public string FileName { get; } LlvmIrModuleTarget target; - int currentIndentLevel = 0; - string currentIndent = String.Empty; protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) { @@ -81,105 +128,115 @@ public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) public void Generate (TextWriter writer, LlvmIrModule module) { + var context = new WriteContext (writer, module); if (!String.IsNullOrEmpty (FilePath)) { - WriteCommentLine (writer, $" ModuleID = '{FileName}'"); - writer.WriteLine ($"source_filename = \"{FileName}\""); + WriteCommentLine (context, $" ModuleID = '{FileName}'"); + context.Output.WriteLine ($"source_filename = \"{FileName}\""); } - writer.WriteLine (target.DataLayout.Render ()); - writer.WriteLine ($"target triple = \"{target.Triple}\""); - WriteStructureDeclarations (writer, module); - WriteGlobalVariables (writer, module); + context.Output.WriteLine (target.DataLayout.Render ()); + context.Output.WriteLine ($"target triple = \"{target.Triple}\""); + WriteStructureDeclarations (context); + WriteGlobalVariables (context); // Bottom of file - WriteStrings (writer, module); - WriteExternalFunctionDeclarations (writer, module); - WriteAttributeSets (writer, module); + WriteStrings (context); + WriteExternalFunctionDeclarations (context); + WriteAttributeSets (context); } - void WriteStrings (TextWriter writer, LlvmIrModule module) + void WriteStrings (WriteContext context) { - if (module.Strings == null || module.Strings.Count == 0) { + if (context.Module.Strings == null || context.Module.Strings.Count == 0) { return; } - writer.WriteLine (); - WriteComment (writer, " Strings"); + context.Output.WriteLine (); + WriteComment (context, " Strings"); - foreach (LlvmIrStringGroup group in module.Strings) { - writer.WriteLine (); + foreach (LlvmIrStringGroup group in context.Module.Strings) { + context.Output.WriteLine (); if (!String.IsNullOrEmpty (group.Comment)) { - WriteCommentLine (writer, group.Comment); + WriteCommentLine (context, group.Comment); } foreach (LlvmIrStringVariable info in group.Strings) { string s = QuoteString ((string)info.Value, out ulong size); - WriteGlobalVariableStart (writer, info); - writer.Write ('['); - writer.Write (size.ToString (CultureInfo.InvariantCulture)); - writer.Write (" x i8] c"); - writer.Write (s); - writer.Write (", align "); - writer.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); + WriteGlobalVariableStart (context, info); + context.Output.Write ('['); + context.Output.Write (size.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x i8] c"); + context.Output.Write (s); + context.Output.Write (", align "); + context.Output.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); } } } - void WriteGlobalVariables (TextWriter writer, LlvmIrModule module) + void WriteGlobalVariables (WriteContext context) { - if (module.GlobalVariables == null || module.GlobalVariables.Count == 0) { + if (context.Module.GlobalVariables == null || context.Module.GlobalVariables.Count == 0) { return; } - writer.WriteLine (); - foreach (LlvmIrGlobalVariable gv in module.GlobalVariables) { - WriteGlobalVariable (writer, gv); + context.Output.WriteLine (); + foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { + if (gv.BeforeWriteCallback != null) { + gv.BeforeWriteCallback (gv, target); + } + WriteGlobalVariable (context, gv); } } - public void WriteGlobalVariableStart (TextWriter writer, LlvmIrGlobalVariable variable) + void WriteGlobalVariableStart (WriteContext context, LlvmIrGlobalVariable variable) { - writer.Write ('@'); - writer.Write (variable.Name); - writer.Write (" = "); + if (!String.IsNullOrEmpty (variable.Comment)) { + WriteCommentLine (context, variable.Comment); + } + context.Output.Write ('@'); + context.Output.Write (variable.Name); + context.Output.Write (" = "); LlvmIrVariableOptions options = variable.Options ?? LlvmIrGlobalVariable.DefaultOptions; - WriteLinkage (writer, options.Linkage); - WritePreemptionSpecifier (writer, options.RuntimePreemption); - WriteVisibility (writer, options.Visibility); - WriteAddressSignificance (writer, options.AddressSignificance); - WriteWritability (writer, options.Writability); + WriteLinkage (context, options.Linkage); + WritePreemptionSpecifier (context, options.RuntimePreemption); + WriteVisibility (context, options.Visibility); + WriteAddressSignificance (context, options.AddressSignificance); + WriteWritability (context, options.Writability); } - public void WriteGlobalVariable (TextWriter writer, LlvmIrGlobalVariable variable) + void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) { - WriteGlobalVariableStart (writer, variable); - WriteTypeAndValue (writer, variable, out ulong typeSize, out bool isPointer, out bool isAggregate); - writer.Write (", align "); + context.Output.WriteLine (); + WriteGlobalVariableStart (context, variable); + WriteTypeAndValue (context, variable, out LlvmTypeInfo typeInfo); + context.Output.Write (", align "); ulong alignment; - if (isAggregate) { + if (typeInfo.IsAggregate) { uint count = GetAggregateValueElementCount (variable); - alignment = (ulong)target.GetAggregateAlignment ((int)typeSize, count * typeSize); - } else if (isPointer) { + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.Size, count * typeInfo.Size); + } else if (typeInfo.IsStructure) { + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); + } else if (typeInfo.IsPointer) { alignment = target.NativePointerSize; } else { - alignment = typeSize; + alignment = typeInfo.Size; } - writer.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); + context.Output.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); } - void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer, out bool isAggregate) + void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - WriteType (writer, variable, out size, out isPointer, out isAggregate); - writer.Write (' '); + WriteType (context, variable, out typeInfo); + context.Output.Write (' '); if (variable.Value == null) { - if (isPointer) { - writer.Write ("null"); + if (typeInfo.IsPointer) { + context.Output.Write ("null"); } throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); @@ -196,7 +253,7 @@ void WriteTypeAndValue (TextWriter writer, LlvmIrVariable variable, out ulong si throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); } - WriteValue (writer, valueType, variable); + WriteValue (context, valueType, variable); } uint GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value); @@ -215,79 +272,156 @@ uint GetAggregateValueElementCount (Type type, object? value) return (uint)info.Entries.Count; } - void WriteType (TextWriter writer, LlvmIrVariable variable, out ulong size, out bool isPointer, out bool isAggregate) + void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - WriteType (writer, variable.Type, variable.Value, out size, out isPointer, out isAggregate); + WriteType (context, variable.Type, variable.Value, out typeInfo); } - void WriteType (TextWriter writer, Type type, object? value, out ulong size, out bool isPointer, out bool isAggregate) + void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo) { + if (type == typeof(StructureInstance)) { + if (value == null) { + throw new ArgumentException ("must not be null for structure instances", nameof (value)); + } + + var si = (StructureInstance)value; + ulong alignment; + + if (si.Info.HasPointers && target.NativePointerSize > si.Info.MaxFieldAlignment) { + alignment = target.NativePointerSize; + } else { + alignment = (ulong)si.Info.MaxFieldAlignment; + } + + typeInfo = new LlvmTypeInfo ( + isPointer: false, + isAggregate: false, + isStructure: true, + size: si.Info.Size, + maxFieldAlignment: alignment + ); + + context.Output.Write ('%'); + context.Output.Write (si.Info.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Info.Name); + return; + } + string irType; + ulong size; + bool isPointer; if (IsArray (type)) { - isAggregate = true; irType = GetIRType (type, out size, out isPointer); - - writer.Write ('['); - writer.Write (GetAggregateValueElementCount (type, value).ToString (CultureInfo.InvariantCulture)); - writer.Write (" x "); - writer.Write (irType); - writer.Write (']'); + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: true, + isStructure: false, + size: size, + maxFieldAlignment: size + ); + + context.Output.Write ('['); + context.Output.Write (GetAggregateValueElementCount (type, value).ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x "); + context.Output.Write (irType); + context.Output.Write (']'); return; } - isAggregate = false; irType = GetIRType (type, out size, out isPointer); - writer.Write (irType); + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: false, + isStructure: false, + size: size, + maxFieldAlignment: size + ); + context.Output.Write (irType); } bool IsArray (Type t) => t == typeof(LlvmIrArrayVariableInfo); - void WriteValue (TextWriter writer, Type valueType, LlvmIrVariable variable) + void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) { - if (variable.Value is LlvmIrVariable variableRef) { - writer.Write (variableRef.Reference); - return; - } - if (IsArray (variable.Type)) { uint count = GetAggregateValueElementCount (variable); if (count == 0) { - writer.Write ("zeroinitializer"); + context.Output.Write ("zeroinitializer"); return; } - WriteArray (writer, (LlvmIrArrayVariableInfo)variable.Value); - return; - } - - if (IsNumeric (valueType)) { - writer.Write (MonoAndroidHelper.CultureInvariantToString (variable.Value)); + WriteArray (context, (LlvmIrArrayVariableInfo)variable.Value); return; } - throw new NotSupportedException ($"Internal error: value type '{valueType}' is unsupported"); + WriteValue (context, valueType, variable.Value); } - void WriteValue (TextWriter writer, Type type, object? value) + void WriteValue (WriteContext context, Type type, object? value) { if (value is LlvmIrVariable variableRef) { - writer.Write (variableRef.Reference); + context.Output.Write (variableRef.Reference); return; } if (IsNumeric (type)) { - writer.Write (MonoAndroidHelper.CultureInvariantToString (value)); + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } + + if (type == typeof(bool)) { + context.Output.Write ((bool)value ? '1' : '0'); + return; + } + + if (type == typeof(StructureInstance)) { + WriteStructureValue (context, (StructureInstance?)value); return; } throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } - void WriteArray (TextWriter writer, LlvmIrArrayVariableInfo arrayInfo) + void WriteStructureValue (WriteContext context, StructureInstance? instance) + { + if (instance == null) { + context.Output.Write ("zeroinitializer"); + return; + } + + context.Output.WriteLine ('{'); + context.IncreaseIndent (); + + StructureInfo info = instance.Info; + int lastMember = info.Members.Count - 1; + + for (int i = 0; i < info.Members.Count; i++) { + StructureMemberInfo smi = info.Members[i]; + + context.Output.Write (context.CurrentIndent); + WriteType (context, smi.MemberType, value: null, out _); + context.Output.Write (' '); + + object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); + WriteValue (context, smi.MemberType, value); + + if (i < lastMember) { + context.Output.Write (", "); + } + + WriteCommentLine (context, $" {MapManagedTypeToNative (smi.MemberType)} {smi.Info.Name}"); + } + + context.DecreaseIndent (); + context.Output.Write ('}'); + } + + void WriteArray (WriteContext context, LlvmIrArrayVariableInfo arrayInfo) { - writer.WriteLine (" ["); - IncreaseIndent (); + context.Output.WriteLine (" ["); + context.IncreaseIndent (); string irType; if (arrayInfo.ElementType == typeof(LlvmIrStringVariable)) { @@ -299,77 +433,77 @@ void WriteArray (TextWriter writer, LlvmIrArrayVariableInfo arrayInfo) bool first = true; foreach (object entry in arrayInfo.Entries) { if (!first) { - writer.WriteLine (','); + context.Output.WriteLine (','); } else { first = false; } - writer.Write (currentIndent); - WriteType (writer, arrayInfo.ElementType, entry, out _, out _, out _); - writer.Write (' '); - WriteValue (writer, arrayInfo.ElementType, entry); + context.Output.Write (context.CurrentIndent); + WriteType (context, arrayInfo.ElementType, entry, out _); + context.Output.Write (' '); + WriteValue (context, arrayInfo.ElementType, entry); } - writer.WriteLine (); + context.Output.WriteLine (); - DecreaseIndent (); - writer.Write (']'); + context.DecreaseIndent (); + context.Output.Write (']'); } - void WriteLinkage (TextWriter writer, LlvmIrLinkage linkage) + void WriteLinkage (WriteContext context, LlvmIrLinkage linkage) { if (linkage == LlvmIrLinkage.Default) { return; } try { - WriteAttribute (writer, llvmLinkage[linkage]); + WriteAttribute (context, llvmLinkage[linkage]); } catch (Exception ex) { throw new InvalidOperationException ($"Internal error: unsupported writability '{linkage}'", ex); } } - void WriteWritability (TextWriter writer, LlvmIrWritability writability) + void WriteWritability (WriteContext context, LlvmIrWritability writability) { try { - WriteAttribute (writer, llvmWritability[writability]); + WriteAttribute (context, llvmWritability[writability]); } catch (Exception ex) { throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); } } - void WriteAddressSignificance (TextWriter writer, LlvmIrAddressSignificance addressSignificance) + void WriteAddressSignificance (WriteContext context, LlvmIrAddressSignificance addressSignificance) { if (addressSignificance == LlvmIrAddressSignificance.Default) { return; } try { - WriteAttribute (writer, llvmAddressSignificance[addressSignificance]); + WriteAttribute (context, llvmAddressSignificance[addressSignificance]); } catch (Exception ex) { throw new InvalidOperationException ($"Internal error: unsupported address significance '{addressSignificance}'", ex); } } - void WriteVisibility (TextWriter writer, LlvmIrVisibility visibility) + void WriteVisibility (WriteContext context, LlvmIrVisibility visibility) { if (visibility == LlvmIrVisibility.Default) { return; } try { - WriteAttribute (writer, llvmVisibility[visibility]); + WriteAttribute (context, llvmVisibility[visibility]); } catch (Exception ex) { throw new InvalidOperationException ($"Internal error: unsupported visibility '{visibility}'", ex); } } - void WritePreemptionSpecifier (TextWriter writer, LlvmIrRuntimePreemption preemptionSpecifier) + void WritePreemptionSpecifier (WriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) { if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { return; } try { - WriteAttribute (writer, llvmRuntimePreemption[preemptionSpecifier]); + WriteAttribute (context, llvmRuntimePreemption[preemptionSpecifier]); } catch (Exception ex) { throw new InvalidOperationException ($"Internal error: unsupported preemption specifier '{preemptionSpecifier}'", ex); } @@ -378,46 +512,44 @@ void WritePreemptionSpecifier (TextWriter writer, LlvmIrRuntimePreemption preemp /// /// Write attribute named in followed by a single space /// - void WriteAttribute (TextWriter writer, string attr) + void WriteAttribute (WriteContext context, string attr) { - writer.Write (attr); - writer.Write (' '); + context.Output.Write (attr); + context.Output.Write (' '); } - void WriteStructureDeclarations (TextWriter writer, LlvmIrModule module) + void WriteStructureDeclarations (WriteContext context) { - if (module.Structures == null || module.Structures.Count == 0) { - Console.WriteLine (" #1"); + if (context.Module.Structures == null || context.Module.Structures.Count == 0) { return; } - foreach (IStructureInfo si in module.Structures) { - Console.WriteLine (" #2"); - writer.WriteLine (); - WriteStructureDeclaration (writer, si); + foreach (StructureInfo si in context.Module.Structures) { + context.Output.WriteLine (); + WriteStructureDeclaration (context, si); } } - void WriteStructureDeclaration (TextWriter writer, IStructureInfo si) + void WriteStructureDeclaration (WriteContext context, StructureInfo si) { // $"%{typeDesignator}.{name} = type " - writer.Write ('%'); - writer.Write (si.NativeTypeDesignator); - writer.Write ('.'); - writer.Write (si.Name); - writer.Write (" = type "); + context.Output.Write ('%'); + context.Output.Write (si.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Name); + context.Output.Write (" = type "); if (si.IsOpaque) { - writer.WriteLine ("opaque"); + context.Output.WriteLine ("opaque"); } else { - writer.WriteLine ('{'); + context.Output.WriteLine ('{'); } if (si.IsOpaque) { return; } - IncreaseIndent (); + context.IncreaseIndent (); for (int i = 0; i < si.Members.Count; i++) { StructureMemberInfo info = si.Members[i]; string nativeType = MapManagedTypeToNative (info.MemberType); @@ -433,24 +565,24 @@ void WriteStructureDeclaration (TextWriter writer, IStructureInfo si) var comment = $" {nativeType} {info.Info.Name}{arraySize}"; WriteStructureDeclarationField (info.IRType, comment, i == si.Members.Count - 1); } - DecreaseIndent (); + context.DecreaseIndent (); - writer.WriteLine ('}'); + context.Output.WriteLine ('}'); void WriteStructureDeclarationField (string typeName, string comment, bool last) { - writer.Write (currentIndent); - writer.Write (typeName); + context.Output.Write (context.CurrentIndent); + context.Output.Write (typeName); if (!last) { - writer.Write (", "); + context.Output.Write (", "); } else { - writer.Write (' '); + context.Output.Write (' '); } if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (writer, comment); + WriteCommentLine (context, comment); } else { - writer.WriteLine (); + context.Output.WriteLine (); } } } @@ -458,14 +590,14 @@ void WriteStructureDeclarationField (string typeName, string comment, bool last) // // Functions syntax: https://llvm.org/docs/LangRef.html#functions // - void WriteExternalFunctionDeclarations (TextWriter writer, LlvmIrModule module) + void WriteExternalFunctionDeclarations (WriteContext context) { - if (module.ExternalFunctions == null || module.ExternalFunctions.Count == 0) { + if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { return; } - writer.WriteLine (); - foreach (LlvmIrFunction func in module.ExternalFunctions) { + context.Output.WriteLine (); + foreach (LlvmIrFunction func in context.Module.ExternalFunctions) { // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) ILlvmIrFunctionState funcState = func.SaveState (); @@ -473,106 +605,106 @@ void WriteExternalFunctionDeclarations (TextWriter writer, LlvmIrModule module) target.SetParameterFlags (parameter); } - WriteFunctionAttributesComment (writer, func); - writer.Write ("declare "); - WriteFunctionDeclarationLeadingDecorations (writer, func); - WriteFunctionSignature (writer, func, writeParameterNames: false); - WriteFunctionDeclarationTrailingDecorations (writer, func); - writer.WriteLine (); + WriteFunctionAttributesComment (context, func); + context.Output.Write ("declare "); + WriteFunctionDeclarationLeadingDecorations (context, func); + WriteFunctionSignature (context, func, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (context, func); + context.Output.WriteLine (); func.RestoreState (funcState); } } - void WriteFunctionAttributesComment (TextWriter writer, LlvmIrFunction func) + void WriteFunctionAttributesComment (WriteContext context, LlvmIrFunction func) { if (func.AttributeSet == null) { return; } - writer.WriteLine (); - WriteCommentLine (writer, $"Function attributes: {func.AttributeSet.Render ()}"); + context.Output.WriteLine (); + WriteCommentLine (context, $"Function attributes: {func.AttributeSet.Render ()}"); } - void WriteFunctionDeclarationLeadingDecorations (TextWriter writer, LlvmIrFunction func) + void WriteFunctionDeclarationLeadingDecorations (WriteContext context, LlvmIrFunction func) { - WriteFunctionLeadingDecorations (writer, func, declaration: true); + WriteFunctionLeadingDecorations (context, func, declaration: true); } - void WriteFunctionDefinitionLeadingDecorations (TextWriter writer, LlvmIrFunction func) + void WriteFunctionDefinitionLeadingDecorations (WriteContext context, LlvmIrFunction func) { - WriteFunctionLeadingDecorations (writer, func, declaration: false); + WriteFunctionLeadingDecorations (context, func, declaration: false); } - void WriteFunctionLeadingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + void WriteFunctionLeadingDecorations (WriteContext context, LlvmIrFunction func, bool declaration) { if (func.Linkage != LlvmIrLinkage.Default) { - writer.Write (llvmLinkage[func.Linkage]); - writer.Write (' '); + context.Output.Write (llvmLinkage[func.Linkage]); + context.Output.Write (' '); } if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { - writer.Write (llvmRuntimePreemption[func.RuntimePreemption]); - writer.Write (' '); + context.Output.Write (llvmRuntimePreemption[func.RuntimePreemption]); + context.Output.Write (' '); } if (func.Visibility != LlvmIrVisibility.Default) { - writer.Write (llvmVisibility[func.Visibility]); - writer.Write (' '); + context.Output.Write (llvmVisibility[func.Visibility]); + context.Output.Write (' '); } } - void WriteFunctionDeclarationTrailingDecorations (TextWriter writer, LlvmIrFunction func) + void WriteFunctionDeclarationTrailingDecorations (WriteContext context, LlvmIrFunction func) { - WriteFunctionTrailingDecorations (writer, func, declaration: true); + WriteFunctionTrailingDecorations (context, func, declaration: true); } - void WriteFunctionDefinitionTrailingDecorations (TextWriter writer, LlvmIrFunction func) + void WriteFunctionDefinitionTrailingDecorations (WriteContext context, LlvmIrFunction func) { - WriteFunctionTrailingDecorations (writer, func, declaration: false); + WriteFunctionTrailingDecorations (context, func, declaration: false); } - void WriteFunctionTrailingDecorations (TextWriter writer, LlvmIrFunction func, bool declaration) + void WriteFunctionTrailingDecorations (WriteContext context, LlvmIrFunction func, bool declaration) { if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { - writer.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); + context.Output.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); } if (func.AttributeSet != null) { - writer.Write ($" #{func.AttributeSet.Number}"); + context.Output.Write ($" #{func.AttributeSet.Number}"); } } - void WriteFunctionSignature (TextWriter writer, LlvmIrFunction func, bool writeParameterNames) + void WriteFunctionSignature (WriteContext context, LlvmIrFunction func, bool writeParameterNames) { - writer.Write (MapToIRType (func.Signature.ReturnType)); - writer.Write (" @"); - writer.Write (func.Signature.Name); - writer.Write ('('); + context.Output.Write (MapToIRType (func.Signature.ReturnType)); + context.Output.Write (" @"); + context.Output.Write (func.Signature.Name); + context.Output.Write ('('); bool first = true; foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { if (!first) { - writer.Write (", "); + context.Output.Write (", "); } else { first = false; } - writer.Write (MapToIRType (parameter.Type)); - WriteParameterAttributes (writer, parameter); + context.Output.Write (MapToIRType (parameter.Type)); + WriteParameterAttributes (context, parameter); if (writeParameterNames) { if (String.IsNullOrEmpty (parameter.Name)) { throw new InvalidOperationException ($"Internal error: parameter must have a name"); } - writer.Write (" %"); // Function arguments are always local variables - writer.Write (parameter.Name); + context.Output.Write (" %"); // Function arguments are always local variables + context.Output.Write (parameter.Name); } } - writer.Write (')'); + context.Output.Write (')'); } - void WriteParameterAttributes (TextWriter writer, LlvmIrFunctionParameter parameter) + void WriteParameterAttributes (WriteContext context, LlvmIrFunctionParameter parameter) { var attributes = new List (); if (AttributeIsSet (parameter.ImmArg)) { @@ -619,20 +751,20 @@ void WriteParameterAttributes (TextWriter writer, LlvmIrFunctionParameter parame return; } - writer.Write (' '); - writer.Write (String.Join (" ", attributes)); + context.Output.Write (' '); + context.Output.Write (String.Join (" ", attributes)); bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; } - void WriteAttributeSets (TextWriter writer, LlvmIrModule module) + void WriteAttributeSets (WriteContext context) { - if (module.AttributeSets == null || module.AttributeSets.Count == 0) { + if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { return; } - writer.WriteLine (); - foreach (LlvmIrFunctionAttributeSet attrSet in module.AttributeSets) { + context.Output.WriteLine (); + foreach (LlvmIrFunctionAttributeSet attrSet in context.Module.AttributeSets) { // Must not modify the original set, it is shared with other targets. var targetSet = new LlvmIrFunctionAttributeSet (attrSet); target.AddTargetSpecificAttributes (targetSet); @@ -642,38 +774,22 @@ void WriteAttributeSets (TextWriter writer, LlvmIrModule module) targetSet.Add (privateTargetSet); } - writer.WriteLine ($"attributes #{targetSet.Number} {{ {targetSet.Render ()} }}"); + context.Output.WriteLine ($"attributes #{targetSet.Number} {{ {targetSet.Render ()} }}"); } } - public void WriteComment (TextWriter writer, string comment) - { - writer.Write (';'); - writer.Write (comment); - } - - public void WriteCommentLine (TextWriter writer, string comment) + void WriteComment (WriteContext context, string comment) { - WriteComment (writer, comment); - writer.WriteLine (); + context.Output.Write (';'); + context.Output.Write (comment); } - void IncreaseIndent () + void WriteCommentLine (WriteContext context, string comment) { - currentIndentLevel++; - currentIndent = MakeIndentString (); + WriteComment (context, comment); + context.Output.WriteLine (); } - void DecreaseIndent () - { - if (currentIndentLevel > 0) { - currentIndentLevel--; - } - currentIndent = MakeIndentString (); - } - - string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; - static Type GetActualType (Type type) { // Arrays of types are handled elsewhere, so we obtain the array base type here @@ -712,6 +828,25 @@ static string MapManagedTypeToNative (Type type) static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; + object? GetTypedMemberValue (WriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) + { + object? value = smi.GetValue (instance.Obj); + if (value == null) { + return defaultValue; + } + + Type valueType = value.GetType (); + if (valueType != expectedType) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should have a value of '{expectedType}' type, instead it had a '{value.GetType ()}'"); + } + + if (valueType == typeof(string)) { + return context.Module.LookupRequiredVariableForString ((string)value); + } + + return value; + } + public static string MapToIRType (Type type) { return MapToIRType (type, out _, out _); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index b1763f8be32..6b2998f711f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -17,13 +17,13 @@ partial class LlvmIrModule public IList? ExternalFunctions { get; private set; } public IList? AttributeSets { get; private set; } - public IList? Structures { get; private set; } + public IList? Structures { get; private set; } public IList? GlobalVariables { get; private set; } public IList? Strings { get; private set; } Dictionary? attributeSets; Dictionary? externalFunctions; - Dictionary? structures; + Dictionary? structures; LlvmIrStringManager? stringManager; List? globalVariables; @@ -46,8 +46,8 @@ public void AfterConstruction () } if (structures != null) { - List list = structures.Values.ToList (); - list.Sort ((IStructureInfo a, IStructureInfo b) => a.Name.CompareTo (b.Name)); + List list = structures.Values.ToList (); + list.Sort ((StructureInfo a, StructureInfo b) => a.Name.CompareTo (b.Name)); Structures = list.AsReadOnly (); } @@ -61,9 +61,12 @@ public void AfterConstruction () /// /// A shortcut way to add a global variable without first having to create an instance of first. /// - public LlvmIrGlobalVariable AddGlobalVariable (Type type, string name, object? value, LlvmIrVariableOptions? options = null) + public LlvmIrGlobalVariable AddGlobalVariable (Type type, string name, object? value, LlvmIrVariableOptions? options = null, string? comment = null) { - var ret = new LlvmIrGlobalVariable (type, name, options) { Value = value }; + var ret = new LlvmIrGlobalVariable (type, name, options) { + Value = value, + Comment = comment, + }; Add (ret); return ret; } @@ -82,7 +85,7 @@ public void Add (LlvmIrGlobalVariable variable, string stringGroupName, string? return; } - throw new InvalidOperationException ("Internal error: this overload is for adding ONLY string or array-of-string variables"); + throw new InvalidOperationException ("Internal error: this overload is ONLY for adding string or array-of-string variables"); } public void Add (LlvmIrGlobalVariable variable) @@ -99,9 +102,32 @@ public void Add (LlvmIrGlobalVariable variable) return; } + if (IsStructureVariable (variable)) { + PrepareStructure (variable); + } + AddStandardGlobalVariable (variable); } + void PrepareStructure (LlvmIrGlobalVariable variable) + { + var structure = variable.Value as StructureInstance; + if (structure == null) { + return; + } + + foreach (StructureMemberInfo smi in structure.Info.Members) { + if (smi.MemberType != typeof(string)) { + continue; + } + + string? value = smi.GetValue (structure.Obj) as string; + if (!String.IsNullOrEmpty (value)) { + RegisterString (value, stringGroupName: structure.Info.Name, symbolSuffix: smi.Info.Name); + } + } + } + void AddStandardGlobalVariable (LlvmIrGlobalVariable variable) { if (globalVariables == null) { @@ -211,6 +237,19 @@ bool IsStringVariable (LlvmIrGlobalVariable variable) return true; } + bool IsStructureVariable (LlvmIrGlobalVariable variable) + { + if (variable.Type != typeof(StructureInstance)) { + return false; + } + + if (variable.Value != null && variable.Value.GetType () != typeof(StructureInstance)) { + throw new InvalidOperationException ("Internal error: variable referring to a structure instance must have its value set to either `null` or an instance of the StructureInstance class"); + } + + return true; + } + void EnsureValidGlobalVariableType (LlvmIrGlobalVariable variable) { if (variable is LlvmIrStringVariable) { @@ -218,6 +257,22 @@ void EnsureValidGlobalVariableType (LlvmIrGlobalVariable variable) } } + /// + /// Looks up LLVM variable for a previously registered string given in . If a variable isn't found, + /// an exception is thrown. This is primarily used by to look up variables related to strings which + /// are part of structure instances. Such strings **MUST** be registered by and, thus, failure to do + /// so is an internal error. + /// + public LlvmIrStringVariable LookupRequiredVariableForString (string value) + { + LlvmIrStringVariable? sv = stringManager?.Lookup (value); + if (sv == null) { + throw new InvalidOperationException ($"Internal error: string '{value}' wasn't registered with string manager"); + } + + return sv; + } + /// /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to @@ -265,11 +320,11 @@ public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) /// data from instances of . This method is typically called from the /// method. /// - public StructureInfo MapStructure () + public StructureInfo MapStructure () { Console.WriteLine ($"Mapping structure: {typeof(T)}"); if (structures == null) { - structures = new Dictionary (); + structures = new Dictionary (); } Type t = typeof(T); @@ -278,24 +333,24 @@ public StructureInfo MapStructure () } // TODO: check if already there - if (structures.TryGetValue (t, out IStructureInfo sinfo)) { - return (StructureInfo)sinfo; + if (structures.TryGetValue (t, out StructureInfo sinfo)) { + return (StructureInfo)sinfo; } - var ret = new StructureInfo (this); + var ret = new StructureInfo (this, typeof(T)); structures.Add (t, ret); return ret; } - internal IStructureInfo GetStructureInfo (Type type) + internal StructureInfo GetStructureInfo (Type type) { if (structures == null) { throw new InvalidOperationException ($"Internal error: no structures have been mapped, cannot return info for {type}"); } foreach (var kvp in structures) { - IStructureInfo si = kvp.Value; + StructureInfo si = kvp.Value; if (si.Type != type) { continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index cacb6f1ada8..666165e13b2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -11,6 +11,7 @@ class LlvmIrModuleAArch64 : LlvmIrModuleTarget public override string Triple => "aarch64-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; public override uint NativePointerSize => 8; + public override bool Is64Bit => true; public LlvmIrModuleAArch64 () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index b536dee422c..705ad1ac673 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -11,6 +11,7 @@ class LlvmIrModuleArmV7a : LlvmIrModuleTarget public override string Triple => "armv7-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; public override uint NativePointerSize => 4; + public override bool Is64Bit => false; public LlvmIrModuleArmV7a () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs index 402e5dd81df..eb2097295db 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -8,6 +8,7 @@ abstract class LlvmIrModuleTarget public abstract string Triple { get; } public abstract AndroidTargetArch TargetArch { get; } public abstract uint NativePointerSize { get; } + public abstract bool Is64Bit { get; } /// /// Adds target-specific attributes which are common to many attribute sets. Usually this specifies CPU type, tuning and diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index 6b862e5f994..e1673600aea 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -11,6 +11,7 @@ class LlvmIrModuleX64 : LlvmIrModuleTarget public override string Triple => "x86_64-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; public override uint NativePointerSize => 8; + public override bool Is64Bit => true; public LlvmIrModuleX64 () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index 0d11bd5ba08..ba6769ee2a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -11,6 +11,7 @@ class LlvmIrModuleX86 : LlvmIrModuleTarget public override string Triple => "i686-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; public override uint NativePointerSize => 4; + public override bool Is64Bit => true; public LlvmIrModuleX86 () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs index 0ef22078dd6..bad4d99c565 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs @@ -59,5 +59,14 @@ public LlvmIrStringVariable Add (string value, string? groupName = null, string? return stringVar; } + + public LlvmIrStringVariable? Lookup (string value) + { + if (stringSymbolCache.TryGetValue (value, out LlvmIrStringVariable? sv)) { + return sv; + } + + return null; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs new file mode 100644 index 00000000000..3bb1d0f37bd --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -0,0 +1,161 @@ +using System; +using System.Globalization; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +// TODO: remove these aliases once the refactoring is done +using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; + +abstract class LlvmIrVariable : IEquatable +{ + public abstract bool Global { get; } + public abstract string NamePrefix { get; } + + public string? Name { get; protected set; } + public Type Type { get; protected set; } + public object? Value { get; set; } + public string? Comment { get; set; } + + /// + /// Both global and local variables will want their names to matter in equality checks, but function + /// parameters must not take it into account, thus this property. If set to false, + /// will ignore name when checking for equality. + protected bool NameMatters { get; set; } = true; + + /// + /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global + /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are + /// referenced. + /// + public string Reference { + get { + if (String.IsNullOrEmpty (Name)) { + throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); + } + + return $"{NamePrefix}{Name}"; + } + } + + /// + /// Certain data must be calculated when the target architecture is known, because it may depend on certain aspects of + /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output + /// stream, allowing updating of any such data as described above. + /// + public Action? BeforeWriteCallback { get; set; } + + /// + /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. + /// + protected LlvmIrVariable (Type type, string? name = null) + { + Type = type; + Name = name; + } + + public override int GetHashCode () + { + return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); + } + + public override bool Equals (object obj) + { + var irVar = obj as LlvmIrVariable; + if (irVar == null) { + return false; + } + + return Equals (irVar); + } + + public virtual bool Equals (LlvmIrVariable other) + { + if (other == null) { + return false; + } + + return + Global == other.Global && + Type == other.Type && + String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && + (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); + } +} + +class LlvmIrLocalVariable : LlvmIrVariable +{ + public override bool Global => false; + public override string NamePrefix => "%"; + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in + /// which case they will be assigned a sequential number when function code is generated. + /// + public LlvmIrLocalVariable (Type type, string? name = null) + : base (type, name) + {} + + public void AssignNumber (uint n) + { + Name = n.ToString (CultureInfo.InvariantCulture); + } +} + +class LlvmIrGlobalVariable : LlvmIrVariable +{ + /// + /// By default a global variable is constant and exported. + /// + public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; + + public override bool Global => true; + public override string NamePrefix => "@"; + + /// + /// Specify variable options. If omitted, it defaults to . + /// + /// + public LlvmIrVariableOptions? Options { get; set; } + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is required because global variables must be named. + /// + public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) + : base (type, name) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Options = options; + } + + /// + /// Supports instances where a variable value must be processed by (for instance for arrays). + /// Should **not** be used by code other than LlvmIrModule. + /// + public void OverrideValue (Type newType, object? newValue) + { + if (newValue != null && !newType.IsAssignableFrom (newValue.GetType ())) { + throw new ArgumentException ($"Must be exactly, or derived from, the '{newType}' type.", nameof (newValue)); + } + + Type = newType; + Value = newValue; + } +} + +class LlvmIrStringVariable : LlvmIrGlobalVariable +{ + public LlvmIrStringVariable (string name, string value) + : base (typeof(string), name, LlvmIrVariableOptions.LocalString) + { + Value = value; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index dc93e6fd6cf..0abda63bfdd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -1,157 +1,4 @@ using System; -using System.Globalization; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - // TODO: remove these aliases once the refactoring is done - using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; - - abstract class LlvmIrVariable : IEquatable - { - public abstract bool Global { get; } - public abstract string NamePrefix { get; } - - public string? Name { get; protected set; } - public Type Type { get; protected set; } - public object? Value { get; set; } - - /// - /// Both global and local variables will want their names to matter in equality checks, but function - /// parameters must not take it into account, thus this property. If set to false, - /// will ignore name when checking for equality. - protected bool NameMatters { get; set; } = true; - - /// - /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global - /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are - /// referenced. - /// - public string Reference { - get { - if (String.IsNullOrEmpty (Name)) { - throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); - } - - return $"{NamePrefix}{Name}"; - } - } - - /// - /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. - /// - protected LlvmIrVariable (Type type, string? name = null) - { - Type = type; - Name = name; - } - - public override int GetHashCode () - { - return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); - } - - public override bool Equals (object obj) - { - var irVar = obj as LlvmIrVariable; - if (irVar == null) { - return false; - } - - return Equals (irVar); - } - - public virtual bool Equals (LlvmIrVariable other) - { - if (other == null) { - return false; - } - - return - Global == other.Global && - Type == other.Type && - String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && - (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); - } - } - - class LlvmIrLocalVariable : LlvmIrVariable - { - public override bool Global => false; - public override string NamePrefix => "%"; - - /// - /// Constructs a local variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in - /// which case they will be assigned a sequential number when function code is generated. - /// - public LlvmIrLocalVariable (Type type, string? name = null) - : base (type, name) - {} - - public void AssignNumber (uint n) - { - Name = n.ToString (CultureInfo.InvariantCulture); - } - } - - class LlvmIrGlobalVariable : LlvmIrVariable - { - /// - /// By default a global variable is constant and exported. - /// - public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; - - public override bool Global => true; - public override string NamePrefix => "@"; - - /// - /// Specify variable options. If omitted, it defaults to . - /// - /// - public LlvmIrVariableOptions? Options { get; set; } - - /// - /// Constructs a local variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. is required because global variables must be named. - /// - public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) - : base (type, name) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - - Options = options; - } - - /// - /// Supports instances where a variable value must be processed by (for instance for arrays). - /// Should **not** be used by code other than LlvmIrModule. - /// - public void OverrideValue (Type newType, object? newValue) - { - if (newValue != null && !newType.IsAssignableFrom (newValue.GetType ())) { - throw new ArgumentException ($"Must be exactly, or derived from, the '{newType}' type.", nameof (newValue)); - } - - Type = newType; - Value = newValue; - } - } - - class LlvmIrStringVariable : LlvmIrGlobalVariable - { - public LlvmIrStringVariable (string name, string value) - : base (typeof(string), name, LlvmIrVariableOptions.LocalString) - { - Value = value; - } - } -} namespace Xamarin.Android.Tasks.LLVMIR { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs index 2be6ce2d1af..034d655361f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs @@ -5,7 +5,7 @@ namespace Xamarin.Android.Tasks.LLVM.IR { // TODO: add cache for members and data provider info - sealed class StructureInfo : IStructureInfo + sealed class StructureInfo { Type type; @@ -17,13 +17,14 @@ sealed class StructureInfo : IStructureInfo public int MaxFieldAlignment { get; private set; } = 0; public bool HasStrings { get; private set; } public bool HasPreAllocatedBuffers { get; private set; } + public bool HasPointers { get; private set; } public bool IsOpaque => Members.Count == 0; public string NativeTypeDesignator { get; } - public StructureInfo (LlvmIrModule module) + public StructureInfo (LlvmIrModule module, Type type) { - type = typeof(T); + this.type = type; Name = type.GetShortName (); Size = GatherMembers (type, module); DataProvider = type.GetDataProvider (); @@ -39,6 +40,10 @@ ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) } var info = new StructureMemberInfo (mi, module); + if (info.IsNativePointer) { + HasPointers = true; + } + if (storeMembers) { Members.Add (info); size += info.Size; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs index aadc348e326..c3dfdf846ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -9,14 +9,20 @@ namespace Xamarin.Android.Tasks.LLVM.IR class StructureInstance { Dictionary? pointees; + StructureInfo info; public object Obj { get; } - public Type Type { get; } + public Type Type => info.Type; + public StructureInfo Info => info; - public StructureInstance (object instance) + public StructureInstance (StructureInfo info, object instance) { + if (instance != null && !info.Type.IsAssignableFrom (instance.GetType ())) { + throw new ArgumentException ($"must be and instance of, or derived from, the {info.Type} type, or `null`", nameof (instance)); + } + + this.info = info; Obj = instance; - Type = instance.GetType (); } public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs index 0f4e215db79..d0f711598bd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs @@ -78,7 +78,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrModule module) IRType = $"[{arrayElements} x {IRType}]"; ArrayElements = (ulong)arrayElements; } else if (this.IsIRStruct ()) { - IStructureInfo si = module.GetStructureInfo (MemberType); + StructureInfo si = module.GetStructureInfo (MemberType); size = si.Size; Alignment = (ulong)si.MaxFieldAlignment; } @@ -93,6 +93,16 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrModule module) } } + public object? GetValue (object 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) { From 42437cdd6554a9a9cf7c38c4d6de369c79608c24 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 31 May 2023 23:42:51 +0200 Subject: [PATCH 38/60] Structure arrays done --- ...cationConfigNativeAssemblyGenerator.New.cs | 12 ++-- .../LlvmIrArrayVariableInfo.cs | 4 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 57 +++++++++++++++---- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 57 +++++++++++++++++-- .../LlvmIrGenerator/LlvmIrVariable.New.cs | 1 + .../MemberInfoUtilities.New.cs | 6 ++ .../LlvmIrGenerator/StructureInfo.New.cs | 20 ++++++- 7 files changed, 132 insertions(+), 25 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index 33474a54d8b..60928e88f1a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -31,11 +31,11 @@ public override string GetComment (object data, string fieldName) { var dso_entry = EnsureType (data); if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { - return $"hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; + return $" hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; } if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {dso_entry.name}"; + return $" name: {dso_entry.name}"; } return String.Empty; @@ -55,6 +55,7 @@ sealed class DSOCacheEntry public ulong hash; public bool ignore; + [NativeAssembler (UsesDataProvider = true)] public string name; public IntPtr handle = IntPtr.Zero; } @@ -159,7 +160,7 @@ protected override void Construct (LlvmIrModule module) application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); - var dso_cache = new LlvmIrGlobalVariable (dsoCache.GetType (), "dso_cache", options: LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + var dso_cache = new LlvmIrGlobalVariable (dsoCache.GetType (), "dso_cache") { Value = dsoCache, Comment = " DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, @@ -169,11 +170,13 @@ protected override void Construct (LlvmIrModule module) void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) { - var cache = variable.Value as List; + var arrayInfo = variable.Value as LlvmIrArrayVariableInfo; + var cache = arrayInfo.OriginalVariableValue as List; if (cache == null) { return; } + Console.WriteLine ("Hashing and sorting DSO cache"); bool is64Bit = target.Is64Bit; foreach (StructureInstance instance in cache) { if (instance.Obj == null) { @@ -186,6 +189,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) } entry.hash = GetXxHash (entry.HashedName, is64Bit); + Console.WriteLine ($" hashed '{entry.HashedName}' as 0x{entry.hash:x}"); } cache.Sort ((StructureInstance a, StructureInstance b) => ((DSOCacheEntry)a.Obj).hash.CompareTo (((DSOCacheEntry)b.Obj).hash)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs index 8dae975a1b5..dcbc643113c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs @@ -8,11 +8,13 @@ sealed class LlvmIrArrayVariableInfo public readonly Type ElementType; public readonly IList Entries; public readonly object OriginalVariableValue; + public readonly StructureInfo? StructureInfo; - public LlvmIrArrayVariableInfo (Type elementType, IList entries, object originalVariableValue) + public LlvmIrArrayVariableInfo (Type elementType, IList entries, object originalVariableValue, StructureInfo? structureInfo = null) { ElementType = elementType; Entries = entries; OriginalVariableValue = originalVariableValue; + StructureInfo = structureInfo; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 0262d622efe..d52b9065457 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -217,7 +217,7 @@ void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) ulong alignment; if (typeInfo.IsAggregate) { uint count = GetAggregateValueElementCount (variable); - alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.Size, count * typeInfo.Size); + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); } else if (typeInfo.IsStructure) { alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); } else if (typeInfo.IsPointer) { @@ -311,15 +311,29 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo string irType; ulong size; bool isPointer; + ulong maxFieldAlignment; if (IsArray (type)) { - irType = GetIRType (type, out size, out isPointer); + var arrayInfo = (LlvmIrArrayVariableInfo)value; + if (arrayInfo.ElementType == typeof(StructureInstance)) { + if (arrayInfo.StructureInfo == null) { + throw new InvalidOperationException ($"Internal error: structure type cannot be determined"); + } + + irType = $"%{arrayInfo.StructureInfo.NativeTypeDesignator}.{arrayInfo.StructureInfo.Name}"; + size = arrayInfo.StructureInfo.Size; + maxFieldAlignment = arrayInfo.StructureInfo.MaxFieldAlignment; + isPointer = false; + } else { + irType = GetIRType (arrayInfo.ElementType, out size, out isPointer); + maxFieldAlignment = size; + } typeInfo = new LlvmTypeInfo ( isPointer: isPointer, isAggregate: true, isStructure: false, size: size, - maxFieldAlignment: size + maxFieldAlignment: maxFieldAlignment ); context.Output.Write ('['); @@ -381,6 +395,16 @@ void WriteValue (WriteContext context, Type type, object? value) return; } + if (type == typeof(IntPtr)) { + var ptr = (IntPtr)value; + if (ptr == IntPtr.Zero) { + context.Output.Write ("null"); + } else { + context.Output.Write (((long)ptr).ToString (CultureInfo.InvariantCulture)); + } + return; + } + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } @@ -411,32 +435,41 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) context.Output.Write (", "); } - WriteCommentLine (context, $" {MapManagedTypeToNative (smi.MemberType)} {smi.Info.Name}"); + string? comment = info.GetCommentFromProvider (smi, instance); + if (String.IsNullOrEmpty (comment)) { + var sb = new StringBuilder (" "); + sb.Append (MapManagedTypeToNative (smi.MemberType)); + sb.Append (' '); + sb.Append (smi.Info.Name); + if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { + sb.Append ($" (0x{value:x})"); + } + comment = sb.ToString (); + } + WriteCommentLine (context, comment); } context.DecreaseIndent (); + context.Output.Write (context.CurrentIndent); context.Output.Write ('}'); } void WriteArray (WriteContext context, LlvmIrArrayVariableInfo arrayInfo) { - context.Output.WriteLine (" ["); + context.Output.WriteLine ('['); context.IncreaseIndent (); - string irType; - if (arrayInfo.ElementType == typeof(LlvmIrStringVariable)) { - irType = MapToIRType (typeof(string)); - } else { - irType = MapToIRType (arrayInfo.ElementType); - } - bool first = true; + ulong counter = 0; foreach (object entry in arrayInfo.Entries) { if (!first) { context.Output.WriteLine (','); } else { first = false; } + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, $" {counter++}"); + context.Output.Write (context.CurrentIndent); WriteType (context, arrayInfo.ElementType, entry, out _); context.Output.Write (' '); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 6b2998f711f..e715a13c089 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -102,6 +102,11 @@ public void Add (LlvmIrGlobalVariable variable) return; } + if (IsStructureArrayVariable (variable)) { + AddStructureArrayGlobalVariable (variable); + return; + } + if (IsStructureVariable (variable)) { PrepareStructure (variable); } @@ -116,6 +121,11 @@ void PrepareStructure (LlvmIrGlobalVariable variable) return; } + PrepareStructure (structure); + } + + void PrepareStructure (StructureInstance structure) + { foreach (StructureMemberInfo smi in structure.Info.Members) { if (smi.MemberType != typeof(string)) { continue; @@ -160,6 +170,39 @@ LlvmIrStringVariable RegisterString (string value, string? stringGroupName = nul return sv; } + void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) + { + if (variable.Value == null) { + AddStandardGlobalVariable (variable); + return; + } + + List? entries = null; + if (typeof(ICollection).IsAssignableFrom (variable.Type)) { + entries = new List ((ICollection)variable.Value); + } else { + throw new InvalidOperationException ($"Internal error: unsupported structure array type `{variable.Type}'"); + } + + // For simplicity we support only arrays with homogenous entry types + StructureInfo? info = null; + foreach (StructureInstance structure in entries) { + if (info == null) { + info = structure.Info; + } + + if (structure.Type != info.Type) { + throw new InvalidOperationException ($"Internal error: only arrays with homogenous element types are currently supported. All entries were expected to be of type '{info.Type}', but the '{structure.Type}' type was encountered."); + } + + PrepareStructure (structure); + } + + var arrayInfo = new LlvmIrArrayVariableInfo (typeof(StructureInstance), entries, variable.Value, info); + variable.OverrideValue (typeof(LlvmIrArrayVariableInfo), arrayInfo); + AddStandardGlobalVariable (variable); + } + void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) { if (variable.Value == null) { @@ -177,10 +220,8 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string } } else if (typeof(ICollection).IsAssignableFrom (variable.Type)) { entries = new List ((ICollection)variable.Value); - } else if (variable.Type == typeof(string[])) { - entries = new List ((string[])variable.Value); - } else { - throw new InvalidOperationException ($"Internal error: unsupported array string type `{variable.Type}'"); + } else { + throw new InvalidOperationException ($"Internal error: unsupported string array type `{variable.Type}'"); } var strings = new List (); @@ -213,7 +254,7 @@ bool IsStringArrayVariable (LlvmIrGlobalVariable variable) return true; } - if (variable.Type.IsArray && variable.Type.GetElementType () == typeof(string)) { + if (variable.Type == typeof(string[])) { if (variable.Value != null && variable.Value.GetType () != typeof(string[])) { throw new InvalidOperationException ($"Internal error: string array variable must have its value set to either `null` or be `{typeof(string[])}`"); } @@ -237,6 +278,12 @@ bool IsStringVariable (LlvmIrGlobalVariable variable) return true; } + bool IsStructureArrayVariable (LlvmIrGlobalVariable variable) + { + var ctype = typeof(ICollection); + return ctype.IsAssignableFrom (variable.Type) || variable.Type == typeof(StructureInstance[]); + } + bool IsStructureVariable (LlvmIrGlobalVariable variable) { if (variable.Type != typeof(StructureInstance)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index 3bb1d0f37bd..af7d9c6179d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -136,6 +136,7 @@ public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? opti Options = options; } + // TODO: fix this, it's cumbersome and clunky /// /// Supports instances where a variable value must be processed by (for instance for arrays). /// Should **not** be used by code other than LlvmIrModule. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs index 65873d35648..ce0ee49ade2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs @@ -31,6 +31,12 @@ public static bool ShouldBeIgnored (this MemberInfo mi) 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 (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs index 034d655361f..4a03cb12af9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs @@ -14,7 +14,7 @@ sealed class StructureInfo public ulong Size { get; } public IList Members { get; } = new List (); public NativeAssemblerStructContextDataProvider? DataProvider { get; } - public int MaxFieldAlignment { get; private set; } = 0; + public ulong MaxFieldAlignment { get; private set; } = 0; public bool HasStrings { get; private set; } public bool HasPreAllocatedBuffers { get; private set; } public bool HasPointers { get; private set; } @@ -31,6 +31,20 @@ public StructureInfo (LlvmIrModule module, Type type) NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; } + 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; + } + ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) { ulong size = 0; @@ -48,8 +62,8 @@ ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) Members.Add (info); size += info.Size; - if ((int)info.Alignment > MaxFieldAlignment) { - MaxFieldAlignment = (int)info.Alignment; + if (info.Alignment > MaxFieldAlignment) { + MaxFieldAlignment = info.Alignment; } } From 0f39485144c448c7cb530ab9326b15443a0311f9 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 1 Jun 2023 22:20:48 +0200 Subject: [PATCH 39/60] Phew, array and structure handling simplified and unified We should be able to support nested structures and arrays. Done away with variable modification when adding to module as well as with the need for wrapper variables and array information structures. Much nicer now! --- ...cationConfigNativeAssemblyGenerator.New.cs | 19 ++-- .../LlvmIrArrayVariableInfo.cs | 20 ---- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 74 +++++++++----- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 71 +++++++------ .../LlvmIrGenerator/LlvmIrVariable.New.cs | 32 +++--- .../LlvmIrGenerator/StructureInstance.New.cs | 25 ++++- .../LlvmIrGenerator/TypeUtilities.New.cs | 99 ++++++++++++++++++- 7 files changed, 224 insertions(+), 116 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index 60928e88f1a..572ba779937 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -67,7 +67,7 @@ sealed class DSOCacheEntry SortedDictionary ? systemProperties; TaskLoggingHelper log; StructureInstance? application_config; - List? dsoCache; + List>? dsoCache; StructureInfo? applicationConfigStructureInfo; StructureInfo? dsoCacheEntryStructureInfo; @@ -117,14 +117,12 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable (FORMAT_TAG.GetType (), "format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); module.AddGlobalVariable (typeof(string), "mono_aot_mode_name", MonoAOTMode); - var envVars = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_environment_variables") { - Value = environmentVariables, + var envVars = new LlvmIrGlobalVariable (environmentVariables, "app_environment_variables") { Comment = " Application environment variables array, name:value", }; module.Add (envVars, stringGroupName: "env", stringGroupComment: " Application environment variables name:value pairs"); - var sysProps = new LlvmIrGlobalVariable (LlvmIrModule.NameValueArrayType, "app_system_properties") { - Value = systemProperties, + var sysProps = new LlvmIrGlobalVariable (systemProperties, "app_system_properties") { Comment = " System properties defined by the application", }; module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); @@ -157,7 +155,7 @@ protected override void Construct (LlvmIrModule module) mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; - application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); + application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); var dso_cache = new LlvmIrGlobalVariable (dsoCache.GetType (), "dso_cache") { @@ -170,8 +168,7 @@ protected override void Construct (LlvmIrModule module) void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) { - var arrayInfo = variable.Value as LlvmIrArrayVariableInfo; - var cache = arrayInfo.OriginalVariableValue as List; + var cache = variable.Value as List; if (cache == null) { return; } @@ -195,7 +192,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) cache.Sort ((StructureInstance a, StructureInstance b) => ((DSOCacheEntry)a.Obj).hash.CompareTo (((DSOCacheEntry)b.Obj).hash)); } - List InitDSOCache () + List> InitDSOCache () { var dsos = new List<(string name, string nameLabel, bool ignore)> (); var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); @@ -214,7 +211,7 @@ List InitDSOCache () dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", 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++) { @@ -230,7 +227,7 @@ List InitDSOCache () name = name, }; - dsoCache.Add (new StructureInstance (dsoCacheEntryStructureInfo, entry)); + dsoCache.Add (new StructureInstance (dsoCacheEntryStructureInfo, entry)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs deleted file mode 100644 index dcbc643113c..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrArrayVariableInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections; - -namespace Xamarin.Android.Tasks.LLVM.IR; - -sealed class LlvmIrArrayVariableInfo -{ - public readonly Type ElementType; - public readonly IList Entries; - public readonly object OriginalVariableValue; - public readonly StructureInfo? StructureInfo; - - public LlvmIrArrayVariableInfo (Type elementType, IList entries, object originalVariableValue, StructureInfo? structureInfo = null) - { - ElementType = elementType; - Entries = entries; - OriginalVariableValue = originalVariableValue; - StructureInfo = structureInfo; - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index d52b9065457..118c3bd7991 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -260,7 +260,7 @@ void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmT uint GetAggregateValueElementCount (Type type, object? value) { - if (!IsArray (type)) { + if (!type.IsArray ()) { throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); } @@ -268,8 +268,16 @@ uint GetAggregateValueElementCount (Type type, object? value) return 0; } - var info = (LlvmIrArrayVariableInfo)value; - return (uint)info.Entries.Count; + // TODO: use caching here + if (type.ImplementsInterface (typeof(IDictionary))) { + return (uint)((IDictionary)value).Count * 2; + } + + if (type.ImplementsInterface (typeof(ICollection))) { + return (uint)((ICollection)value).Count; + } + + throw new InvalidOperationException ($"Internal error: should never get here"); } void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) @@ -279,7 +287,7 @@ void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo) { - if (type == typeof(StructureInstance)) { + if (IsStructureInstance (type)) { if (value == null) { throw new ArgumentException ("must not be null for structure instances", nameof (value)); } @@ -313,19 +321,17 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo bool isPointer; ulong maxFieldAlignment; - if (IsArray (type)) { - var arrayInfo = (LlvmIrArrayVariableInfo)value; - if (arrayInfo.ElementType == typeof(StructureInstance)) { - if (arrayInfo.StructureInfo == null) { - throw new InvalidOperationException ($"Internal error: structure type cannot be determined"); - } + if (type.IsArray ()) { + Type elementType = type.GetArrayElementType (); + if (elementType.IsStructureInstance (out Type? structureType)) { + StructureInfo si = context.Module.GetStructureInfo (structureType); - irType = $"%{arrayInfo.StructureInfo.NativeTypeDesignator}.{arrayInfo.StructureInfo.Name}"; - size = arrayInfo.StructureInfo.Size; - maxFieldAlignment = arrayInfo.StructureInfo.MaxFieldAlignment; + irType = $"%{si.NativeTypeDesignator}.{si.Name}"; + size = si.Size; + maxFieldAlignment = si.MaxFieldAlignment; isPointer = false; } else { - irType = GetIRType (arrayInfo.ElementType, out size, out isPointer); + irType = GetIRType (elementType, out size, out isPointer); maxFieldAlignment = size; } typeInfo = new LlvmTypeInfo ( @@ -355,18 +361,18 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo context.Output.Write (irType); } - bool IsArray (Type t) => t == typeof(LlvmIrArrayVariableInfo); + bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) { - if (IsArray (variable.Type)) { + if (variable.Type.IsArray ()) { uint count = GetAggregateValueElementCount (variable); if (count == 0) { context.Output.Write ("zeroinitializer"); return; } - WriteArray (context, (LlvmIrArrayVariableInfo)variable.Value); + WriteArray (context, variable); return; } @@ -390,7 +396,7 @@ void WriteValue (WriteContext context, Type type, object? value) return; } - if (type == typeof(StructureInstance)) { + if (IsStructureInstance (type)) { WriteStructureValue (context, (StructureInstance?)value); return; } @@ -405,6 +411,17 @@ void WriteValue (WriteContext context, Type type, object? value) return; } + if (type == typeof(string)) { + if (value == null) { + context.Output.Write ("null"); + return; + } + + LlvmIrStringVariable sv = context.Module.LookupRequiredVariableForString ((string)value); + context.Output.Write (sv.Reference); + return; + } + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } @@ -454,14 +471,27 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) context.Output.Write ('}'); } - void WriteArray (WriteContext context, LlvmIrArrayVariableInfo arrayInfo) + void WriteArray (WriteContext context, LlvmIrVariable variable) { context.Output.WriteLine ('['); context.IncreaseIndent (); + ICollection entries; + if (variable.Type.ImplementsInterface (typeof(IDictionary))) { + var list = new List (); + foreach (var kvp in (IDictionary)variable.Value) { + list.Add (kvp.Key); + list.Add (kvp.Value); + } + entries = list; + } else { + entries = (ICollection)variable.Value; + } + + Type elementType = variable.Type.GetArrayElementType (); bool first = true; ulong counter = 0; - foreach (object entry in arrayInfo.Entries) { + foreach (object entry in entries) { if (!first) { context.Output.WriteLine (','); } else { @@ -471,9 +501,9 @@ void WriteArray (WriteContext context, LlvmIrArrayVariableInfo arrayInfo) WriteCommentLine (context, $" {counter++}"); context.Output.Write (context.CurrentIndent); - WriteType (context, arrayInfo.ElementType, entry, out _); + WriteType (context, elementType, entry, out _); context.Output.Write (' '); - WriteValue (context, arrayInfo.ElementType, entry); + WriteValue (context, elementType, entry); } context.Output.WriteLine (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index e715a13c089..e80100eee74 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -149,25 +149,22 @@ void AddStandardGlobalVariable (LlvmIrGlobalVariable variable) void AddStringGlobalVariable (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) { - if (stringManager == null) { - stringManager = new LlvmIrStringManager (); - } - - LlvmIrStringVariable sv = RegisterString (variable, stringGroupName, stringGroupComment, symbolSuffix); - variable.Value = sv; - + RegisterString (variable, stringGroupName, stringGroupComment, symbolSuffix); AddStandardGlobalVariable (variable); } - LlvmIrStringVariable RegisterString (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + void RegisterString (LlvmIrGlobalVariable variable, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) { - return RegisterString ((string)variable.Value, stringGroupName, stringGroupComment, symbolSuffix); + RegisterString ((string)variable.Value, stringGroupName, stringGroupComment, symbolSuffix); } - LlvmIrStringVariable RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + void RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) { - LlvmIrStringVariable sv = stringManager.Add (value, stringGroupName, stringGroupComment, symbolSuffix); - return sv; + if (stringManager == null) { + stringManager = new LlvmIrStringManager (); + } + + stringManager.Add (value, stringGroupName, stringGroupComment, symbolSuffix); } void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) @@ -177,16 +174,9 @@ void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) return; } - List? entries = null; - if (typeof(ICollection).IsAssignableFrom (variable.Type)) { - entries = new List ((ICollection)variable.Value); - } else { - throw new InvalidOperationException ($"Internal error: unsupported structure array type `{variable.Type}'"); - } - // For simplicity we support only arrays with homogenous entry types StructureInfo? info = null; - foreach (StructureInstance structure in entries) { + foreach (StructureInstance structure in (IEnumerable)variable.Value) { if (info == null) { info = structure.Info; } @@ -198,8 +188,6 @@ void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) PrepareStructure (structure); } - var arrayInfo = new LlvmIrArrayVariableInfo (typeof(StructureInstance), entries, variable.Value, info); - variable.OverrideValue (typeof(LlvmIrArrayVariableInfo), arrayInfo); AddStandardGlobalVariable (variable); } @@ -215,24 +203,23 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string entries = new List (); var dict = (IDictionary)variable.Value; foreach (var kvp in dict) { - entries.Add (kvp.Key); - entries.Add (kvp.Value); + Register (kvp.Key); + Register (kvp.Value); } } else if (typeof(ICollection).IsAssignableFrom (variable.Type)) { - entries = new List ((ICollection)variable.Value); + foreach (string s in (ICollection)variable.Value) { + Register (s); + } } else { throw new InvalidOperationException ($"Internal error: unsupported string array type `{variable.Type}'"); } - var strings = new List (); - foreach (string entry in entries) { - var sv = RegisterString (entry, stringGroupName, stringGroupComment, symbolSuffix); - strings.Add (sv); - } - - var arrayInfo = new LlvmIrArrayVariableInfo (typeof(LlvmIrStringVariable), strings, variable.Value); - variable.OverrideValue (typeof(LlvmIrArrayVariableInfo), arrayInfo); AddStandardGlobalVariable (variable); + + void Register (string value) + { + RegisterString (value, stringGroupName, stringGroupComment, symbolSuffix); + } } bool IsStringArrayVariable (LlvmIrGlobalVariable variable) @@ -280,17 +267,25 @@ bool IsStringVariable (LlvmIrGlobalVariable variable) bool IsStructureArrayVariable (LlvmIrGlobalVariable variable) { - var ctype = typeof(ICollection); - return ctype.IsAssignableFrom (variable.Type) || variable.Type == typeof(StructureInstance[]); + if (typeof(StructureInstance[]).IsAssignableFrom (variable.Type)) { + return true; + } + + if (!variable.Type.IsArray ()) { + return false; + } + + Type elementType = variable.Type.GetArrayElementType (); + return typeof(StructureInstance).IsAssignableFrom (elementType); } bool IsStructureVariable (LlvmIrGlobalVariable variable) { - if (variable.Type != typeof(StructureInstance)) { + if (!typeof(StructureInstance).IsAssignableFrom (variable.Type)) { return false; } - if (variable.Value != null && variable.Value.GetType () != typeof(StructureInstance)) { + if (variable.Value != null && !typeof(StructureInstance).IsAssignableFrom (variable.Value.GetType ())) { throw new InvalidOperationException ("Internal error: variable referring to a structure instance must have its value set to either `null` or an instance of the StructureInstance class"); } @@ -405,7 +400,7 @@ internal StructureInfo GetStructureInfo (Type type) return si; } - throw new InvalidOperationException ($"Unmapped structure {type}"); + throw new InvalidOperationException ($"Internal error: unmapped structure {type}"); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index af7d9c6179d..45ae396399f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -11,10 +11,10 @@ abstract class LlvmIrVariable : IEquatable public abstract bool Global { get; } public abstract string NamePrefix { get; } - public string? Name { get; protected set; } - public Type Type { get; protected set; } - public object? Value { get; set; } - public string? Comment { get; set; } + public string? Name { get; protected set; } + public Type Type { get; protected set; } + public virtual object? Value { get; set; } + public virtual string? Comment { get; set; } /// /// Both global and local variables will want their names to matter in equality checks, but function @@ -27,7 +27,7 @@ abstract class LlvmIrVariable : IEquatable /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are /// referenced. /// - public string Reference { + public virtual string Reference { get { if (String.IsNullOrEmpty (Name)) { throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); @@ -42,7 +42,7 @@ public string Reference { /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output /// stream, allowing updating of any such data as described above. /// - public Action? BeforeWriteCallback { get; set; } + public virtual Action? BeforeWriteCallback { get; set; } /// /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see @@ -119,7 +119,7 @@ class LlvmIrGlobalVariable : LlvmIrVariable /// Specify variable options. If omitted, it defaults to . /// /// - public LlvmIrVariableOptions? Options { get; set; } + public virtual LlvmIrVariableOptions? Options { get; set; } /// /// Constructs a local variable. is translated to one of the LLVM IR first class types (see @@ -136,19 +136,15 @@ public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? opti Options = options; } - // TODO: fix this, it's cumbersome and clunky /// - /// Supports instances where a variable value must be processed by (for instance for arrays). - /// Should **not** be used by code other than LlvmIrModule. - /// - public void OverrideValue (Type newType, object? newValue) + /// Constructs a local variable and sets the property to and + /// property to its type. For that reason, **must not** be null. is + /// required because global variables must be named. + /// + public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? options = null) + : this ((value ?? throw new ArgumentNullException (nameof (value))).GetType (), name, options) { - if (newValue != null && !newType.IsAssignableFrom (newValue.GetType ())) { - throw new ArgumentException ($"Must be exactly, or derived from, the '{newType}' type.", nameof (newValue)); - } - - Type = newType; - Value = newValue; + Value = value; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs index c3dfdf846ec..0266cc899f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -6,16 +6,16 @@ namespace Xamarin.Android.Tasks.LLVM.IR // TODO: remove these aliases once the refactoring is done using StructurePointerData = LLVMIR.StructurePointerData; - class StructureInstance + abstract class StructureInstance { Dictionary? pointees; StructureInfo info; - public object Obj { get; } + public object? Obj { get; } public Type Type => info.Type; public StructureInfo Info => info; - public StructureInstance (StructureInfo info, object instance) + protected StructureInstance (StructureInfo info, object? instance) { if (instance != null && !info.Type.IsAssignableFrom (instance.GetType ())) { throw new ArgumentException ($"must be and instance of, or derived from, the {info.Type} type, or `null`", nameof (instance)); @@ -43,4 +43,23 @@ public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong return null; } } + + /// + /// Represents a typed structure instance, derived from the class. The slightly weird + /// approach is because on one hand we need to operate on a heterogenous set of structures (in which generic types would + /// only get in the way), but on the other hand we need to be able to get the structure type (whose instance is in + /// and ) only by looking at the **type**. This is needed in situations when we have + /// an array of some structures that is empty - we wouldn't be able to gleam the structure type from any instance and we still + /// need to output a stronly typed LLVM IR declaration of the structure array. With this class, most of the code will use the + /// abstract type, and knowing we have only one non-abstract implementation of the class allows + /// us to use StructureInstance<T> in a cast, to get T via reflection. + /// + sealed class StructureInstance : StructureInstance + { + public T? Instance => (T)Obj; + + public StructureInstance (StructureInfo info, T? instance) + : base (info, instance) + {} + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs index 666ca99b8d6..af74260e929 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Reflection; namespace Xamarin.Android.Tasks.LLVM.IR @@ -74,29 +76,118 @@ public static bool IsNativeClass (this Type t) public static bool ImplementsInterface (this Type type, Type requiredIfaceType) { + Console.WriteLine ($"{type}.ImplementsInterface ({requiredIfaceType})"); if (type == null || requiredIfaceType == null) { + Console.WriteLine (" nope #1"); return false; } + if (type == requiredIfaceType) { + return true; + } + bool generic = requiredIfaceType.IsGenericType; + Console.WriteLine ($" required iface is generic? {generic}"); foreach (Type iface in type.GetInterfaces ()) { + Console.WriteLine ($" impl: {iface}"); + if (iface == requiredIfaceType) { + Console.WriteLine (" yep #1"); + return true; + } + if (generic) { if (!iface.IsGenericType) { + Console.WriteLine (" not generic"); continue; } if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { + Console.WriteLine (" yep #2"); return true; } - continue; } + } - if (iface == requiredIfaceType) { - return true; + Console.WriteLine (" nope #2"); + return false; + } + + public static bool IsStructureInstance (this Type type, out Type? structureType) + { + structureType = null; + if (!type.IsGenericType) { + return false; + } + + if (type.GetGenericTypeDefinition () != typeof(StructureInstance<>)) { + return false; + } + + structureType = type.GetGenericArguments ()[0]; + return true; + } + + /// + /// Return element type of a single-dimensional (with one exception, see below) array. Parameter **MUST** + /// correspond to one of the following array types: T[], ICollection<T> or IDictionary<string, string>. The latter is + /// used to comfortably represent name:value arrays, which are output as single dimensional arrays in the native code. + /// + /// + /// Thrown when is not one of the array types listed above. + /// + public static Type GetArrayElementType (this Type type) + { + if (type.IsArray) { + return type.GetElementType (); + } + + if (!type.IsGenericType) { + throw WrongTypeException (); + } + + Type genericType = type.GetGenericTypeDefinition (); + if (genericType.ImplementsInterface (typeof(ICollection<>))) { + Type[] genericArgs = type.GetGenericArguments (); + return genericArgs[0]; + } + + if (!genericType.ImplementsInterface (typeof(IDictionary))) { + throw WrongTypeException (); + } + + return typeof(string); + + // Dictionary + Exception WrongTypeException () => new InvalidOperationException ($"Internal error: type '{type}' is not an array, ICollection or IDictionary"); + } + + /// + /// Determine whether type represents an array, in our understanding. That means the type has to be + /// a standard single-dimensional language array (i.e. T[]), implement ICollection<T> together with ICollection or, + /// as a special case for name:value pair collections, implement IDictionary<string, string> + /// + public static bool IsArray (this Type t) + { + if (t.IsPrimitive) { + return false; + } + + if (t == typeof(string)) { + return false; + } + + if (t.IsArray) { + if (t.GetArrayRank () > 1) { + throw new NotSupportedException ("Internal error: multi-dimensional arrays aren't supported"); } + + return true; } - return false; + // TODO: cache results here + // IDictionary is a special case for name:value string arrays which we use for some constructs. + return (t.ImplementsInterface (typeof(ICollection<>)) && t.ImplementsInterface (typeof(ICollection))) || + t.ImplementsInterface (typeof(IDictionary)); } } } From f81fa8587e4b9f4da8753dc2c970ec86a9f9011f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 2 Jun 2023 21:55:26 +0200 Subject: [PATCH 40/60] ApplicationConfigNativeAssemblyGenerator switched to the new LLVM IR generator Code is less cluttered, easier to follow and has less quirks. --- .../Tasks/GeneratePackageManagerJava.cs | 18 +-- ...cationConfigNativeAssemblyGenerator.New.cs | 136 +++++++++++++++++- ...pplicationConfigNativeAssemblyGenerator.cs | 4 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 121 +++++++++++----- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 13 ++ .../LlvmIrGenerator/LlvmIrVariable.New.cs | 3 + .../LlvmIrGenerator/LlvmIrVariableOptions.cs | 1 + .../LlvmIrGenerator/StructureMemberInfo.cs | 6 + 8 files changed, 253 insertions(+), 49 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 3af27dd41d2..eb502d79f20 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -459,26 +459,26 @@ void AddEnvironment () string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}"); string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll"; string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll"; - AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi); - using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - appConfigAsmGen.Write (targetArch, sw, environmentLlFilePath); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); - } using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - string newEnvironmentLlFilePath = Path.Combine (Path.GetDirectoryName (environmentLlFilePath), $"new-{Path.GetFileName (environmentLlFilePath)}"); + //string newEnvironmentLlFilePath = Path.Combine (Path.GetDirectoryName (environmentLlFilePath), $"new-{Path.GetFileName (environmentLlFilePath)}"); try { - appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, newEnvironmentLlFilePath); + appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, environmentLlFilePath); } catch { throw; } finally { sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, newEnvironmentLlFilePath); + Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); } } + // using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + // appConfigAsmGen.Write (targetArch, sw, environmentLlFilePath); + // sw.Flush (); + // Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); + // } + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); sw.Flush (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index 572ba779937..afc157d6997 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -12,6 +12,7 @@ namespace Xamarin.Android.Tasks.New { // TODO: remove these aliases once the refactoring is done using ApplicationConfig = Xamarin.Android.Tasks.ApplicationConfig; + using NativePointerAttribute = LLVMIR.NativePointerAttribute; // Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh [Flags] @@ -60,6 +61,79 @@ sealed class DSOCacheEntry 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 + { + [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 + { + [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; + + [NativePointer] + public byte data; + public uint name_length; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] + public string name; + } + // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x015E6972616D58; @@ -68,9 +142,13 @@ sealed class DSOCacheEntry TaskLoggingHelper log; StructureInstance? application_config; List>? dsoCache; + List>? xamarinAndroidBundledAssemblies; StructureInfo? applicationConfigStructureInfo; StructureInfo? dsoCacheEntryStructureInfo; + StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; + StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; + StructureInfo? assemblyStoreRuntimeDataStructureInfo; public bool UsesMonoAOT { get; set; } public bool UsesMonoLLVM { get; set; } @@ -156,21 +234,63 @@ protected override void Construct (LlvmIrModule module) android_package_name = AndroidPackageName, }; application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); - module.AddGlobalVariable (application_config.GetType (), "application_config", application_config); + module.AddGlobalVariable ("application_config", application_config); - var dso_cache = new LlvmIrGlobalVariable (dsoCache.GetType (), "dso_cache") { - Value = dsoCache, + var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { Comment = " DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, }; module.Add (dso_cache); + + 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 = null, + }; + + for (int i = 0; i < NumberOfAssembliesInApk; i++) { + xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData)); + } + } + + string bundledBuffersSize = xamarinAndroidBundledAssemblies == null ? "empty (unused when assembly stores are enabled)" : $"{BundledAssemblyNameWidth} bytes long"; + var bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "bundled_assemblies", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + Value = xamarinAndroidBundledAssemblies, + Comment = $" Bundled assembly name buffers, all {bundledBuffersSize}", + }; + module.Add (bundled_assemblies); + + AddAssemblyStores (module); + } + + void AddAssemblyStores (LlvmIrModule module) + { + ulong itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); + var assembly_store_bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "assembly_store_bundled_assemblies", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_store_bundled_assemblies); + + itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); + var assembly_stores = new LlvmIrGlobalVariable (typeof(List>), "assembly_stores", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_stores); } void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) { - var cache = variable.Value as List; + var cache = variable.Value as List>; if (cache == null) { - return; + throw new InvalidOperationException ($"Internal error: DSO cache must no be empty"); } Console.WriteLine ("Hashing and sorting DSO cache"); @@ -189,7 +309,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) Console.WriteLine ($" hashed '{entry.HashedName}' as 0x{entry.hash:x}"); } - cache.Sort ((StructureInstance a, StructureInstance b) => ((DSOCacheEntry)a.Obj).hash.CompareTo (((DSOCacheEntry)b.Obj).hash)); + cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); } List> InitDSOCache () @@ -257,6 +377,10 @@ void AddNameMutations (string name) void MapStructures (LlvmIrModule module) { applicationConfigStructureInfo = module.MapStructure (); + module.MapStructure (); + assemblyStoreSingleAssemblyRuntimeDataStructureinfo = module.MapStructure (); + assemblyStoreRuntimeDataStructureInfo = module.MapStructure (); + xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure (); dsoCacheEntryStructureInfo = module.MapStructure (); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 675e1b5abb9..91d75674070 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -131,7 +131,7 @@ sealed class XamarinAndroidBundledAssembly public uint name_length; [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] - public char name; + public string name; } // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh @@ -229,7 +229,7 @@ public override void Init () data_size = 0, data = 0, name_length = (uint)BundledAssemblyNameWidth, - name = '\0', + name = null, }; for (int i = 0; i < NumberOfAssembliesInApk; i++) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 118c3bd7991..d896e76e0e9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -216,7 +216,7 @@ void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) ulong alignment; if (typeInfo.IsAggregate) { - uint count = GetAggregateValueElementCount (variable); + ulong count = GetAggregateValueElementCount (variable); alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); } else if (typeInfo.IsStructure) { alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); @@ -234,19 +234,25 @@ void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmT WriteType (context, variable, out typeInfo); context.Output.Write (' '); + Type valueType; + if (variable.Value is LlvmIrVariable referencedVariable) { + valueType = referencedVariable.Type; + } else { + valueType = variable.Value?.GetType () ?? variable.Type; + } + if (variable.Value == null) { if (typeInfo.IsPointer) { context.Output.Write ("null"); + return; } - throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); - } + if (typeInfo.IsAggregate) { + WriteValue (context, valueType, variable); + return; + } - Type valueType; - if (variable.Value is LlvmIrVariable referencedVariable) { - valueType = referencedVariable.Type; - } else { - valueType = variable.Value.GetType (); + throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); } if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { @@ -256,15 +262,18 @@ void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmT WriteValue (context, valueType, variable); } - uint GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value); + ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable); - uint GetAggregateValueElementCount (Type type, object? value) + ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) { if (!type.IsArray ()) { throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); } if (value == null) { + if (globalVariable != null) { + return globalVariable.ArrayItemCount; + } return 0; } @@ -282,10 +291,28 @@ uint GetAggregateValueElementCount (Type type, object? value) void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - WriteType (context, variable.Type, variable.Value, out typeInfo); + WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); + } + + void WriteType (WriteContext context, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) + { + if (memberInfo.IsNativePointer) { + typeInfo = new LlvmTypeInfo ( + isPointer: true, + isAggregate: false, + isStructure: false, + size: target.NativePointerSize, + maxFieldAlignment: target.NativePointerSize + ); + + context.Output.Write (IRPointerType); + return; + } + + WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } - void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo) + void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) { if (IsStructureInstance (type)) { if (value == null) { @@ -293,13 +320,7 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo } var si = (StructureInstance)value; - ulong alignment; - - if (si.Info.HasPointers && target.NativePointerSize > si.Info.MaxFieldAlignment) { - alignment = target.NativePointerSize; - } else { - alignment = (ulong)si.Info.MaxFieldAlignment; - } + ulong alignment = GetStructureMaxFieldAlignment (si.Info); typeInfo = new LlvmTypeInfo ( isPointer: false, @@ -328,7 +349,7 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo irType = $"%{si.NativeTypeDesignator}.{si.Name}"; size = si.Size; - maxFieldAlignment = si.MaxFieldAlignment; + maxFieldAlignment = GetStructureMaxFieldAlignment (si); isPointer = false; } else { irType = GetIRType (elementType, out size, out isPointer); @@ -343,7 +364,7 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo ); context.Output.Write ('['); - context.Output.Write (GetAggregateValueElementCount (type, value).ToString (CultureInfo.InvariantCulture)); + context.Output.Write (GetAggregateValueElementCount (type, value, globalVariable).ToString (CultureInfo.InvariantCulture)); context.Output.Write (" x "); context.Output.Write (irType); context.Output.Write (']'); @@ -359,6 +380,15 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo maxFieldAlignment: size ); context.Output.Write (irType); + + ulong GetStructureMaxFieldAlignment (StructureInfo si) + { + if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { + return target.NativePointerSize; + } + + return si.MaxFieldAlignment; + } } bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); @@ -366,8 +396,14 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) { if (variable.Type.IsArray ()) { - uint count = GetAggregateValueElementCount (variable); - if (count == 0) { + bool zeroInitialize = false; + if (variable is LlvmIrGlobalVariable gv) { + zeroInitialize = gv.ZeroInitializeArray || variable.Value == null; + } else { + zeroInitialize = GetAggregateValueElementCount (variable) == 0; + } + + if (zeroInitialize) { context.Output.Write ("zeroinitializer"); return; } @@ -379,6 +415,16 @@ void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) WriteValue (context, valueType, variable.Value); } + void WriteValue (WriteContext context, StructureMemberInfo smi, object? value) + { + if (smi.IsNativePointer && value == null) { + context.Output.Write ("null"); + return; + } + + WriteValue (context, smi.MemberType, value); + } + void WriteValue (WriteContext context, Type type, object? value) { if (value is LlvmIrVariable variableRef) { @@ -402,12 +448,8 @@ void WriteValue (WriteContext context, Type type, object? value) } if (type == typeof(IntPtr)) { - var ptr = (IntPtr)value; - if (ptr == IntPtr.Zero) { - context.Output.Write ("null"); - } else { - context.Output.Write (((long)ptr).ToString (CultureInfo.InvariantCulture)); - } + // Pointers can only be `null` or a reference to variable + context.Output.Write ("null"); return; } @@ -442,11 +484,11 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) StructureMemberInfo smi = info.Members[i]; context.Output.Write (context.CurrentIndent); - WriteType (context, smi.MemberType, value: null, out _); + WriteType (context, smi, out _); context.Output.Write (' '); object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); - WriteValue (context, smi.MemberType, value); + WriteValue (context, smi, value); if (i < lastMember) { context.Output.Write (", "); @@ -455,7 +497,7 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) string? comment = info.GetCommentFromProvider (smi, instance); if (String.IsNullOrEmpty (comment)) { var sb = new StringBuilder (" "); - sb.Append (MapManagedTypeToNative (smi.MemberType)); + sb.Append (MapManagedTypeToNative (smi)); sb.Append (' '); sb.Append (smi.Info.Name); if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { @@ -889,6 +931,21 @@ static string MapManagedTypeToNative (Type type) return type.GetShortName (); } + static string MapManagedTypeToNative (StructureMemberInfo smi) + { + string nativeType = MapManagedTypeToNative (smi.MemberType); + // Silly, but effective + if (nativeType[nativeType.Length - 1] == '*') { + return nativeType; + } + + if (!smi.IsNativePointer) { + return nativeType; + } + + return $"{nativeType}*"; + } + static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; object? GetTypedMemberValue (WriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index e80100eee74..d08bc0acd7c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -58,6 +58,19 @@ public void AfterConstruction () GlobalVariables = globalVariables?.AsReadOnly (); } + /// + /// A shortcut way to add a global variable without first having to create an instance of first. This overload + /// requires the parameter to not be null. + /// + public LlvmIrGlobalVariable AddGlobalVariable (string name, object value, LlvmIrVariableOptions? options = null, string? comment = null) + { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + return AddGlobalVariable (value.GetType (), name, value, options, comment); + } + /// /// A shortcut way to add a global variable without first having to create an instance of first. /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index 45ae396399f..796156deed5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -121,6 +121,9 @@ class LlvmIrGlobalVariable : LlvmIrVariable /// public virtual LlvmIrVariableOptions? Options { get; set; } + public bool ZeroInitializeArray { get; set; } + public ulong ArrayItemCount { get; set; } + /// /// Constructs a local variable. is translated to one of the LLVM IR first class types (see /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs index db2f69b43e3..680881ca31f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -23,6 +23,7 @@ class LlvmIrVariableOptions /// public static readonly LlvmIrVariableOptions GlobalWritable = new LlvmIrVariableOptions { Writability = LlvmIrWritability.Writable, + AddressSignificance = LlvmIrAddressSignificance.LocalUnnamed, }; /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs index c09d46910fe..63b6f111a48 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs @@ -110,5 +110,11 @@ int GetArraySizeFromProvider (NativeAssemblerStructContextDataProvider? provider return (int)provider.GetMaxInlineWidth (null, fieldName); } + + public override int GetHashCode () + { + return base.GetHashCode () ^ Info.GetHashCode () ^ MemberType.GetHashCode (); + } + } } From 09fbf9c144081c07195bac45911d82aa4455b88d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 5 Jun 2023 23:04:15 +0200 Subject: [PATCH 41/60] Moving on with new LLVM IR generator Started on the typemap composer --- ...cationConfigNativeAssemblyGenerator.New.cs | 6 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 133 ++++++-- .../LlvmIrGenerator/LlvmIrKnownMetadata.cs | 7 + .../LlvmIrMetadataManager.New.cs | 176 ++++++++++ .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 27 +- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 13 + .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 10 + .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 13 + .../LlvmIrGenerator/LlvmIrModuleX86.cs | 10 + .../StructureMemberInfo.New.cs | 4 + .../Utilities/TypeMapGenerator.cs | 23 ++ ...ppingReleaseNativeAssemblyGenerator.New.cs | 316 +++++++++++++++++- 12 files changed, 699 insertions(+), 39 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index afc157d6997..6827d88ad47 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -192,8 +192,8 @@ protected override void Construct (LlvmIrModule module) { MapStructures (module); - module.AddGlobalVariable (FORMAT_TAG.GetType (), "format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); - module.AddGlobalVariable (typeof(string), "mono_aot_mode_name", MonoAOTMode); + module.AddGlobalVariable ("format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); + module.AddGlobalVariable ("mono_aot_mode_name", MonoAOTMode); var envVars = new LlvmIrGlobalVariable (environmentVariables, "app_environment_variables") { Comment = " Application environment variables array, name:value", @@ -293,7 +293,6 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) throw new InvalidOperationException ($"Internal error: DSO cache must no be empty"); } - Console.WriteLine ("Hashing and sorting DSO cache"); bool is64Bit = target.Is64Bit; foreach (StructureInstance instance in cache) { if (instance.Obj == null) { @@ -306,7 +305,6 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) } entry.hash = GetXxHash (entry.HashedName, is64Bit); - Console.WriteLine ($" hashed '{entry.HashedName}' as 0x{entry.hash:x}"); } cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index d896e76e0e9..a8529873477 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -46,12 +46,14 @@ sealed class WriteContext public readonly TextWriter Output; public readonly LlvmIrModule Module; + public readonly LlvmIrMetadataManager MetadataManager; public string CurrentIndent { get; private set; } = String.Empty; - public WriteContext (TextWriter writer, LlvmIrModule module) + public WriteContext (TextWriter writer, LlvmIrModule module, LlvmIrMetadataManager metadataManager) { Output = writer; Module = module; + MetadataManager = metadataManager; } public void IncreaseIndent () @@ -128,7 +130,10 @@ public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) public void Generate (TextWriter writer, LlvmIrModule module) { - var context = new WriteContext (writer, module); + LlvmIrMetadataManager metadataManager = module.GetMetadataManagerCopy (); + target.AddTargetSpecificMetadata (metadataManager); + + var context = new WriteContext (writer, module, metadataManager); if (!String.IsNullOrEmpty (FilePath)) { WriteCommentLine (context, $" ModuleID = '{FileName}'"); context.Output.WriteLine ($"source_filename = \"{FileName}\""); @@ -143,6 +148,7 @@ public void Generate (TextWriter writer, LlvmIrModule module) WriteStrings (context); WriteExternalFunctionDeclarations (context); WriteAttributeSets (context); + WriteMetadata (context); } void WriteStrings (WriteContext context) @@ -309,6 +315,11 @@ void WriteType (WriteContext context, StructureMemberInfo memberInfo, out LlvmTy return; } + if (memberInfo.IsInlineArray) { + WriteArrayType (context, memberInfo.MemberType.GetArrayElementType (), memberInfo.ArrayElements, out typeInfo); + return; + } + WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } @@ -340,34 +351,12 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo string irType; ulong size; bool isPointer; - ulong maxFieldAlignment; if (type.IsArray ()) { Type elementType = type.GetArrayElementType (); - if (elementType.IsStructureInstance (out Type? structureType)) { - StructureInfo si = context.Module.GetStructureInfo (structureType); + ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable); - irType = $"%{si.NativeTypeDesignator}.{si.Name}"; - size = si.Size; - maxFieldAlignment = GetStructureMaxFieldAlignment (si); - isPointer = false; - } else { - irType = GetIRType (elementType, out size, out isPointer); - maxFieldAlignment = size; - } - typeInfo = new LlvmTypeInfo ( - isPointer: isPointer, - isAggregate: true, - isStructure: false, - size: size, - maxFieldAlignment: maxFieldAlignment - ); - - context.Output.Write ('['); - context.Output.Write (GetAggregateValueElementCount (type, value, globalVariable).ToString (CultureInfo.InvariantCulture)); - context.Output.Write (" x "); - context.Output.Write (irType); - context.Output.Write (']'); + WriteArrayType (context, elementType, elementCount, out typeInfo); return; } @@ -380,15 +369,48 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo maxFieldAlignment: size ); context.Output.Write (irType); + } - ulong GetStructureMaxFieldAlignment (StructureInfo si) - { - if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { - return target.NativePointerSize; - } + void WriteArrayType (WriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) + { + string irType; + ulong size; + ulong maxFieldAlignment; + bool isPointer; - return si.MaxFieldAlignment; + if (elementType.IsStructureInstance (out Type? structureType)) { + StructureInfo si = context.Module.GetStructureInfo (structureType); + + irType = $"%{si.NativeTypeDesignator}.{si.Name}"; + size = si.Size; + maxFieldAlignment = GetStructureMaxFieldAlignment (si); + isPointer = false; + } else { + irType = GetIRType (elementType, out size, out isPointer); + maxFieldAlignment = size; } + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: true, + isStructure: false, + size: size, + maxFieldAlignment: maxFieldAlignment + ); + + context.Output.Write ('['); + context.Output.Write (elementCount.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x "); + context.Output.Write (irType); + context.Output.Write (']'); + } + + ulong GetStructureMaxFieldAlignment (StructureInfo si) + { + if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { + return target.NativePointerSize; + } + + return si.MaxFieldAlignment; } bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); @@ -415,13 +437,41 @@ void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) WriteValue (context, valueType, variable.Value); } + void AssertArraySize (StructureMemberInfo smi, ulong length, ulong expectedLength) + { + if (length == expectedLength) { + return; + } + + throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{smi.Info.DeclaringType.Name}', expected {expectedLength}, found {length}"); + } + void WriteValue (WriteContext context, StructureMemberInfo smi, object? value) { - if (smi.IsNativePointer && value == null) { + // Structure members decorated with the [NativePointer] attribute cannot have a + // value other than `null`, unless they are strings + if (smi.IsNativePointer && smi.MemberType != typeof(string)) { context.Output.Write ("null"); return; } + if (smi.IsInlineArray) { + Array a = (Array)value; + ulong length = smi.ArrayElements == 0 ? (ulong)a.Length : smi.ArrayElements; + + if (smi.MemberType == typeof(byte[])) { + var bytes = (byte[])value; + + // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte + AssertArraySize (smi, length, smi.ArrayElements); + context.Output.Write ('c'); + context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + return; + } + + throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point"); + } + WriteValue (context, smi.MemberType, value); } @@ -464,6 +514,10 @@ void WriteValue (WriteContext context, Type type, object? value) return; } + if (type.IsInlineArray ()) { + + } + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } @@ -883,6 +937,19 @@ void WriteAttributeSets (WriteContext context) } } + void WriteMetadata (WriteContext context) + { + if (context.MetadataManager.Items.Count == 0) { + return; + } + + context.Output.WriteLine (); + WriteCommentLine (context, " Metadata"); + foreach (LlvmIrMetadataItem metadata in context.MetadataManager.Items) { + context.Output.WriteLine (metadata.Render ()); + } + } + void WriteComment (WriteContext context, string comment) { context.Output.Write (';'); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs new file mode 100644 index 00000000000..c77b6451ec4 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs @@ -0,0 +1,7 @@ +namespace Xamarin.Android.Tasks.LLVM.IR; + +sealed class LlvmIrKnownMetadata +{ + public const string LlvmModuleFlags = "llvm.module.flags"; + public const string LlvmIdent = "llvm.ident"; +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs new file mode 100644 index 00000000000..78bcbdf5e6f --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + class LlvmIrMetadataField + { + public string Contents { get; } + public bool IsReference { get; } + + public LlvmIrMetadataField (LlvmIrMetadataField other) + { + Contents = other.Contents; + IsReference = other.IsReference; + } + + public LlvmIrMetadataField (string value, bool isReference = false) + { + if (isReference) { + 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.MapToIRType (vt); + return $"{irType} {MonoAndroidHelper.CultureInvariantToString (value)}"; + } + + string QuoteString (string value) + { + return $"!{LlvmIrGenerator.QuoteStringNoEscape (value)}"; + } + } + + class LlvmIrMetadataItem + { + List fields; + + public string Name { get; } + + public LlvmIrMetadataItem (LlvmIrMetadataItem other) + { + Name = other.Name; + fields = new List (); + foreach (LlvmIrMetadataField field in other.fields) { + fields.Add (new LlvmIrMetadataField (field)); + } + } + + public LlvmIrMetadataItem (string name) + { + if (name.Length == 0) { + 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 AddReferenceField (LlvmIrMetadataItem referencedItem) + { + AddReferenceField (referencedItem.Name); + } + + public void AddField (object value) + { + AddField (new LlvmIrMetadataField (value)); + } + + public void AddField (LlvmIrMetadataField field) + { + fields.Add (field); + } + + 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 (); + Dictionary nameToItem = new Dictionary (StringComparer.Ordinal); + + public List Items => items; + + public LlvmIrMetadataManager () + {} + + public LlvmIrMetadataManager (LlvmIrMetadataManager other) + { + foreach (LlvmIrMetadataItem item in other.items) { + var newItem = new LlvmIrMetadataItem (item); + items.Add (newItem); + nameToItem.Add (newItem.Name, newItem); + } + counter = other.counter; + } + + public LlvmIrMetadataItem Add (string name, params object[]? values) + { + if (nameToItem.ContainsKey (name)) { + throw new InvalidOperationException ($"Internal error: metadata item '{name}' has already been added"); + } + + var ret = new LlvmIrMetadataItem (name); + + if (values != null && values.Length > 0) { + foreach (object v in values) { + ret.AddField (v); + } + } + items.Add (ret); + + nameToItem.Add (name, ret); + return ret; + } + + public LlvmIrMetadataItem AddNumbered (params object[]? values) + { + string name = counter.ToString (CultureInfo.InvariantCulture); + counter++; + return Add (name, values); + } + + public LlvmIrMetadataItem? GetItem (string name) + { + if (nameToItem.TryGetValue (name, out LlvmIrMetadataItem? item)) { + return item; + } + + return null; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index d08bc0acd7c..3407475b342 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.Linq; +using Xamarin.Android.Tools; + namespace Xamarin.Android.Tasks.LLVM.IR { // TODO: remove these aliases once the refactoring is done using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; + using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; partial class LlvmIrModule { @@ -17,7 +20,7 @@ partial class LlvmIrModule public IList? ExternalFunctions { get; private set; } public IList? AttributeSets { get; private set; } - public IList? Structures { get; private set; } + public IList? Structures { get; private set; } public IList? GlobalVariables { get; private set; } public IList? Strings { get; private set; } @@ -25,9 +28,31 @@ partial class LlvmIrModule Dictionary? externalFunctions; Dictionary? structures; LlvmIrStringManager? stringManager; + LlvmIrMetadataManager metadataManager; List? globalVariables; + public LlvmIrModule () + { + metadataManager = new LlvmIrMetadataManager (); + + // Only model agnostic items can be added here + LlvmIrMetadataItem flags = metadataManager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); + flags.AddReferenceField (metadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "wchar_size", 4)); + flags.AddReferenceField (metadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Max, "PIC Level", 2)); + + LlvmIrMetadataItem ident = metadataManager.Add (LlvmIrKnownMetadata.LlvmIdent); + LlvmIrMetadataItem identValue = metadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); + ident.AddReferenceField (identValue.Name); + } + + /// + /// Return a metadata manager instance which includes copies of all the target-agnostic metadata items. + /// We must not modify the original manager since each target may have conflicting values for certain + /// flags. + /// + public LlvmIrMetadataManager GetMetadataManagerCopy () => new LlvmIrMetadataManager (metadataManager); + /// /// Perform any tasks that need to be done after construction is complete. /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index 666165e13b2..865c5591bd5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -5,6 +5,9 @@ namespace Xamarin.Android.Tasks.LLVM.IR { + // TODO: remove these aliases once the refactoring is done + using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + class LlvmIrModuleAArch64 : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } @@ -40,5 +43,15 @@ public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet att attrSet.Add (new TargetCpuFunctionAttribute ("generic")); attrSet.Add (new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics,+v8a")); } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index 705ad1ac673..35506303c0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -5,6 +5,9 @@ namespace Xamarin.Android.Tasks.LLVM.IR { + // TODO: remove these aliases once the refactoring is done + using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + class LlvmIrModuleArmV7a : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } @@ -54,5 +57,12 @@ public override void SetParameterFlags (LlvmIrFunctionParameter parameter) base.SetParameterFlags (parameter); SetIntegerParameterUpcastFlags (parameter); } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs index eb2097295db..b58a6372017 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -17,6 +17,9 @@ abstract class LlvmIrModuleTarget public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) {} + public virtual void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + {} + public virtual void SetParameterFlags (LlvmIrFunctionParameter parameter) { if (!parameter.NoUndef.HasValue) { @@ -57,5 +60,15 @@ public virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) { return maxFieldAlignment; } + + protected LlvmIrMetadataItem GetFlagsMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem? flags = manager.GetItem (LlvmIrKnownMetadata.LlvmModuleFlags); + if (flags == null) { + flags = manager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); + } + + return flags; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index ba6769ee2a6..13e5768faf1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -5,6 +5,9 @@ namespace Xamarin.Android.Tasks.LLVM.IR { + // TODO: remove these aliases once the refactoring is done + using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + class LlvmIrModuleX86 : LlvmIrModuleTarget { public override LlvmIrDataLayout DataLayout { get; } @@ -59,5 +62,12 @@ public override void SetParameterFlags (LlvmIrFunctionParameter parameter) base.SetParameterFlags (parameter); SetIntegerParameterUpcastFlags (parameter); } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs index d0f711598bd..c67af949525 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs @@ -63,6 +63,10 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrModule module) if (IsNativePointer) { size = 0; // Real size will be determined when code is generated and we know the target architecture } else if (mi.IsInlineArray ()) { + if (!MemberType.IsArray) { + throw new InvalidOperationException ($"Internal error: member {mi.Name} of structure {mi.DeclaringType.Name} is marked as inline array, but is not of an array type."); + } + IsInlineArray = true; IsNativeArray = true; NeedsPadding = mi.InlineArrayNeedsPadding (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index f59656518fc..6c18dec7c0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -418,6 +418,9 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List generator.Init (); GenerateNativeAssembly (generator, outputDirectory); + var generatorNew = new New.TypeMappingReleaseNativeAssemblyGenerator (data); + GenerateNativeAssembly (generatorNew, generatorNew.Construct (), outputDirectory); + return true; } @@ -426,6 +429,26 @@ bool ShouldSkipInJavaToManaged (TypeDefinition td) return td.IsInterface || td.HasGenericParameters; } + void GenerateNativeAssembly (New.TypeMappingReleaseNativeAssemblyGenerator generator, LLVM.IR.LlvmIrModule typeMapModule, string baseFileName) + { + AndroidTargetArch arch; + foreach (string abi in supportedAbis) { + arch = GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi); + + string outputFile = $"{baseFileName}-new.{abi}.ll"; + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + try { + generator.Generate (typeMapModule, arch, sw, outputFile); + } catch { + throw; + } finally { + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, outputFile); + } + } + } + } + void GenerateNativeAssembly (TypeMappingAssemblyGenerator generator, string baseFileName) { AndroidTargetArch arch; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs index 70b86f37b75..5776570579f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs @@ -1,10 +1,324 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks.New { + // TODO: remove these aliases once the refactoring is done + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + partial class TypeMappingReleaseNativeAssemblyGenerator : LlvmIrComposer { - protected override void Construct (LlvmIrModule module) + sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("module_uuid", fieldName, StringComparison.Ordinal) == 0) { + return $" module_uuid: {map_module.MVID}"; + } + + if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { + 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 JavaNameHashComparer : IComparer> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash); + } + } + + // This is here only to generate strongly-typed IR + internal 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 + { + public uint type_token_id; + public uint java_map_index; + } + + // Order of fields and their type must correspond *exactly* to that in + // src/monodroid/jni/xamarin-app.hh TypeMapModule structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapModuleContextDataProvider))] + sealed class TypeMapModule + { + [NativeAssembler (Ignore = true)] + public Guid MVID; + + [NativeAssembler (Ignore = true)] + public string? MapSymbolName; + + [NativeAssembler (Ignore = true)] + public string? DuplicateMapSymbolName; + + [NativeAssembler (Ignore = true)] + public TypeMapGenerator.ModuleReleaseData Data; + + [NativeAssembler (UsesDataProvider = true, InlineArray = true, InlineArraySize = 16)] + public byte[] module_uuid; + public uint entry_count; + public uint duplicate_count; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapModuleEntry map; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapModuleEntry duplicate_map; + + [NativeAssembler (UsesDataProvider = true)] + public string assembly_name; + + [NativePointer (IsNull = true)] + public MonoImage image; + public uint java_name_width; + + [NativePointer (IsNull = true)] + public byte java_map; + } + + // Order of fields and their type must correspond *exactly* to that in + // src/monodroid/jni/xamarin-app.hh TypeMapJava structure + sealed class TypeMapJava + { + [NativeAssembler (Ignore = true)] + public string JavaName; + + [NativeAssembler (Ignore = true)] + public ulong JavaNameHash; + + public uint module_index; + public uint type_token_id; + public uint java_name_index; + } + + sealed class ModuleMapData + { + public string SymbolLabel { get; } + public List> Entries { get; } + + public ModuleMapData (string symbolLabel, List> entries) + { + SymbolLabel = symbolLabel; + Entries = entries; + } + } + + sealed class ConstructionState + { + public List> mapModules; + public Dictionary javaTypesByName; + public List javaNames; + public List> javaMap; + } + + readonly NativeTypeMappingData mappingData; + StructureInfo typeMapJavaStructureInfo; + StructureInfo typeMapModuleStructureInfo; + StructureInfo typeMapModuleEntryStructureInfo; + JavaNameHashComparer javaNameHashComparer; + + ulong moduleCounter = 0; + + public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) + { + this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); + javaNameHashComparer = new JavaNameHashComparer (); + } + + protected override void Construct (LlvmIrModule module) + { + MapStructures (module); + + var cs = new ConstructionState (); + cs.javaTypesByName = new Dictionary (StringComparer.Ordinal); + cs.javaNames = new List (); + InitJavaMap (cs); + InitMapModules (cs); + + module.AddGlobalVariable ("map_module_count", mappingData.MapModuleCount); + module.AddGlobalVariable ("java_type_count", cs.javaMap.Count); + + var map_modules = new LlvmIrGlobalVariable (cs.mapModules, "map_modules", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + Comment = " Managed modules map", + }; + module.Add (map_modules); + } + + void InitJavaMap (ConstructionState cs) + { + cs.javaMap = new List> (); + TypeMapJava map_entry; + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { + cs.javaNames.Add (entry.JavaName); + + map_entry = new TypeMapJava { + module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, + type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, + java_name_index = (uint)(cs.javaNames.Count - 1), + JavaName = entry.JavaName, + }; + + cs.javaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); + cs.javaTypesByName.Add (map_entry.JavaName, map_entry); + } + } + + void InitMapModules (ConstructionState cs) + { + cs.mapModules = new List> (); + foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { + string mapName = $"module{moduleCounter++}_managed_to_java"; + string duplicateMapName; + + if (data.DuplicateTypes.Count == 0) { + duplicateMapName = String.Empty; + } else { + duplicateMapName = $"{mapName}_duplicates"; + } + + var map_module = new TypeMapModule { + MVID = data.Mvid, + MapSymbolName = mapName, + DuplicateMapSymbolName = duplicateMapName.Length == 0 ? null : duplicateMapName, + Data = data, + + module_uuid = data.MvidBytes, + entry_count = (uint)data.Types.Length, + duplicate_count = (uint)data.DuplicateTypes.Count, + assembly_name = data.AssemblyName, + java_name_width = 0, + }; + + cs.mapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); + } + } + + void MapStructures (LlvmIrModule module) + { + typeMapJavaStructureInfo = module.MapStructure (); + typeMapModuleStructureInfo = module.MapStructure (); + typeMapModuleEntryStructureInfo = module.MapStructure (); + } + + // Prepare module map entries by sorting them on the managed token, and then mapping each entry to its corresponding Java type map index. + // Requires that `javaMap` is sorted on the type name hash. + void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData, ConstructionState cs) + { + var mapModuleEntries = new List> (); + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { + var map_entry = new TypeMapModuleEntry { + type_token_id = entry.Token, + java_map_index = GetJavaEntryIndex (entry.JavaName), + }; + mapModuleEntries.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); + } + + mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Instance.type_token_id.CompareTo (b.Instance.type_token_id)); + allModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); + + uint GetJavaEntryIndex (string javaTypeName) + { + if (!cs.javaTypesByName.TryGetValue (javaTypeName, out TypeMapJava javaType)) { + throw new InvalidOperationException ($"INTERNAL ERROR: Java type '{javaTypeName}' not found in cache"); + } + + var key = new StructureInstance (typeMapJavaStructureInfo, javaType); + int idx = cs.javaMap.BinarySearch (key, javaNameHashComparer); + if (idx < 0) { + throw new InvalidOperationException ($"Could not map entry '{javaTypeName}' to array index"); + } + + return (uint)idx; + } + } + + // Generate hashes for all Java type names, then sort javaMap on the name hash. This has to be done in the writing phase because hashes + // will depend on architecture (or, actually, on its bitness) and may differ between architectures (they will be the same for all architectures + // with the same bitness) + (List allMapModulesData, List javaMapHashes) PrepareMapsForWriting (LlvmIrModuleTarget target, ConstructionState cs) + { + bool is64Bit = target.Is64Bit; + + // Generate Java type name hashes... + for (int i = 0; i < cs.javaMap.Count; i++) { + TypeMapJava entry = cs.javaMap[i].Instance; + entry.JavaNameHash = HashName (entry.JavaName); + } + + // ...sort them... + cs.javaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash)); + + var allMapModulesData = new List (); + + // ...and match managed types to Java... + foreach (StructureInstance moduleInstance in cs.mapModules) { + TypeMapModule module = moduleInstance.Instance; + PrepareMapModuleData (module.MapSymbolName, module.Data.Types, allMapModulesData, cs); + if (module.Data.DuplicateTypes.Count > 0) { + PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, allMapModulesData, cs); + } + } + + var javaMapHashes = new HashSet (); + foreach (StructureInstance entry in cs.javaMap) { + javaMapHashes.Add (entry.Instance.JavaNameHash); + } + + return (allMapModulesData, javaMapHashes.ToList ()); + + ulong HashName (string name) + { + if (name.Length == 0) { + return UInt64.MaxValue; + } + + // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do + // the same + return GetXxHash (name, is64Bit); + } + } } } From c32dc3305df7743611bca06b73e4ce3a74b66034 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 6 Jun 2023 22:47:33 +0200 Subject: [PATCH 42/60] Release TypeMap composer converted to the new generator --- ...cationConfigNativeAssemblyGenerator.New.cs | 2 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 87 ++++-- .../LlvmIrGenerator/LlvmIrModuleX86.cs | 2 +- .../LlvmIrGenerator/LlvmIrVariable.New.cs | 57 +++- .../MemberInfoUtilities.New.cs | 12 + .../LlvmIrGenerator/TypeUtilities.New.cs | 8 - .../Utilities/TypeMapGenerator.cs | 6 +- ...ppingReleaseNativeAssemblyGenerator.New.cs | 266 +++++++++++++----- src/monodroid/jni/application_dso_stub.cc | 5 +- 9 files changed, 333 insertions(+), 112 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs index 6827d88ad47..cc6afe5bb44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs @@ -286,7 +286,7 @@ void AddAssemblyStores (LlvmIrModule module) module.Add (assembly_stores); } - void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target) + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) { var cache = variable.Value as List>; if (cache == null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index a8529873477..6981fe130f6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -190,7 +190,7 @@ void WriteGlobalVariables (WriteContext context) context.Output.WriteLine (); foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { if (gv.BeforeWriteCallback != null) { - gv.BeforeWriteCallback (gv, target); + gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); } WriteGlobalVariable (context, gv); } @@ -430,29 +430,28 @@ void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) return; } - WriteArray (context, variable); + WriteArrayValue (context, variable); return; } WriteValue (context, valueType, variable.Value); } - void AssertArraySize (StructureMemberInfo smi, ulong length, ulong expectedLength) + void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong length, ulong expectedLength) { if (length == expectedLength) { return; } - throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{smi.Info.DeclaringType.Name}', expected {expectedLength}, found {length}"); + throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}"); } - void WriteValue (WriteContext context, StructureMemberInfo smi, object? value) + void WriteValue (WriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) { - // Structure members decorated with the [NativePointer] attribute cannot have a - // value other than `null`, unless they are strings - if (smi.IsNativePointer && smi.MemberType != typeof(string)) { - context.Output.Write ("null"); - return; + if (smi.IsNativePointer) { + if (WriteNativePointerValue (context, structInstance, smi, value)) { + return; + } } if (smi.IsInlineArray) { @@ -463,18 +462,48 @@ void WriteValue (WriteContext context, StructureMemberInfo smi, object? value) var bytes = (byte[])value; // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte - AssertArraySize (smi, length, smi.ArrayElements); + AssertArraySize (structInstance, smi, length, smi.ArrayElements); context.Output.Write ('c'); context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); return; } - throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point"); + throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point. Field {smi.Info.Name} in structure {structInstance.Info.Name}"); } WriteValue (context, smi.MemberType, value); } + bool WriteNativePointerValue (WriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) + { + // Structure members decorated with the [NativePointer] attribute cannot have a + // value other than `null`, unless they are strings or references to symbols + + if (smi.Info.PointsToSymbol (out string? symbolName)) { + if (String.IsNullOrEmpty (symbolName) && smi.Info.UsesDataProvider ()) { + if (si.Info.DataProvider == null) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{si.Info.Name}' points to a symbol, but symbol name wasn't provided and there's no configured data context provider"); + } + symbolName = si.Info.DataProvider.GetPointedToSymbolName (si.Obj, smi.Info.Name); + } + + if (String.IsNullOrEmpty (symbolName)) { + context.Output.Write ("null"); + } else { + context.Output.Write ('@'); + context.Output.Write (symbolName); + } + return true; + } + + if (smi.MemberType != typeof(string)) { + context.Output.Write ("null"); + return true; + } + + return false; + } + void WriteValue (WriteContext context, Type type, object? value) { if (value is LlvmIrVariable variableRef) { @@ -542,7 +571,7 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) context.Output.Write (' '); object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); - WriteValue (context, smi, value); + WriteValue (context, instance, smi, value); if (i < lastMember) { context.Output.Write (", "); @@ -567,7 +596,7 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) context.Output.Write ('}'); } - void WriteArray (WriteContext context, LlvmIrVariable variable) + void WriteArrayValue (WriteContext context, LlvmIrVariable variable) { context.Output.WriteLine ('['); context.IncreaseIndent (); @@ -586,25 +615,47 @@ void WriteArray (WriteContext context, LlvmIrVariable variable) Type elementType = variable.Type.GetArrayElementType (); bool first = true; + bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; ulong counter = 0; + string? prevItemComment = null; foreach (object entry in entries) { if (!first) { - context.Output.WriteLine (','); + context.Output.Write (','); + WritePrevItemCommentOrNewline (); } else { first = false; } - context.Output.Write (context.CurrentIndent); - WriteCommentLine (context, $" {counter++}"); + prevItemComment = null; + if (variable.GetArrayItemCommentCallback != null) { + prevItemComment = variable.GetArrayItemCommentCallback (variable, counter, entry, variable.GetArrayItemCommentCallbackCallerState); + } + + if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { + prevItemComment = $" {counter}"; + } + + counter++; context.Output.Write (context.CurrentIndent); WriteType (context, elementType, entry, out _); + context.Output.Write (' '); WriteValue (context, elementType, entry); } - context.Output.WriteLine (); + WritePrevItemCommentOrNewline (); context.DecreaseIndent (); context.Output.Write (']'); + + void WritePrevItemCommentOrNewline () + { + if (!String.IsNullOrEmpty (prevItemComment)) { + context.Output.Write (' '); + WriteCommentLine (context, prevItemComment); + } else { + context.Output.WriteLine (); + } + } } void WriteLinkage (WriteContext context, LlvmIrLinkage linkage) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index 13e5768faf1..3f532daa606 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -14,7 +14,7 @@ class LlvmIrModuleX86 : LlvmIrModuleTarget public override string Triple => "i686-unknown-linux-android21"; public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; public override uint NativePointerSize => 4; - public override bool Is64Bit => true; + public override bool Is64Bit => false; public LlvmIrModuleX86 () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index 796156deed5..c3da752bf78 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -6,15 +6,23 @@ namespace Xamarin.Android.Tasks.LLVM.IR; // TODO: remove these aliases once the refactoring is done using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; +[Flags] +enum LlvmIrVariableWriteOptions +{ + None = 0x0000, + ArrayWriteIndexComments = 0x0001, +} + abstract class LlvmIrVariable : IEquatable { public abstract bool Global { get; } public abstract string NamePrefix { get; } - public string? Name { get; protected set; } - public Type Type { get; protected set; } - public virtual object? Value { get; set; } - public virtual string? Comment { get; set; } + public string? Name { get; protected set; } + public Type Type { get; protected set; } + public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + public virtual object? Value { get; set; } + public virtual string? Comment { get; set; } /// /// Both global and local variables will want their names to matter in equality checks, but function @@ -38,11 +46,39 @@ public virtual string Reference { } /// + /// /// Certain data must be calculated when the target architecture is known, because it may depend on certain aspects of /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output /// stream, allowing updating of any such data as described above. + /// + /// + /// First parameter passed to the callback is the variable itself, second parameter is the current + /// and the third is the value previously assigned to + /// + /// + public Action? BeforeWriteCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? BeforeWriteCallbackCallerState { get; set; } + + /// + /// + /// Callback used when processing array variables, called for each item of the array in order to obtain the item's comment, if any. + /// + /// + /// The first argument is the variable which contains the array, second is the item index, third is the item value and fourth is + /// the caller state object, previously assigned to the property. The callback + /// can return an empty string or null, in which case no comment is written. + /// /// - public virtual Action? BeforeWriteCallback { get; set; } + public Func? GetArrayItemCommentCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? GetArrayItemCommentCallbackCallerState { get; set; } /// /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see @@ -149,6 +185,17 @@ public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? o { Value = value; } + + /// + /// This is, unfortunately, needed to be able to address scenarios when a single symbol can have a different type when + /// generating output for a specific target (e.g. 32-bit vs 64-bit integer variables). If the variable requires such + /// type changes, this should be done at generation time from within the method. + /// + public void OverrideValueAndType (Type newType, object? newValue) + { + Type = newType; + Value = newValue; + } } class LlvmIrStringVariable : LlvmIrGlobalVariable diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs index ce0ee49ade2..c7742aa7444 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs @@ -25,6 +25,18 @@ public static bool IsNativePointerToPreallocatedBuffer (this MemberInfo mi, out return attr.PointsToPreAllocatedBuffer; } + public static bool PointsToSymbol (this MemberInfo mi, out string? symbolName) + { + var attr = mi.GetCustomAttribute (); + if (attr == null || attr.PointsToSymbol == null) { + symbolName = null; + return false; + } + + symbolName = attr.PointsToSymbol; + return true; + } + public static bool ShouldBeIgnored (this MemberInfo mi) { var attr = mi.GetCustomAttribute (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs index af74260e929..a67a3c41859 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs @@ -76,9 +76,7 @@ public static bool IsNativeClass (this Type t) public static bool ImplementsInterface (this Type type, Type requiredIfaceType) { - Console.WriteLine ($"{type}.ImplementsInterface ({requiredIfaceType})"); if (type == null || requiredIfaceType == null) { - Console.WriteLine (" nope #1"); return false; } @@ -87,28 +85,22 @@ public static bool ImplementsInterface (this Type type, Type requiredIfaceType) } bool generic = requiredIfaceType.IsGenericType; - Console.WriteLine ($" required iface is generic? {generic}"); foreach (Type iface in type.GetInterfaces ()) { - Console.WriteLine ($" impl: {iface}"); if (iface == requiredIfaceType) { - Console.WriteLine (" yep #1"); return true; } if (generic) { if (!iface.IsGenericType) { - Console.WriteLine (" not generic"); continue; } if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { - Console.WriteLine (" yep #2"); return true; } } } - Console.WriteLine (" nope #2"); return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 6c18dec7c0f..5dc89229473 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -414,10 +414,6 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List NativeTypeMappingData data; data = new NativeTypeMappingData (logger, modules); - var generator = new TypeMappingReleaseNativeAssemblyGenerator (data); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); - var generatorNew = new New.TypeMappingReleaseNativeAssemblyGenerator (data); GenerateNativeAssembly (generatorNew, generatorNew.Construct (), outputDirectory); @@ -435,7 +431,7 @@ void GenerateNativeAssembly (New.TypeMappingReleaseNativeAssemblyGenerator gener foreach (string abi in supportedAbis) { arch = GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi); - string outputFile = $"{baseFileName}-new.{abi}.ll"; + string outputFile = $"{baseFileName}.{abi}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { try { generator.Generate (typeMapModule, arch, sw, outputFile); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs index 5776570579f..a8603c33dab 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs @@ -1,6 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; +using System.IO.Hashing; using System.Text; using Xamarin.Android.Tasks.LLVM.IR; @@ -60,14 +61,6 @@ public override ulong GetBufferSize (object data, string fieldName) } } - sealed class JavaNameHashComparer : IComparer> - { - public int Compare (StructureInstance a, StructureInstance b) - { - return a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash); - } - } - // This is here only to generate strongly-typed IR internal sealed class MonoImage {} @@ -76,6 +69,9 @@ internal sealed class MonoImage // src/monodroid/jni/xamarin-app.hh TypeMapModuleEntry structure sealed class TypeMapModuleEntry { + [NativeAssembler (Ignore = true)] + public TypeMapJava JavaTypeMapEntry; + public uint type_token_id; public uint java_map_index; } @@ -127,7 +123,10 @@ sealed class TypeMapJava public string JavaName; [NativeAssembler (Ignore = true)] - public ulong JavaNameHash; + public uint JavaNameHash32; + + [NativeAssembler (Ignore = true)] + public ulong JavaNameHash64; public uint module_index; public uint type_token_id; @@ -146,26 +145,45 @@ public ModuleMapData (string symbolLabel, List> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32); + } + } + + sealed class JavaNameHash64Comparer : IComparer> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64); + } + } + sealed class ConstructionState { - public List> mapModules; - public Dictionary javaTypesByName; - public List javaNames; - public List> javaMap; + public List> MapModules; + public Dictionary JavaTypesByName; + public List JavaNames; + public List> JavaMap; + public List AllModulesData; } readonly NativeTypeMappingData mappingData; StructureInfo typeMapJavaStructureInfo; StructureInfo typeMapModuleStructureInfo; StructureInfo typeMapModuleEntryStructureInfo; - JavaNameHashComparer javaNameHashComparer; + JavaNameHash32Comparer javaNameHash32Comparer; + JavaNameHash64Comparer javaNameHash64Comparer; ulong moduleCounter = 0; public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) { this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - javaNameHashComparer = new JavaNameHashComparer (); + javaNameHash32Comparer = new JavaNameHash32Comparer (); + javaNameHash64Comparer = new JavaNameHash64Comparer (); } protected override void Construct (LlvmIrModule module) @@ -173,42 +191,150 @@ protected override void Construct (LlvmIrModule module) MapStructures (module); var cs = new ConstructionState (); - cs.javaTypesByName = new Dictionary (StringComparer.Ordinal); - cs.javaNames = new List (); + cs.JavaTypesByName = new Dictionary (StringComparer.Ordinal); + cs.JavaNames = new List (); InitJavaMap (cs); InitMapModules (cs); + HashJavaNames (cs); + PrepareModules (cs); module.AddGlobalVariable ("map_module_count", mappingData.MapModuleCount); - module.AddGlobalVariable ("java_type_count", cs.javaMap.Count); + module.AddGlobalVariable ("java_type_count", cs.JavaMap.Count); - var map_modules = new LlvmIrGlobalVariable (cs.mapModules, "map_modules", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + var map_modules = new LlvmIrGlobalVariable (cs.MapModules, "map_modules", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { Comment = " Managed modules map", }; module.Add (map_modules); + + // Java hashes are output bafore Java type map **and** managed modules, because they will also sort the Java map for us. + // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save + // time sorting only once. + var map_java_hashes = new LlvmIrGlobalVariable (typeof(List), "map_java_hashes") { + Comment = " Java types name hashes", + BeforeWriteCallback = GenerateAndSortJavaHashes, + BeforeWriteCallbackCallerState = cs, + GetArrayItemCommentCallback = GetJavaHashesItemComment, + GetArrayItemCommentCallbackCallerState = cs, + }; + map_java_hashes.WriteOptions &= ~LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + module.Add (map_java_hashes); + + foreach (ModuleMapData mmd in cs.AllModulesData) { + var mmdVar = new LlvmIrGlobalVariable (mmd.Entries, mmd.SymbolLabel, LLVMIR.LlvmIrVariableOptions.LocalConstant) { + BeforeWriteCallback = UpdateJavaIndexes, + BeforeWriteCallbackCallerState = cs, + }; + module.Add (mmdVar); + } + + module.AddGlobalVariable ("map_java", cs.JavaMap, LLVMIR.LlvmIrVariableOptions.GlobalConstant, " Java to managed map"); + module.AddGlobalVariable ("java_type_names", cs.JavaNames, LLVMIR.LlvmIrVariableOptions.GlobalConstant, " Java type names"); + } + + void UpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + IComparer> hashComparer = target.Is64Bit ? javaNameHash64Comparer : javaNameHash32Comparer; + + var entries = (List>)variable.Value; + foreach (StructureInstance entry in entries) { + entry.Instance.java_map_index = GetJavaEntryIndex (entry.Instance.JavaTypeMapEntry); + } + + uint GetJavaEntryIndex (TypeMapJava javaEntry) + { + var key = new StructureInstance (typeMapJavaStructureInfo, javaEntry); + int idx = cs.JavaMap.BinarySearch (key, hashComparer); + if (idx < 0) { + throw new InvalidOperationException ($"Could not map entry '{javaEntry.JavaName}' to array index"); + } + + return (uint)idx; + } + } + + string? GetJavaHashesItemComment (LlvmIrVariable v, ulong index, object? value, object? callerState) + { + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } + + return $" {index}: 0x{value:x} => {cs.JavaMap[(int)index].Instance.JavaName}"; + } + + void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + Type listType; + IList hashes; + if (target.Is64Bit) { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash64); + } + hashes = list; + } else { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash32); + } + hashes = list; + } + + gv.OverrideValueAndType (listType, hashes); + } + + LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) + { + var gv = variable as LlvmIrGlobalVariable; + if (gv == null) { + throw new InvalidOperationException ("Internal error: global variable expected"); + } + + return gv; + } + + ConstructionState EnsureConstructionState (object? callerState) + { + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } + + return cs; } void InitJavaMap (ConstructionState cs) { - cs.javaMap = new List> (); + cs.JavaMap = new List> (); TypeMapJava map_entry; foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { - cs.javaNames.Add (entry.JavaName); + cs.JavaNames.Add (entry.JavaName); map_entry = new TypeMapJava { module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, - java_name_index = (uint)(cs.javaNames.Count - 1), + java_name_index = (uint)(cs.JavaNames.Count - 1), JavaName = entry.JavaName, }; - cs.javaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); - cs.javaTypesByName.Add (map_entry.JavaName, map_entry); + cs.JavaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); + cs.JavaTypesByName.Add (map_entry.JavaName, map_entry); } } void InitMapModules (ConstructionState cs) { - cs.mapModules = new List> (); + cs.MapModules = new List> (); foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { string mapName = $"module{moduleCounter++}_managed_to_java"; string duplicateMapName; @@ -232,7 +358,7 @@ void InitMapModules (ConstructionState cs) java_name_width = 0, }; - cs.mapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); + cs.MapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); } } @@ -243,73 +369,58 @@ void MapStructures (LlvmIrModule module) typeMapModuleEntryStructureInfo = module.MapStructure (); } - // Prepare module map entries by sorting them on the managed token, and then mapping each entry to its corresponding Java type map index. - // Requires that `javaMap` is sorted on the type name hash. - void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData, ConstructionState cs) + void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, ConstructionState cs) { var mapModuleEntries = new List> (); foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { + if (!cs.JavaTypesByName.TryGetValue (entry.JavaName, out TypeMapJava javaType)) { + throw new InvalidOperationException ($"Internal error: Java type '{entry.JavaName}' not found in cache"); + } + var map_entry = new TypeMapModuleEntry { + JavaTypeMapEntry = javaType, type_token_id = entry.Token, - java_map_index = GetJavaEntryIndex (entry.JavaName), + java_map_index = UInt32.MaxValue, // will be set later, when the target is known }; mapModuleEntries.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); } mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Instance.type_token_id.CompareTo (b.Instance.type_token_id)); - allModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); - - uint GetJavaEntryIndex (string javaTypeName) - { - if (!cs.javaTypesByName.TryGetValue (javaTypeName, out TypeMapJava javaType)) { - throw new InvalidOperationException ($"INTERNAL ERROR: Java type '{javaTypeName}' not found in cache"); - } + cs.AllModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); + } - var key = new StructureInstance (typeMapJavaStructureInfo, javaType); - int idx = cs.javaMap.BinarySearch (key, javaNameHashComparer); - if (idx < 0) { - throw new InvalidOperationException ($"Could not map entry '{javaTypeName}' to array index"); + void PrepareModules (ConstructionState cs) + { + cs.AllModulesData = new List (); + foreach (StructureInstance moduleInstance in cs.MapModules) { + TypeMapModule module = moduleInstance.Instance; + PrepareMapModuleData (module.MapSymbolName, module.Data.Types, cs); + if (module.Data.DuplicateTypes.Count > 0) { + PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, cs); } - - return (uint)idx; } } - // Generate hashes for all Java type names, then sort javaMap on the name hash. This has to be done in the writing phase because hashes - // will depend on architecture (or, actually, on its bitness) and may differ between architectures (they will be the same for all architectures - // with the same bitness) - (List allMapModulesData, List javaMapHashes) PrepareMapsForWriting (LlvmIrModuleTarget target, ConstructionState cs) + void HashJavaNames (ConstructionState cs) { - bool is64Bit = target.Is64Bit; + // We generate both 32-bit and 64-bit hashes at the construction time. Which set will be used depends on the target. + // Java map list will also be sorted when the target is known + var hashes32 = new HashSet (); + var hashes64 = new HashSet (); // Generate Java type name hashes... - for (int i = 0; i < cs.javaMap.Count; i++) { - TypeMapJava entry = cs.javaMap[i].Instance; - entry.JavaNameHash = HashName (entry.JavaName); - } + for (int i = 0; i < cs.JavaMap.Count; i++) { + TypeMapJava entry = cs.JavaMap[i].Instance; - // ...sort them... - cs.javaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash)); + // The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit + entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false); + hashes32.Add (entry.JavaNameHash32); - var allMapModulesData = new List (); - - // ...and match managed types to Java... - foreach (StructureInstance moduleInstance in cs.mapModules) { - TypeMapModule module = moduleInstance.Instance; - PrepareMapModuleData (module.MapSymbolName, module.Data.Types, allMapModulesData, cs); - if (module.Data.DuplicateTypes.Count > 0) { - PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, allMapModulesData, cs); - } + entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true); + hashes64.Add (entry.JavaNameHash64); } - var javaMapHashes = new HashSet (); - foreach (StructureInstance entry in cs.javaMap) { - javaMapHashes.Add (entry.Instance.JavaNameHash); - } - - return (allMapModulesData, javaMapHashes.ToList ()); - - ulong HashName (string name) + ulong HashName (string name, bool is64Bit) { if (name.Length == 0) { return UInt64.MaxValue; @@ -317,7 +428,16 @@ ulong HashName (string name) // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do // the same - return GetXxHash (name, is64Bit); + return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit); + } + + ulong HashBytes (byte[] bytes, bool is64Bit) + { + if (is64Bit) { + return XxHash64.HashToUInt64 (bytes); + } + + return (ulong)XxHash32.HashToUInt32 (bytes); } } } diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index c8bc36c277a..86eaa48bb79 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -25,7 +25,10 @@ const TypeMap type_map = { #else const uint32_t map_module_count = 2; const uint32_t java_type_count = 0; -const char* const java_type_names[] = {}; +const char* const java_type_names[] = { + "java/lang/String", + "java/lang/Exception", +}; static TypeMapModuleEntry module1[] = { { From f35855be7d15d415cfaf52bde557b5252e29c57e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Jun 2023 16:10:24 +0200 Subject: [PATCH 43/60] Debug typemap generator converted --- .../LlvmIrGenerator/StructureInstance.New.cs | 2 +- .../Utilities/TypeMapGenerator.cs | 18 +- ...MappingDebugNativeAssemblyGenerator.New.cs | 172 +++++++++++++++++- .../Xamarin.Android.Common.targets | 4 +- 4 files changed, 182 insertions(+), 14 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs index 0266cc899f9..b5284bb616b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -18,7 +18,7 @@ abstract class StructureInstance protected StructureInstance (StructureInfo info, object? instance) { if (instance != null && !info.Type.IsAssignableFrom (instance.GetType ())) { - throw new ArgumentException ($"must be and instance of, or derived from, the {info.Type} type, or `null`", nameof (instance)); + throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance)); } this.info = info; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 5dc89229473..7abb701f62e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -211,9 +211,8 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L } GeneratedBinaryTypeMaps.Add (typeMapIndexPath); - var generator = new TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); + var composer = new New.TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); + GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; } @@ -243,9 +242,8 @@ bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttribu PrepareDebugMaps (data); - var generator = new TypeMappingDebugNativeAssemblyGenerator (data); - generator.Init (); - GenerateNativeAssembly (generator, outputDirectory); + var composer = new New.TypeMappingDebugNativeAssemblyGenerator (data); + GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; } @@ -414,8 +412,8 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List NativeTypeMappingData data; data = new NativeTypeMappingData (logger, modules); - var generatorNew = new New.TypeMappingReleaseNativeAssemblyGenerator (data); - GenerateNativeAssembly (generatorNew, generatorNew.Construct (), outputDirectory); + var composer = new New.TypeMappingReleaseNativeAssemblyGenerator (data); + GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; } @@ -425,7 +423,7 @@ bool ShouldSkipInJavaToManaged (TypeDefinition td) return td.IsInterface || td.HasGenericParameters; } - void GenerateNativeAssembly (New.TypeMappingReleaseNativeAssemblyGenerator generator, LLVM.IR.LlvmIrModule typeMapModule, string baseFileName) + void GenerateNativeAssembly (LLVM.IR.LlvmIrComposer composer, LLVM.IR.LlvmIrModule typeMapModule, string baseFileName) { AndroidTargetArch arch; foreach (string abi in supportedAbis) { @@ -434,7 +432,7 @@ void GenerateNativeAssembly (New.TypeMappingReleaseNativeAssemblyGenerator gener string outputFile = $"{baseFileName}.{abi}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { try { - generator.Generate (typeMapModule, arch, sw, outputFile); + composer.Generate (typeMapModule, arch, sw, outputFile); } catch { throw; } finally { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs index b9ca6216a39..85bca8f93b9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs @@ -1,10 +1,180 @@ +using System; +using System.Collections.Generic; + using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks.New { + // TODO: remove these aliases once the refactoring is done + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer { + 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 + + sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { + return "assembly_name (unused in this mode)"; + } + + if (String.Compare ("data", fieldName, StringComparison.Ordinal) == 0) { + return "data (unused in this mode)"; + } + + 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 + { + public string from; + public string to; + }; + + // Order of fields and their type must correspond *exactly* to that in + // src/monodroid/jni/xamarin-app.hh TypeMap structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))] + sealed class TypeMap + { + [NativeAssembler (Ignore = true)] + public int JavaToManagedCount; + + [NativeAssembler (Ignore = true)] + public int ManagedToJavaCount; + + public uint entry_count; + + [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] + public string? assembly_name = null; // unused in Debug mode + + [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] + public byte data = 0; // unused in Debug mode + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? java_to_managed = null; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? managed_to_java = null; + }; + + readonly TypeMapGenerator.ModuleDebugData data; + + StructureInfo typeMapEntryStructureInfo; + StructureInfo typeMapStructureInfo; + List> javaToManagedMap; + List> managedToJavaMap; + StructureInstance type_map; + + public TypeMappingDebugNativeAssemblyGenerator (TypeMapGenerator.ModuleDebugData data) + { + this.data = data; + + javaToManagedMap = new List> (); + managedToJavaMap = new List> (); + } + protected override void Construct (LlvmIrModule module) - {} + { + MapStructures (module); + + if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { + var m2j = new TypeMapEntry { + from = entry.ManagedName, + to = entry.JavaName, + }; + managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); + } + } + + if (data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { + TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; + + var j2m = new TypeMapEntry { + from = entry.JavaName, + to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, + }; + javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); + } + } + + var map = new TypeMap { + JavaToManagedCount = data.JavaToManagedMap == null ? 0 : data.JavaToManagedMap.Count, + ManagedToJavaCount = data.ManagedToJavaMap == null ? 0 : data.ManagedToJavaMap.Count, + + entry_count = data.EntryCount, + }; + type_map = new StructureInstance (typeMapStructureInfo, map); + module.AddGlobalVariable (TypeMapSymbol, type_map, LLVMIR.LlvmIrVariableOptions.GlobalConstant); + + if (managedToJavaMap.Count > 0) { + module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LLVMIR.LlvmIrVariableOptions.LocalConstant); + } + + if (javaToManagedMap.Count > 0) { + module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LLVMIR.LlvmIrVariableOptions.LocalConstant); + } + } + + void MapStructures (LlvmIrModule module) + { + typeMapEntryStructureInfo = module.MapStructure (); + typeMapStructureInfo = module.MapStructure (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 53c8a8dd2be..371822887f3 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1401,8 +1401,8 @@ because xbuild doesn't support framework reference assemblies. Outputs="$(_AndroidStaticResourcesFlag)" DependsOnTargets="_CollectRuntimeJarFilenames;$(_BeforeAddStaticResources);_GetMonoPlatformJarPath"> - <_UseStockTypeManager Condition=" '$(_UseNativeStackTraces)' == 'True' And '$(_AndroidUseMarshalMethods)' != 'True' ">true - <_UseStockTypeManager Condition=" '$(_UseStockTypeManager)' == '' ">false + <_UseStockTypeManager Condition=" '$(_UseStockTypeManager)' == '' ">true + <_UseStockTypeManager Condition=" '$(_UseNativeStackTraces)' == 'True' And '$(_AndroidUseMarshalMethods)' == 'True' ">false From 2990041bd4295d629b42002d6bec4969d1d59a8d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Jun 2023 21:30:12 +0200 Subject: [PATCH 44/60] Compressed assemblies composer converted --- ...teCompressedAssembliesNativeSourceFiles.cs | 13 +- .../Tasks/GeneratePackageManagerJava.cs | 6 - ...edAssembliesNativeAssemblyGenerator.New.cs | 141 +++++++++++++++++- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 17 ++- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 7 + .../LlvmIrGenerator/LlvmIrVariable.New.cs | 17 ++- .../LlvmIrGenerator/StructureInstance.New.cs | 19 ++- .../Xamarin.Android.Common.targets | 9 +- 8 files changed, 205 insertions(+), 24 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs index 8367c21b744..38f81e8e17a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs @@ -77,13 +77,22 @@ void Generate (IDictionary dict) var llvmAsmgen = new CompressedAssembliesNativeAssemblyGenerator (dict); llvmAsmgen.Init (); + var composer = new New.CompressedAssembliesNativeAssemblyGenerator (dict); + LLVM.IR.LlvmIrModule compressedAssemblies = composer.Construct (); + foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}"); string llvmIrFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - llvmAsmgen.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath); - sw.Flush (); + try { + composer.Generate (compressedAssemblies, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath); + } catch { + throw; + } finally { + sw.Flush (); + } + if (Files.CopyIfStreamChanged (sw.BaseStream, llvmIrFilePath)) { Log.LogDebugMessage ($"File {llvmIrFilePath} was regenerated"); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index eb502d79f20..b7ccf970381 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -473,12 +473,6 @@ void AddEnvironment () } } - // using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - // appConfigAsmGen.Write (targetArch, sw, environmentLlFilePath); - // sw.Flush (); - // Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath); - // } - using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); sw.Flush (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs index 0bbc0f4b8dc..9cf12bcbf26 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs @@ -1,10 +1,149 @@ +using System; +using System.Collections.Generic; + using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks.New { + // TODO: remove these aliases once the refactoring is done + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer { + const string DescriptorsArraySymbolName = "compressed_assembly_descriptors"; + const string CompressedAssembliesSymbolName = "compressed_assemblies"; + + sealed class CompressedAssemblyDescriptorContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string? GetPointedToSymbolName (object data, string fieldName) + { + if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) { + return null; + } + + var descriptor = EnsureType (data); + return descriptor.BufferSymbolName; + } + } + + // 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 + { + [NativeAssembler (Ignore = true)] + public string BufferSymbolName; + + public uint uncompressed_file_size; + public bool loaded; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + 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; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = DescriptorsArraySymbolName)] + public CompressedAssemblyDescriptor descriptors; + }; + + IDictionary assemblies; + StructureInfo compressedAssemblyDescriptorStructureInfo; + StructureInfo compressedAssembliesStructureInfo; + + public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies) + { + this.assemblies = assemblies; + } + + void InitCompressedAssemblies (out List>? compressedAssemblyDescriptors, + out StructureInstance? compressedAssemblies, + out List? buffers) + { + if (assemblies == null || assemblies.Count == 0) { + compressedAssemblyDescriptors = null; + compressedAssemblies = null; + buffers = null; + return; + } + + ulong counter = 0; + compressedAssemblyDescriptors = new List> (assemblies.Count); + buffers = new List (assemblies.Count); + foreach (var kvp in assemblies) { + string assemblyName = kvp.Key; + CompressedAssemblyInfo info = kvp.Value; + + string bufferName = $"__compressedAssemblyData_{counter++}"; + var descriptor = new CompressedAssemblyDescriptor { + BufferSymbolName = bufferName, + uncompressed_file_size = info.FileSize, + loaded = false, + data = 0 + }; + + var bufferVar = new LlvmIrGlobalVariable (typeof(List), bufferName, LLVMIR.LlvmIrVariableOptions.LocalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = descriptor.uncompressed_file_size, + }; + buffers.Add (bufferVar); + + compressedAssemblyDescriptors.Add (new StructureInstance (compressedAssemblyDescriptorStructureInfo, descriptor)); + } + + compressedAssemblies = new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies { count = (uint)assemblies.Count }); + } + protected override void Construct (LlvmIrModule module) - {} + { + MapStructures (module); + + List>? compressedAssemblyDescriptors; + StructureInstance? compressedAssemblies; + List? buffers; + + InitCompressedAssemblies (out compressedAssemblyDescriptors, out compressedAssemblies, out buffers); + + if (compressedAssemblyDescriptors == null) { + module.AddGlobalVariable ( + typeof(StructureInstance), + CompressedAssembliesSymbolName, + new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies ()) { IsZeroInitialized = true }, + LLVMIR.LlvmIrVariableOptions.GlobalWritable + ); + return; + } + + module.AddGlobalVariable (CompressedAssembliesSymbolName, compressedAssemblies, LLVMIR.LlvmIrVariableOptions.GlobalWritable); + module.AddGlobalVariable (DescriptorsArraySymbolName, compressedAssemblyDescriptors, LLVMIR.LlvmIrVariableOptions.LocalWritable); + + module.Add (new LlvmIrGroupDelimiterVariable ()); + module.Add (buffers); + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void MapStructures (LlvmIrModule module) + { + compressedAssemblyDescriptorStructureInfo = module.MapStructure (); + compressedAssembliesStructureInfo = module.MapStructure (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 6981fe130f6..7d6d7dc3d75 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -48,6 +48,7 @@ sealed class WriteContext public readonly LlvmIrModule Module; public readonly LlvmIrMetadataManager MetadataManager; public string CurrentIndent { get; private set; } = String.Empty; + public bool InVariableGroup { get; set; } public WriteContext (TextWriter writer, LlvmIrModule module, LlvmIrMetadataManager metadataManager) { @@ -187,8 +188,15 @@ void WriteGlobalVariables (WriteContext context) return; } - context.Output.WriteLine (); foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { + if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { + context.InVariableGroup = !context.InVariableGroup; + if (context.InVariableGroup) { + context.Output.WriteLine (); + } + continue; + } + if (gv.BeforeWriteCallback != null) { gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); } @@ -215,7 +223,10 @@ void WriteGlobalVariableStart (WriteContext context, LlvmIrGlobalVariable variab void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) { - context.Output.WriteLine (); + if (!context.InVariableGroup) { + context.Output.WriteLine (); + } + WriteGlobalVariableStart (context, variable); WriteTypeAndValue (context, variable, out LlvmTypeInfo typeInfo); context.Output.Write (", align "); @@ -552,7 +563,7 @@ void WriteValue (WriteContext context, Type type, object? value) void WriteStructureValue (WriteContext context, StructureInstance? instance) { - if (instance == null) { + if (instance == null || instance.IsZeroInitialized) { context.Output.Write ("zeroinitializer"); return; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 3407475b342..a4526bd1a53 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -126,6 +126,13 @@ public void Add (LlvmIrGlobalVariable variable, string stringGroupName, string? throw new InvalidOperationException ("Internal error: this overload is ONLY for adding string or array-of-string variables"); } + public void Add (IList variables) + { + foreach (LlvmIrGlobalVariable variable in variables) { + Add (variable); + } + } + public void Add (LlvmIrGlobalVariable variable) { EnsureValidGlobalVariableType (variable); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index c3da752bf78..d7550ff4ba0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -21,8 +21,8 @@ abstract class LlvmIrVariable : IEquatable public string? Name { get; protected set; } public Type Type { get; protected set; } public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; - public virtual object? Value { get; set; } - public virtual string? Comment { get; set; } + public object? Value { get; set; } + public string? Comment { get; set; } /// /// Both global and local variables will want their names to matter in equality checks, but function @@ -206,3 +206,16 @@ public LlvmIrStringVariable (string name, string value) Value = value; } } + +/// +/// This is to address my dislike to have single-line variables separated by empty lines :P. +/// When an instance of this "variable" is first encountered, it enables variable grouping, that is +/// they will be followed by just a single newline. The next instance of this "variable" turns +/// grouping off, meaning the following variables will be followed by two newlines. +/// +class LlvmIrGroupDelimiterVariable : LlvmIrGlobalVariable +{ + public LlvmIrGroupDelimiterVariable () + : base (typeof(void), ".:!GroupDelimiter!:.", LlvmIrVariableOptions.LocalConstant) + {} +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs index b5284bb616b..3d7260fcaba 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -15,9 +15,22 @@ abstract class StructureInstance public Type Type => info.Type; public StructureInfo Info => info; - protected StructureInstance (StructureInfo info, object? instance) + /// + /// This is a cludge to support zero-initialized structures. In order to output proper variable type + /// when a structure is used, the generator must be able to read the structure descrption, which is + /// provided in the property and, thus, it requires a variable of structural type to + /// **always** have a non-null value. To support zero initialization of such structures, this property + /// can be set to true + /// + public bool IsZeroInitialized { get; set; } + + protected StructureInstance (StructureInfo info, object instance) { - if (instance != null && !info.Type.IsAssignableFrom (instance.GetType ())) { + if (instance == null) { + throw new ArgumentNullException (nameof (instance)); + } + + if (!info.Type.IsAssignableFrom (instance.GetType ())) { throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance)); } @@ -58,7 +71,7 @@ sealed class StructureInstance : StructureInstance { public T? Instance => (T)Obj; - public StructureInstance (StructureInfo info, T? instance) + public StructureInstance (StructureInfo info, T instance) : base (info, instance) {} } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 371822887f3..52e8dc4124a 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1400,11 +1400,6 @@ because xbuild doesn't support framework reference assemblies. Inputs="$(MonoPlatformJarPath);$(_AndroidBuildPropertiesCache)" Outputs="$(_AndroidStaticResourcesFlag)" DependsOnTargets="_CollectRuntimeJarFilenames;$(_BeforeAddStaticResources);_GetMonoPlatformJarPath"> - - <_UseStockTypeManager Condition=" '$(_UseStockTypeManager)' == '' ">true - <_UseStockTypeManager Condition=" '$(_UseNativeStackTraces)' == 'True' And '$(_AndroidUseMarshalMethods)' == 'True' ">false - - From d126d2617981bde13a776c3e46cc62f5aa17d85c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 12 Jun 2023 21:40:34 +0200 Subject: [PATCH 45/60] JNI remapping composer converted --- .../Tasks/GenerateJniRemappingNativeCode.cs | 22 +- .../JniRemappingAssemblyGenerator.New.cs | 320 +++++++++++++++++- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 57 +++- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 13 +- 4 files changed, 382 insertions(+), 30 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs index 7bd5824012a..feab8d8059c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs @@ -54,13 +54,13 @@ public override bool RunTask () void GenerateEmpty () { - Generate (new JniRemappingAssemblyGenerator (), typeReplacementsCount: 0); + Generate (new New.JniRemappingAssemblyGenerator (), typeReplacementsCount: 0); } void Generate () { - var typeReplacements = new List (); - var methodReplacements = new List (); + var typeReplacements = new List (); + var methodReplacements = new List (); var readerSettings = new XmlReaderSettings { XmlResolver = null, @@ -74,19 +74,19 @@ void Generate () } } - Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count); + Generate (new New.JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count); } - void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeReplacementsCount) + void Generate (New.JniRemappingAssemblyGenerator jniRemappingComposer, int typeReplacementsCount) { - jniRemappingGenerator.Init (); + LLVM.IR.LlvmIrModule module = jniRemappingComposer.Construct (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}"); string llFilePath = $"{baseAsmFilePath}.ll"; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - jniRemappingGenerator.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath); + jniRemappingComposer.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath); sw.Flush (); Files.CopyIfStreamChanged (sw.BaseStream, llFilePath); } @@ -94,12 +94,12 @@ void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeRepl BuildEngine4.RegisterTaskObjectAssemblyLocal ( ProjectSpecificTaskObjectKey (JniRemappingNativeCodeInfoKey), - new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingGenerator.ReplacementMethodIndexEntryCount), + new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingComposer.ReplacementMethodIndexEntryCount), RegisteredTaskObjectLifetime.Build ); } - void ReadXml (XmlReader reader, List typeReplacements, List methodReplacements) + void ReadXml (XmlReader reader, List typeReplacements, List methodReplacements) { bool haveAllAttributes; @@ -116,7 +116,7 @@ void ReadXml (XmlReader reader, List typeReplacemen continue; } - typeReplacements.Add (new JniRemappingTypeReplacement (from, to)); + typeReplacements.Add (new New.JniRemappingTypeReplacement (from, to)); } else if (String.Compare ("replace-method", reader.LocalName, StringComparison.Ordinal) == 0) { haveAllAttributes &= GetRequiredAttribute ("source-type", out string sourceType); haveAllAttributes &= GetRequiredAttribute ("source-method-name", out string sourceMethodName); @@ -135,7 +135,7 @@ void ReadXml (XmlReader reader, List typeReplacemen string sourceMethodSignature = reader.GetAttribute ("source-method-signature"); methodReplacements.Add ( - new JniRemappingMethodReplacement ( + new New.JniRemappingMethodReplacement ( sourceType, sourceMethodName, sourceMethodSignature, targetType, targetMethodName, isStatic ) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs index 3b79b0eb358..f456c7a5c96 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs @@ -1,10 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Text; + using Xamarin.Android.Tasks.LLVM.IR; namespace Xamarin.Android.Tasks.New { + // TODO: remove these aliases once the refactoring is done + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + + sealed class JniRemappingTypeReplacement + { + public string From { get; } + public string To { get; } + + public JniRemappingTypeReplacement (string from, string to) + { + From = from; + To = to; + } + } + + sealed class JniRemappingMethodReplacement + { + public string SourceType { get; } + public string SourceMethod { get; } + public string SourceMethodSignature { get; } + + public string TargetType { get; } + public string TargetMethod { get; } + + public bool TargetIsStatic { get; } + + public JniRemappingMethodReplacement (string sourceType, string sourceMethod, string sourceMethodSignature, + string targetType, string targetMethod, bool targetIsStatic) + { + SourceType = sourceType; + SourceMethod = sourceMethod; + SourceMethodSignature = sourceMethodSignature; + + TargetType = targetType; + TargetMethod = targetMethod; + TargetIsStatic = targetIsStatic; + } + } + class JniRemappingAssemblyGenerator : LlvmIrComposer { - protected override void Construct (LlvmIrModule module) + const string TypeReplacementsVariableName = "jni_remapping_type_replacements"; + const string MethodReplacementIndexVariableName = "jni_remapping_method_replacement_index"; + + sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType(data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $" name: {entry.name.str}"; + } + + if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { + return $" replacement: {entry.replacement}"; + } + + return String.Empty; + } + } + + sealed class JniRemappingIndexTypeEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $" name: {entry.name.str}"; + } + + return String.Empty; + } + + public override string GetPointedToSymbolName (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { + return entry.MethodsArraySymbolName; + } + + return base.GetPointedToSymbolName (data, fieldName); + } + + public override ulong GetBufferSize (object data, string fieldName) + { + var entry = EnsureType (data); + if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { + return (ulong)entry.TypeMethods.Count; + } + + return 0; + } + } + + sealed class JniRemappingIndexMethodEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $" name: {entry.name.str}"; + } + + if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { + return $" replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; + } + + if (String.Compare ("signature", fieldName, StringComparison.Ordinal) == 0) { + if (entry.signature.length == 0) { + return String.Empty; + } + + return $"signature: {entry.signature.str}"; + } + + return String.Empty; + } + } + + sealed class JniRemappingString + { + public uint length; + public string str; + }; + + sealed class JniRemappingReplacementMethod + { + public string target_type; + public string target_name; + public bool is_static; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexMethodEntryContextDataProvider))] + sealed class JniRemappingIndexMethodEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString signature; + + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingReplacementMethod replacement; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexTypeEntryContextDataProvider))] + sealed class JniRemappingIndexTypeEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + public uint method_count; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public JniRemappingIndexMethodEntry methods; + + [NativeAssembler (Ignore = true)] + public string MethodsArraySymbolName; + + [NativeAssembler (Ignore = true)] + public List> TypeMethods; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingTypeReplacementEntryContextDataProvider))] + sealed class JniRemappingTypeReplacementEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + + [NativeAssembler (UsesDataProvider = true)] + public string replacement; + }; + + List typeReplacementsInput; + List methodReplacementsInput; + + StructureInfo jniRemappingStringStructureInfo; + StructureInfo jniRemappingReplacementMethodStructureInfo; + StructureInfo jniRemappingIndexMethodEntryStructureInfo; + StructureInfo jniRemappingIndexTypeEntryStructureInfo; + StructureInfo jniRemappingTypeReplacementEntryStructureInfo; + + public int ReplacementMethodIndexEntryCount { get; private set; } = 0; + + public JniRemappingAssemblyGenerator () {} + + public JniRemappingAssemblyGenerator (List typeReplacements, List methodReplacements) + { + this.typeReplacementsInput = typeReplacements ?? throw new ArgumentNullException (nameof (typeReplacements)); + this.methodReplacementsInput = methodReplacements ?? throw new ArgumentNullException (nameof (methodReplacements)); + } + + (List>? typeReplacements, List>? methodIndexTypes) Init () + { + if (typeReplacementsInput == null) { + return (null, null); + } + + var typeReplacements = new List> (); + Console.WriteLine ($"Type replacement input count: {typeReplacementsInput.Count}"); + foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { + var entry = new JniRemappingTypeReplacementEntry { + name = MakeJniRemappingString (mtr.From), + replacement = mtr.To, + }; + + typeReplacements.Add (new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, entry)); + } + typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); + + var methodIndexTypes = new List> (); + var types = new Dictionary> (StringComparer.Ordinal); + + foreach (JniRemappingMethodReplacement mmr in methodReplacementsInput) { + if (!types.TryGetValue (mmr.SourceType, out StructureInstance typeEntry)) { + var entry = new JniRemappingIndexTypeEntry { + name = MakeJniRemappingString (mmr.SourceType), + MethodsArraySymbolName = MakeMethodsArrayName (mmr.SourceType), + TypeMethods = new List> (), + }; + + typeEntry = new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, entry); + methodIndexTypes.Add (typeEntry); + types.Add (mmr.SourceType, typeEntry); + } + + var method = new JniRemappingIndexMethodEntry { + name = MakeJniRemappingString (mmr.SourceMethod), + signature = MakeJniRemappingString (mmr.SourceMethodSignature), + replacement = new JniRemappingReplacementMethod { + target_type = mmr.TargetType, + target_name = mmr.TargetMethod, + is_static = mmr.TargetIsStatic, + }, + }; + + typeEntry.Instance.TypeMethods.Add (new StructureInstance (jniRemappingIndexMethodEntryStructureInfo, method)); + } + + foreach (var kvp in types) { + kvp.Value.Instance.method_count = (uint)kvp.Value.Instance.TypeMethods.Count; + kvp.Value.Instance.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); + } + + methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); + ReplacementMethodIndexEntryCount = methodIndexTypes.Count; + + return (typeReplacements, methodIndexTypes); + + string MakeMethodsArrayName (string typeName) + { + return $"mm_{typeName.Replace ('/', '_')}"; + } + + JniRemappingString MakeJniRemappingString (string str) + { + return new JniRemappingString { + length = GetLength (str), + str = str, + }; + } + + uint GetLength (string str) + { + if (String.IsNullOrEmpty (str)) { + return 0; + } + + return (uint)Encoding.UTF8.GetBytes (str).Length; + } + } + + protected override void Construct (LlvmIrModule module) + { + MapStructures (module); + List>? typeReplacements; + List>? methodIndexTypes; + + (typeReplacements, methodIndexTypes) = Init (); + + if (typeReplacements == null) { + module.AddGlobalVariable ( + typeof(StructureInstance), + TypeReplacementsVariableName, + new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, new JniRemappingTypeReplacementEntry ()) { IsZeroInitialized = true }, + LLVMIR.LlvmIrVariableOptions.GlobalConstant + ); + + module.AddGlobalVariable ( + typeof(StructureInstance), + MethodReplacementIndexVariableName, + new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, new JniRemappingIndexTypeEntry ()) { IsZeroInitialized = true }, + LLVMIR.LlvmIrVariableOptions.GlobalConstant + ); + return; + } + + module.AddGlobalVariable (TypeReplacementsVariableName, typeReplacements, LLVMIR.LlvmIrVariableOptions.GlobalConstant); + + foreach (StructureInstance entry in methodIndexTypes) { + module.AddGlobalVariable (entry.Instance.MethodsArraySymbolName, entry.Instance.TypeMethods, LLVMIR.LlvmIrVariableOptions.LocalConstant); + } + + module.AddGlobalVariable (MethodReplacementIndexVariableName, methodIndexTypes, LLVMIR.LlvmIrVariableOptions.GlobalConstant); + } + + void MapStructures (LlvmIrModule module) + { + jniRemappingStringStructureInfo = module.MapStructure (); + jniRemappingReplacementMethodStructureInfo = module.MapStructure (); + jniRemappingIndexMethodEntryStructureInfo = module.MapStructure (); + jniRemappingIndexTypeEntryStructureInfo = module.MapStructure (); + jniRemappingTypeReplacementEntryStructureInfo = module.MapStructure (); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 7d6d7dc3d75..6f6688e26e2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -18,6 +18,13 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrWritability = LLVMIR.LlvmIrWritability; using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; + sealed class GeneratorStructureInstance : StructureInstance + { + public GeneratorStructureInstance (StructureInfo info, object instance) + : base (info, instance) + {} + } + partial class LlvmIrGenerator { const char IndentChar = '\t'; @@ -311,7 +318,7 @@ void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); } - void WriteType (WriteContext context, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) + void WriteType (WriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) { if (memberInfo.IsNativePointer) { typeInfo = new LlvmTypeInfo ( @@ -331,9 +338,33 @@ void WriteType (WriteContext context, StructureMemberInfo memberInfo, out LlvmTy return; } + if (memberInfo.IsIRStruct ()) { + var sim = new GeneratorStructureInstance (context.Module.GetStructureInfo (memberInfo.MemberType), memberInfo.GetValue (si.Obj)); + WriteStructureType (context, sim, out typeInfo); + return; + } + WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } + void WriteStructureType (WriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) + { + ulong alignment = GetStructureMaxFieldAlignment (si.Info); + + typeInfo = new LlvmTypeInfo ( + isPointer: false, + isAggregate: false, + isStructure: true, + size: si.Info.Size, + maxFieldAlignment: alignment + ); + + context.Output.Write ('%'); + context.Output.Write (si.Info.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Info.Name); + } + void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) { if (IsStructureInstance (type)) { @@ -341,21 +372,7 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo throw new ArgumentException ("must not be null for structure instances", nameof (value)); } - var si = (StructureInstance)value; - ulong alignment = GetStructureMaxFieldAlignment (si.Info); - - typeInfo = new LlvmTypeInfo ( - isPointer: false, - isAggregate: false, - isStructure: true, - size: si.Info.Size, - maxFieldAlignment: alignment - ); - - context.Output.Write ('%'); - context.Output.Write (si.Info.NativeTypeDesignator); - context.Output.Write ('.'); - context.Output.Write (si.Info.Name); + WriteStructureType (context, (StructureInstance)value, out typeInfo); return; } @@ -482,6 +499,12 @@ void WriteValue (WriteContext context, StructureInstance structInstance, Structu throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point. Field {smi.Info.Name} in structure {structInstance.Info.Name}"); } + if (smi.IsIRStruct ()) { + StructureInfo si = context.Module.GetStructureInfo (smi.MemberType); + WriteValue (context, typeof(GeneratorStructureInstance), new GeneratorStructureInstance (si, value)); + return; + } + WriteValue (context, smi.MemberType, value); } @@ -578,7 +601,7 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) StructureMemberInfo smi = info.Members[i]; context.Output.Write (context.CurrentIndent); - WriteType (context, smi, out _); + WriteType (context, instance, smi, out _); context.Output.Write (' '); object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index a4526bd1a53..528f00f1ec7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -172,6 +172,17 @@ void PrepareStructure (LlvmIrGlobalVariable variable) void PrepareStructure (StructureInstance structure) { foreach (StructureMemberInfo smi in structure.Info.Members) { + if (smi.IsIRStruct ()) { + object? instance = structure.Obj == null ? null : smi.GetValue (structure.Obj); + if (instance == null) { + continue; + } + + StructureInfo si = GetStructureInfo (smi.MemberType); + PrepareStructure (new GeneratorStructureInstance (si, instance)); + continue; + } + if (smi.MemberType != typeof(string)) { continue; } @@ -424,7 +435,7 @@ public StructureInfo MapStructure () return (StructureInfo)sinfo; } - var ret = new StructureInfo (this, typeof(T)); + var ret = new StructureInfo (this, t); structures.Add (t, ret); return ret; From 7a231587e83eae1942fe19bfb87fb106d8570ae8 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 13 Jun 2023 20:42:53 +0200 Subject: [PATCH 46/60] Data portion of the marshal methods generator done Code tomorrow --- .../Dependencies/Linux.Debian.cs | 6 +- .../Tasks/GeneratePackageManagerJava.cs | 61 +- .../LlvmIrGenerator/LlvmIrComposer.New.cs | 10 + .../LlvmIrGenerator/LlvmIrFunction.cs | 5 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 27 +- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 12 +- .../LlvmIrGenerator/LlvmIrVariable.New.cs | 10 +- ...rshalMethodsNativeAssemblyGenerator.New.cs | 846 ++++++++++++++++-- ...hodsNativeAssemblyGenerator.Tracing.New.cs | 98 ++ ...ppingReleaseNativeAssemblyGenerator.New.cs | 12 +- 10 files changed, 965 insertions(+), 122 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/Linux.Debian.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/Linux.Debian.cs index 4c613d73118..f904472855a 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/Linux.Debian.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/Linux.Debian.cs @@ -25,6 +25,8 @@ class LinuxDebian : LinuxDebianCommon static readonly Dictionary DebianUnstableVersionMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { { "bookworm", "12" }, { "bookworm/sid", "12" }, + { "trixie", "13" }, + { "trixie/sid", "13" }, }; protected Version DebianRelease { get; private set; } = new Version (0, 0); @@ -56,6 +58,7 @@ static bool IsDebian10OrNewer (string? version) return version!.IndexOf ("bullseye", StringComparison.OrdinalIgnoreCase) >= 0 || version.IndexOf ("bookworm", StringComparison.OrdinalIgnoreCase) >= 0 || + version.IndexOf ("trixie", StringComparison.OrdinalIgnoreCase) >= 0 || version.IndexOf ("sid", StringComparison.OrdinalIgnoreCase) >= 0; } @@ -75,7 +78,8 @@ static bool IsBookwormSidOrNewer (string? debian_version) return false; } - return debian_version!.IndexOf ("bookworm", StringComparison.OrdinalIgnoreCase) >= 0; + return debian_version!.IndexOf ("bookworm", StringComparison.OrdinalIgnoreCase) >= 0 || + debian_version!.IndexOf ("trixie", StringComparison.OrdinalIgnoreCase) >= 0; } protected override bool EnsureVersionInformation (Context context) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index b7ccf970381..69969a47422 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -375,38 +375,7 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (ApplicationConfigTaskState.RegisterTaskObjectKey), RegisteredTaskObjectLifetime.Build); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); - var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { - UsesMonoAOT = usesMonoAOT, - UsesMonoLLVM = EnableLLVM, - UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, - MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), - AotEnableLazyLoad = AndroidAotEnableLazyLoad, - AndroidPackageName = AndroidPackageName, - BrokenExceptionTransitions = environmentParser.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, - AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, - JNIEnvInitializeToken = jnienv_initialize_method_token, - JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, - JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, - JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, - MarshalMethodsEnabled = EnableMarshalMethods, - }; - appConfigAsmGen.Init (); - - var appConfigAsmGenNew = new New.ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { + var appConfigAsmGen = new New.ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, @@ -435,10 +404,11 @@ void AddEnvironment () JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, MarshalMethodsEnabled = EnableMarshalMethods, }; - LLVM.IR.LlvmIrModule appConfigModule = appConfigAsmGenNew.Construct (); + LLVM.IR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct (); var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build); MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; + New.MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGenNew; if (enableMarshalMethods) { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( @@ -448,10 +418,20 @@ void AddEnvironment () Log, mmTracingMode ); + + marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator ( + assemblyCount, + uniqueAssemblyNames, + marshalMethodsState?.MarshalMethods, + Log, + mmTracingMode + ); } else { marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); + marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); } marshalMethodsAsmGen.Init (); + LLVM.IR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGenNew.Construct (); foreach (string abi in SupportedAbis) { string targetAbi = abi.ToLowerInvariant (); @@ -459,12 +439,12 @@ void AddEnvironment () string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}"); string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll"; string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll"; + string marshalMethodsLlFilePathNew = $"{marshalMethodsBaseAsmFilePath}-new.ll"; AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - //string newEnvironmentLlFilePath = Path.Combine (Path.GetDirectoryName (environmentLlFilePath), $"new-{Path.GetFileName (environmentLlFilePath)}"); try { - appConfigAsmGenNew.Generate (appConfigModule, targetArch, sw, environmentLlFilePath); + appConfigAsmGen.Generate (appConfigModule, targetArch, sw, environmentLlFilePath); } catch { throw; } finally { @@ -473,6 +453,17 @@ void AddEnvironment () } } + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { + try { + marshalMethodsAsmGenNew.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePathNew); + } catch { + throw; + } finally { + sw.Flush (); + Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePathNew); + } + } + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); sw.Flush (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs index a9c24576b8b..07f45698b9d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs @@ -43,5 +43,15 @@ public static ulong GetXxHash (string str, bool is64Bit) return (ulong)XxHash32.HashToUInt32 (stringBytes); } + + protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) + { + var gv = variable as LlvmIrGlobalVariable; + if (gv == null) { + throw new InvalidOperationException ("Internal error: global variable expected"); + } + + return gv; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index ceae057f451..a442d55ce41 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -222,7 +222,7 @@ public void RestoreState (ILlvmIrFunctionSignatureState savedState) { var oldState = savedState as SignatureState; if (oldState == null) { - throw new InvalidOperationException ("Internal error: savedState not an instance of {nameof(SignatureState)}"); + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SignatureState)}"); } if (oldState.Owner != this) { @@ -299,6 +299,7 @@ sealed class FunctionState : ILlvmIrFunctionState public FunctionState (LlvmIrFunction owner, ILlvmIrFunctionSignatureState signatureState) { Owner = owner; + SignatureState = signatureState; } } @@ -359,7 +360,7 @@ public void RestoreState (ILlvmIrFunctionState savedState) { var oldState = savedState as FunctionState; if (oldState == null) { - throw new InvalidOperationException ("Internal error: savedState not an instance of {nameof(FunctionState)}"); + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(FunctionState)}"); } if (oldState.Owner != this) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 6f6688e26e2..21d6f690ce7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -266,13 +266,16 @@ void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmT } if (variable.Value == null) { - if (typeInfo.IsPointer) { - context.Output.Write ("null"); + // Order of checks is important here. Aggregates can contain pointer types, in which case typeInfo.IsPointer + // will be `true` and the aggregate would be incorrectly initialized with `null` instead of the correct + // `zeroinitializer` + if (typeInfo.IsAggregate) { + WriteValue (context, valueType, variable); return; } - if (typeInfo.IsAggregate) { - WriteValue (context, valueType, variable); + if (typeInfo.IsPointer) { + context.Output.Write ("null"); return; } @@ -648,10 +651,20 @@ void WriteArrayValue (WriteContext context, LlvmIrVariable variable) } Type elementType = variable.Type.GetArrayElementType (); - bool first = true; bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; ulong counter = 0; string? prevItemComment = null; + uint stride; + + if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) { + stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1; + } else { + stride = 1; + } + + bool first = true; + + // TODO: implement output in rows foreach (object entry in entries) { if (!first) { context.Output.Write (','); @@ -662,7 +675,7 @@ void WriteArrayValue (WriteContext context, LlvmIrVariable variable) prevItemComment = null; if (variable.GetArrayItemCommentCallback != null) { - prevItemComment = variable.GetArrayItemCommentCallback (variable, counter, entry, variable.GetArrayItemCommentCallbackCallerState); + prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); } if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { @@ -1018,7 +1031,7 @@ void WriteAttributeSets (WriteContext context) targetSet.Add (privateTargetSet); } - context.Output.WriteLine ($"attributes #{targetSet.Number} {{ {targetSet.Render ()} }}"); + context.Output.WriteLine ($"attributes #{targetSet.Number} = {{ {targetSet.Render ()} }}"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 528f00f1ec7..f37a04700e3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -188,7 +188,7 @@ void PrepareStructure (StructureInstance structure) } string? value = smi.GetValue (structure.Obj) as string; - if (!String.IsNullOrEmpty (value)) { + if (value != null) { RegisterString (value, stringGroupName: structure.Info.Name, symbolSuffix: smi.Info.Name); } } @@ -335,6 +335,16 @@ bool IsStructureArrayVariable (LlvmIrGlobalVariable variable) return typeof(StructureInstance).IsAssignableFrom (elementType); } + bool IsPointerArrayVariable (LlvmIrGlobalVariable variable) + { + if (!variable.Type.IsArray ()) { + return false; + } + + Type elementType = variable.Type.GetArrayElementType (); + return elementType == typeof(IntPtr) || elementType == typeof(UIntPtr); + } + bool IsStructureVariable (LlvmIrGlobalVariable variable) { if (!typeof(StructureInstance).IsAssignableFrom (variable.Type)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index d7550ff4ba0..2881537ab2f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -11,6 +11,7 @@ enum LlvmIrVariableWriteOptions { None = 0x0000, ArrayWriteIndexComments = 0x0001, + ArrayFormatInRows = 0x0002, } abstract class LlvmIrVariable : IEquatable @@ -21,6 +22,13 @@ abstract class LlvmIrVariable : IEquatable public string? Name { get; protected set; } public Type Type { get; protected set; } public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + + /// + /// Number of columns an array that is written in rows should have. By default, arrays are written one item in a line, but + /// when the flag is set in , then + /// the value of this property dictates how many items are to be placed in a single row. + /// + public uint ArrayStride { get; set; } = 8; public object? Value { get; set; } public string? Comment { get; set; } @@ -73,7 +81,7 @@ public virtual string Reference { /// can return an empty string or null, in which case no comment is written. /// /// - public Func? GetArrayItemCommentCallback { get; set; } + public Func? GetArrayItemCommentCallback { get; set; } /// /// Object passed to the method, if any, as the caller state. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index 69350e245c3..0d7e12edcf6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -1,102 +1,820 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVM.IR; +using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; +using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; + namespace Xamarin.Android.Tasks.New { // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + using NativePointerAttribute = LLVMIR.NativePointerAttribute; + using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { - LlvmIrFunction? mm_trace_func_enter; - LlvmIrFunction? mm_trace_func_leave; - LlvmIrFunction? llvm_lifetime_start; - LlvmIrFunction? llvm_lifetime_end; + // This is here only to generate strongly-typed IR + internal sealed class MonoClass + {} + + [NativeClass] + sealed class _JNIEnv + {} + + // Empty class must have at least one member so that the class address can be obtained + [NativeClass] + class _jobject + { + public byte b; + } + + sealed class _jclass : _jobject + {} + + sealed class _jstring : _jobject + {} + + sealed class _jthrowable : _jobject + {} + + class _jarray : _jobject + {} + + sealed class _jobjectArray : _jarray + {} + + sealed class _jbooleanArray : _jarray + {} + + sealed class _jbyteArray : _jarray + {} + + sealed class _jcharArray : _jarray + {} + + sealed class _jshortArray : _jarray + {} + + sealed class _jintArray : _jarray + {} + + sealed class _jlongArray : _jarray + {} + + sealed class _jfloatArray : _jarray + {} + + sealed class _jdoubleArray : _jarray + {} + + sealed class MarshalMethodInfo + { + public MarshalMethodEntry Method { get; } + public string NativeSymbolName { get; set; } + public List Parameters { get; } + public Type ReturnType { get; } + public uint ClassCacheIndex { get; } + + // This one isn't known until the generation time, which happens after we instantiate the class + // in Init and it may be different between architectures/ABIs, hence it needs to be settable from + // the outside. + public uint AssemblyCacheIndex { get; set; } + + public MarshalMethodInfo (MarshalMethodEntry method, Type returnType, string nativeSymbolName, int classCacheIndex) + { + Method = method ?? throw new ArgumentNullException (nameof (method)); + ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); + if (String.IsNullOrEmpty (nativeSymbolName)) { + throw new ArgumentException ("must not be null or empty", nameof (nativeSymbolName)); + } + NativeSymbolName = nativeSymbolName; + Parameters = new List { + new LlvmIrFunctionParameter (typeof (_JNIEnv), "env"), // JNIEnv *env + new LlvmIrFunctionParameter (typeof (_jclass), "klass"), // jclass klass + }; + ClassCacheIndex = (uint)classCacheIndex; + } + } + + sealed class MarshalMethodsManagedClassDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var klass = EnsureType (data); + + if (String.Compare ("token", fieldName, StringComparison.Ordinal) == 0) { + return $" token 0x{klass.token:x}; class name: {klass.ClassName}"; + } + + return String.Empty; + } + } + + [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodsManagedClassDataProvider))] + sealed class MarshalMethodsManagedClass + { + [NativeAssembler (UsesDataProvider = true)] + public uint token; + + [NativePointer (IsNull = true)] + public MonoClass klass; + + [NativeAssembler (Ignore = true)] + public string ClassName; + }; + + sealed class MarshalMethodNameDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var methodName = EnsureType (data); + + if (String.Compare ("id", fieldName, StringComparison.Ordinal) == 0) { + return $" id 0x{methodName.id:x}; name: {methodName.name}"; + } + + return String.Empty; + } + } + + [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodNameDataProvider))] + sealed class MarshalMethodName + { + [NativeAssembler (Ignore = true)] + public ulong Id32; + + [NativeAssembler (Ignore = true)] + public ulong Id64; + + [NativeAssembler (UsesDataProvider = true)] + public ulong id; + public string name; + } + + sealed class AssemblyCacheState + { + public Dictionary? AsmNameToIndexData32; + public Dictionary Hashes32; + public List Keys32; + public List Indices32; + + public Dictionary? AsmNameToIndexData64; + public Dictionary Hashes64; + public List Keys64; + public List Indices64; + } + + static readonly Dictionary jniSimpleTypeMap = new Dictionary { + { 'Z', typeof(bool) }, + { 'B', typeof(byte) }, + { 'C', typeof(char) }, + { 'S', typeof(short) }, + { 'I', typeof(int) }, + { 'J', typeof(long) }, + { 'F', typeof(float) }, + { 'D', typeof(double) }, + }; + + static readonly Dictionary jniArrayTypeMap = new Dictionary { + { 'Z', typeof(_jbooleanArray) }, + { 'B', typeof(_jbyteArray) }, + { 'C', typeof(_jcharArray) }, + { 'S', typeof(_jshortArray) }, + { 'I', typeof(_jintArray) }, + { 'J', typeof(_jlongArray) }, + { 'F', typeof(_jfloatArray) }, + { 'D', typeof(_jdoubleArray) }, + { 'L', typeof(_jobjectArray) }, + }; + + ICollection uniqueAssemblyNames; + int numberOfAssembliesInApk; + IDictionary> marshalMethods; + TaskLoggingHelper logger; + + StructureInfo marshalMethodsManagedClassStructureInfo; + StructureInfo marshalMethodNameStructureInfo; + + List methods; + List> classes = new List> (); + + LlvmIrCallMarker defaultCallMarker; + + readonly bool generateEmptyCode; + + /// + /// Constructor to be used ONLY when marshal methods are DISABLED + /// + public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames) + { + this.numberOfAssembliesInApk = numberOfAssembliesInApk; + this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); + generateEmptyCode = true; + defaultCallMarker = LlvmIrCallMarker.Tail; + } + + /// + /// Constructor to be used ONLY when marshal methods are ENABLED + /// + public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger, MarshalMethodsTracingMode tracingMode) + { + this.numberOfAssembliesInApk = numberOfAssembliesInApk; + this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); + this.marshalMethods = marshalMethods; + this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); + + generateEmptyCode = false; + this.tracingMode = tracingMode; + defaultCallMarker = tracingMode != MarshalMethodsTracingMode.None ? LlvmIrCallMarker.None : LlvmIrCallMarker.Tail; + } + + void Init () + { + if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { + return; + } + + var seenClasses = new Dictionary (StringComparer.Ordinal); + var allMethods = new List (); + + // It's possible that several otherwise different methods (from different classes, but with the same + // names and similar signatures) will actually share the same **short** native symbol name. In this case we must + // ensure that they all use long symbol names. This has to be done as a post-processing step, after we + // have already iterated over the entire method collection. + // + // A handful of examples from the Hello World MAUI app: + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnActionItemClicked(Android.Views.ActionMode,Android.Views.IMenuItem)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnActionItemClicked(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenuItem)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked__Landroidx_appcompat_view_ActionMode_2Landroid_view_MenuItem_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnCreateActionMode(Android.Views.ActionMode,Android.Views.IMenu)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnCreateActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void Android.Views.ActionMode/ICallback::OnDestroyActionMode(Android.Views.ActionMode)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void AndroidX.AppCompat.View.ActionMode/ICallback::OnDestroyActionMode(AndroidX.AppCompat.View.ActionMode)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode__Landroidx_appcompat_view_ActionMode_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnPrepareActionMode(Android.Views.ActionMode,Android.Views.IMenu)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnPrepareActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 + // + var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); + foreach (IList entryList in marshalMethods.Values) { + bool useFullNativeSignature = entryList.Count > 1; + foreach (MarshalMethodEntry entry in entryList) { + ProcessAndAddMethod (allMethods, entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); + } + } + + foreach (List mmiList in overloadedNativeSymbolNames.Values) { + if (mmiList.Count <= 1) { + continue; + } + + foreach (MarshalMethodInfo overloadedMethod in mmiList) { + overloadedMethod.NativeSymbolName = MakeNativeSymbolName (overloadedMethod.Method, useFullNativeSignature: true); + } + } + + // In some cases it's possible that a single type implements two different interfaces which have methods with the same native signature: + // + // Microsoft.Maui.Controls.Handlers.TabbedPageManager/Listeners + // System.Void AndroidX.ViewPager.Widget.ViewPager/IOnPageChangeListener::OnPageSelected(System.Int32) + // System.Void AndroidX.ViewPager2.Widget.ViewPager2/OnPageChangeCallback::OnPageSelected(System.Int32) + // + // Both of the above methods will have the same native implementation and symbol name. e.g. (Java type name being `crc649ff77a65592e7d55/TabbedPageManager_Listeners`): + // Java_crc649ff77a65592e7d55_TabbedPageManager_1Listeners_n_1onPageSelected__I + // + // We need to de-duplicate the entries or the generated native code will fail to build. + var seenNativeSymbols = new HashSet (StringComparer.Ordinal); + methods = new List (); + + foreach (MarshalMethodInfo method in allMethods) { + if (seenNativeSymbols.Contains (method.NativeSymbolName)) { + logger.LogDebugMessage ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}"); + continue; + } + + seenNativeSymbols.Add (method.NativeSymbolName); + methods.Add (method); + } + } + + string MakeNativeSymbolName (MarshalMethodEntry entry, bool useFullNativeSignature) + { + var sb = new StringBuilder ("Java_"); + sb.Append (MangleForJni (entry.JniTypeName)); + sb.Append ('_'); + sb.Append (MangleForJni ($"n_{entry.JniMethodName}")); + + if (useFullNativeSignature) { + string signature = entry.JniMethodSignature; + if (signature.Length < 2) { + ThrowInvalidSignature (signature, "must be at least two characters long"); + } + + if (signature[0] != '(') { + ThrowInvalidSignature (signature, "must start with '('"); + } + + int sigEndIdx = signature.LastIndexOf (')'); + if (sigEndIdx < 1) { // the first position where ')' can appear is 1, for a method without parameters + ThrowInvalidSignature (signature, "missing closing parenthesis"); + } + + string sigParams = signature.Substring (1, sigEndIdx - 1); + if (sigParams.Length > 0) { + sb.Append ("__"); + sb.Append (MangleForJni (sigParams)); + } + } + + return sb.ToString (); + + void ThrowInvalidSignature (string signature, string reason) + { + throw new InvalidOperationException ($"Invalid JNI signature '{signature}': {reason}"); + } + } + + void ProcessAndAddMethod (List allMethods, MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) + { + CecilMethodDefinition nativeCallback = entry.NativeCallback; + string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); + string klass = $"{nativeCallback.DeclaringType.FullName}, {nativeCallback.Module.Assembly.FullName}"; + + if (!seenClasses.TryGetValue (klass, out int classIndex)) { + classIndex = classes.Count; + seenClasses.Add (klass, classIndex); + + var mc = new MarshalMethodsManagedClass { + token = nativeCallback.DeclaringType.MetadataToken.ToUInt32 (), + ClassName = klass, + }; + + classes.Add (new StructureInstance (marshalMethodsManagedClassStructureInfo, mc)); + } + + // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference + (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.IsSpecial ? entry.NativeCallback : entry.ImplementedMethod); + + var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: nativeSymbolName, classIndex); + if (parameters != null && parameters.Count > 0) { + method.Parameters.AddRange (parameters); + } + + if (!overloadedNativeSymbolNames.TryGetValue (method.NativeSymbolName, out List overloadedMethods)) { + overloadedMethods = new List (); + overloadedNativeSymbolNames.Add (method.NativeSymbolName, overloadedMethods); + } + overloadedMethods.Add (method); + + allMethods.Add (method); + } + + string MangleForJni (string name) + { + var sb = new StringBuilder (); + + foreach (char ch in name) { + switch (ch) { + case '/': + case '.': + sb.Append ('_'); + break; + + case '_': + sb.Append ("_1"); + break; + + case ';': + sb.Append ("_2"); + break; + + case '[': + sb.Append ("_3"); + break; + + default: + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { + sb.Append (ch); + } else { + sb.Append ("_0"); + sb.Append (((int)ch).ToString ("x04")); + } + break; + } + } + + return sb.ToString (); + } + + (Type returnType, List? functionParams) ParseJniSignature (string signature, Mono.Cecil.MethodDefinition implementedMethod) + { + Type returnType = null; + List? parameters = null; + bool paramsDone = false; + int idx = 0; + while (!paramsDone && idx < signature.Length) { + char jniType = signature[idx]; + + if (jniType == '(') { + idx++; + continue; + } + + if (jniType == ')') { + paramsDone = true; + continue; + } + + Type? managedType = JniTypeToManaged (jniType); + if (managedType != null) { + AddParameter (managedType); + continue; + } + + throw new InvalidOperationException ($"Unsupported JNI type '{jniType}' at position {idx} of signature '{signature}'"); + } + + if (!paramsDone || idx >= signature.Length || signature[idx] != ')') { + throw new InvalidOperationException ($"Missing closing arguments parenthesis: '{signature}'"); + } + + idx++; + if (signature[idx] == 'V') { + returnType = typeof(void); + } else { + returnType = JniTypeToManaged (signature[idx]); + } + + return (returnType, parameters); + + Type? JniTypeToManaged (char jniType) + { + if (jniSimpleTypeMap.TryGetValue (jniType, out Type managedType)) { + idx++; + return managedType; + } + + if (jniType == 'L') { + return JavaClassToManaged (justSkip: false); + } + + if (jniType == '[') { + // Arrays of arrays (any rank) are bound as a simple pointer, which makes the generated code much simpler (no need to generate pointers to + // pointers to pointers etc), especially that we don't need to dereference these pointers in generated code, we simply pass them along to + // the managed land after all. + while (signature[idx] == '[') { + idx++; + } + + jniType = signature[idx]; + if (jniArrayTypeMap.TryGetValue (jniType, out managedType)) { + if (jniType == 'L') { + JavaClassToManaged (justSkip: true); + } else { + idx++; + } + + return managedType; + } + + throw new InvalidOperationException ($"Unsupported JNI array type '{jniType}' at index {idx} of signature '{signature}'"); + } + + return null; + } + + Type? JavaClassToManaged (bool justSkip) + { + idx++; + StringBuilder sb = null; + if (!justSkip) { + sb = new StringBuilder (); + } + + while (idx < signature.Length) { + if (signature[idx] == ')') { + throw new InvalidOperationException ($"Syntax error: unterminated class type (missing ';' before closing parenthesis) in signature '{signature}'"); + } + + if (signature[idx] == ';') { + idx++; + break; + } + + sb?.Append (signature[idx]); + idx++; + } + + if (justSkip) { + return null; + } + + string typeName = sb.ToString (); + if (String.Compare (typeName, "java/lang/Class", StringComparison.Ordinal) == 0) { + return typeof(_jclass); + } + + if (String.Compare (typeName, "java/lang/String", StringComparison.Ordinal) == 0) { + return typeof(_jstring); + } + + if (String.Compare (typeName, "java/lang/Throwable", StringComparison.Ordinal) == 0) { + return typeof(_jthrowable); + } + + return typeof(_jobject); + } + + void AddParameter (Type type) + { + if (parameters == null) { + parameters = new List (); + } + + if (implementedMethod.Parameters.Count <= parameters.Count) { + throw new InvalidOperationException ($"Method {implementedMethod.FullName} managed signature doesn't match its JNI signature '{signature}' (not enough parameters)"); + } + + // Every parameter which isn't a primitive type becomes a pointer + parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name)); + } + } protected override void Construct (LlvmIrModule module) { - InitTracing (module); + MapStructures (module); + + Init (); + // InitTracing (module); + AddAssemblyImageCache (module, out AssemblyCacheState acs); + + // class cache + module.AddGlobalVariable ("marshal_methods_number_of_classes", (uint)classes.Count, LLVMIR.LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable ("marshal_methods_class_cache", classes, LLVMIR.LlvmIrVariableOptions.GlobalWritable); + + // Marshal methods class names + var mm_class_names = new List (); + foreach (StructureInstance klass in classes) { + mm_class_names.Add (klass.Instance.ClassName); + } + module.AddGlobalVariable ("mm_class_names", mm_class_names, LLVMIR.LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); + + AddMarshalMethodNames (module, acs); } - void InitTracing (LlvmIrModule module) + void MapStructures (LlvmIrModule module) { - var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); - var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + marshalMethodsManagedClassStructureInfo = module.MapStructure (); + marshalMethodNameStructureInfo = module.MapStructure (); + } + + void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) + { + var uniqueMethods = new Dictionary (); + + if (!generateEmptyCode && methods != null) { + foreach (MarshalMethodInfo mmi in methods) { + string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); + + if (!acs.AsmNameToIndexData32.TryGetValue (asmName, out uint idx32)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 32-bit cache array index"); + } + + if (!acs.AsmNameToIndexData64.TryGetValue (asmName, out uint idx64)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 64-bit cache array index"); + } + + ulong methodToken = (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + ulong id32 = ((ulong)idx32 << 32) | methodToken; + if (uniqueMethods.ContainsKey (id32)) { + continue; + } + + ulong id64 = ((ulong)idx64 << 32) | methodToken; + uniqueMethods.Add (id32, (mmi, id32, id64)); + } + } + + MarshalMethodName name; + var methodName = new StringBuilder (); + var mm_method_names = new List> (); + foreach (var kvp in uniqueMethods) { + ulong id = kvp.Key; + (MarshalMethodInfo mmi, ulong id32, ulong id64) = kvp.Value; - var llvm_lifetime_params = new List { - new (typeof(ulong), "size"), - new (typeof(IntPtr), "pointer"), + RenderMethodNameWithParams (mmi.Method.NativeCallback, methodName); + name = new MarshalMethodName { + Id32 = id32, + Id64 = id64, + + // Tokens are unique per assembly + id = 0, + name = methodName.ToString (), + }; + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); + } + + // Must terminate with an "invalid" entry + name = new MarshalMethodName { + Id32 = 0, + Id64 = 0, + + id = 0, + name = String.Empty, }; + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); - var lifetime_sig = new LlvmIrFunctionSignature ( - name: "llvm.lifetime.start", - returnType: typeof(void), - parameters: llvm_lifetime_params - ); - - llvm_lifetime_start = module.DeclareExternalFunction ( - new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); - llvm_lifetime_start = module.DeclareExternalFunction ( - new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); - - // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - var mm_trace_func_enter_or_leave_params = new List { - new (typeof(IntPtr), "env"), // JNIEnv *env - new (typeof(int), "tracing_mode"), - new (typeof(uint), "mono_image_index"), - new (typeof(uint), "class_index"), - new (typeof(uint), "method_token"), - new (typeof(string), "native_method_name"), - new (typeof(string), "method_extra_info"), + var mm_method_names_variable = new LlvmIrGlobalVariable (mm_method_names, "mm_method_names", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { + BeforeWriteCallback = UpdateMarshalMethodNameIds, + BeforeWriteCallbackCallerState = acs, }; + module.Add (mm_method_names_variable); + + void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) + { + buffer.Clear (); + buffer.Append (md.Name); + buffer.Append ('('); - var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_func_enter", - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - ); + if (md.HasParameters) { + bool first = true; + foreach (CecilParameterDefinition pd in md.Parameters) { + if (!first) { + buffer.Append (','); + } else { + first = false; + } - mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + buffer.Append (pd.ParameterType.Name); + } + } + + buffer.Append (')'); + } } - LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) + void UpdateMarshalMethodNameIds (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { - return new LlvmIrFunctionAttributeSet { - new ArgmemonlyFunctionAttribute (), - new MustprogressFunctionAttribute (), - new NocallbackFunctionAttribute (), - new NofreeFunctionAttribute (), - new NosyncFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - }; + var mm_method_names = (List>)variable.Value; + bool is64Bit = target.Is64Bit; + + foreach (StructureInstance mmn in mm_method_names) { + mmn.Instance.id = is64Bit ? mmn.Instance.Id64 : mmn.Instance.Id32; + } } - LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) + // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache + void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) { - var ret = new LlvmIrFunctionAttributeSet { - new NounwindFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), + var assembly_image_cache = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = (ulong)numberOfAssembliesInApk, + }; + module.Add (assembly_image_cache); + + acs = new AssemblyCacheState { + AsmNameToIndexData32 = new Dictionary (StringComparer.Ordinal), + Indices32 = new List (), + + AsmNameToIndexData64 = new Dictionary (StringComparer.Ordinal), + Indices64 = new List (), }; - ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); - ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); - ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); - ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); + acs.Hashes32 = new Dictionary (); + acs.Hashes64 = new Dictionary (); + uint index = 0; + + foreach (string name in uniqueAssemblyNames) { + // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here + string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); + ulong hashFull32 = GetXxHash (name, is64Bit: false); + ulong hashClipped32 = GetXxHash (clippedName, is64Bit: false); + ulong hashFull64 = GetXxHash (name, is64Bit: true); + ulong hashClipped64 = GetXxHash (clippedName, is64Bit: true); + + // + // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the + // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. + // + acs.Hashes32.Add ((uint)Convert.ChangeType (hashFull32, typeof(uint)), (name, index)); + acs.Hashes32.Add ((uint)Convert.ChangeType (hashClipped32, typeof(uint)), (clippedName, index)); + acs.Hashes64.Add (hashFull64, (name, index)); + acs.Hashes64.Add (hashClipped64, (clippedName, index)); + + index++; + } + + acs.Keys32 = acs.Hashes32.Keys.ToList (); + acs.Keys32.Sort (); + for (int i = 0; i < acs.Keys32.Count; i++) { + (string name, uint idx) = acs.Hashes32[acs.Keys32[i]]; + acs.Indices32.Add (idx); + acs.AsmNameToIndexData32.Add (name, idx); + } + + acs.Keys64 = acs.Hashes64.Keys.ToList (); + acs.Keys64.Sort (); + for (int i = 0; i < acs.Keys64.Count; i++) { + (string name, uint idx) = acs.Hashes64[acs.Keys64[i]]; + acs.Indices64.Add (idx); + acs.AsmNameToIndexData64.Add (name, idx); + } + + var assembly_image_cache_hashes = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_hashes", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { + Comment = " Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array", + BeforeWriteCallback = UpdateAssemblyImageCacheHashes, + BeforeWriteCallbackCallerState = acs, + GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment, + GetArrayItemCommentCallbackCallerState = acs, + }; + module.Add (assembly_image_cache_hashes); + + var assembly_image_cache_indices = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_indices", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { + WriteOptions = LlvmIrVariableWriteOptions.ArrayWriteIndexComments | LlvmIrVariableWriteOptions.ArrayFormatInRows, + BeforeWriteCallback = UpdateAssemblyImageCacheIndices, + BeforeWriteCallbackCallerState = acs, + }; + module.Add (assembly_image_cache_indices); + } + + void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; + Type type; + + if (target.Is64Bit) { + value = acs.Keys64; + type = typeof(List); + } else { + value = acs.Keys32; + type = typeof(List); + } + + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (type, value); + } + + string? GetAssemblyImageCacheItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) + { + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + + string name; + uint i; + if (target.Is64Bit) { + var v64 = (ulong)value; + name = acs.Hashes64[v64].name; + i = acs.Hashes64[v64].index; + } else { + var v32 = (uint)value; + name = acs.Hashes32[v32].name; + i = acs.Hashes32[v32].index; + } + + return $" {index}: {name} => 0x{value:x} => {i}"; + } + + void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; + + if (target.Is64Bit) { + value = acs.Indices64; + } else { + value = acs.Indices32; + } + + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (variable.Type, value); + } + + AssemblyCacheState EnsureAssemblyCacheState (object? callerState) + { + var acs = callerState as AssemblyCacheState; + if (acs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } - return ret; + return acs; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs new file mode 100644 index 00000000000..2b7ec72cea2 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; + +using Xamarin.Android.Tools; +using Xamarin.Android.Tasks.LLVM.IR; + +namespace Xamarin.Android.Tasks.New +{ + // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace + using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; + + partial class MarshalMethodsNativeAssemblyGenerator + { + readonly MarshalMethodsTracingMode tracingMode; + + LlvmIrFunction? mm_trace_func_enter; + LlvmIrFunction? mm_trace_func_leave; + LlvmIrFunction? llvm_lifetime_start; + LlvmIrFunction? llvm_lifetime_end; + + void InitTracing (LlvmIrModule module) + { + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); + var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + + var llvm_lifetime_params = new List { + new (typeof(ulong), "size"), + new (typeof(IntPtr), "pointer"), + }; + + var lifetime_sig = new LlvmIrFunctionSignature ( + name: "llvm.lifetime.start", + returnType: typeof(void), + parameters: llvm_lifetime_params + ); + + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_func_enter_or_leave_params = new List { + new (typeof(IntPtr), "env"), // JNIEnv *env + new (typeof(int), "tracing_mode"), + new (typeof(uint), "mono_image_index"), + new (typeof(uint), "class_index"), + new (typeof(uint), "method_token"), + new (typeof(string), "native_method_name"), + new (typeof(string), "method_extra_info"), + }; + + var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_func_enter", + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + ); + + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + } + + LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) + { + return new LlvmIrFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NocallbackFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; + } + + LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); + ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); + ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); + ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); + + return ret; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs index a8603c33dab..13f4ab235e1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs @@ -254,7 +254,7 @@ uint GetJavaEntryIndex (TypeMapJava javaEntry) } } - string? GetJavaHashesItemComment (LlvmIrVariable v, ulong index, object? value, object? callerState) + string? GetJavaHashesItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) { var cs = callerState as ConstructionState; if (cs == null) { @@ -293,16 +293,6 @@ void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget targ gv.OverrideValueAndType (listType, hashes); } - LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) - { - var gv = variable as LlvmIrGlobalVariable; - if (gv == null) { - throw new InvalidOperationException ("Internal error: global variable expected"); - } - - return gv; - } - ConstructionState EnsureConstructionState (object? callerState) { var cs = callerState as ConstructionState; From 3d27e22202bcf17df0019d9c0eb082b3cdcb0051 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 14 Jun 2023 22:42:00 +0200 Subject: [PATCH 47/60] Beginnings of code generation --- .../LlvmIrGenerator/FunctionAttributes.cs | 81 +++++++++ .../LlvmIrGenerator/LlvmIrFunction.cs | 130 +++++++++----- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 160 ++++++++++++++++++ .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 76 +++++++-- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 21 +++ .../LlvmIrGenerator/LlvmIrVariable.New.cs | 2 +- ...rshalMethodsNativeAssemblyGenerator.New.cs | 51 ++++++ 7 files changed, 465 insertions(+), 56 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index e413e534bfc..f5d4b46677f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -461,6 +461,87 @@ public JumptableFunctionAttribute () {} } + enum MemoryAttributeAccessKind + { + None, + Read, + Write, + ReadWrite, + } + + class MemoryFunctionAttribute : LlvmIrFunctionAttribute + { + public MemoryAttributeAccessKind? Default { get; set; } + public MemoryAttributeAccessKind? Argmem { get; set; } + public MemoryAttributeAccessKind? InaccessibleMem { get; set; } + + public MemoryFunctionAttribute () + : base ("memory", quoted: false, supportsParams: true, optionalParams: true, hasValueAssignment: false) + {} + + protected override bool HasOptionalParams () + { + // All of them are optional, but at least one of them must be specified + bool ret = Default.HasValue || Argmem.HasValue || InaccessibleMem.HasValue; + if (!ret) { + throw new InvalidOperationException ("Internal error: at least one access kind must be specified"); + } + + return ret; + } + + protected override void RenderParams (StringBuilder sb) + { + bool haveSomething = false; + + if (Default.HasValue) { + AppendParam (GetAccessKindString (Default)); + } + + if (Argmem.HasValue) { + AppendParam ($"argmem: {GetAccessKindString (Argmem)}"); + } + + if (InaccessibleMem.HasValue) { + AppendParam ($"inaccessiblemem: {GetAccessKindString (InaccessibleMem)}"); + } + + void AppendParam (string text) + { + if (haveSomething) { + sb.Append (", "); + } + sb.Append (text); + haveSomething = true; + } + } + + string GetAccessKindString (MemoryAttributeAccessKind? kind) + { + return kind.Value switch { + MemoryAttributeAccessKind.None => "none", + MemoryAttributeAccessKind.Read => "read", + MemoryAttributeAccessKind.Write => "write", + MemoryAttributeAccessKind.ReadWrite => "readwrite", + _ => throw new InvalidOperationException ($"Internal error: unsupported access kind {kind}") + }; + } + + public override bool Equals (LlvmIrFunctionAttribute other) + { + if (!base.Equals (other)) { + return false; + } + + var attr = other as MemoryFunctionAttribute; + if (attr == null) { + return false; + } + + return Default == attr.Default && Argmem == attr.Argmem && InaccessibleMem == attr.InaccessibleMem; + } + } + class MinsizeFunctionAttribute : LlvmIrFlagFunctionAttribute { public MinsizeFunctionAttribute () diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index a442d55ce41..3d6116d03d8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -12,11 +12,11 @@ namespace Xamarin.Android.Tasks.LLVM.IR using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - interface ILlvmIrFunctionParameterState {} + interface ILlvmIrSavedFunctionParameterState {} class LlvmIrFunctionParameter : LlvmIrLocalVariable { - sealed class ParameterState : ILlvmIrFunctionParameterState + sealed class SavedParameterState : ILlvmIrSavedFunctionParameterState { public readonly LlvmIrFunctionParameter Owner; @@ -31,13 +31,26 @@ sealed class ParameterState : ILlvmIrFunctionParameterState public bool? SignExt; public bool? ZeroExt; - public ParameterState (LlvmIrFunctionParameter owner) + public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? previousState = null) { Owner = owner; + if (previousState == null) { + return; + } + + Align = previousState.Align; + AllocPtr = previousState.AllocPtr; + Dereferenceable = previousState.Dereferenceable; + ImmArg = previousState.ImmArg; + NoCapture = previousState.NoCapture; + NonNull = previousState.NonNull; + ReadNone = previousState.ReadNone; + SignExt = previousState.SignExt; + ZeroExt = previousState.ZeroExt; } } - ParameterState state; + SavedParameterState state; // To save on time, we declare only attributes that are actually used in our generated code. More will be added, as needed. @@ -125,7 +138,7 @@ public LlvmIrFunctionParameter (Type type, string? name = null) : base (type, name) { NameMatters = false; - state = new ParameterState (this); + state = new SavedParameterState (this); } /// @@ -135,19 +148,19 @@ public LlvmIrFunctionParameter (Type type, string? name = null) /// Instances are **still** shared and thus different threads would step on each other's toes should they saved and restored /// state without synchronization. /// - public ILlvmIrFunctionParameterState SaveState () + public ILlvmIrSavedFunctionParameterState SaveState () { - ILlvmIrFunctionParameterState ret = state; - state = new ParameterState (this); + SavedParameterState ret = state; + state = new SavedParameterState (this, ret); return ret; } /// /// Restore (opaque) state. for more info /// - public void RestoreState (ILlvmIrFunctionParameterState savedState) + public void RestoreState (ILlvmIrSavedFunctionParameterState savedState) { - var oldState = savedState as ParameterState; + var oldState = savedState as SavedParameterState; if (oldState == null) { throw new InvalidOperationException ("Internal error: savedState not an instance of ParameterState"); } @@ -160,16 +173,16 @@ public void RestoreState (ILlvmIrFunctionParameterState savedState) } } - interface ILlvmIrFunctionSignatureState {} + interface ILlvmIrSavedFunctionSignatureState {} class LlvmIrFunctionSignature : IEquatable { - sealed class SignatureState : ILlvmIrFunctionSignatureState + sealed class SavedSignatureState : ILlvmIrSavedFunctionSignatureState { public readonly LlvmIrFunctionSignature Owner; - public readonly IList ParameterStates; + public readonly IList ParameterStates; - public SignatureState (LlvmIrFunctionSignature owner, IList parameterStates) + public SavedSignatureState (LlvmIrFunctionSignature owner, IList parameterStates) { Owner = owner; ParameterStates = parameterStates; @@ -203,26 +216,26 @@ public LlvmIrFunctionSignature (string name, LlvmIrFunctionSignature templateSig /// Save (opaque) signature state. This includes states of all the parameters. /// for more information. /// - public ILlvmIrFunctionSignatureState SaveState () + public ILlvmIrSavedFunctionSignatureState SaveState () { - var list = new List (); + var list = new List (); foreach (LlvmIrFunctionParameter parameter in Parameters) { list.Add (parameter.SaveState ()); } - return new SignatureState (this, list.AsReadOnly ()); + return new SavedSignatureState (this, list.AsReadOnly ()); } /// - /// Restore (opaque) signature state. This includes states of all the parameters. + /// Restore (opaque) signature state. This includes states of all the parameters. /// for more information. /// - public void RestoreState (ILlvmIrFunctionSignatureState savedState) + public void RestoreState (ILlvmIrSavedFunctionSignatureState savedState) { - var oldState = savedState as SignatureState; + var oldState = savedState as SavedSignatureState; if (oldState == null) { - throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SignatureState)}"); + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SavedSignatureState)}"); } if (oldState.Owner != this) { @@ -230,7 +243,7 @@ public void RestoreState (ILlvmIrFunctionSignatureState savedState) } for (int i = 0; i < oldState.ParameterStates.Count; i++) { - ILlvmIrFunctionParameterState parameterState = oldState.ParameterStates[i]; + ILlvmIrSavedFunctionParameterState parameterState = oldState.ParameterStates[i]; Parameters[i].RestoreState (parameterState); } @@ -283,7 +296,7 @@ public bool Equals (LlvmIrFunctionSignature other) } } - interface ILlvmIrFunctionState {} + interface ILlvmIrSavedFunctionState {} /// /// Describes a native function to be emitted or declared and keeps code emitting state between calls to various generator. @@ -291,44 +304,81 @@ interface ILlvmIrFunctionState {} /// class LlvmIrFunction : IEquatable { - sealed class FunctionState : ILlvmIrFunctionState + public class FunctionState + { + // Counter shared by unnamed local variables (including function parameters) and unnamed labels. + ulong unnamedTemporaryCounter = 0; + + // Implicit unnamed label at the start of the function + ulong? startingBlockNumber; + + public ulong StartingBlockNumber { + get { + if (startingBlockNumber.HasValue) { + return startingBlockNumber.Value; + } + + throw new InvalidOperationException ($"Internal error: starting block number not set"); + } + } + + public FunctionState () + {} + + public ulong NextTemporary () + { + ulong ret = unnamedTemporaryCounter++; + return ret; + } + + public void ConfigureStartingBlockNumber () + { + if (startingBlockNumber.HasValue) { + return; + } + + startingBlockNumber = unnamedTemporaryCounter++; + } + } + + sealed class SavedFunctionState : ILlvmIrSavedFunctionState { public readonly LlvmIrFunction Owner; - public readonly ILlvmIrFunctionSignatureState SignatureState; + public readonly ILlvmIrSavedFunctionSignatureState SignatureState; - public FunctionState (LlvmIrFunction owner, ILlvmIrFunctionSignatureState signatureState) + public SavedFunctionState (LlvmIrFunction owner, ILlvmIrSavedFunctionSignatureState signatureState) { Owner = owner; SignatureState = signatureState; } } + FunctionState functionState; + public LlvmIrFunctionSignature Signature { get; } public LlvmIrAddressSignificance AddressSignificance { get; set; } = LlvmIrAddressSignificance.LocalUnnamed; public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } public LlvmIrLinkage Linkage { get; set; } = LlvmIrLinkage.Default; public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; - - // Counter shared by unnamed local variables (including function parameters) and unnamed labels. - uint unnamedTemporaryCounter = 0; - - // Implicit unnamed label at the start of the function - readonly uint startingBlockNumber; + public LlvmIrFunctionBody Body { get; } public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) { Signature = signature; AttributeSet = attributeSet; + functionState = new FunctionState (); foreach (LlvmIrFunctionParameter parameter in signature.Parameters) { if (!String.IsNullOrEmpty (parameter.Name)) { continue; } - parameter.AssignNumber (unnamedTemporaryCounter++); + parameter.AssignNumber (functionState.NextTemporary ()); } - startingBlockNumber = unnamedTemporaryCounter++; + functionState.ConfigureStartingBlockNumber (); + + Body = new LlvmIrFunctionBody (this, functionState); } /// @@ -347,20 +397,20 @@ public LlvmIrFunction (string name, Type returnType, List /// for more information. /// - public ILlvmIrFunctionState SaveState () + public ILlvmIrSavedFunctionState SaveState () { - return new FunctionState (this, Signature.SaveState ()); + return new SavedFunctionState (this, Signature.SaveState ()); } /// - /// Restore (opaque) function state. This includes signature state. + /// Restore (opaque) function state. This includes signature state. /// for more information. /// - public void RestoreState (ILlvmIrFunctionState savedState) + public void RestoreState (ILlvmIrSavedFunctionState savedState) { - var oldState = savedState as FunctionState; + var oldState = savedState as SavedFunctionState; if (oldState == null) { - throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(FunctionState)}"); + throw new InvalidOperationException ($"Internal error: savedState not an instance of {nameof(SavedFunctionState)}"); } if (oldState.Owner != this) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs new file mode 100644 index 00000000000..a89288e9a23 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Xamarin.Android.Tasks.LLVM.IR +{ + /// + /// Abstract class from which all of the items (labels, function parameters, + /// local variables and instructions) derive. + /// + abstract class LlvmIrFunctionBodyItem + { + /// + /// If an item has this property set to true, it won't be written to output when + /// code is generated. This is used for implicit items that don't need to be part of + /// the generated code (e.g. the starting block label) + /// + public bool SkipInOutput { get; protected set; } + } + + /// + /// Base class for function labels and local variables (including parameters), which + /// obtain automatic names derived from a shared counter, unless explicitly named. + /// + abstract class LlvmIrFunctionLocalItem : LlvmIrFunctionBodyItem + { + string? name; + + public string Name { + get { + if (String.IsNullOrEmpty (name)) { + throw new InvalidOperationException ("Internal error: name hasn't been set yet"); + } + return name; + } + + protected set { + if (String.IsNullOrEmpty (value)) { + throw new InvalidOperationException ("Internal error: value must not be null or empty"); + } + name = value; + } + } + + protected LlvmIrFunctionLocalItem (string? name) + { + if (name != null) { + Name = name; + } + } + + protected LlvmIrFunctionLocalItem (LlvmIrFunction.FunctionState state, string? name) + { + if (name != null) { + if (name.Length == 0) { + throw new ArgumentException ("must not be an empty string", nameof (name)); + } + + Name = name; + return; + } + + SetName (state.NextTemporary ()); + } + + protected void SetName (ulong num) + { + Name = num.ToString (CultureInfo.InvariantCulture); + } + } + + class LlvmIrFunctionLabelItem : LlvmIrFunctionLocalItem + { + /// + /// Labels are a bit peculiar in that they must not have their name set to the automatic value (based on + /// a counter shared with function parameters) at creation time, but only when they are actually added to + /// the function body. The reason is that LLVM IR requires all the unnamed temporaries (function parameters and + /// labels) to be named sequentially, but sometimes a label must be referenced before it is added to the instruction + /// stream, e.g. in the br instruction. On the other hand, it is perfectly fine to assign label a name that + /// isn't an integer at **instantiation** time, which is why we have the parameter here. + /// + public LlvmIrFunctionLabelItem (string? name = null) + : base (name) + {} + + public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.FunctionState state) + { + if (!String.IsNullOrEmpty (Name)) { + return; + } + + SetName (state.NextTemporary ()); + } + } + + class LlvmIrFunctionBodyComment : LlvmIrFunctionBodyItem + { + public string Text { get; } + + public LlvmIrFunctionBodyComment (string comment) + { + Text = comment; + } + } + + class LlvmIrFunctionBody + { + class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem + { + public LlvmIrFunctionImplicitStartLabel (ulong num) + { + SetName (num); + SkipInOutput = true; + } + } + + class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem + { + public LlvmIrFunctionParameter Parameter { get; } + + public LlvmIrFunctionParameterItem (LlvmIrFunction.FunctionState state, LlvmIrFunctionParameter parameter) + : base (state, parameter.Name) + { + Parameter = parameter; + SkipInOutput = true; + } + } + + List items; + HashSet definedLabels; + LlvmIrFunction function; + LlvmIrFunction.FunctionState functionState; + + public bool IsEmpty => items.Count == 0; + + public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) + { + function = func; + this.functionState = functionState; + definedLabels = new HashSet (StringComparer.Ordinal); + items = new List (); + items.Add (new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber)); + } + + public void Add (LlvmIrFunctionLabelItem label) + { + label.WillAddToBody (this, functionState); + if (definedLabels.Contains (label.Name)) { + throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{function.Signature.Name}' body"); + } + items.Add (label); + definedLabels.Add (label.Name); + } + + public void Add (LlvmIrFunctionBodyItem item) + { + items.Add (item); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 21d6f690ce7..4f818f48f05 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -151,8 +151,9 @@ public void Generate (TextWriter writer, LlvmIrModule module) context.Output.WriteLine ($"target triple = \"{target.Triple}\""); WriteStructureDeclarations (context); WriteGlobalVariables (context); + WriteFunctions (context); - // Bottom of file + // Bottom of the file WriteStrings (context); WriteExternalFunctionDeclarations (context); WriteAttributeSets (context); @@ -847,29 +848,74 @@ void WriteStructureDeclarationField (string typeName, string comment, bool last) // // Functions syntax: https://llvm.org/docs/LangRef.html#functions // - void WriteExternalFunctionDeclarations (WriteContext context) + void WriteFunctions (WriteContext context) { - if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { + if (context.Module.Functions == null || context.Module.Functions.Count == 0) { return; } context.Output.WriteLine (); - foreach (LlvmIrFunction func in context.Module.ExternalFunctions) { - // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) - ILlvmIrFunctionState funcState = func.SaveState (); + WriteComment (context, " Functions"); - foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { - target.SetParameterFlags (parameter); - } + foreach (LlvmIrFunction function in context.Module.Functions) { + context.Output.WriteLine (); + + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); + WriteFunctionDefinitionLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: true); + WriteFunctionDefinitionTrailingDecorations (context, function); + WriteFunctionBody (context, function); + function.RestoreState (funcState); + } + } + + void WriteFunctionBody (WriteContext context, LlvmIrFunction function) + { + context.Output.WriteLine (); + context.Output.WriteLine ('{'); + context.IncreaseIndent (); + + // TODO: body here + + context.DecreaseIndent (); + context.Output.WriteLine (); + context.Output.WriteLine ('}'); + } + + ILlvmIrSavedFunctionState WriteFunctionPreamble (WriteContext context, LlvmIrFunction function, string keyword) + { + ILlvmIrSavedFunctionState funcState = function.SaveState (); + + foreach (LlvmIrFunctionParameter parameter in function.Signature.Parameters) { + target.SetParameterFlags (parameter); + } + + WriteFunctionAttributesComment (context, function); + context.Output.Write (keyword); + context.Output.Write (' '); + + return funcState; + } - WriteFunctionAttributesComment (context, func); - context.Output.Write ("declare "); - WriteFunctionDeclarationLeadingDecorations (context, func); - WriteFunctionSignature (context, func, writeParameterNames: false); - WriteFunctionDeclarationTrailingDecorations (context, func); + void WriteExternalFunctionDeclarations (WriteContext context) + { + if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { + return; + } + + context.Output.WriteLine (); + WriteComment (context, " External functions"); + foreach (LlvmIrFunction function in context.Module.ExternalFunctions) { context.Output.WriteLine (); - func.RestoreState (funcState); + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "declare"); + WriteFunctionDeclarationLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (context, function); + + function.RestoreState (funcState); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index f37a04700e3..8bab656889a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -19,6 +19,7 @@ partial class LlvmIrModule public static readonly Type NameValueArrayType = typeof(IDictionary); public IList? ExternalFunctions { get; private set; } + public IList? Functions { get; private set; } public IList? AttributeSets { get; private set; } public IList? Structures { get; private set; } public IList? GlobalVariables { get; private set; } @@ -26,6 +27,7 @@ partial class LlvmIrModule Dictionary? attributeSets; Dictionary? externalFunctions; + Dictionary? functions; Dictionary? structures; LlvmIrStringManager? stringManager; LlvmIrMetadataManager metadataManager; @@ -64,6 +66,12 @@ public void AfterConstruction () ExternalFunctions = list.AsReadOnly (); } + if (functions != null) { + List list = functions.Values.ToList (); + // TODO: sort or not? + Functions = list.AsReadOnly (); + } + if (attributeSets != null) { List list = attributeSets.Values.ToList (); list.Sort ((LlvmIrFunctionAttributeSet a, LlvmIrFunctionAttributeSet b) => a.Number.CompareTo (b.Number)); @@ -83,6 +91,19 @@ public void AfterConstruction () GlobalVariables = globalVariables?.AsReadOnly (); } + public void Add (LlvmIrFunction func) + { + if (functions == null) { + functions = new Dictionary (); + } + + if (functions.TryGetValue (func, out LlvmIrFunction existingFunc)) { + throw new InvalidOperationException ($"Internal error: identical function has already been added (\"{func.Signature.Name}\")"); + } + + functions.Add (func, func); + } + /// /// A shortcut way to add a global variable without first having to create an instance of first. This overload /// requires the parameter to not be null. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs index 2881537ab2f..2488bc7585a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs @@ -143,7 +143,7 @@ public LlvmIrLocalVariable (Type type, string? name = null) : base (type, name) {} - public void AssignNumber (uint n) + public void AssignNumber (ulong n) { Name = n.ToString (CultureInfo.InvariantCulture); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index 0d7e12edcf6..b54de889018 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -22,6 +22,8 @@ namespace Xamarin.Android.Tasks.New partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { + const string GetFunctionPointerVariableName = "get_function_pointer"; + // This is here only to generate strongly-typed IR internal sealed class MonoClass {} @@ -578,6 +580,8 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable ("mm_class_names", mm_class_names, LLVMIR.LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); AddMarshalMethodNames (module, acs); + + AddXamarinAppInitFunction (module); } void MapStructures (LlvmIrModule module) @@ -586,6 +590,53 @@ void MapStructures (LlvmIrModule module) marshalMethodNameStructureInfo = module.MapStructure (); } + void AddXamarinAppInitFunction (LlvmIrModule module) + { + module.AddGlobalVariable (typeof(IntPtr), GetFunctionPointerVariableName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); + + var init_params = new List { + new (typeof(_JNIEnv), "env") { + NoCapture = true, + NoUndef = true, + ReadNone = true, + }, + new (typeof(IntPtr), "fn") { + NoUndef = true, + }, + }; + + var init_signature = new LlvmIrFunctionSignature ( + name: "xamarin_app_init", + returnType: typeof(void), + parameters: init_params + ); + + LlvmIrFunctionAttributeSet attrSet = module.AddAttributeSet (MakeXamarinAppInitAttributeSet (module)); + var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); + module.Add (xamarin_app_init); + } + + LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) + { + return new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NofreeFunctionAttribute (), + new NorecurseFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + new MemoryFunctionAttribute { + Default = MemoryAttributeAccessKind.Write, + Argmem = MemoryAttributeAccessKind.None, + InaccessibleMem = MemoryAttributeAccessKind.None, + }, + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + } + void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) { var uniqueMethods = new Dictionary (); From 5e98bc3fa6ac3409c42d7709f4ee77c78acaad56 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 15 Jun 2023 20:48:32 +0200 Subject: [PATCH 48/60] Code generation works First two instructions implemented (`store` and `ret`), together with support for TBAA metadata (https://llvm.org/docs/LangRef.html#tbaa-metadata) `xamarin_app_init` now fully generated. --- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 35 +++- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 168 +++++++++--------- .../LlvmIrGenerator/LlvmIrInstructions.cs | 137 ++++++++++++++ .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 24 +++ ...rshalMethodsNativeAssemblyGenerator.New.cs | 20 ++- 5 files changed, 293 insertions(+), 91 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index a89288e9a23..58b03c63dff 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -16,6 +16,7 @@ abstract class LlvmIrFunctionBodyItem /// the generated code (e.g. the starting block label) /// public bool SkipInOutput { get; protected set; } + public abstract void Write (GeneratorWriteContext context); } /// @@ -91,21 +92,39 @@ public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.Funct SetName (state.NextTemporary ()); } + + public override void Write (GeneratorWriteContext context) + { + context.DecreaseIndent (); + + context.Output.Write (context.CurrentIndent); + context.Output.Write (Name); + context.Output.WriteLine (':'); + + context.IncreaseIndent (); + } } class LlvmIrFunctionBodyComment : LlvmIrFunctionBodyItem { - public string Text { get; } + public string Text { get; } public LlvmIrFunctionBodyComment (string comment) { Text = comment; } + + public override void Write (GeneratorWriteContext context) + { + context.Output.Write (context.CurrentIndent); + context.Output.Write (';'); + context.Output.WriteLine (Text); + } } class LlvmIrFunctionBody { - class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem + sealed class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem { public LlvmIrFunctionImplicitStartLabel (ulong num) { @@ -114,7 +133,7 @@ public LlvmIrFunctionImplicitStartLabel (ulong num) } } - class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem + sealed class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem { public LlvmIrFunctionParameter Parameter { get; } @@ -124,14 +143,20 @@ public LlvmIrFunctionParameterItem (LlvmIrFunction.FunctionState state, LlvmIrFu Parameter = parameter; SkipInOutput = true; } + + public override void Write (GeneratorWriteContext context) + { + throw new NotSupportedException ("Internal error: writing not supported for this item"); + } } List items; HashSet definedLabels; LlvmIrFunction function; LlvmIrFunction.FunctionState functionState; + LlvmIrFunctionLabelItem implicitStartBlock; - public bool IsEmpty => items.Count == 0; + public IList Items => items.AsReadOnly (); public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) { @@ -139,7 +164,7 @@ public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState fun this.functionState = functionState; definedLabels = new HashSet (StringComparer.Ordinal); items = new List (); - items.Add (new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber)); + implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); } public void Add (LlvmIrFunctionLabelItem label) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 4f818f48f05..749e1d3e7b4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -25,10 +25,46 @@ public GeneratorStructureInstance (StructureInfo info, object instance) {} } - partial class LlvmIrGenerator + sealed class GeneratorWriteContext { const char IndentChar = '\t'; + int currentIndentLevel = 0; + + public readonly TextWriter Output; + public readonly LlvmIrModule Module; + public readonly LlvmIrModuleTarget Target; + public readonly LlvmIrMetadataManager MetadataManager; + public string CurrentIndent { get; private set; } = String.Empty; + public bool InVariableGroup { get; set; } + + public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager) + { + Output = writer; + Module = module; + Target = target; + MetadataManager = metadataManager; + } + + public void IncreaseIndent () + { + currentIndentLevel++; + CurrentIndent = MakeIndentString (); + } + + public void DecreaseIndent () + { + if (currentIndentLevel > 0) { + currentIndentLevel--; + } + CurrentIndent = MakeIndentString (); + } + + string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; + } + + partial class LlvmIrGenerator + { sealed class LlvmTypeInfo { public readonly bool IsPointer; @@ -47,40 +83,6 @@ public LlvmTypeInfo (bool isPointer, bool isAggregate, bool isStructure, ulong s } } - sealed class WriteContext - { - int currentIndentLevel = 0; - - public readonly TextWriter Output; - public readonly LlvmIrModule Module; - public readonly LlvmIrMetadataManager MetadataManager; - public string CurrentIndent { get; private set; } = String.Empty; - public bool InVariableGroup { get; set; } - - public WriteContext (TextWriter writer, LlvmIrModule module, LlvmIrMetadataManager metadataManager) - { - Output = writer; - Module = module; - MetadataManager = metadataManager; - } - - public void IncreaseIndent () - { - currentIndentLevel++; - CurrentIndent = MakeIndentString (); - } - - public void DecreaseIndent () - { - if (currentIndentLevel > 0) { - currentIndentLevel--; - } - CurrentIndent = MakeIndentString (); - } - - string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; - } - sealed class BasicType { public readonly string Name; @@ -141,7 +143,7 @@ public void Generate (TextWriter writer, LlvmIrModule module) LlvmIrMetadataManager metadataManager = module.GetMetadataManagerCopy (); target.AddTargetSpecificMetadata (metadataManager); - var context = new WriteContext (writer, module, metadataManager); + var context = new GeneratorWriteContext (writer, module, target, metadataManager); if (!String.IsNullOrEmpty (FilePath)) { WriteCommentLine (context, $" ModuleID = '{FileName}'"); context.Output.WriteLine ($"source_filename = \"{FileName}\""); @@ -160,7 +162,7 @@ public void Generate (TextWriter writer, LlvmIrModule module) WriteMetadata (context); } - void WriteStrings (WriteContext context) + void WriteStrings (GeneratorWriteContext context) { if (context.Module.Strings == null || context.Module.Strings.Count == 0) { return; @@ -190,7 +192,7 @@ void WriteStrings (WriteContext context) } } - void WriteGlobalVariables (WriteContext context) + void WriteGlobalVariables (GeneratorWriteContext context) { if (context.Module.GlobalVariables == null || context.Module.GlobalVariables.Count == 0) { return; @@ -212,7 +214,7 @@ void WriteGlobalVariables (WriteContext context) } } - void WriteGlobalVariableStart (WriteContext context, LlvmIrGlobalVariable variable) + void WriteGlobalVariableStart (GeneratorWriteContext context, LlvmIrGlobalVariable variable) { if (!String.IsNullOrEmpty (variable.Comment)) { WriteCommentLine (context, variable.Comment); @@ -229,7 +231,7 @@ void WriteGlobalVariableStart (WriteContext context, LlvmIrGlobalVariable variab WriteWritability (context, options.Writability); } - void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) + void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable variable) { if (!context.InVariableGroup) { context.Output.WriteLine (); @@ -254,7 +256,7 @@ void WriteGlobalVariable (WriteContext context, LlvmIrGlobalVariable variable) context.Output.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); } - void WriteTypeAndValue (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) + void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { WriteType (context, variable, out typeInfo); context.Output.Write (' '); @@ -317,12 +319,12 @@ ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVaria throw new InvalidOperationException ($"Internal error: should never get here"); } - void WriteType (WriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) + void WriteType (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); } - void WriteType (WriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) + void WriteType (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) { if (memberInfo.IsNativePointer) { typeInfo = new LlvmTypeInfo ( @@ -351,7 +353,7 @@ void WriteType (WriteContext context, StructureInstance si, StructureMemberInfo WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } - void WriteStructureType (WriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) + void WriteStructureType (GeneratorWriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) { ulong alignment = GetStructureMaxFieldAlignment (si.Info); @@ -369,7 +371,7 @@ void WriteStructureType (WriteContext context, StructureInstance si, out LlvmTyp context.Output.Write (si.Info.Name); } - void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) + void WriteType (GeneratorWriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) { if (IsStructureInstance (type)) { if (value == null) { @@ -403,7 +405,7 @@ void WriteType (WriteContext context, Type type, object? value, out LlvmTypeInfo context.Output.Write (irType); } - void WriteArrayType (WriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) + void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) { string irType; ulong size; @@ -447,7 +449,7 @@ ulong GetStructureMaxFieldAlignment (StructureInfo si) bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); - void WriteValue (WriteContext context, Type valueType, LlvmIrVariable variable) + void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable) { if (variable.Type.IsArray ()) { bool zeroInitialize = false; @@ -478,7 +480,7 @@ void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong lengt throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}"); } - void WriteValue (WriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) + void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) { if (smi.IsNativePointer) { if (WriteNativePointerValue (context, structInstance, smi, value)) { @@ -512,7 +514,7 @@ void WriteValue (WriteContext context, StructureInstance structInstance, Structu WriteValue (context, smi.MemberType, value); } - bool WriteNativePointerValue (WriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) + bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) { // Structure members decorated with the [NativePointer] attribute cannot have a // value other than `null`, unless they are strings or references to symbols @@ -542,7 +544,7 @@ bool WriteNativePointerValue (WriteContext context, StructureInstance si, Struct return false; } - void WriteValue (WriteContext context, Type type, object? value) + void WriteValue (GeneratorWriteContext context, Type type, object? value) { if (value is LlvmIrVariable variableRef) { context.Output.Write (variableRef.Reference); @@ -588,7 +590,7 @@ void WriteValue (WriteContext context, Type type, object? value) throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } - void WriteStructureValue (WriteContext context, StructureInstance? instance) + void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance) { if (instance == null || instance.IsZeroInitialized) { context.Output.Write ("zeroinitializer"); @@ -634,7 +636,7 @@ void WriteStructureValue (WriteContext context, StructureInstance? instance) context.Output.Write ('}'); } - void WriteArrayValue (WriteContext context, LlvmIrVariable variable) + void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) { context.Output.WriteLine ('['); context.IncreaseIndent (); @@ -706,7 +708,7 @@ void WritePrevItemCommentOrNewline () } } - void WriteLinkage (WriteContext context, LlvmIrLinkage linkage) + void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage) { if (linkage == LlvmIrLinkage.Default) { return; @@ -719,7 +721,7 @@ void WriteLinkage (WriteContext context, LlvmIrLinkage linkage) } } - void WriteWritability (WriteContext context, LlvmIrWritability writability) + void WriteWritability (GeneratorWriteContext context, LlvmIrWritability writability) { try { WriteAttribute (context, llvmWritability[writability]); @@ -728,7 +730,7 @@ void WriteWritability (WriteContext context, LlvmIrWritability writability) } } - void WriteAddressSignificance (WriteContext context, LlvmIrAddressSignificance addressSignificance) + void WriteAddressSignificance (GeneratorWriteContext context, LlvmIrAddressSignificance addressSignificance) { if (addressSignificance == LlvmIrAddressSignificance.Default) { return; @@ -741,7 +743,7 @@ void WriteAddressSignificance (WriteContext context, LlvmIrAddressSignificance a } } - void WriteVisibility (WriteContext context, LlvmIrVisibility visibility) + void WriteVisibility (GeneratorWriteContext context, LlvmIrVisibility visibility) { if (visibility == LlvmIrVisibility.Default) { return; @@ -754,7 +756,7 @@ void WriteVisibility (WriteContext context, LlvmIrVisibility visibility) } } - void WritePreemptionSpecifier (WriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) + void WritePreemptionSpecifier (GeneratorWriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) { if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { return; @@ -770,13 +772,13 @@ void WritePreemptionSpecifier (WriteContext context, LlvmIrRuntimePreemption pre /// /// Write attribute named in followed by a single space /// - void WriteAttribute (WriteContext context, string attr) + void WriteAttribute (GeneratorWriteContext context, string attr) { context.Output.Write (attr); context.Output.Write (' '); } - void WriteStructureDeclarations (WriteContext context) + void WriteStructureDeclarations (GeneratorWriteContext context) { if (context.Module.Structures == null || context.Module.Structures.Count == 0) { return; @@ -788,7 +790,7 @@ void WriteStructureDeclarations (WriteContext context) } } - void WriteStructureDeclaration (WriteContext context, StructureInfo si) + void WriteStructureDeclaration (GeneratorWriteContext context, StructureInfo si) { // $"%{typeDesignator}.{name} = type " context.Output.Write ('%'); @@ -848,7 +850,7 @@ void WriteStructureDeclarationField (string typeName, string comment, bool last) // // Functions syntax: https://llvm.org/docs/LangRef.html#functions // - void WriteFunctions (WriteContext context) + void WriteFunctions (GeneratorWriteContext context) { if (context.Module.Functions == null || context.Module.Functions.Count == 0) { return; @@ -870,20 +872,21 @@ void WriteFunctions (WriteContext context) } } - void WriteFunctionBody (WriteContext context, LlvmIrFunction function) + void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) { context.Output.WriteLine (); context.Output.WriteLine ('{'); context.IncreaseIndent (); - // TODO: body here + foreach (LlvmIrFunctionBodyItem item in function.Body.Items) { + item.Write (context); + } context.DecreaseIndent (); - context.Output.WriteLine (); context.Output.WriteLine ('}'); } - ILlvmIrSavedFunctionState WriteFunctionPreamble (WriteContext context, LlvmIrFunction function, string keyword) + ILlvmIrSavedFunctionState WriteFunctionPreamble (GeneratorWriteContext context, LlvmIrFunction function, string keyword) { ILlvmIrSavedFunctionState funcState = function.SaveState (); @@ -898,7 +901,7 @@ ILlvmIrSavedFunctionState WriteFunctionPreamble (WriteContext context, LlvmIrFun return funcState; } - void WriteExternalFunctionDeclarations (WriteContext context) + void WriteExternalFunctionDeclarations (GeneratorWriteContext context) { if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { return; @@ -919,7 +922,7 @@ void WriteExternalFunctionDeclarations (WriteContext context) } } - void WriteFunctionAttributesComment (WriteContext context, LlvmIrFunction func) + void WriteFunctionAttributesComment (GeneratorWriteContext context, LlvmIrFunction func) { if (func.AttributeSet == null) { return; @@ -929,17 +932,17 @@ void WriteFunctionAttributesComment (WriteContext context, LlvmIrFunction func) WriteCommentLine (context, $"Function attributes: {func.AttributeSet.Render ()}"); } - void WriteFunctionDeclarationLeadingDecorations (WriteContext context, LlvmIrFunction func) + void WriteFunctionDeclarationLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { WriteFunctionLeadingDecorations (context, func, declaration: true); } - void WriteFunctionDefinitionLeadingDecorations (WriteContext context, LlvmIrFunction func) + void WriteFunctionDefinitionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { WriteFunctionLeadingDecorations (context, func, declaration: false); } - void WriteFunctionLeadingDecorations (WriteContext context, LlvmIrFunction func, bool declaration) + void WriteFunctionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { if (func.Linkage != LlvmIrLinkage.Default) { context.Output.Write (llvmLinkage[func.Linkage]); @@ -957,17 +960,17 @@ void WriteFunctionLeadingDecorations (WriteContext context, LlvmIrFunction func, } } - void WriteFunctionDeclarationTrailingDecorations (WriteContext context, LlvmIrFunction func) + void WriteFunctionDeclarationTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { WriteFunctionTrailingDecorations (context, func, declaration: true); } - void WriteFunctionDefinitionTrailingDecorations (WriteContext context, LlvmIrFunction func) + void WriteFunctionDefinitionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { WriteFunctionTrailingDecorations (context, func, declaration: false); } - void WriteFunctionTrailingDecorations (WriteContext context, LlvmIrFunction func, bool declaration) + void WriteFunctionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { context.Output.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); @@ -978,7 +981,7 @@ void WriteFunctionTrailingDecorations (WriteContext context, LlvmIrFunction func } } - void WriteFunctionSignature (WriteContext context, LlvmIrFunction func, bool writeParameterNames) + void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, bool writeParameterNames) { context.Output.Write (MapToIRType (func.Signature.ReturnType)); context.Output.Write (" @"); @@ -1007,7 +1010,7 @@ void WriteFunctionSignature (WriteContext context, LlvmIrFunction func, bool wri context.Output.Write (')'); } - void WriteParameterAttributes (WriteContext context, LlvmIrFunctionParameter parameter) + void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) { var attributes = new List (); if (AttributeIsSet (parameter.ImmArg)) { @@ -1060,7 +1063,7 @@ void WriteParameterAttributes (WriteContext context, LlvmIrFunctionParameter par bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; } - void WriteAttributeSets (WriteContext context) + void WriteAttributeSets (GeneratorWriteContext context) { if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { return; @@ -1081,7 +1084,7 @@ void WriteAttributeSets (WriteContext context) } } - void WriteMetadata (WriteContext context) + void WriteMetadata (GeneratorWriteContext context) { if (context.MetadataManager.Items.Count == 0) { return; @@ -1094,13 +1097,13 @@ void WriteMetadata (WriteContext context) } } - void WriteComment (WriteContext context, string comment) + void WriteComment (GeneratorWriteContext context, string comment) { context.Output.Write (';'); context.Output.Write (comment); } - void WriteCommentLine (WriteContext context, string comment) + void WriteCommentLine (GeneratorWriteContext context, string comment) { WriteComment (context, comment); context.Output.WriteLine (); @@ -1159,7 +1162,7 @@ static string MapManagedTypeToNative (StructureMemberInfo smi) static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; - object? GetTypedMemberValue (WriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) + object? GetTypedMemberValue (GeneratorWriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) { object? value = smi.GetValue (instance.Obj); if (value == null) { @@ -1188,6 +1191,11 @@ public static string MapToIRType (Type type, out ulong size) return MapToIRType (type, out size, out _); } + public static string MapToIRType (Type type, out bool isPointer) + { + return MapToIRType (type, out _, out isPointer); + } + /// /// Maps managed type to equivalent IR type. Puts type size in and whether or not the type /// is a pointer in . When a type is determined to be a pointer, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs new file mode 100644 index 00000000000..c5c4a775bbe --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -0,0 +1,137 @@ +using System; +using System.Globalization; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem +{ + // TODO: add support for metadata + public string Mnemonic { get; } + public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } + + /// + /// TBAA (Type Based Alias Analysis) metadata item the instruction references, if any. + /// for more information about TBAA. + /// + public LlvmIrMetadataItem? TBAA { get; set; } + + protected LlvmIrInstruction (string mnemonic) + { + if (String.IsNullOrEmpty (mnemonic)) { + throw new ArgumentException ("must not be null or empty", nameof (mnemonic)); + } + + Mnemonic = mnemonic; + } + + public override void Write (GeneratorWriteContext context) + { + context.Output.Write (context.CurrentIndent); + WriteValueAssignment (context); + context.Output.Write (Mnemonic); + context.Output.Write (' '); + WriteBody (context); + + if (TBAA != null) { + context.Output.Write (", !tbaa !"); + context.Output.Write (TBAA.Name); + } + + if (AttributeSet != null) { + context.Output.Write (" #"); + context.Output.Write (AttributeSet.Number.ToString (CultureInfo.InvariantCulture)); + } + context.Output.WriteLine (); + } + + protected virtual void WriteValueAssignment (GeneratorWriteContext context) + {} + + protected virtual void WriteBody (GeneratorWriteContext context) + {} + + protected void WriteValue (GeneratorWriteContext context, Type type, object? value, bool isPointer) + { + if (value == null) { + if (!isPointer) { + throw new InvalidOperationException ($"Internal error: non-pointer type '{type}' must not have a `null` value"); + } + context.Output.Write ("null"); + } else if (value is LlvmIrVariable variable) { + context.Output.Write (variable.Reference); + } else { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + } + } + + protected void WriteAlignment (GeneratorWriteContext context, ulong typeSize, bool isPointer) + { + context.Output.Write (", align "); + + ulong alignment; + if (isPointer) { + alignment = context.Target.NativePointerSize; + } else { + alignment = typeSize; + } + context.Output.Write (alignment.ToString (CultureInfo.InvariantCulture)); + } +} + +sealed class LlvmIrInstructions +{ + public class Store : LlvmIrInstruction + { + public object? From { get; } + public LlvmIrVariable To { get; } + + public Store (LlvmIrVariable from, LlvmIrVariable to) + : base ("store") + { + From = from; + To = to; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (To.Type, out ulong size, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (' '); + + WriteValue (context, To.Type, From, isPointer); + + context.Output.Write (", ptr "); + context.Output.Write (To.Reference); + + WriteAlignment (context, size, isPointer); + } + } + + public class Ret : LlvmIrInstruction + { + public Type RetvalType { get; } + public object? Value { get; } + + public Ret (Type retvalType, object? retval = null) + : base ("ret") + { + RetvalType = retvalType; + Value = retval; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + if (RetvalType == typeof(void)) { + context.Output.Write ("void"); + return; + } + + string irType = LlvmIrGenerator.MapToIRType (RetvalType, out bool isPointer); + context.Output.Write (' '); + context.Output.Write (irType); + context.Output.Write (' '); + + WriteValue (context, RetvalType, Value, isPointer); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 8bab656889a..9b395f04125 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -25,12 +25,21 @@ partial class LlvmIrModule public IList? GlobalVariables { get; private set; } public IList? Strings { get; private set; } + /// + /// TBAA stands for "Type Based Alias Analysis" and is used by LLVM to implemente a description of + /// a higher level language typesystem to LLVM IR (in which memory doesn't have types). This metadata + /// item describes pointer usage for certain instructions we output and is common enough to warrant + /// a shortcut property like that. More information about TBAA can be found at https://llvm.org/docs/LangRef.html#tbaa-metadata + /// + public LlvmIrMetadataItem TbaaAnyPointer => tbaaAnyPointer; + Dictionary? attributeSets; Dictionary? externalFunctions; Dictionary? functions; Dictionary? structures; LlvmIrStringManager? stringManager; LlvmIrMetadataManager metadataManager; + LlvmIrMetadataItem tbaaAnyPointer; List? globalVariables; @@ -46,6 +55,21 @@ public LlvmIrModule () LlvmIrMetadataItem ident = metadataManager.Add (LlvmIrKnownMetadata.LlvmIdent); LlvmIrMetadataItem identValue = metadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); ident.AddReferenceField (identValue.Name); + + tbaaAnyPointer = metadataManager.AddNumbered (); + LlvmIrMetadataItem anyPointer = metadataManager.AddNumbered ("any pointer"); + LlvmIrMetadataItem omnipotentChar = metadataManager.AddNumbered ("omnipotent char"); + LlvmIrMetadataItem simpleCppTBAA = metadataManager.AddNumbered ("Simple C++ TBAA"); + + anyPointer.AddReferenceField (omnipotentChar.Name); + anyPointer.AddField ((ulong)0); + + omnipotentChar.AddReferenceField (simpleCppTBAA); + omnipotentChar.AddField ((ulong)0); + + tbaaAnyPointer.AddReferenceField (anyPointer); + tbaaAnyPointer.AddReferenceField (anyPointer); + tbaaAnyPointer.AddField ((ulong)0); } /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index b54de889018..d9e9ca1bd80 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -592,7 +592,7 @@ void MapStructures (LlvmIrModule module) void AddXamarinAppInitFunction (LlvmIrModule module) { - module.AddGlobalVariable (typeof(IntPtr), GetFunctionPointerVariableName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); + LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable (typeof(IntPtr), GetFunctionPointerVariableName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); var init_params = new List { new (typeof(_JNIEnv), "env") { @@ -613,6 +613,13 @@ void AddXamarinAppInitFunction (LlvmIrModule module) LlvmIrFunctionAttributeSet attrSet = module.AddAttributeSet (MakeXamarinAppInitAttributeSet (module)); var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); + xamarin_app_init.Body.Add ( + new LlvmIrInstructions.Store (init_params[1], getFunctionPtrVariable) { + TBAA = module.TbaaAnyPointer, + } + ); + xamarin_app_init.Body.Add (new LlvmIrInstructions.Ret (typeof(void))); + module.Add (xamarin_app_init); } @@ -625,11 +632,12 @@ LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) new NosyncFunctionAttribute (), new NounwindFunctionAttribute (), new WillreturnFunctionAttribute (), - new MemoryFunctionAttribute { - Default = MemoryAttributeAccessKind.Write, - Argmem = MemoryAttributeAccessKind.None, - InaccessibleMem = MemoryAttributeAccessKind.None, - }, + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, new UwtableFunctionAttribute (), new MinLegalVectorWidthFunctionAttribute (0), new NoTrappingMathFunctionAttribute (true), From 1ddf89f735954fd69f00ea60991e2b3aa7006263 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 16 Jun 2023 19:47:23 +0200 Subject: [PATCH 49/60] Halfway through code generation --- .../LlvmIrGenerator/LlvmIrFunction.cs | 26 ++- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 4 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 19 ++- .../LlvmIrGenerator/LlvmIrInstructions.cs | 158 ++++++++++++++++-- ...rshalMethodsNativeAssemblyGenerator.New.cs | 117 ++++++++++++- 5 files changed, 303 insertions(+), 21 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 3d6116d03d8..9fda309d6dc 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -111,7 +111,7 @@ public bool? NoUndef { } /// - /// noundef attribute, see + /// readnone attribute, see /// public bool? ReadNone { get => state.ReadNone; @@ -119,7 +119,7 @@ public bool? ReadNone { } /// - /// zeroext attribute, see + /// signext attribute, see /// public bool? SignExt { get => state.SignExt; @@ -139,6 +139,13 @@ public LlvmIrFunctionParameter (Type type, string? name = null) { NameMatters = false; state = new SavedParameterState (this); + + // TODO: check why it doesn't work as expected - can't see the flags set in the output + if (type == typeof(sbyte) || type == typeof (short)) { + SignExt = true; + } else if (type == typeof(byte) || type == typeof (ushort)) { + ZeroExt = true; + } } /// @@ -362,6 +369,7 @@ public SavedFunctionState (LlvmIrFunction owner, ILlvmIrSavedFunctionSignatureSt public LlvmIrRuntimePreemption RuntimePreemption { get; set; } = LlvmIrRuntimePreemption.Default; public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; public LlvmIrFunctionBody Body { get; } + public string? Comment { get; set; } public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) { @@ -393,6 +401,20 @@ public LlvmIrFunction (string name, Type returnType, List + /// Creates a local variable which, if is null or empty, is assinged the correct + /// name based on a counter local to the function. + /// + public LlvmIrLocalVariable CreateLocalVariable (Type type, string? name = null) + { + var ret = new LlvmIrLocalVariable (type, name); + if (String.IsNullOrEmpty (name)) { + ret.AssignNumber (functionState.NextTemporary ()); + } + + return ret; + } + /// /// Save (opaque) function state. This includes signature state. /// for more information. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index 58b03c63dff..a2759ab3b44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -68,6 +68,8 @@ protected void SetName (ulong num) { Name = num.ToString (CultureInfo.InvariantCulture); } + + protected bool NameIsSet () => !String.IsNullOrEmpty (name); } class LlvmIrFunctionLabelItem : LlvmIrFunctionLocalItem @@ -86,7 +88,7 @@ public LlvmIrFunctionLabelItem (string? name = null) public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.FunctionState state) { - if (!String.IsNullOrEmpty (Name)) { + if (NameIsSet ()) { return; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index 749e1d3e7b4..be34a23ca52 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -200,6 +200,12 @@ void WriteGlobalVariables (GeneratorWriteContext context) foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { + if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) { + context.Output.WriteLine (); + context.Output.Write (context.CurrentIndent); + WriteComment (context, groupDelimiter.Comment); + } + context.InVariableGroup = !context.InVariableGroup; if (context.InVariableGroup) { context.Output.WriteLine (); @@ -862,6 +868,13 @@ void WriteFunctions (GeneratorWriteContext context) foreach (LlvmIrFunction function in context.Module.Functions) { context.Output.WriteLine (); + if (!String.IsNullOrEmpty (function.Comment)) { + foreach (string commentLine in function.Comment.Split ('\n')) { + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, commentLine); + } + } + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); WriteFunctionDefinitionLeadingDecorations (context, function); @@ -928,8 +941,10 @@ void WriteFunctionAttributesComment (GeneratorWriteContext context, LlvmIrFuncti return; } - context.Output.WriteLine (); - WriteCommentLine (context, $"Function attributes: {func.AttributeSet.Render ()}"); + if (String.IsNullOrEmpty (func.Comment)) { + context.Output.WriteLine (); + } + WriteCommentLine (context, $" Function attributes: {func.AttributeSet.Render ()}"); } void WriteFunctionDeclarationLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index c5c4a775bbe..33cf264a4c6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -3,6 +3,9 @@ namespace Xamarin.Android.Tasks.LLVM.IR; +// TODO: remove these aliases once the refactoring is done +using LlvmIrIcmpCond = LLVMIR.LlvmIrIcmpCond; + abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem { // TODO: add support for metadata @@ -80,29 +83,134 @@ protected void WriteAlignment (GeneratorWriteContext context, ulong typeSize, bo sealed class LlvmIrInstructions { - public class Store : LlvmIrInstruction + public class Br : LlvmIrInstruction { - public object? From { get; } - public LlvmIrVariable To { get; } + const string OpName = "br"; - public Store (LlvmIrVariable from, LlvmIrVariable to) - : base ("store") + LlvmIrVariable? cond; + LlvmIrFunctionLabelItem ifTrue; + LlvmIrFunctionLabelItem? ifFalse; + + /// + /// Outputs a conditional branch to label if condition is + /// true, and to label otherwise. must be a variable + /// of type bool + /// + public Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem ifTrue, LlvmIrFunctionLabelItem ifFalse) + : base (OpName) { - From = from; - To = to; + if (cond.Type != typeof(bool)) { + throw new ArgumentException ($"Internal error: condition must refer to a variable of type 'bool', was 'cond.Type' instead", nameof (cond)); + } + + this.cond = cond; + this.ifTrue = ifTrue; + this.ifFalse = ifFalse; + } + + /// + /// Outputs an unconditional branch to label + /// + public Br (LlvmIrFunctionLabelItem label) + : base (OpName) + { + ifTrue = label; } protected override void WriteBody (GeneratorWriteContext context) { - string irType = LlvmIrGenerator.MapToIRType (To.Type, out ulong size, out bool isPointer); + if (cond == null) { + context.Output.Write ("label %"); + context.Output.Write (ifTrue.Name); + return; + } + + context.Output.Write ("i1 "); + context.Output.Write (cond.Reference); + context.Output.Write (", label %"); + context.Output.Write (ifTrue.Name); + context.Output.Write (", label %"); + context.Output.Write (ifFalse.Name); + } + } + + public class Icmp : LlvmIrInstruction + { + LlvmIrIcmpCond cond; + LlvmIrVariable op1; + object? op2; + LlvmIrVariable result; + + public Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) + : base ("icmp") + { + if (result.Type != typeof(bool)) { + throw new ArgumentException ($"Internal error: result must be a variable of type 'bool', was '{result.Type}' instead", nameof (result)); + } + + this.cond = cond; + this.op1 = op1; + this.op2 = op2; + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (op1.Type, out ulong size, out bool isPointer); + string condOp = cond switch { + LlvmIrIcmpCond.Equal => "eq", + LlvmIrIcmpCond.NotEqual => "ne", + LlvmIrIcmpCond.UnsignedGreaterThan => "ugt", + LlvmIrIcmpCond.UnsignedGreaterOrEqual => "uge", + LlvmIrIcmpCond.UnsignedLessThan => "ult", + LlvmIrIcmpCond.UnsignedLessOrEqual => "ule", + LlvmIrIcmpCond.SignedGreaterThan => "sgt", + LlvmIrIcmpCond.SignedGreaterOrEqual => "sge", + LlvmIrIcmpCond.SignedLessThan => "slt", + LlvmIrIcmpCond.SignedLessOrEqual => "sle", + _ => throw new InvalidOperationException ($"Unsupported `icmp` conditional '{cond}'"), + }; + + context.Output.Write (condOp); + context.Output.Write (' '); context.Output.Write (irType); context.Output.Write (' '); + context.Output.Write (op1.Reference); + context.Output.Write (", "); + WriteValue (context, op1.Type, op2, isPointer); + } + } - WriteValue (context, To.Type, From, isPointer); + public class Load : LlvmIrInstruction + { + public LlvmIrVariable Source { get; } + public LlvmIrVariable Result { get; } - context.Output.Write (", ptr "); - context.Output.Write (To.Reference); + public Load (LlvmIrVariable source, LlvmIrVariable result) + : base ("load") + { + Source = source; + Result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (Result.Reference); + context.Output.Write (" = "); + } + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (Result.Type, out ulong size, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (", ptr "); + WriteValue (context, Result.Type, Source, isPointer); WriteAlignment (context, size, isPointer); } } @@ -127,11 +235,37 @@ protected override void WriteBody (GeneratorWriteContext context) } string irType = LlvmIrGenerator.MapToIRType (RetvalType, out bool isPointer); - context.Output.Write (' '); context.Output.Write (irType); context.Output.Write (' '); WriteValue (context, RetvalType, Value, isPointer); } } + + public class Store : LlvmIrInstruction + { + public object? From { get; } + public LlvmIrVariable To { get; } + + public Store (LlvmIrVariable from, LlvmIrVariable to) + : base ("store") + { + From = from; + To = to; + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (To.Type, out ulong size, out bool isPointer); + context.Output.Write (irType); + context.Output.Write (' '); + + WriteValue (context, To.Type, From, isPointer); + + context.Output.Write (", ptr "); + context.Output.Write (To.Reference); + + WriteAlignment (context, size, isPointer); + } + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index d9e9ca1bd80..07f7e30614c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -580,8 +580,9 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable ("mm_class_names", mm_class_names, LLVMIR.LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); AddMarshalMethodNames (module, acs); + LlvmIrVariable getFunctionPtrVariable = AddXamarinAppInitFunction (module); - AddXamarinAppInitFunction (module); + AddMarshalMethods (module, getFunctionPtrVariable); } void MapStructures (LlvmIrModule module) @@ -590,7 +591,111 @@ void MapStructures (LlvmIrModule module) marshalMethodNameStructureInfo = module.MapStructure (); } - void AddXamarinAppInitFunction (LlvmIrModule module) + void AddMarshalMethods (LlvmIrModule module, LlvmIrVariable getFunctionPtrVariable) + { + if (generateEmptyCode || methods == null || methods.Count == 0) { + return; + } + + // This will make all the backing fields to appear in a block without empty lines separating them. + module.Add ( + new LlvmIrGroupDelimiterVariable () { + Comment = " Marshal methods backing fields, pointers to native functions" + } + ); + + LlvmIrFunctionAttributeSet attrSet = MakeMarshalMethodAttributeSet (module); + var usedBackingFields = new Dictionary (StringComparer.Ordinal); + var uniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase); + foreach (MarshalMethodInfo mmi in methods) { + CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; + string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; + + if (!uniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { + asmId = (ulong)uniqueAssemblyId.Count; + uniqueAssemblyId.Add (asmName, asmId); + } + + AddMarshalMethod (module, mmi, asmId, attrSet, usedBackingFields, getFunctionPtrVariable); + } + + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, LlvmIrFunctionAttributeSet attrSet, Dictionary usedBackingFields, LlvmIrVariable getFunctionPtrVariable) + { + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{asmId}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; + + if (!usedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { + backingField = module.AddGlobalVariable (typeof(IntPtr), backingFieldName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); + usedBackingFields.Add (backingFieldName, backingField); + } + + var funcComment = new StringBuilder (" Method: "); + funcComment.AppendLine (nativeCallback.FullName); + funcComment.Append (" Assembly: "); + funcComment.AppendLine (nativeCallback.Module.Assembly.Name.FullName); + funcComment.Append (" Registered: "); + funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); + + var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, attrSet) { + Comment = funcComment.ToString (), + }; + + LlvmIrLocalVariable callback = func.CreateLocalVariable (typeof(IntPtr)); + func.Body.Add ( + new LlvmIrInstructions.Load (backingField, callback) { + TBAA = module.TbaaAnyPointer, + } + ); + + LlvmIrLocalVariable callbackIsNullResult = func.CreateLocalVariable (typeof(bool)); + func.Body.Add (new LlvmIrInstructions.Icmp (LLVMIR.LlvmIrIcmpCond.Equal, callback, null, callbackIsNullResult)); + + var callbackIsNullLabel = new LlvmIrFunctionLabelItem (); + var callbackNotNullLabel = new LlvmIrFunctionLabelItem (); + func.Body.Add (new LlvmIrInstructions.Br (callbackIsNullResult, callbackIsNullLabel, callbackNotNullLabel)); + + // Callback variable was null + func.Body.Add (callbackIsNullLabel); + LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr)); + func.Body.Add ( + new LlvmIrInstructions.Load (getFunctionPtrVariable, getFuncPtrResult) { + TBAA = module.TbaaAnyPointer, + } + ); + func.Body.Add (new LlvmIrFunctionBodyComment (" TODO: call get_function_pointer here")); + + LlvmIrLocalVariable loadedCallback = func.CreateLocalVariable (typeof(IntPtr)); + func.Body.Add ( + new LlvmIrInstructions.Load (backingField, loadedCallback) { + TBAA = module.TbaaAnyPointer, + } + ); + + func.Body.Add (new LlvmIrInstructions.Br (callbackNotNullLabel)); + + // Callback variable has just been set or it wasn't null + func.Body.Add (callbackNotNullLabel); + + module.Add (func); + } + + LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) + { + var attrSet = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (attrSet); + } + + LlvmIrVariable AddXamarinAppInitFunction (LlvmIrModule module) { LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable (typeof(IntPtr), GetFunctionPointerVariableName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); @@ -611,7 +716,7 @@ void AddXamarinAppInitFunction (LlvmIrModule module) parameters: init_params ); - LlvmIrFunctionAttributeSet attrSet = module.AddAttributeSet (MakeXamarinAppInitAttributeSet (module)); + LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); xamarin_app_init.Body.Add ( new LlvmIrInstructions.Store (init_params[1], getFunctionPtrVariable) { @@ -621,11 +726,13 @@ void AddXamarinAppInitFunction (LlvmIrModule module) xamarin_app_init.Body.Add (new LlvmIrInstructions.Ret (typeof(void))); module.Add (xamarin_app_init); + + return getFunctionPtrVariable; } LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) { - return new LlvmIrFunctionAttributeSet { + var attrSet = new LlvmIrFunctionAttributeSet { new MustprogressFunctionAttribute (), new NofreeFunctionAttribute (), new NorecurseFunctionAttribute (), @@ -643,6 +750,8 @@ LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) new NoTrappingMathFunctionAttribute (true), new StackProtectorBufferSizeFunctionAttribute (8), }; + + return module.AddAttributeSet (attrSet); } void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) From 5c17d76a8d71605cbc112b617a65e70902950eb1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 19 Jun 2023 19:29:33 +0200 Subject: [PATCH 50/60] `call` sorted out, tomorrow onto `phi` --- .../LlvmIrGenerator/LlvmIrFunction.cs | 16 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 46 +++- .../LlvmIrGenerator/LlvmIrInstructions.cs | 196 ++++++++++++++++-- ...rshalMethodsNativeAssemblyGenerator.New.cs | 132 ++++++++++-- 4 files changed, 337 insertions(+), 53 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 9fda309d6dc..ea8e5dffe43 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -30,6 +30,7 @@ sealed class SavedParameterState : ILlvmIrSavedFunctionParameterState public bool? ReadNone; public bool? SignExt; public bool? ZeroExt; + public bool? IsCplusPlusReference; public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? previousState = null) { @@ -55,7 +56,8 @@ public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? // To save on time, we declare only attributes that are actually used in our generated code. More will be added, as needed. /// - /// align(n) attribute, see + /// align(n) attribute, see . + /// As a special case for us, a value of 0 means use the natural target pointer alignment. /// public uint? Align { get => state.Align; @@ -71,7 +73,8 @@ public bool? AllocPtr { } /// - /// dereferenceable(n) attribute, see + /// dereferenceable(n) attribute, see . + /// As a special case for us, a value of 0 means use the natural target pointer alignment. /// public uint? Dereferenceable { get => state.Dereferenceable; @@ -134,6 +137,15 @@ public bool? ZeroExt { set => state.ZeroExt = value; } + /// + /// This serves a purely documentational purpose, when generating comments about types. It describes a parameter that is a C++ reference, something we can't + /// reflect on the managed side. + /// + public bool? IsCplusPlusReference { + get => state.IsCplusPlusReference; + set => state.IsCplusPlusReference = value; + } + public LlvmIrFunctionParameter (Type type, string? name = null) : base (type, name) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index be34a23ca52..e2c348339a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -867,13 +867,7 @@ void WriteFunctions (GeneratorWriteContext context) foreach (LlvmIrFunction function in context.Module.Functions) { context.Output.WriteLine (); - - if (!String.IsNullOrEmpty (function.Comment)) { - foreach (string commentLine in function.Comment.Split ('\n')) { - context.Output.Write (context.CurrentIndent); - WriteCommentLine (context, commentLine); - } - } + WriteFunctionComment (context, function); // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); @@ -885,6 +879,18 @@ void WriteFunctions (GeneratorWriteContext context) } } + void WriteFunctionComment (GeneratorWriteContext context, LlvmIrFunction function) + { + if (String.IsNullOrEmpty (function.Comment)) { + return; + } + + foreach (string commentLine in function.Comment.Split ('\n')) { + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, commentLine); + } + } + void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) { context.Output.WriteLine (); @@ -1025,7 +1031,7 @@ void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, context.Output.Write (')'); } - void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) + public static void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) { var attributes = new List (); if (AttributeIsSet (parameter.ImmArg)) { @@ -1061,11 +1067,11 @@ void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionPara } if (parameter.Align.HasValue) { - attributes.Add ($"align({parameter.Align.Value})"); + attributes.Add ($"align({ValueOrPointerSize (parameter.Align.Value)})"); } if (parameter.Dereferenceable.HasValue) { - attributes.Add ($"dereferenceable({parameter.Dereferenceable.Value})"); + attributes.Add ($"dereferenceable({ValueOrPointerSize (parameter.Dereferenceable.Value)})"); } if (attributes.Count == 0) { @@ -1076,6 +1082,15 @@ void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionPara context.Output.Write (String.Join (" ", attributes)); bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; + + uint ValueOrPointerSize (uint? value) + { + if (value.Value == 0) { + return context.Target.NativePointerSize; + } + + return value.Value; + } } void WriteAttributeSets (GeneratorWriteContext context) @@ -1138,7 +1153,7 @@ static Type GetActualType (Type type) /// Map a managed to its C++ counterpart. Only primitive types, /// string and IntPtr are supported. /// - static string MapManagedTypeToNative (Type type) + public static string MapManagedTypeToNative (Type type) { Type baseType = GetActualType (type); @@ -1243,6 +1258,15 @@ string GetIRType (Type type, out ulong size, out bool isPointer) return ret; } + public static bool IsFirstClassNonPointerType (Type type) + { + if (type == typeof(void)) { + return false; + } + + return basicTypeMap.ContainsKey (type); + } + public static string QuoteStringNoEscape (string s) { return $"\"{s}\""; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 33cf264a4c6..8354e78a124 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Globalization; namespace Xamarin.Android.Tasks.LLVM.IR; // TODO: remove these aliases once the refactoring is done using LlvmIrIcmpCond = LLVMIR.LlvmIrIcmpCond; +using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem { @@ -31,6 +33,7 @@ public override void Write (GeneratorWriteContext context) { context.Output.Write (context.CurrentIndent); WriteValueAssignment (context); + WritePreamble (context); context.Output.Write (Mnemonic); context.Output.Write (' '); WriteBody (context); @@ -47,9 +50,23 @@ public override void Write (GeneratorWriteContext context) context.Output.WriteLine (); } + /// + /// Write the '<variable_reference> = ' part of the instruction line. + /// protected virtual void WriteValueAssignment (GeneratorWriteContext context) {} + /// + /// Write part of the instruction that comes between the optional value assignment and the instruction + /// mnemonic. If any text is written, it must end with a whitespace. + /// + protected virtual void WritePreamble (GeneratorWriteContext context) + {} + + /// + /// Write the "body" of the instruction, that is the part that follows instruction mnemonic but precedes the + /// metadata and attribute set references. + /// protected virtual void WriteBody (GeneratorWriteContext context) {} @@ -81,6 +98,14 @@ protected void WriteAlignment (GeneratorWriteContext context, ulong typeSize, bo } } +abstract class LlvmIrInstructionArgumentValuePlaceholder +{ + protected LlvmIrInstructionArgumentValuePlaceholder () + {} + + public abstract object? GetValue (LlvmIrModuleTarget target); +} + sealed class LlvmIrInstructions { public class Br : LlvmIrInstruction @@ -134,6 +159,135 @@ protected override void WriteBody (GeneratorWriteContext context) } } + public class Call : LlvmIrInstruction + { + LlvmIrFunction function; + IList? arguments; + LlvmIrVariable? result; + + public LlvmIrCallMarker CallMarker { get; set; } = LlvmIrCallMarker.None; + public LlvmIrVariable? FuncPointer { get; set; } + + public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null) + : base ("call") + { + this.function = function; + this.result = result; + + if (function.Signature.ReturnType != typeof(void)) { + if (result == null) { + throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' returns '{function.Signature.ReturnType} and thus requires a result variable", nameof (result)); + } + } else if (result != null) { + throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' returns no value and yet a result variable was provided", nameof (result)); + } + + int argCount = function.Signature.Parameters.Count; + if (argCount != 0) { + if (arguments == null) { + throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments", nameof (arguments)); + } + + if (arguments.Count != argCount) { + throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments, but {arguments.Count} were provided", nameof (arguments)); + } + + this.arguments = new List (arguments).AsReadOnly (); + } + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + if (result == null) { + return; + } + + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WritePreamble (GeneratorWriteContext context) + { + string? callMarker = CallMarker switch { + LlvmIrCallMarker.None => null, + LlvmIrCallMarker.Tail => "tail", + LlvmIrCallMarker.NoTail => "notail", + LlvmIrCallMarker.MustTail => "musttail", + _ => throw new InvalidOperationException ($"Internal error: call marker '{CallMarker}' not supported"), + }; + + if (!String.IsNullOrEmpty (callMarker)) { + context.Output.Write (callMarker); + context.Output.Write (' '); + } + } + + protected override void WriteBody (GeneratorWriteContext context) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (function.Signature.ReturnType)); + if (FuncPointer == null) { + context.Output.Write (" @"); + context.Output.Write (function.Signature.Name); + } else { + context.Output.Write (' '); + context.Output.Write (FuncPointer.Reference); + } + context.Output.Write ('('); + + for (int i = 0; i < function.Signature.Parameters.Count; i++) { + if (i > 0) { + context.Output.Write (", "); + } + + WriteArgument (context, function.Signature.Parameters[i], i); + } + + context.Output.Write (')'); + } + + void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter parameter, int index) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (parameter.Type)); + LlvmIrGenerator.WriteParameterAttributes (context, parameter); + context.Output.Write (' '); + + object? value = arguments[index]; + if (value is LlvmIrInstructionArgumentValuePlaceholder placeholder) { + value = placeholder.GetValue (context.Target); + } + + if (value == null) { + if (!parameter.Type.IsNativePointer ()) { + throw new InvalidOperationException ($"Internal error: value for argument {index} to function '{function.Signature.Name}' must not be null"); + } + + context.Output.Write ("null"); + return; + } + + if (value is LlvmIrVariable variable) { + context.Output.Write (variable.Reference); + return; + } + + if (!parameter.Type.IsAssignableFrom (value.GetType ())) { + throw new InvalidOperationException ($"Internal error: value type '{value.GetType ()}' for argument {index} to function '{function.Signature.Name}' is invalid. Expected '{parameter.Type}' or compatible"); + } + + if (value is string str) { + context.Output.Write (context.Module.LookupRequiredVariableForString (str).Reference); + return; + } + + if (LlvmIrGenerator.IsFirstClassNonPointerType (value.GetType ())) { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; + } + + throw new InvalidOperationException ($"Internal error: unsupported type '{value.GetType ()}' in call to function '{function.Signature.Name}'"); + } + } + public class Icmp : LlvmIrInstruction { LlvmIrIcmpCond cond; @@ -189,81 +343,81 @@ protected override void WriteBody (GeneratorWriteContext context) public class Load : LlvmIrInstruction { - public LlvmIrVariable Source { get; } - public LlvmIrVariable Result { get; } + LlvmIrVariable source; + LlvmIrVariable result; public Load (LlvmIrVariable source, LlvmIrVariable result) : base ("load") { - Source = source; - Result = result; + this.source = source; + this.result = result; } protected override void WriteValueAssignment (GeneratorWriteContext context) { - context.Output.Write (Result.Reference); + context.Output.Write (result.Reference); context.Output.Write (" = "); } protected override void WriteBody (GeneratorWriteContext context) { - string irType = LlvmIrGenerator.MapToIRType (Result.Type, out ulong size, out bool isPointer); + string irType = LlvmIrGenerator.MapToIRType (result.Type, out ulong size, out bool isPointer); context.Output.Write (irType); context.Output.Write (", ptr "); - WriteValue (context, Result.Type, Source, isPointer); + WriteValue (context, result.Type, source, isPointer); WriteAlignment (context, size, isPointer); } } public class Ret : LlvmIrInstruction { - public Type RetvalType { get; } - public object? Value { get; } + Type retvalType; + object? retVal; public Ret (Type retvalType, object? retval = null) : base ("ret") { - RetvalType = retvalType; - Value = retval; + this.retvalType = retvalType; + retVal = retval; } protected override void WriteBody (GeneratorWriteContext context) { - if (RetvalType == typeof(void)) { + if (retvalType == typeof(void)) { context.Output.Write ("void"); return; } - string irType = LlvmIrGenerator.MapToIRType (RetvalType, out bool isPointer); + string irType = LlvmIrGenerator.MapToIRType (retvalType, out bool isPointer); context.Output.Write (irType); context.Output.Write (' '); - WriteValue (context, RetvalType, Value, isPointer); + WriteValue (context, retvalType, retVal, isPointer); } } public class Store : LlvmIrInstruction { - public object? From { get; } - public LlvmIrVariable To { get; } + object? from; + LlvmIrVariable to; public Store (LlvmIrVariable from, LlvmIrVariable to) : base ("store") { - From = from; - To = to; + this.from = from; + this.to = to; } protected override void WriteBody (GeneratorWriteContext context) { - string irType = LlvmIrGenerator.MapToIRType (To.Type, out ulong size, out bool isPointer); + string irType = LlvmIrGenerator.MapToIRType (to.Type, out ulong size, out bool isPointer); context.Output.Write (irType); context.Output.Write (' '); - WriteValue (context, To.Type, From, isPointer); + WriteValue (context, to.Type, from, isPointer); context.Output.Write (", ptr "); - context.Output.Write (To.Reference); + context.Output.Write (to.Reference); WriteAlignment (context, size, isPointer); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index 07f7e30614c..6b65e11d6ba 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -175,6 +175,39 @@ sealed class AssemblyCacheState public List Indices64; } + sealed class MarshalMethodsWriteState + { + public AssemblyCacheState AssemblyCacheState; + public LlvmIrFunctionAttributeSet AttributeSet; + public Dictionary UniqueAssemblyId; + public Dictionary UsedBackingFields; + public LlvmIrVariable GetFunctionPtrVariable; + public LlvmIrFunction GetFunctionPtrFunction; + } + + sealed class MarshalMethodAssemblyIndexValuePlaceholder : LlvmIrInstructionArgumentValuePlaceholder + { + MarshalMethodInfo mmi; + AssemblyCacheState acs; + + public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, AssemblyCacheState acs) + { + this.mmi = mmi; + this.acs = acs; + } + + public override object? GetValue (LlvmIrModuleTarget target) + { + // What a monstrosity... + string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; + Dictionary asmNameToIndex = target.Is64Bit ? acs.AsmNameToIndexData64 : acs.AsmNameToIndexData32; + if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { + throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); + } + return asmIndex; + } + } + static readonly Dictionary jniSimpleTypeMap = new Dictionary { { 'Z', typeof(bool) }, { 'B', typeof(byte) }, @@ -580,9 +613,9 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable ("mm_class_names", mm_class_names, LLVMIR.LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); AddMarshalMethodNames (module, acs); - LlvmIrVariable getFunctionPtrVariable = AddXamarinAppInitFunction (module); + (LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) = AddXamarinAppInitFunction (module); - AddMarshalMethods (module, getFunctionPtrVariable); + AddMarshalMethods (module, acs, getFunctionPtrVariable, getFunctionPtrFunction); } void MapStructures (LlvmIrModule module) @@ -591,7 +624,7 @@ void MapStructures (LlvmIrModule module) marshalMethodNameStructureInfo = module.MapStructure (); } - void AddMarshalMethods (LlvmIrModule module, LlvmIrVariable getFunctionPtrVariable) + void AddMarshalMethods (LlvmIrModule module, AssemblyCacheState acs, LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) { if (generateEmptyCode || methods == null || methods.Count == 0) { return; @@ -604,32 +637,37 @@ void AddMarshalMethods (LlvmIrModule module, LlvmIrVariable getFunctionPtrVariab } ); - LlvmIrFunctionAttributeSet attrSet = MakeMarshalMethodAttributeSet (module); - var usedBackingFields = new Dictionary (StringComparer.Ordinal); - var uniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase); + var writeState = new MarshalMethodsWriteState { + AssemblyCacheState = acs, + AttributeSet = MakeMarshalMethodAttributeSet (module), + UsedBackingFields = new Dictionary (StringComparer.Ordinal), + UniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase), + GetFunctionPtrVariable = getFunctionPtrVariable, + GetFunctionPtrFunction = getFunctionPtrFunction, + }; foreach (MarshalMethodInfo mmi in methods) { CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; - if (!uniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { - asmId = (ulong)uniqueAssemblyId.Count; - uniqueAssemblyId.Add (asmName, asmId); + if (!writeState.UniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { + asmId = (ulong)writeState.UniqueAssemblyId.Count; + writeState.UniqueAssemblyId.Add (asmName, asmId); } - AddMarshalMethod (module, mmi, asmId, attrSet, usedBackingFields, getFunctionPtrVariable); + AddMarshalMethod (module, mmi, asmId, writeState); } module.Add (new LlvmIrGroupDelimiterVariable ()); } - void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, LlvmIrFunctionAttributeSet attrSet, Dictionary usedBackingFields, LlvmIrVariable getFunctionPtrVariable) + void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, MarshalMethodsWriteState writeState) { CecilMethodDefinition nativeCallback = method.Method.NativeCallback; string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{asmId}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; - if (!usedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { + if (!writeState.UsedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { backingField = module.AddGlobalVariable (typeof(IntPtr), backingFieldName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); - usedBackingFields.Add (backingFieldName, backingField); + writeState.UsedBackingFields.Add (backingFieldName, backingField); } var funcComment = new StringBuilder (" Method: "); @@ -639,7 +677,7 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI funcComment.Append (" Registered: "); funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); - var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, attrSet) { + var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, writeState.AttributeSet) { Comment = funcComment.ToString (), }; @@ -661,12 +699,20 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI func.Body.Add (callbackIsNullLabel); LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr)); func.Body.Add ( - new LlvmIrInstructions.Load (getFunctionPtrVariable, getFuncPtrResult) { + new LlvmIrInstructions.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult) { TBAA = module.TbaaAnyPointer, } ); - func.Body.Add (new LlvmIrFunctionBodyComment (" TODO: call get_function_pointer here")); + var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + func.Body.Add ( + new LlvmIrInstructions.Call ( + writeState.GetFunctionPtrFunction, + arguments: new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken.ToUInt32 (), backingField } + ) { + FuncPointer = getFuncPtrResult, + } + ); LlvmIrLocalVariable loadedCallback = func.CreateLocalVariable (typeof(IntPtr)); func.Body.Add ( new LlvmIrInstructions.Load (backingField, loadedCallback) { @@ -695,9 +741,57 @@ LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) return module.AddAttributeSet (attrSet); } - LlvmIrVariable AddXamarinAppInitFunction (LlvmIrModule module) + (LlvmIrVariable getFuncPtrVariable, LlvmIrFunction getFuncPtrFunction) AddXamarinAppInitFunction (LlvmIrModule module) { - LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable (typeof(IntPtr), GetFunctionPointerVariableName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); + var getFunctionPtrParams = new List { + new (typeof(uint), "mono_image_index") { + NoUndef = true, + }, + new (typeof(uint), "class_index") { + NoUndef = true, + }, + new (typeof(uint), "method_token") { + NoUndef = true, + }, + new (typeof(IntPtr), "target_ptr") { + NoUndef = true, + NonNull = true, + Align = 0, // 0 means use natural pointer alignment + Dereferenceable = 0, // ditto 👆 + IsCplusPlusReference = true, + }, + }; + + var getFunctionPtrComment = new StringBuilder (" "); + getFunctionPtrComment.Append (GetFunctionPointerVariableName); + getFunctionPtrComment.Append (" ("); + for (int i = 0; i < getFunctionPtrParams.Count; i++) { + if (i > 0) { + getFunctionPtrComment.Append (", "); + } + LlvmIrFunctionParameter parameter = getFunctionPtrParams[i]; + getFunctionPtrComment.Append (LlvmIrGenerator.MapManagedTypeToNative (parameter.Type)); + if (parameter.IsCplusPlusReference.HasValue && parameter.IsCplusPlusReference.Value) { + getFunctionPtrComment.Append ('&'); + } + getFunctionPtrComment.Append (' '); + getFunctionPtrComment.Append (parameter.Name); + } + getFunctionPtrComment.Append (')'); + + LlvmIrFunction getFunctionPtrFunc = new LlvmIrFunction ( + name: GetFunctionPointerVariableName, + returnType: typeof(void), + parameters: getFunctionPtrParams + ); + + LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable ( + typeof(IntPtr), + GetFunctionPointerVariableName, + null, + LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr, + getFunctionPtrComment.ToString () + ); var init_params = new List { new (typeof(_JNIEnv), "env") { @@ -727,7 +821,7 @@ LlvmIrVariable AddXamarinAppInitFunction (LlvmIrModule module) module.Add (xamarin_app_init); - return getFunctionPtrVariable; + return (getFunctionPtrVariable, getFunctionPtrFunc); } LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) From 33c9ac3e65725bde4cb5e2b99f671a0b610fe518 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 20 Jun 2023 21:58:49 +0200 Subject: [PATCH 51/60] Conversion to the new LLVM IR complete for everything except tracing Tomorrow, testing and cleanup --- .../Tasks/GeneratePackageManagerJava.cs | 22 +- .../LlvmIrGenerator/LlvmIrBufferManager.cs | 68 ++++ .../LlvmIrGenerator/LlvmIrFunction.cs | 36 +- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 351 +++++++++++------- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 36 +- .../LlvmIrGenerator/LlvmIrInstructions.cs | 56 ++- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 59 +++ .../LlvmIrGenerator/StructureInfo.New.cs | 9 + .../LlvmIrGenerator/StructureInstance.New.cs | 7 + ...rshalMethodsNativeAssemblyGenerator.New.cs | 80 ++-- 10 files changed, 528 insertions(+), 196 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 69969a47422..a4f5c353442 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -407,18 +407,9 @@ void AddEnvironment () LLVM.IR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct (); var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build); - MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; New.MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGenNew; if (enableMarshalMethods) { - marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( - assemblyCount, - uniqueAssemblyNames, - marshalMethodsState?.MarshalMethods, - Log, - mmTracingMode - ); - marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator ( assemblyCount, uniqueAssemblyNames, @@ -427,10 +418,8 @@ void AddEnvironment () mmTracingMode ); } else { - marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); } - marshalMethodsAsmGen.Init (); LLVM.IR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGenNew.Construct (); foreach (string abi in SupportedAbis) { @@ -439,7 +428,6 @@ void AddEnvironment () string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}"); string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll"; string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll"; - string marshalMethodsLlFilePathNew = $"{marshalMethodsBaseAsmFilePath}-new.ll"; AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { @@ -455,20 +443,14 @@ void AddEnvironment () using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { try { - marshalMethodsAsmGenNew.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePathNew); + marshalMethodsAsmGenNew.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePath); } catch { throw; } finally { sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePathNew); + Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath); } } - - using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { - marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath); - } } void AddEnvironmentVariable (string name, string value) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs new file mode 100644 index 00000000000..9fc85ab6277 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVM.IR; + +partial class LlvmIrModule +{ + sealed class LlvmIrBufferManager + { + Dictionary counters; + Dictionary> bufferVariableNames; + + public LlvmIrBufferManager () + { + counters = new Dictionary (StringComparer.Ordinal); + } + + public string Allocate (StructureInstance structure, StructureMemberInfo smi, ulong size) + { + string baseName = $"_{structure.Info.Name}_{smi.Info.Name}"; + + if (!counters.TryGetValue (baseName, out ulong count)) { + count = 0; + counters.Add (baseName, count); + } else { + count++; + counters[baseName] = count; + } + + return Register (structure, smi, $"{baseName}_{count:x}_{structure.IndexInArray:x}"); + } + + public string? GetBufferVariableName (StructureInstance structure, StructureMemberInfo smi) + { + if (bufferVariableNames == null || bufferVariableNames.Count == 0) { + return null; + } + + if (!bufferVariableNames.TryGetValue (structure.Obj, out Dictionary members)) { + return null; + } + + if (!members.TryGetValue (MakeUniqueMemberId (structure, smi), out string bufferVariableName)) { + return null; + } + + return bufferVariableName; + } + + string Register (StructureInstance structure, StructureMemberInfo smi, string bufferVariableName) + { + if (bufferVariableNames == null) { + bufferVariableNames = new Dictionary> (); + } + + if (!bufferVariableNames.TryGetValue (structure.Obj, out Dictionary members)) { + members = new Dictionary (StringComparer.Ordinal); + bufferVariableNames.Add (structure.Obj, members); + } + + members.Add (MakeUniqueMemberId (structure, smi), bufferVariableName); + return bufferVariableName; + } + + string MakeUniqueMemberId (StructureInstance structure, StructureMemberInfo smi) => $"{smi.Info.Name}_{structure.IndexInArray}"; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index ea8e5dffe43..87a705b3836 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -196,23 +196,47 @@ interface ILlvmIrSavedFunctionSignatureState {} class LlvmIrFunctionSignature : IEquatable { + public sealed class ReturnTypeAttributes + { + public bool? InReg; + public bool? NoUndef; + public bool? SignExt; + public bool? ZeroExt; + + public ReturnTypeAttributes () + {} + + public ReturnTypeAttributes (ReturnTypeAttributes other) + { + InReg = other.InReg; + NoUndef = other.NoUndef; + SignExt = other.SignExt; + ZeroExt = other.ZeroExt; + } + } + sealed class SavedSignatureState : ILlvmIrSavedFunctionSignatureState { public readonly LlvmIrFunctionSignature Owner; public readonly IList ParameterStates; + public readonly ReturnTypeAttributes ReturnAttributes; - public SavedSignatureState (LlvmIrFunctionSignature owner, IList parameterStates) + public SavedSignatureState (LlvmIrFunctionSignature owner, IList parameterStates, ReturnTypeAttributes returnAttributes) { Owner = owner; ParameterStates = parameterStates; + ReturnAttributes = returnAttributes; } } + ReturnTypeAttributes returnAttributes; + public string Name { get; } public Type ReturnType { get; } + public ReturnTypeAttributes ReturnAttributes => returnAttributes; public IList Parameters { get; } - public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null) + public LlvmIrFunctionSignature (string name, Type returnType, IList? parameters = null, ReturnTypeAttributes? returnAttributes = null) { if (String.IsNullOrEmpty (name)) { throw new ArgumentException ("must not be null or empty", nameof (name)); @@ -220,6 +244,7 @@ public LlvmIrFunctionSignature (string name, Type returnType, IList (); } @@ -243,7 +268,9 @@ public ILlvmIrSavedFunctionSignatureState SaveState () list.Add (parameter.SaveState ()); } - return new SavedSignatureState (this, list.AsReadOnly ()); + var ret = new SavedSignatureState (this, list.AsReadOnly (), returnAttributes); + returnAttributes = new ReturnTypeAttributes (returnAttributes); + return ret; } /// @@ -264,8 +291,8 @@ public void RestoreState (ILlvmIrSavedFunctionSignatureState savedState) for (int i = 0; i < oldState.ParameterStates.Count; i++) { ILlvmIrSavedFunctionParameterState parameterState = oldState.ParameterStates[i]; Parameters[i].RestoreState (parameterState); - } + returnAttributes = new ReturnTypeAttributes (oldState.ReturnAttributes); } public override int GetHashCode () @@ -382,6 +409,7 @@ public SavedFunctionState (LlvmIrFunction owner, ILlvmIrSavedFunctionSignatureSt public LlvmIrVisibility Visibility { get; set; } = LlvmIrVisibility.Default; public LlvmIrFunctionBody Body { get; } public string? Comment { get; set; } + public bool ReturnsValue => Signature.ReturnType != typeof(void); public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index a2759ab3b44..7e097a863cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -1,187 +1,282 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Text; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVM.IR; + +// TODO: remove these aliases once the refactoring is done +using LlvmIrIcmpCond = LLVMIR.LlvmIrIcmpCond; +using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; + +/// +/// Abstract class from which all of the items (labels, function parameters, +/// local variables and instructions) derive. +/// +abstract class LlvmIrFunctionBodyItem { /// - /// Abstract class from which all of the items (labels, function parameters, - /// local variables and instructions) derive. + /// If an item has this property set to true, it won't be written to output when + /// code is generated. This is used for implicit items that don't need to be part of + /// the generated code (e.g. the starting block label) /// - abstract class LlvmIrFunctionBodyItem + public bool SkipInOutput { get; protected set; } + public string? Comment { get; set; } + + public void Write (GeneratorWriteContext context, LlvmIrGenerator generator) { - /// - /// If an item has this property set to true, it won't be written to output when - /// code is generated. This is used for implicit items that don't need to be part of - /// the generated code (e.g. the starting block label) - /// - public bool SkipInOutput { get; protected set; } - public abstract void Write (GeneratorWriteContext context); + DoWrite (context, generator); + if (!String.IsNullOrEmpty (Comment)) { + context.Output.Write (' '); + generator.WriteComment (context, Comment); + } + context.Output.WriteLine (); } - /// - /// Base class for function labels and local variables (including parameters), which - /// obtain automatic names derived from a shared counter, unless explicitly named. - /// - abstract class LlvmIrFunctionLocalItem : LlvmIrFunctionBodyItem - { - string? name; + protected abstract void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator); +} - public string Name { - get { - if (String.IsNullOrEmpty (name)) { - throw new InvalidOperationException ("Internal error: name hasn't been set yet"); - } - return name; - } +/// +/// Base class for function labels and local variables (including parameters), which +/// obtain automatic names derived from a shared counter, unless explicitly named. +/// +abstract class LlvmIrFunctionLocalItem : LlvmIrFunctionBodyItem +{ + string? name; - protected set { - if (String.IsNullOrEmpty (value)) { - throw new InvalidOperationException ("Internal error: value must not be null or empty"); - } - name = value; + public string Name { + get { + if (String.IsNullOrEmpty (name)) { + throw new InvalidOperationException ("Internal error: name hasn't been set yet"); } + return name; } - protected LlvmIrFunctionLocalItem (string? name) - { - if (name != null) { - Name = name; + protected set { + if (String.IsNullOrEmpty (value)) { + throw new InvalidOperationException ("Internal error: value must not be null or empty"); } + name = value; } + } - protected LlvmIrFunctionLocalItem (LlvmIrFunction.FunctionState state, string? name) - { - if (name != null) { - if (name.Length == 0) { - throw new ArgumentException ("must not be an empty string", nameof (name)); - } + protected LlvmIrFunctionLocalItem (string? name) + { + if (name != null) { + Name = name; + } + } - Name = name; - return; + protected LlvmIrFunctionLocalItem (LlvmIrFunction.FunctionState state, string? name) + { + if (name != null) { + if (name.Length == 0) { + throw new ArgumentException ("must not be an empty string", nameof (name)); } - SetName (state.NextTemporary ()); + Name = name; + return; } - protected void SetName (ulong num) - { - Name = num.ToString (CultureInfo.InvariantCulture); + SetName (state.NextTemporary ()); + } + + protected void SetName (ulong num) + { + Name = num.ToString (CultureInfo.InvariantCulture); + } + + protected bool NameIsSet () => !String.IsNullOrEmpty (name); +} + +class LlvmIrFunctionLabelItem : LlvmIrFunctionLocalItem +{ + /// + /// Labels are a bit peculiar in that they must not have their name set to the automatic value (based on + /// a counter shared with function parameters) at creation time, but only when they are actually added to + /// the function body. The reason is that LLVM IR requires all the unnamed temporaries (function parameters and + /// labels) to be named sequentially, but sometimes a label must be referenced before it is added to the instruction + /// stream, e.g. in the br instruction. On the other hand, it is perfectly fine to assign label a name that + /// isn't an integer at **instantiation** time, which is why we have the parameter here. + /// + public LlvmIrFunctionLabelItem (string? name = null) + : base (name) + {} + + public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.FunctionState state) + { + if (NameIsSet ()) { + return; } - protected bool NameIsSet () => !String.IsNullOrEmpty (name); + SetName (state.NextTemporary ()); } - class LlvmIrFunctionLabelItem : LlvmIrFunctionLocalItem + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) { - /// - /// Labels are a bit peculiar in that they must not have their name set to the automatic value (based on - /// a counter shared with function parameters) at creation time, but only when they are actually added to - /// the function body. The reason is that LLVM IR requires all the unnamed temporaries (function parameters and - /// labels) to be named sequentially, but sometimes a label must be referenced before it is added to the instruction - /// stream, e.g. in the br instruction. On the other hand, it is perfectly fine to assign label a name that - /// isn't an integer at **instantiation** time, which is why we have the parameter here. - /// - public LlvmIrFunctionLabelItem (string? name = null) - : base (name) - {} + context.DecreaseIndent (); - public void WillAddToBody (LlvmIrFunctionBody functionBody, LlvmIrFunction.FunctionState state) - { - if (NameIsSet ()) { - return; - } + context.Output.Write (context.CurrentIndent); + context.Output.Write (Name); + context.Output.Write (':'); - SetName (state.NextTemporary ()); - } + context.IncreaseIndent (); + } +} - public override void Write (GeneratorWriteContext context) - { - context.DecreaseIndent (); +class LlvmIrFunctionBodyComment : LlvmIrFunctionBodyItem +{ + public string Text { get; } - context.Output.Write (context.CurrentIndent); - context.Output.Write (Name); - context.Output.WriteLine (':'); + public LlvmIrFunctionBodyComment (string comment) + { + Text = comment; + } + + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) + { + context.Output.Write (context.CurrentIndent); + generator.WriteCommentLine (context, Text); + } +} - context.IncreaseIndent (); +class LlvmIrFunctionBody +{ + sealed class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem + { + public LlvmIrFunctionImplicitStartLabel (ulong num) + { + SetName (num); + SkipInOutput = true; } } - class LlvmIrFunctionBodyComment : LlvmIrFunctionBodyItem + sealed class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem { - public string Text { get; } + public LlvmIrFunctionParameter Parameter { get; } - public LlvmIrFunctionBodyComment (string comment) + public LlvmIrFunctionParameterItem (LlvmIrFunction.FunctionState state, LlvmIrFunctionParameter parameter) + : base (state, parameter.Name) { - Text = comment; + Parameter = parameter; + SkipInOutput = true; } - public override void Write (GeneratorWriteContext context) + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) { - context.Output.Write (context.CurrentIndent); - context.Output.Write (';'); - context.Output.WriteLine (Text); + throw new NotSupportedException ("Internal error: writing not supported for this item"); } } - class LlvmIrFunctionBody + List items; + HashSet definedLabels; + LlvmIrFunction function; + LlvmIrFunction.FunctionState functionState; + LlvmIrFunctionLabelItem implicitStartBlock; + + LlvmIrFunctionLabelItem? precedingBlock1; + LlvmIrFunctionLabelItem? precedingBlock2; + LlvmIrFunctionLabelItem? previousLabel; + + public IList Items => items.AsReadOnly (); + public LlvmIrFunctionLabelItem? PrecedingBlock1 => precedingBlock1; + public LlvmIrFunctionLabelItem? PrecedingBlock2 => precedingBlock2; + + public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) { - sealed class LlvmIrFunctionImplicitStartLabel : LlvmIrFunctionLabelItem - { - public LlvmIrFunctionImplicitStartLabel (ulong num) - { - SetName (num); - SkipInOutput = true; - } - } + function = func; + this.functionState = functionState; + definedLabels = new HashSet (StringComparer.Ordinal); + items = new List (); + previousLabel = implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); + } - sealed class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem - { - public LlvmIrFunctionParameter Parameter { get; } + public LlvmIrInstructions.Br Br (LlvmIrFunctionLabelItem label) + { + var ret = new LlvmIrInstructions.Br (label); + Add (ret); + return ret; + } - public LlvmIrFunctionParameterItem (LlvmIrFunction.FunctionState state, LlvmIrFunctionParameter parameter) - : base (state, parameter.Name) - { - Parameter = parameter; - SkipInOutput = true; - } + public LlvmIrInstructions.Br Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem ifTrue, LlvmIrFunctionLabelItem ifFalse) + { + var ret = new LlvmIrInstructions.Br (cond, ifTrue, ifFalse); + Add (ret); + return ret; + } - public override void Write (GeneratorWriteContext context) - { - throw new NotSupportedException ("Internal error: writing not supported for this item"); - } - } + public LlvmIrInstructions.Call Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null) + { + var ret = new LlvmIrInstructions.Call (function, result, arguments); + Add (ret); + return ret; + } - List items; - HashSet definedLabels; - LlvmIrFunction function; - LlvmIrFunction.FunctionState functionState; - LlvmIrFunctionLabelItem implicitStartBlock; + public LlvmIrInstructions.Icmp Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) + { + var ret = new LlvmIrInstructions.Icmp (cond, op1, op2, result); + Add (ret); + return ret; + } - public IList Items => items.AsReadOnly (); + public LlvmIrInstructions.Load Load (LlvmIrVariable source, LlvmIrVariable result, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Load (source, result) { + TBAA = tbaa, + }; + Add (ret); + return ret; + } - public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) - { - function = func; - this.functionState = functionState; - definedLabels = new HashSet (StringComparer.Ordinal); - items = new List (); - implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); + /// + /// Creates the `phi` instruction form we use the most throughout marshal methods generator - one which refers to an if/else block and where + /// **both** value:label pairs are **required**. Parameters and are nullable because, in theory, + /// it is possible that hasn't had the required blocks defined prior to adding the `phi` instruction and, thus, + /// we must check for the possibility here. + /// + public LlvmIrInstructions.Phi Phi (LlvmIrVariable result, LlvmIrVariable val1, LlvmIrFunctionLabelItem? label1, LlvmIrVariable val2, LlvmIrFunctionLabelItem? label2) + { + var ret = new LlvmIrInstructions.Phi (result, val1, label1, val2, label2); + Add (ret); + return ret; + } + + public LlvmIrInstructions.Ret Ret (Type retvalType, object? retval = null) + { + var ret = new LlvmIrInstructions.Ret (retvalType, retval); + Add (ret); + return ret; + } + + public void Add (LlvmIrFunctionLabelItem label) + { + label.WillAddToBody (this, functionState); + if (definedLabels.Contains (label.Name)) { + throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{function.Signature.Name}' body"); } + items.Add (label); + definedLabels.Add (label.Name); - public void Add (LlvmIrFunctionLabelItem label) - { - label.WillAddToBody (this, functionState); - if (definedLabels.Contains (label.Name)) { - throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{function.Signature.Name}' body"); - } - items.Add (label); - definedLabels.Add (label.Name); + // Rotate preceding blocks + if (precedingBlock2 != null) { + precedingBlock2 = null; } - public void Add (LlvmIrFunctionBodyItem item) - { - items.Add (item); + precedingBlock2 = precedingBlock1; + precedingBlock1 = previousLabel; + previousLabel = label; + + var comment = new StringBuilder (" preds = %"); + comment.Append (precedingBlock1.Name); + if (precedingBlock2 != null) { + comment.Append (", %"); + comment.Append (precedingBlock2.Name); } + label.Comment = comment.ToString (); + } + + public void Add (LlvmIrFunctionBodyItem item) + { + items.Add (item); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs index e2c348339a6..9cd5b6e4731 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs @@ -517,6 +517,13 @@ void WriteValue (GeneratorWriteContext context, StructureInstance structInstance return; } + if (smi.Info.IsNativePointerToPreallocatedBuffer (out _)) { + string bufferVariableName = context.Module.LookupRequiredBufferVariableName (structInstance, smi); + context.Output.Write ('@'); + context.Output.Write (bufferVariableName); + return; + } + WriteValue (context, smi.MemberType, value); } @@ -898,7 +905,7 @@ void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) context.IncreaseIndent (); foreach (LlvmIrFunctionBodyItem item in function.Body.Items) { - item.Write (context); + item.Write (context, this); } context.DecreaseIndent (); @@ -1002,8 +1009,27 @@ void WriteFunctionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunc } } + public static void WriteReturnAttributes (GeneratorWriteContext context, LlvmIrFunctionSignature.ReturnTypeAttributes returnAttrs) + { + if (AttributeIsSet (returnAttrs.NoUndef)) { + context.Output.Write ("noundef "); + } + + if (AttributeIsSet (returnAttrs.SignExt)) { + context.Output.Write ("signext "); + } + + if (AttributeIsSet (returnAttrs.ZeroExt)) { + context.Output.Write ("zeroext "); + } + } + void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, bool writeParameterNames) { + if (func.ReturnsValue) { + WriteReturnAttributes (context, func.Signature.ReturnAttributes); + } + context.Output.Write (MapToIRType (func.Signature.ReturnType)); context.Output.Write (" @"); context.Output.Write (func.Signature.Name); @@ -1081,8 +1107,6 @@ public static void WriteParameterAttributes (GeneratorWriteContext context, Llvm context.Output.Write (' '); context.Output.Write (String.Join (" ", attributes)); - bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; - uint ValueOrPointerSize (uint? value) { if (value.Value == 0) { @@ -1093,6 +1117,8 @@ uint ValueOrPointerSize (uint? value) } } + static bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; + void WriteAttributeSets (GeneratorWriteContext context) { if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { @@ -1127,13 +1153,13 @@ void WriteMetadata (GeneratorWriteContext context) } } - void WriteComment (GeneratorWriteContext context, string comment) + public void WriteComment (GeneratorWriteContext context, string comment) { context.Output.Write (';'); context.Output.Write (comment); } - void WriteCommentLine (GeneratorWriteContext context, string comment) + public void WriteCommentLine (GeneratorWriteContext context, string comment) { WriteComment (context, comment); context.Output.WriteLine (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 8354e78a124..f5a9f1d655c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -29,7 +29,7 @@ protected LlvmIrInstruction (string mnemonic) Mnemonic = mnemonic; } - public override void Write (GeneratorWriteContext context) + protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) { context.Output.Write (context.CurrentIndent); WriteValueAssignment (context); @@ -47,7 +47,10 @@ public override void Write (GeneratorWriteContext context) context.Output.Write (" #"); context.Output.Write (AttributeSet.Number.ToString (CultureInfo.InvariantCulture)); } - context.Output.WriteLine (); + + if (!String.IsNullOrEmpty (Comment)) { + generator.WriteComment (context, Comment); + } } /// @@ -224,6 +227,10 @@ protected override void WritePreamble (GeneratorWriteContext context) protected override void WriteBody (GeneratorWriteContext context) { + if (function.ReturnsValue) { + LlvmIrGenerator.WriteReturnAttributes (context, function.Signature.ReturnAttributes); + } + context.Output.Write (LlvmIrGenerator.MapToIRType (function.Signature.ReturnType)); if (FuncPointer == null) { context.Output.Write (" @"); @@ -369,6 +376,51 @@ protected override void WriteBody (GeneratorWriteContext context) } } + public class Phi : LlvmIrInstruction + { + LlvmIrVariable result; + LlvmIrVariable val1; + LlvmIrFunctionLabelItem label1; + LlvmIrVariable val2; + LlvmIrFunctionLabelItem label2; + + /// + /// Represents the `phi` instruction form we use the most throughout marshal methods generator - one which refers to an if/else block and where + /// **both** value:label pairs are **required**. Parameters and are nullable because, in theory, + /// it is possible that hasn't had the required blocks defined prior to adding the `phi` instruction and, thus, + /// we must check for the possibility here. + /// + public Phi (LlvmIrVariable result, LlvmIrVariable val1, LlvmIrFunctionLabelItem? label1, LlvmIrVariable val2, LlvmIrFunctionLabelItem? label2) + : base ("phi") + { + this.result = result; + this.val1 = val1; + this.label1 = label1 ?? throw new ArgumentNullException (nameof (label1)); + this.val2 = val2; + this.label2 = label2 ?? throw new ArgumentNullException (nameof (label2)); + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (result.Type)); + context.Output.Write (" ["); + context.Output.Write (val1.Reference); + context.Output.Write (", %"); + context.Output.Write (label1.Name); + context.Output.Write ("], ["); + context.Output.Write (val2.Reference); + context.Output.Write (", %"); + context.Output.Write (label2.Name); + context.Output.Write (']'); + } + } + public class Ret : LlvmIrInstruction { Type retvalType; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 9b395f04125..8a125c0f287 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Xamarin.Android.Tools; @@ -40,6 +41,7 @@ partial class LlvmIrModule LlvmIrStringManager? stringManager; LlvmIrMetadataManager metadataManager; LlvmIrMetadataItem tbaaAnyPointer; + LlvmIrBufferManager? bufferManager; List? globalVariables; @@ -228,6 +230,15 @@ void PrepareStructure (StructureInstance structure) continue; } + if (smi.Info.IsNativePointerToPreallocatedBuffer (out ulong bufferSize)) { + if (bufferSize == 0) { + bufferSize = structure.Info.GetBufferSizeFromProvider (smi, structure); + } + + AddAutomaticBuffer (structure, smi, bufferSize); + continue; + } + if (smi.MemberType != typeof(string)) { continue; } @@ -239,6 +250,20 @@ void PrepareStructure (StructureInstance structure) } } + void AddAutomaticBuffer (StructureInstance structure, StructureMemberInfo smi, ulong bufferSize) + { + if (bufferManager == null) { + bufferManager = new LlvmIrBufferManager (); + } + + string bufferName = bufferManager.Allocate (structure, smi, bufferSize); + var buffer = new LlvmIrGlobalVariable (typeof(List), bufferName, LLVMIR.LlvmIrVariableOptions.LocalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = bufferSize, + }; + Add (buffer); + } + void AddStandardGlobalVariable (LlvmIrGlobalVariable variable) { if (globalVariables == null) { @@ -277,18 +302,38 @@ void AddStructureArrayGlobalVariable (LlvmIrGlobalVariable variable) // For simplicity we support only arrays with homogenous entry types StructureInfo? info = null; + ulong index = 0; + foreach (StructureInstance structure in (IEnumerable)variable.Value) { if (info == null) { info = structure.Info; + if (info.HasPreAllocatedBuffers) { + // let's group them... + Add (new LlvmIrGroupDelimiterVariable ()); + } } if (structure.Type != info.Type) { throw new InvalidOperationException ($"Internal error: only arrays with homogenous element types are currently supported. All entries were expected to be of type '{info.Type}', but the '{structure.Type}' type was encountered."); } + // This is a bit of a kludge to make a specific corner case work seamlessly from the LlvmIrModule user's point of view. + // The scenario is used in ApplicationConfigNativeAssemblyGenerator and it involves an array of structures where each + // array index contains the same object in structure.Obj but each instance needs to allocate a unique buffer at runtime. + // LlvmIrBufferManager makes it possible, but it must be able to uniquely identify each instance, which in this scenario + // wouldn't be possible if we had to rely only on the StructureInstance contents. Enter `StructureInstance.IndexInArray`, + // which is used to create unique buffers and unambiguously assign them to each structure instance. + // + // See LlvmIrBufferManager for how it is used. + structure.IndexInArray = index++; + PrepareStructure (structure); } + if (info != null && info.HasPreAllocatedBuffers) { + Add (new LlvmIrGroupDelimiterVariable ()); + } + AddStandardGlobalVariable (variable); } @@ -426,6 +471,20 @@ public LlvmIrStringVariable LookupRequiredVariableForString (string value) return sv; } + public string LookupRequiredBufferVariableName (StructureInstance structure, StructureMemberInfo smi) + { + if (bufferManager == null) { + throw new InvalidOperationException ("Internal error: no buffer variables have been registed with the buffer manager"); + } + + string? variableName = bufferManager.GetBufferVariableName (structure, smi); + if (String.IsNullOrEmpty (variableName)) { + throw new InvalidOperationException ($"Internal error: buffer for member '{smi.Info.Name}' of structure '{structure.Info.Name}' (index {structure.IndexInArray}) not found"); + } + + return variableName; + } + /// /// Add a new attribute set. The caller MUST use the returned value to refer to the set, instead of the one passed /// as parameter, since this function de-duplicates sets and may return a previously added one that's identical to diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs index 4a03cb12af9..aafa57ad53a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs @@ -45,6 +45,15 @@ public StructureInfo (LlvmIrModule module, Type type) 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, LlvmIrModule module, bool storeMembers = true) { ulong size = 0; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs index 3d7260fcaba..dc8f8f8ce99 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs @@ -15,6 +15,13 @@ abstract class StructureInstance public Type Type => info.Type; public StructureInfo Info => info; + /// + /// Do **not** set this property, it is used internally by , + /// and when dealing with arrays of objects where each + /// array index contains the same object instance + /// + internal ulong IndexInArray { get; set; } + /// /// This is a cludge to support zero-initialized structures. In order to output proper variable type /// when a structure is used, the generator must be able to read the structure descrption, which is diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs index 6b65e11d6ba..f665834367b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs @@ -7,7 +7,6 @@ using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; -using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVM.IR; using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; @@ -681,51 +680,58 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI Comment = funcComment.ToString (), }; - LlvmIrLocalVariable callback = func.CreateLocalVariable (typeof(IntPtr)); - func.Body.Add ( - new LlvmIrInstructions.Load (backingField, callback) { - TBAA = module.TbaaAnyPointer, - } - ); + WriteBody (func.Body); + module.Add (func); - LlvmIrLocalVariable callbackIsNullResult = func.CreateLocalVariable (typeof(bool)); - func.Body.Add (new LlvmIrInstructions.Icmp (LLVMIR.LlvmIrIcmpCond.Equal, callback, null, callbackIsNullResult)); + void WriteBody (LlvmIrFunctionBody body) + { + LlvmIrLocalVariable callback = func.CreateLocalVariable (typeof(IntPtr)); + body.Load (backingField, callback, tbaa: module.TbaaAnyPointer); - var callbackIsNullLabel = new LlvmIrFunctionLabelItem (); - var callbackNotNullLabel = new LlvmIrFunctionLabelItem (); - func.Body.Add (new LlvmIrInstructions.Br (callbackIsNullResult, callbackIsNullLabel, callbackNotNullLabel)); + LlvmIrLocalVariable callbackIsNullResult = func.CreateLocalVariable (typeof(bool)); + body.Icmp (LLVMIR.LlvmIrIcmpCond.Equal, callback, null, callbackIsNullResult); - // Callback variable was null - func.Body.Add (callbackIsNullLabel); - LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr)); - func.Body.Add ( - new LlvmIrInstructions.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult) { - TBAA = module.TbaaAnyPointer, - } - ); + var callbackIsNullLabel = new LlvmIrFunctionLabelItem (); + var callbackNotNullLabel = new LlvmIrFunctionLabelItem (); + body.Br (callbackIsNullResult, callbackIsNullLabel, callbackNotNullLabel); + + // Callback variable was null + body.Add (callbackIsNullLabel); - var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); - func.Body.Add ( - new LlvmIrInstructions.Call ( + LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr)); + body.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult, tbaa: module.TbaaAnyPointer); + + var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + LlvmIrInstructions.Call call = body.Call ( writeState.GetFunctionPtrFunction, arguments: new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken.ToUInt32 (), backingField } - ) { - FuncPointer = getFuncPtrResult, - } - ); - LlvmIrLocalVariable loadedCallback = func.CreateLocalVariable (typeof(IntPtr)); - func.Body.Add ( - new LlvmIrInstructions.Load (backingField, loadedCallback) { - TBAA = module.TbaaAnyPointer, - } - ); + ); + call.FuncPointer = getFuncPtrResult; - func.Body.Add (new LlvmIrInstructions.Br (callbackNotNullLabel)); + LlvmIrLocalVariable newlySetCallback = func.CreateLocalVariable (typeof(IntPtr)); + body.Load (backingField, newlySetCallback, tbaa: module.TbaaAnyPointer); + body.Br (callbackNotNullLabel); - // Callback variable has just been set or it wasn't null - func.Body.Add (callbackNotNullLabel); + // Callback variable has just been set or it wasn't null + body.Add (callbackNotNullLabel); + LlvmIrLocalVariable selectedCallback = func.CreateLocalVariable (typeof(IntPtr)); - module.Add (func); + // Preceding blocks are ordered from the newest to the oldest, so we need to pass the variables referring to our callback in "reverse" order + body.Phi (selectedCallback, newlySetCallback, body.PrecedingBlock1, callback, body.PrecedingBlock2); + + var nativeFunc = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters); + nativeFunc.Signature.ReturnAttributes.NoUndef = true; + + var arguments = new List (); + foreach (LlvmIrFunctionParameter parameter in nativeFunc.Signature.Parameters) { + arguments.Add (new LlvmIrLocalVariable (parameter.Type, parameter.Name)); + } + LlvmIrLocalVariable? result = nativeFunc.ReturnsValue ? func.CreateLocalVariable (nativeFunc.Signature.ReturnType) : null; + call = body.Call (nativeFunc, result, arguments); + call.CallMarker = LlvmIrCallMarker.Tail; + + body.Ret (nativeFunc.Signature.ReturnType, result); + } } LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) From 4d8d4dc79608d5ded12e4231215a5bf5ef9b82b7 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 21 Jun 2023 20:47:35 +0200 Subject: [PATCH 52/60] Cleanup --- ...teCompressedAssembliesNativeSourceFiles.cs | 7 +- .../Tasks/GenerateJniRemappingNativeCode.cs | 18 +- .../Tasks/GeneratePackageManagerJava.cs | 16 +- ...cationConfigNativeAssemblyGenerator.New.cs | 385 ---- ...pplicationConfigNativeAssemblyGenerator.cs | 166 +- ...edAssembliesNativeAssemblyGenerator.New.cs | 149 -- ...ressedAssembliesNativeAssemblyGenerator.cs | 79 +- .../JniRemappingAssemblyGenerator.New.cs | 328 --- .../JniRemappingAssemblyGenerator.cs | 164 +- .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 49 - .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 53 - .../LlvmIrGenerator/FunctionAttributes.cs | 2 +- .../LlvmIrGenerator/LlvmIrBufferManager.cs | 2 +- .../LlvmIrGenerator/LlvmIrComposer.New.cs | 57 - .../LlvmIrGenerator/LlvmIrComposer.cs | 80 +- .../LlvmIrGenerator/LlvmIrDataLayout.cs | 523 +++-- .../LlvmIrGenerator/LlvmIrFunction.cs | 300 +-- .../LlvmIrFunctionAttributeSet.cs | 181 +- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 17 +- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 927 -------- ...stants.cs => LlvmIrGenerator.Constants.cs} | 9 +- .../LlvmIrGenerator/LlvmIrGenerator.New.cs | 1349 ----------- .../LlvmIrGenerator/LlvmIrGenerator.cs | 2026 ++++++++--------- .../LlvmIrGenerator/LlvmIrInstructions.cs | 15 +- .../LlvmIrGenerator/LlvmIrKnownMetadata.cs | 2 +- .../LlvmIrMetadataManager.New.cs | 176 -- .../LlvmIrGenerator/LlvmIrMetadataManager.cs | 57 +- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 8 +- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 90 +- .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 94 +- .../LlvmIrGenerator/LlvmIrModuleTarget.cs | 109 +- .../LlvmIrGenerator/LlvmIrModuleX64.cs | 117 +- .../LlvmIrGenerator/LlvmIrModuleX86.cs | 106 +- .../LlvmIrGenerator/LlvmIrStringGroup.cs | 2 +- .../LlvmIrStringManager.New.cs | 72 - .../LlvmIrGenerator/LlvmIrStringManager.cs | 96 +- .../LlvmIrGenerator/LlvmIrVariable.New.cs | 229 -- .../LlvmIrGenerator/LlvmIrVariable.cs | 234 +- .../LlvmIrVariableReference.cs | 50 - .../LlvmNativeFunctionSignature.cs | 49 - .../MemberInfoUtilities.New.cs | 78 - .../LlvmIrGenerator/MemberInfoUtilities.cs | 2 +- .../LlvmIrGenerator/StructureInfo.New.cs | 99 - .../LlvmIrGenerator/StructureInfo.cs | 75 +- .../LlvmIrGenerator/StructureInstance.New.cs | 85 - .../LlvmIrGenerator/StructureInstance.cs | 63 +- .../StructureMemberInfo.New.cs | 119 - .../LlvmIrGenerator/StructureMemberInfo.cs | 29 +- .../LlvmIrGenerator/TypeUtilities.New.cs | 185 -- .../LlvmIrGenerator/TypeUtilities.cs | 114 +- .../LlvmIrGenerator/X64LlvmIrGenerator.cs | 59 - .../LlvmIrGenerator/X86LlvmIrGenerator.cs | 53 - ...rshalMethodsNativeAssemblyGenerator.New.cs | 1088 --------- ...hodsNativeAssemblyGenerator.Tracing.New.cs | 98 - ...lMethodsNativeAssemblyGenerator.Tracing.cs | 673 +----- .../MarshalMethodsNativeAssemblyGenerator.cs | 709 ++++-- .../Utilities/TypeMapGenerator.cs | 42 +- ...MappingDebugNativeAssemblyGenerator.New.cs | 180 -- ...TypeMappingDebugNativeAssemblyGenerator.cs | 36 +- ...ppingReleaseNativeAssemblyGenerator.New.cs | 434 ---- ...peMappingReleaseNativeAssemblyGenerator.cs | 373 +-- 61 files changed, 3038 insertions(+), 9949 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs rename src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/{LlvmIrGenerator.New.Constants.cs => LlvmIrGenerator.Constants.cs} (83%) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs index 38f81e8e17a..48f849596e0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs @@ -74,11 +74,8 @@ void GenerateCompressedAssemblySources () void Generate (IDictionary dict) { - var llvmAsmgen = new CompressedAssembliesNativeAssemblyGenerator (dict); - llvmAsmgen.Init (); - - var composer = new New.CompressedAssembliesNativeAssemblyGenerator (dict); - LLVM.IR.LlvmIrModule compressedAssemblies = composer.Construct (); + var composer = new CompressedAssembliesNativeAssemblyGenerator (dict); + LLVMIR.LlvmIrModule compressedAssemblies = composer.Construct (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}"); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs index feab8d8059c..b89ce87d26d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs @@ -54,13 +54,13 @@ public override bool RunTask () void GenerateEmpty () { - Generate (new New.JniRemappingAssemblyGenerator (), typeReplacementsCount: 0); + Generate (new JniRemappingAssemblyGenerator (), typeReplacementsCount: 0); } void Generate () { - var typeReplacements = new List (); - var methodReplacements = new List (); + var typeReplacements = new List (); + var methodReplacements = new List (); var readerSettings = new XmlReaderSettings { XmlResolver = null, @@ -74,12 +74,12 @@ void Generate () } } - Generate (new New.JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count); + Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count); } - void Generate (New.JniRemappingAssemblyGenerator jniRemappingComposer, int typeReplacementsCount) + void Generate (JniRemappingAssemblyGenerator jniRemappingComposer, int typeReplacementsCount) { - LLVM.IR.LlvmIrModule module = jniRemappingComposer.Construct (); + LLVMIR.LlvmIrModule module = jniRemappingComposer.Construct (); foreach (string abi in SupportedAbis) { string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}"); @@ -99,7 +99,7 @@ void Generate (New.JniRemappingAssemblyGenerator jniRemappingComposer, int typeR ); } - void ReadXml (XmlReader reader, List typeReplacements, List methodReplacements) + void ReadXml (XmlReader reader, List typeReplacements, List methodReplacements) { bool haveAllAttributes; @@ -116,7 +116,7 @@ void ReadXml (XmlReader reader, List typeReplac continue; } - typeReplacements.Add (new New.JniRemappingTypeReplacement (from, to)); + typeReplacements.Add (new JniRemappingTypeReplacement (from, to)); } else if (String.Compare ("replace-method", reader.LocalName, StringComparison.Ordinal) == 0) { haveAllAttributes &= GetRequiredAttribute ("source-type", out string sourceType); haveAllAttributes &= GetRequiredAttribute ("source-method-name", out string sourceMethodName); @@ -135,7 +135,7 @@ void ReadXml (XmlReader reader, List typeReplac string sourceMethodSignature = reader.GetAttribute ("source-method-signature"); methodReplacements.Add ( - new New.JniRemappingMethodReplacement ( + new JniRemappingMethodReplacement ( sourceType, sourceMethodName, sourceMethodSignature, targetType, targetMethodName, isStatic ) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index a4f5c353442..abe13ce7e7f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -375,7 +375,7 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (ApplicationConfigTaskState.RegisterTaskObjectKey), RegisteredTaskObjectLifetime.Build); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); - var appConfigAsmGen = new New.ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { + var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, @@ -394,7 +394,7 @@ void AddEnvironment () // and up to 4 other for arch-specific assemblies. Only **one** arch-specific store is ever loaded on the app // runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names // and in the same order. - MonoComponents = (New.MonoComponent)monoComponents, + MonoComponents = (MonoComponent)monoComponents, NativeLibraries = uniqueNativeLibraries, HaveAssemblyStore = UseAssemblyStore, AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, @@ -404,13 +404,13 @@ void AddEnvironment () JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, MarshalMethodsEnabled = EnableMarshalMethods, }; - LLVM.IR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct (); + LLVMIR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct (); var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build); - New.MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGenNew; + MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; if (enableMarshalMethods) { - marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator ( + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( assemblyCount, uniqueAssemblyNames, marshalMethodsState?.MarshalMethods, @@ -418,9 +418,9 @@ void AddEnvironment () mmTracingMode ); } else { - marshalMethodsAsmGenNew = new New.MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames); } - LLVM.IR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGenNew.Construct (); + LLVMIR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGen.Construct (); foreach (string abi in SupportedAbis) { string targetAbi = abi.ToLowerInvariant (); @@ -443,7 +443,7 @@ void AddEnvironment () using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) { try { - marshalMethodsAsmGenNew.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePath); + marshalMethodsAsmGen.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePath); } catch { throw; } finally { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs deleted file mode 100644 index cc6afe5bb44..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.New.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; - -using Java.Interop.Tools.TypeNameMappings; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once the refactoring is done - using ApplicationConfig = Xamarin.Android.Tasks.ApplicationConfig; - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - // Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh - [Flags] - enum MonoComponent - { - None = 0x00, - Debugger = 0x01, - HotReload = 0x02, - Tracing = 0x04, - } - - class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer - { - sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var dso_entry = EnsureType (data); - if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { - return $" hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; - } - - if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $" name: {dso_entry.name}"; - } - - return String.Empty; - } - } - - // 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 - { - [NativeAssembler (Ignore = true)] - public string HashedName; - - [NativeAssembler (UsesDataProvider = true)] - public ulong hash; - public bool ignore; - - [NativeAssembler (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 - { - [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 - { - [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; - - [NativePointer] - public byte data; - public uint name_length; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] - public string name; - } - - // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh - const ulong FORMAT_TAG = 0x015E6972616D58; - - SortedDictionary ? environmentVariables; - SortedDictionary ? systemProperties; - TaskLoggingHelper log; - StructureInstance? application_config; - List>? dsoCache; - List>? xamarinAndroidBundledAssemblies; - - StructureInfo? applicationConfigStructureInfo; - StructureInfo? dsoCacheEntryStructureInfo; - StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; - StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; - StructureInfo? assemblyStoreRuntimeDataStructureInfo; - - public bool UsesMonoAOT { get; set; } - public bool UsesMonoLLVM { get; set; } - public bool UsesAssemblyPreload { get; set; } - public string MonoAOTMode { get; set; } - public bool AotEnableLazyLoad { get; set; } - public string AndroidPackageName { get; set; } - public bool BrokenExceptionTransitions { get; set; } - public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; } - public bool InstantRunEnabled { get; set; } - public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } - public bool HaveRuntimeConfigBlob { get; set; } - public bool HaveAssemblyStore { get; set; } - public int NumberOfAssembliesInApk { get; set; } - public int NumberOfAssemblyStoresInApks { get; set; } - public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL - public int AndroidRuntimeJNIEnvToken { get; set; } - public int JNIEnvInitializeToken { get; set; } - public int JNIEnvRegisterJniNativesToken { get; set; } - public int JniRemappingReplacementTypeCount { get; set; } - public int JniRemappingReplacementMethodIndexEntryCount { get; set; } - public MonoComponent MonoComponents { get; set; } - public PackageNamingPolicy PackageNamingPolicy { get; set; } - public List NativeLibraries { get; set; } - public bool MarshalMethodsEnabled { get; set; } - - public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) - { - if (environmentVariables != null) { - this.environmentVariables = new SortedDictionary (environmentVariables, StringComparer.Ordinal); - } - - if (systemProperties != null) { - this.systemProperties = new SortedDictionary (systemProperties, StringComparer.Ordinal); - } - - this.log = log; - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - - module.AddGlobalVariable ("format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); - module.AddGlobalVariable ("mono_aot_mode_name", MonoAOTMode); - - var envVars = new LlvmIrGlobalVariable (environmentVariables, "app_environment_variables") { - Comment = " Application environment variables array, name:value", - }; - module.Add (envVars, stringGroupName: "env", stringGroupComment: " Application environment variables name:value pairs"); - - var sysProps = new LlvmIrGlobalVariable (systemProperties, "app_system_properties") { - Comment = " System properties defined by the application", - }; - module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); - - dsoCache = InitDSOCache (); - var app_cfg = new ApplicationConfig { - uses_mono_llvm = UsesMonoLLVM, - uses_mono_aot = UsesMonoAOT, - aot_lazy_load = AotEnableLazyLoad, - uses_assembly_preload = UsesAssemblyPreload, - broken_exception_transitions = BrokenExceptionTransitions, - instant_run_enabled = InstantRunEnabled, - jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, - have_runtime_config_blob = HaveRuntimeConfigBlob, - have_assemblies_blob = HaveAssemblyStore, - marshal_methods_enabled = MarshalMethodsEnabled, - bound_stream_io_exception_type = (byte)BoundExceptionType, - package_naming_policy = (uint)PackageNamingPolicy, - environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), - system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2), - 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 = (uint)dsoCache.Count, - android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, - jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, - jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, - jni_remapping_replacement_type_count = (uint)JniRemappingReplacementTypeCount, - jni_remapping_replacement_method_index_entry_count = (uint)JniRemappingReplacementMethodIndexEntryCount, - mono_components_mask = (uint)MonoComponents, - android_package_name = AndroidPackageName, - }; - application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); - module.AddGlobalVariable ("application_config", application_config); - - var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - Comment = " DSO cache entries", - BeforeWriteCallback = HashAndSortDSOCache, - }; - module.Add (dso_cache); - - 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 = null, - }; - - for (int i = 0; i < NumberOfAssembliesInApk; i++) { - xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData)); - } - } - - string bundledBuffersSize = xamarinAndroidBundledAssemblies == null ? "empty (unused when assembly stores are enabled)" : $"{BundledAssemblyNameWidth} bytes long"; - var bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "bundled_assemblies", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - Value = xamarinAndroidBundledAssemblies, - Comment = $" Bundled assembly name buffers, all {bundledBuffersSize}", - }; - module.Add (bundled_assemblies); - - AddAssemblyStores (module); - } - - void AddAssemblyStores (LlvmIrModule module) - { - ulong itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); - var assembly_store_bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "assembly_store_bundled_assemblies", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - ZeroInitializeArray = true, - ArrayItemCount = itemCount, - }; - module.Add (assembly_store_bundled_assemblies); - - itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); - var assembly_stores = new LlvmIrGlobalVariable (typeof(List>), "assembly_stores", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - ZeroInitializeArray = true, - ArrayItemCount = itemCount, - }; - module.Add (assembly_stores); - } - - void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) - { - var cache = variable.Value as List>; - if (cache == null) { - throw new InvalidOperationException ($"Internal error: DSO cache must no be empty"); - } - - bool is64Bit = target.Is64Bit; - foreach (StructureInstance instance in cache) { - if (instance.Obj == null) { - throw new InvalidOperationException ("Internal error: DSO cache must not contain null entries"); - } - - var entry = instance.Obj as DSOCacheEntry; - if (entry == null) { - throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); - } - - entry.hash = GetXxHash (entry.HashedName, is64Bit); - } - - cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); - } - - List> InitDSOCache () - { - var dsos = new List<(string name, string nameLabel, bool ignore)> (); - var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); - - foreach (ITaskItem item in NativeLibraries) { - string? name = item.GetMetadata ("ArchiveFileName"); - if (String.IsNullOrEmpty (name)) { - name = item.ItemSpec; - } - name = Path.GetFileName (name); - - if (nameCache.Contains (name)) { - continue; - } - - dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (log, item.ItemSpec))); - } - - var dsoCache = new List> (); - var nameMutations = new List (); - - for (int i = 0; i < dsos.Count; i++) { - string name = dsos[i].name; - nameMutations.Clear(); - AddNameMutations (name); - // All mutations point to the actual library name, but have hash of the mutated one - foreach (string entryName in nameMutations) { - 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 (dsoCacheEntryStructureInfo, entry)); - } - } - - return dsoCache; - - void AddNameMutations (string name) - { - nameMutations.Add (name); - if (name.EndsWith (".dll.so", StringComparison.OrdinalIgnoreCase)) { - nameMutations.Add (Path.GetFileNameWithoutExtension (Path.GetFileNameWithoutExtension (name))!); - } else { - nameMutations.Add (Path.GetFileNameWithoutExtension (name)!); - } - - const string aotPrefix = "libaot-"; - if (name.StartsWith (aotPrefix, StringComparison.OrdinalIgnoreCase)) { - AddNameMutations (name.Substring (aotPrefix.Length)); - } - - const string libPrefix = "lib"; - if (name.StartsWith (libPrefix, StringComparison.OrdinalIgnoreCase)) { - AddNameMutations (name.Substring (libPrefix.Length)); - } - } - } - - void MapStructures (LlvmIrModule module) - { - applicationConfigStructureInfo = module.MapStructure (); - module.MapStructure (); - assemblyStoreSingleAssemblyRuntimeDataStructureinfo = module.MapStructure (); - assemblyStoreRuntimeDataStructureInfo = module.MapStructure (); - xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure (); - dsoCacheEntryStructureInfo = module.MapStructure (); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 91d75674070..2dcbac7b0e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -6,7 +6,6 @@ using Java.Interop.Tools.TypeNameMappings; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; - using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks @@ -21,23 +20,19 @@ enum MonoComponent Tracing = 0x04, } - partial class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer + class ApplicationConfigNativeAssemblyGenerator : LlvmIrComposer { sealed class DSOCacheEntryContextDataProvider : NativeAssemblerStructContextDataProvider { public override string GetComment (object data, string fieldName) { - var dso_entry = data as DSOCacheEntry; - if (dso_entry == null) { - throw new InvalidOperationException ("Invalid data type, expected an instance of DSOCacheEntry"); - } - + var dso_entry = EnsureType (data); if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) { - return $"hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; + return $" hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}"; } if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {dso_entry.name}"; + return $" name: {dso_entry.name}"; } return String.Empty; @@ -57,6 +52,7 @@ sealed class DSOCacheEntry public ulong hash; public bool ignore; + [NativeAssembler (UsesDataProvider = true)] public string name; public IntPtr handle = IntPtr.Zero; } @@ -137,18 +133,18 @@ sealed class XamarinAndroidBundledAssembly // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x015E6972616D58; - SortedDictionary environmentVariables; - SortedDictionary systemProperties; + SortedDictionary ? environmentVariables; + SortedDictionary ? systemProperties; TaskLoggingHelper log; - StructureInstance? application_config; + StructureInstance? application_config; List>? dsoCache; List>? xamarinAndroidBundledAssemblies; - StructureInfo? applicationConfigStructureInfo; - StructureInfo? dsoCacheEntryStructureInfo; - StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; - StructureInfo assemblyStoreSingleAssemblyRuntimeDataStructureinfo; - StructureInfo assemblyStoreRuntimeDataStructureInfo; + StructureInfo? applicationConfigStructureInfo; + StructureInfo? dsoCacheEntryStructureInfo; + StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; + StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; + StructureInfo? assemblyStoreRuntimeDataStructureInfo; public bool UsesMonoAOT { get; set; } public bool UsesMonoLLVM { get; set; } @@ -188,8 +184,23 @@ public ApplicationConfigNativeAssemblyGenerator (IDictionary env this.log = log; } - public override void Init () + protected override void Construct (LlvmIrModule module) { + MapStructures (module); + + module.AddGlobalVariable ("format_tag", FORMAT_TAG, comment: $" 0x{FORMAT_TAG:x}"); + module.AddGlobalVariable ("mono_aot_mode_name", MonoAOTMode); + + var envVars = new LlvmIrGlobalVariable (environmentVariables, "app_environment_variables") { + Comment = " Application environment variables array, name:value", + }; + module.Add (envVars, stringGroupName: "env", stringGroupComment: " Application environment variables name:value pairs"); + + var sysProps = new LlvmIrGlobalVariable (systemProperties, "app_system_properties") { + Comment = " System properties defined by the application", + }; + module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); + dsoCache = InitDSOCache (); var app_cfg = new ApplicationConfig { uses_mono_llvm = UsesMonoLLVM, @@ -218,7 +229,14 @@ public override void Init () mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; - application_config = new StructureInstance (app_cfg); + application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); + module.AddGlobalVariable ("application_config", application_config); + + var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { + Comment = " DSO cache entries", + BeforeWriteCallback = HashAndSortDSOCache, + }; + module.Add (dso_cache); if (!HaveAssemblyStore) { xamarinAndroidBundledAssemblies = new List> (NumberOfAssembliesInApk); @@ -233,9 +251,59 @@ public override void Init () }; for (int i = 0; i < NumberOfAssembliesInApk; i++) { - xamarinAndroidBundledAssemblies.Add (new StructureInstance (emptyBundledAssemblyData)); + xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData)); } } + + string bundledBuffersSize = xamarinAndroidBundledAssemblies == null ? "empty (unused when assembly stores are enabled)" : $"{BundledAssemblyNameWidth} bytes long"; + var bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "bundled_assemblies", LlvmIrVariableOptions.GlobalWritable) { + Value = xamarinAndroidBundledAssemblies, + Comment = $" Bundled assembly name buffers, all {bundledBuffersSize}", + }; + module.Add (bundled_assemblies); + + AddAssemblyStores (module); + } + + void AddAssemblyStores (LlvmIrModule module) + { + ulong itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); + var assembly_store_bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "assembly_store_bundled_assemblies", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_store_bundled_assemblies); + + itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); + var assembly_stores = new LlvmIrGlobalVariable (typeof(List>), "assembly_stores", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = itemCount, + }; + module.Add (assembly_stores); + } + + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) + { + var cache = variable.Value as List>; + if (cache == null) { + throw new InvalidOperationException ($"Internal error: DSO cache must no be empty"); + } + + bool is64Bit = target.Is64Bit; + foreach (StructureInstance instance in cache) { + if (instance.Obj == null) { + throw new InvalidOperationException ("Internal error: DSO cache must not contain null entries"); + } + + var entry = instance.Obj as DSOCacheEntry; + if (entry == null) { + throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); + } + + entry.hash = GetXxHash (entry.HashedName, is64Bit); + } + + cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); } List> InitDSOCache () @@ -273,7 +341,7 @@ List> InitDSOCache () name = name, }; - dsoCache.Add (new StructureInstance (entry)); + dsoCache.Add (new StructureInstance (dsoCacheEntryStructureInfo, entry)); } } @@ -300,56 +368,14 @@ void AddNameMutations (string name) } } - protected override void MapStructures (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - applicationConfigStructureInfo = generator.MapStructure (); - generator.MapStructure (); - assemblyStoreSingleAssemblyRuntimeDataStructureinfo = generator.MapStructure (); - assemblyStoreRuntimeDataStructureInfo = generator.MapStructure (); - xamarinAndroidBundledAssemblyStructureInfo = generator.MapStructure (); - dsoCacheEntryStructureInfo = generator.MapStructure (); - } - - protected override void Write (LlvmIrGenerator generator) - { - generator.WriteVariable ("format_tag", FORMAT_TAG); - generator.WriteString ("mono_aot_mode_name", MonoAOTMode); - - generator.WriteNameValueArray ("app_environment_variables", environmentVariables); - generator.WriteNameValueArray ("app_system_properties", systemProperties); - - generator.WriteStructure (applicationConfigStructureInfo, application_config, LlvmIrVariableOptions.GlobalConstant, "application_config"); - - WriteDSOCache (generator); - WriteBundledAssemblies (generator); - WriteAssemblyStoreAssemblies (generator); - } - - void WriteAssemblyStoreAssemblies (LlvmIrGenerator generator) - { - ulong count = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0); - generator.WriteStructureArray (assemblyStoreSingleAssemblyRuntimeDataStructureinfo, count, "assembly_store_bundled_assemblies", initialComment: "Assembly store individual assembly data"); - - count = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0); - generator.WriteStructureArray (assemblyStoreRuntimeDataStructureInfo, count, "assembly_stores", initialComment: "Assembly store data"); - } - - void WriteBundledAssemblies (LlvmIrGenerator generator) - { - generator.WriteStructureArray (xamarinAndroidBundledAssemblyStructureInfo, xamarinAndroidBundledAssemblies, "bundled_assemblies", initialComment: $"Bundled assembly name buffers, all {BundledAssemblyNameWidth} bytes long"); - } - - void WriteDSOCache (LlvmIrGenerator generator) - { - bool is64Bit = generator.Is64Bit; - - // We need to hash here, because the hash is architecture-specific - foreach (StructureInstance entry in dsoCache) { - entry.Obj.hash = HashName (entry.Obj.HashedName, is64Bit); - } - dsoCache.Sort ((StructureInstance a, StructureInstance b) => a.Obj.hash.CompareTo (b.Obj.hash)); - - generator.WriteStructureArray (dsoCacheEntryStructureInfo, dsoCache, "dso_cache"); + applicationConfigStructureInfo = module.MapStructure (); + module.MapStructure (); + assemblyStoreSingleAssemblyRuntimeDataStructureinfo = module.MapStructure (); + assemblyStoreRuntimeDataStructureInfo = module.MapStructure (); + xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure (); + dsoCacheEntryStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs deleted file mode 100644 index 9cf12bcbf26..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.New.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Collections.Generic; - -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once the refactoring is done - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer - { - const string DescriptorsArraySymbolName = "compressed_assembly_descriptors"; - const string CompressedAssembliesSymbolName = "compressed_assemblies"; - - sealed class CompressedAssemblyDescriptorContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string? GetPointedToSymbolName (object data, string fieldName) - { - if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) { - return null; - } - - var descriptor = EnsureType (data); - return descriptor.BufferSymbolName; - } - } - - // 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 - { - [NativeAssembler (Ignore = true)] - public string BufferSymbolName; - - public uint uncompressed_file_size; - public bool loaded; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - 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; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = DescriptorsArraySymbolName)] - public CompressedAssemblyDescriptor descriptors; - }; - - IDictionary assemblies; - StructureInfo compressedAssemblyDescriptorStructureInfo; - StructureInfo compressedAssembliesStructureInfo; - - public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies) - { - this.assemblies = assemblies; - } - - void InitCompressedAssemblies (out List>? compressedAssemblyDescriptors, - out StructureInstance? compressedAssemblies, - out List? buffers) - { - if (assemblies == null || assemblies.Count == 0) { - compressedAssemblyDescriptors = null; - compressedAssemblies = null; - buffers = null; - return; - } - - ulong counter = 0; - compressedAssemblyDescriptors = new List> (assemblies.Count); - buffers = new List (assemblies.Count); - foreach (var kvp in assemblies) { - string assemblyName = kvp.Key; - CompressedAssemblyInfo info = kvp.Value; - - string bufferName = $"__compressedAssemblyData_{counter++}"; - var descriptor = new CompressedAssemblyDescriptor { - BufferSymbolName = bufferName, - uncompressed_file_size = info.FileSize, - loaded = false, - data = 0 - }; - - var bufferVar = new LlvmIrGlobalVariable (typeof(List), bufferName, LLVMIR.LlvmIrVariableOptions.LocalWritable) { - ZeroInitializeArray = true, - ArrayItemCount = descriptor.uncompressed_file_size, - }; - buffers.Add (bufferVar); - - compressedAssemblyDescriptors.Add (new StructureInstance (compressedAssemblyDescriptorStructureInfo, descriptor)); - } - - compressedAssemblies = new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies { count = (uint)assemblies.Count }); - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - - List>? compressedAssemblyDescriptors; - StructureInstance? compressedAssemblies; - List? buffers; - - InitCompressedAssemblies (out compressedAssemblyDescriptors, out compressedAssemblies, out buffers); - - if (compressedAssemblyDescriptors == null) { - module.AddGlobalVariable ( - typeof(StructureInstance), - CompressedAssembliesSymbolName, - new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies ()) { IsZeroInitialized = true }, - LLVMIR.LlvmIrVariableOptions.GlobalWritable - ); - return; - } - - module.AddGlobalVariable (CompressedAssembliesSymbolName, compressedAssemblies, LLVMIR.LlvmIrVariableOptions.GlobalWritable); - module.AddGlobalVariable (DescriptorsArraySymbolName, compressedAssemblyDescriptors, LLVMIR.LlvmIrVariableOptions.LocalWritable); - - module.Add (new LlvmIrGroupDelimiterVariable ()); - module.Add (buffers); - module.Add (new LlvmIrGroupDelimiterVariable ()); - } - - void MapStructures (LlvmIrModule module) - { - compressedAssemblyDescriptorStructureInfo = module.MapStructure (); - compressedAssembliesStructureInfo = module.MapStructure (); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs index 31e714e8625..e0b3740a453 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs @@ -12,14 +12,14 @@ partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer sealed class CompressedAssemblyDescriptorContextDataProvider : NativeAssemblerStructContextDataProvider { - public override ulong GetBufferSize (object data, string fieldName) + public override string? GetPointedToSymbolName (object data, string fieldName) { if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) { - return 0; + return null; } var descriptor = EnsureType (data); - return descriptor.uncompressed_file_size; + return descriptor.BufferSymbolName; } } @@ -28,10 +28,13 @@ public override ulong GetBufferSize (object data, string fieldName) [NativeAssemblerStructContextDataProvider (typeof (CompressedAssemblyDescriptorContextDataProvider))] sealed class CompressedAssemblyDescriptor { + [NativeAssembler (Ignore = true)] + public string BufferSymbolName; + public uint uncompressed_file_size; public bool loaded; - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToPreAllocatedBuffer = true)] + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] public byte data; }; @@ -60,60 +63,84 @@ sealed class CompressedAssemblies }; IDictionary assemblies; - StructureInfo compressedAssemblyDescriptorStructureInfo; - StructureInfo compressedAssembliesStructureInfo; - List>? compressedAssemblyDescriptors; - StructureInstance compressedAssemblies; + StructureInfo compressedAssemblyDescriptorStructureInfo; + StructureInfo compressedAssembliesStructureInfo; public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies) { this.assemblies = assemblies; } - public override void Init () + void InitCompressedAssemblies (out List>? compressedAssemblyDescriptors, + out StructureInstance? compressedAssemblies, + out List? buffers) { if (assemblies == null || assemblies.Count == 0) { + compressedAssemblyDescriptors = null; + compressedAssemblies = null; + buffers = null; return; } + ulong counter = 0; compressedAssemblyDescriptors = new List> (assemblies.Count); + buffers = new List (assemblies.Count); foreach (var kvp in assemblies) { string assemblyName = kvp.Key; CompressedAssemblyInfo info = kvp.Value; + string bufferName = $"__compressedAssemblyData_{counter++}"; var descriptor = new CompressedAssemblyDescriptor { + BufferSymbolName = bufferName, uncompressed_file_size = info.FileSize, loaded = false, data = 0 }; - compressedAssemblyDescriptors.Add (new StructureInstance (descriptor)); + var bufferVar = new LlvmIrGlobalVariable (typeof(List), bufferName, LlvmIrVariableOptions.LocalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = descriptor.uncompressed_file_size, + }; + buffers.Add (bufferVar); + + compressedAssemblyDescriptors.Add (new StructureInstance (compressedAssemblyDescriptorStructureInfo, descriptor)); } - compressedAssemblies = new StructureInstance (new CompressedAssemblies { count = (uint)assemblies.Count }); + compressedAssemblies = new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies { count = (uint)assemblies.Count }); } - protected override void MapStructures (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - compressedAssemblyDescriptorStructureInfo = generator.MapStructure (); - compressedAssembliesStructureInfo = generator.MapStructure (); - } + MapStructures (module); + + List>? compressedAssemblyDescriptors; + StructureInstance? compressedAssemblies; + List? buffers; + + InitCompressedAssemblies (out compressedAssemblyDescriptors, out compressedAssemblies, out buffers); - protected override void Write (LlvmIrGenerator generator) - { if (compressedAssemblyDescriptors == null) { - generator.WriteStructure (compressedAssembliesStructureInfo, null, CompressedAssembliesSymbolName); + module.AddGlobalVariable ( + typeof(StructureInstance), + CompressedAssembliesSymbolName, + new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalWritable + ); return; } - generator.WriteStructureArray ( - compressedAssemblyDescriptorStructureInfo, - compressedAssemblyDescriptors, - LlvmIrVariableOptions.LocalWritable, - DescriptorsArraySymbolName, - initialComment: "Compressed assembly data storage" - ); - generator.WriteStructure (compressedAssembliesStructureInfo, compressedAssemblies, CompressedAssembliesSymbolName); + module.AddGlobalVariable (CompressedAssembliesSymbolName, compressedAssemblies, LlvmIrVariableOptions.GlobalWritable); + module.AddGlobalVariable (DescriptorsArraySymbolName, compressedAssemblyDescriptors, LlvmIrVariableOptions.LocalWritable); + + module.Add (new LlvmIrGroupDelimiterVariable ()); + module.Add (buffers); + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void MapStructures (LlvmIrModule module) + { + compressedAssemblyDescriptorStructureInfo = module.MapStructure (); + compressedAssembliesStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs deleted file mode 100644 index f456c7a5c96..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.New.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once the refactoring is done - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - sealed class JniRemappingTypeReplacement - { - public string From { get; } - public string To { get; } - - public JniRemappingTypeReplacement (string from, string to) - { - From = from; - To = to; - } - } - - sealed class JniRemappingMethodReplacement - { - public string SourceType { get; } - public string SourceMethod { get; } - public string SourceMethodSignature { get; } - - public string TargetType { get; } - public string TargetMethod { get; } - - public bool TargetIsStatic { get; } - - public JniRemappingMethodReplacement (string sourceType, string sourceMethod, string sourceMethodSignature, - string targetType, string targetMethod, bool targetIsStatic) - { - SourceType = sourceType; - SourceMethod = sourceMethod; - SourceMethodSignature = sourceMethodSignature; - - TargetType = targetType; - TargetMethod = targetMethod; - TargetIsStatic = targetIsStatic; - } - } - - class JniRemappingAssemblyGenerator : LlvmIrComposer - { - const string TypeReplacementsVariableName = "jni_remapping_type_replacements"; - const string MethodReplacementIndexVariableName = "jni_remapping_method_replacement_index"; - - sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var entry = EnsureType(data); - - if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $" name: {entry.name.str}"; - } - - if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $" replacement: {entry.replacement}"; - } - - return String.Empty; - } - } - - sealed class JniRemappingIndexTypeEntryContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var entry = EnsureType (data); - - if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $" name: {entry.name.str}"; - } - - return String.Empty; - } - - public override string GetPointedToSymbolName (object data, string fieldName) - { - var entry = EnsureType (data); - - if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { - return entry.MethodsArraySymbolName; - } - - return base.GetPointedToSymbolName (data, fieldName); - } - - public override ulong GetBufferSize (object data, string fieldName) - { - var entry = EnsureType (data); - if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { - return (ulong)entry.TypeMethods.Count; - } - - return 0; - } - } - - sealed class JniRemappingIndexMethodEntryContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var entry = EnsureType (data); - - if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $" name: {entry.name.str}"; - } - - if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $" replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; - } - - if (String.Compare ("signature", fieldName, StringComparison.Ordinal) == 0) { - if (entry.signature.length == 0) { - return String.Empty; - } - - return $"signature: {entry.signature.str}"; - } - - return String.Empty; - } - } - - sealed class JniRemappingString - { - public uint length; - public string str; - }; - - sealed class JniRemappingReplacementMethod - { - public string target_type; - public string target_name; - public bool is_static; - }; - - [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexMethodEntryContextDataProvider))] - sealed class JniRemappingIndexMethodEntry - { - [NativeAssembler (UsesDataProvider = true)] - public JniRemappingString name; - - [NativeAssembler (UsesDataProvider = true)] - public JniRemappingString signature; - - [NativeAssembler (UsesDataProvider = true)] - public JniRemappingReplacementMethod replacement; - }; - - [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexTypeEntryContextDataProvider))] - sealed class JniRemappingIndexTypeEntry - { - [NativeAssembler (UsesDataProvider = true)] - public JniRemappingString name; - public uint method_count; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - public JniRemappingIndexMethodEntry methods; - - [NativeAssembler (Ignore = true)] - public string MethodsArraySymbolName; - - [NativeAssembler (Ignore = true)] - public List> TypeMethods; - }; - - [NativeAssemblerStructContextDataProvider (typeof(JniRemappingTypeReplacementEntryContextDataProvider))] - sealed class JniRemappingTypeReplacementEntry - { - [NativeAssembler (UsesDataProvider = true)] - public JniRemappingString name; - - [NativeAssembler (UsesDataProvider = true)] - public string replacement; - }; - - List typeReplacementsInput; - List methodReplacementsInput; - - StructureInfo jniRemappingStringStructureInfo; - StructureInfo jniRemappingReplacementMethodStructureInfo; - StructureInfo jniRemappingIndexMethodEntryStructureInfo; - StructureInfo jniRemappingIndexTypeEntryStructureInfo; - StructureInfo jniRemappingTypeReplacementEntryStructureInfo; - - public int ReplacementMethodIndexEntryCount { get; private set; } = 0; - - public JniRemappingAssemblyGenerator () - {} - - public JniRemappingAssemblyGenerator (List typeReplacements, List methodReplacements) - { - this.typeReplacementsInput = typeReplacements ?? throw new ArgumentNullException (nameof (typeReplacements)); - this.methodReplacementsInput = methodReplacements ?? throw new ArgumentNullException (nameof (methodReplacements)); - } - - (List>? typeReplacements, List>? methodIndexTypes) Init () - { - if (typeReplacementsInput == null) { - return (null, null); - } - - var typeReplacements = new List> (); - Console.WriteLine ($"Type replacement input count: {typeReplacementsInput.Count}"); - foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { - var entry = new JniRemappingTypeReplacementEntry { - name = MakeJniRemappingString (mtr.From), - replacement = mtr.To, - }; - - typeReplacements.Add (new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, entry)); - } - typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); - - var methodIndexTypes = new List> (); - var types = new Dictionary> (StringComparer.Ordinal); - - foreach (JniRemappingMethodReplacement mmr in methodReplacementsInput) { - if (!types.TryGetValue (mmr.SourceType, out StructureInstance typeEntry)) { - var entry = new JniRemappingIndexTypeEntry { - name = MakeJniRemappingString (mmr.SourceType), - MethodsArraySymbolName = MakeMethodsArrayName (mmr.SourceType), - TypeMethods = new List> (), - }; - - typeEntry = new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, entry); - methodIndexTypes.Add (typeEntry); - types.Add (mmr.SourceType, typeEntry); - } - - var method = new JniRemappingIndexMethodEntry { - name = MakeJniRemappingString (mmr.SourceMethod), - signature = MakeJniRemappingString (mmr.SourceMethodSignature), - replacement = new JniRemappingReplacementMethod { - target_type = mmr.TargetType, - target_name = mmr.TargetMethod, - is_static = mmr.TargetIsStatic, - }, - }; - - typeEntry.Instance.TypeMethods.Add (new StructureInstance (jniRemappingIndexMethodEntryStructureInfo, method)); - } - - foreach (var kvp in types) { - kvp.Value.Instance.method_count = (uint)kvp.Value.Instance.TypeMethods.Count; - kvp.Value.Instance.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); - } - - methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); - ReplacementMethodIndexEntryCount = methodIndexTypes.Count; - - return (typeReplacements, methodIndexTypes); - - string MakeMethodsArrayName (string typeName) - { - return $"mm_{typeName.Replace ('/', '_')}"; - } - - JniRemappingString MakeJniRemappingString (string str) - { - return new JniRemappingString { - length = GetLength (str), - str = str, - }; - } - - uint GetLength (string str) - { - if (String.IsNullOrEmpty (str)) { - return 0; - } - - return (uint)Encoding.UTF8.GetBytes (str).Length; - } - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - List>? typeReplacements; - List>? methodIndexTypes; - - (typeReplacements, methodIndexTypes) = Init (); - - if (typeReplacements == null) { - module.AddGlobalVariable ( - typeof(StructureInstance), - TypeReplacementsVariableName, - new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, new JniRemappingTypeReplacementEntry ()) { IsZeroInitialized = true }, - LLVMIR.LlvmIrVariableOptions.GlobalConstant - ); - - module.AddGlobalVariable ( - typeof(StructureInstance), - MethodReplacementIndexVariableName, - new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, new JniRemappingIndexTypeEntry ()) { IsZeroInitialized = true }, - LLVMIR.LlvmIrVariableOptions.GlobalConstant - ); - return; - } - - module.AddGlobalVariable (TypeReplacementsVariableName, typeReplacements, LLVMIR.LlvmIrVariableOptions.GlobalConstant); - - foreach (StructureInstance entry in methodIndexTypes) { - module.AddGlobalVariable (entry.Instance.MethodsArraySymbolName, entry.Instance.TypeMethods, LLVMIR.LlvmIrVariableOptions.LocalConstant); - } - - module.AddGlobalVariable (MethodReplacementIndexVariableName, methodIndexTypes, LLVMIR.LlvmIrVariableOptions.GlobalConstant); - } - - void MapStructures (LlvmIrModule module) - { - jniRemappingStringStructureInfo = module.MapStructure (); - jniRemappingReplacementMethodStructureInfo = module.MapStructure (); - jniRemappingIndexMethodEntryStructureInfo = module.MapStructure (); - jniRemappingIndexTypeEntryStructureInfo = module.MapStructure (); - jniRemappingTypeReplacementEntryStructureInfo = module.MapStructure (); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs index 1fefb8ced46..b7cbd4b49fd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs @@ -42,8 +42,11 @@ public JniRemappingMethodReplacement (string sourceType, string sourceMethod, st } } - partial class JniRemappingAssemblyGenerator : LlvmIrComposer + class JniRemappingAssemblyGenerator : LlvmIrComposer { + const string TypeReplacementsVariableName = "jni_remapping_type_replacements"; + const string MethodReplacementIndexVariableName = "jni_remapping_method_replacement_index"; + sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider { public override string GetComment (object data, string fieldName) @@ -51,11 +54,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType(data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $"replacement: {entry.replacement}"; + return $" replacement: {entry.replacement}"; } return String.Empty; @@ -69,7 +72,7 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } return String.Empty; @@ -104,11 +107,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { - return $"name: {entry.name.str}"; + return $" name: {entry.name.str}"; } if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { - return $"replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; + return $" replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; } if (String.Compare ("signature", fieldName, StringComparison.Ordinal) == 0) { @@ -179,14 +182,11 @@ sealed class JniRemappingTypeReplacementEntry List typeReplacementsInput; List methodReplacementsInput; - StructureInfo jniRemappingStringStructureInfo; - StructureInfo jniRemappingReplacementMethodStructureInfo; - StructureInfo jniRemappingIndexMethodEntryStructureInfo; - StructureInfo jniRemappingIndexTypeEntryStructureInfo; - StructureInfo jniRemappingTypeReplacementEntryStructureInfo; - - List> typeReplacements; - List> methodIndexTypes; + StructureInfo jniRemappingStringStructureInfo; + StructureInfo jniRemappingReplacementMethodStructureInfo; + StructureInfo jniRemappingIndexMethodEntryStructureInfo; + StructureInfo jniRemappingIndexTypeEntryStructureInfo; + StructureInfo jniRemappingTypeReplacementEntryStructureInfo; public int ReplacementMethodIndexEntryCount { get; private set; } = 0; @@ -199,24 +199,25 @@ public JniRemappingAssemblyGenerator (List typeRepl this.methodReplacementsInput = methodReplacements ?? throw new ArgumentNullException (nameof (methodReplacements)); } - public override void Init () + (List>? typeReplacements, List>? methodIndexTypes) Init () { if (typeReplacementsInput == null) { - return; + return (null, null); } - typeReplacements = new List> (); + var typeReplacements = new List> (); + Console.WriteLine ($"Type replacement input count: {typeReplacementsInput.Count}"); foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { var entry = new JniRemappingTypeReplacementEntry { name = MakeJniRemappingString (mtr.From), replacement = mtr.To, }; - typeReplacements.Add (new StructureInstance (entry)); + typeReplacements.Add (new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, entry)); } - typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); - methodIndexTypes = new List> (); + var methodIndexTypes = new List> (); var types = new Dictionary> (StringComparer.Ordinal); foreach (JniRemappingMethodReplacement mmr in methodReplacementsInput) { @@ -227,7 +228,7 @@ public override void Init () TypeMethods = new List> (), }; - typeEntry = new StructureInstance (entry); + typeEntry = new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, entry); methodIndexTypes.Add (typeEntry); types.Add (mmr.SourceType, typeEntry); } @@ -242,17 +243,19 @@ public override void Init () }, }; - typeEntry.Obj.TypeMethods.Add (new StructureInstance (method)); + typeEntry.Instance.TypeMethods.Add (new StructureInstance (jniRemappingIndexMethodEntryStructureInfo, method)); } foreach (var kvp in types) { - kvp.Value.Obj.method_count = (uint)kvp.Value.Obj.TypeMethods.Count; - kvp.Value.Obj.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + kvp.Value.Instance.method_count = (uint)kvp.Value.Instance.TypeMethods.Count; + kvp.Value.Instance.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); } - methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Instance.name.str.CompareTo (r.Instance.name.str)); ReplacementMethodIndexEntryCount = methodIndexTypes.Count; + return (typeReplacements, methodIndexTypes); + string MakeMethodsArrayName (string typeName) { return $"mm_{typeName.Replace ('/', '_')}"; @@ -265,101 +268,58 @@ JniRemappingString MakeJniRemappingString (string str) str = str, }; } - } - - uint GetLength (string str) - { - if (String.IsNullOrEmpty (str)) { - return 0; - } - - return (uint)Encoding.UTF8.GetBytes (str).Length; - } - - protected override void MapStructures (LlvmIrGenerator generator) - { - jniRemappingStringStructureInfo = generator.MapStructure (); - jniRemappingReplacementMethodStructureInfo = generator.MapStructure (); - jniRemappingIndexMethodEntryStructureInfo = generator.MapStructure (); - jniRemappingIndexTypeEntryStructureInfo = generator.MapStructure (); - jniRemappingTypeReplacementEntryStructureInfo = generator.MapStructure (); - } - - void WriteNestedStructure (LlvmIrGenerator generator, LlvmIrGenerator.StructureBodyWriterOptions bodyWriterOptions, Type structureType, object fieldInstance) - { - if (fieldInstance == null) { - return; - } - if (structureType == typeof (JniRemappingString)) { - generator.WriteNestedStructure (jniRemappingStringStructureInfo, new StructureInstance ((JniRemappingString)fieldInstance), bodyWriterOptions); - return; - } - - if (structureType == typeof (JniRemappingReplacementMethod)) { - generator.WriteNestedStructure (jniRemappingReplacementMethodStructureInfo, new StructureInstance ((JniRemappingReplacementMethod)fieldInstance), bodyWriterOptions); - return; - } - - if (structureType == typeof (JniRemappingIndexTypeEntry)) { - generator.WriteNestedStructure (jniRemappingIndexTypeEntryStructureInfo, new StructureInstance ((JniRemappingIndexTypeEntry)fieldInstance), bodyWriterOptions); - } + uint GetLength (string str) + { + if (String.IsNullOrEmpty (str)) { + return 0; + } - if (structureType == typeof (JniRemappingIndexMethodEntry)) { - generator.WriteNestedStructure (jniRemappingIndexMethodEntryStructureInfo, new StructureInstance ((JniRemappingIndexMethodEntry)fieldInstance), bodyWriterOptions); + return (uint)Encoding.UTF8.GetBytes (str).Length; } - - throw new InvalidOperationException ($"Unsupported nested structure type {structureType}"); } - protected override void Write (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - generator.WriteEOL (); - generator.WriteEOL ("JNI remapping data"); + MapStructures (module); + List>? typeReplacements; + List>? methodIndexTypes; + + (typeReplacements, methodIndexTypes) = Init (); if (typeReplacements == null) { - generator.WriteStructureArray ( - jniRemappingTypeReplacementEntryStructureInfo, - 0, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_type_replacements" + module.AddGlobalVariable ( + typeof(StructureInstance), + TypeReplacementsVariableName, + new StructureInstance (jniRemappingTypeReplacementEntryStructureInfo, new JniRemappingTypeReplacementEntry ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalConstant ); - generator.WriteStructureArray ( - jniRemappingIndexTypeEntryStructureInfo, - 0, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_method_replacement_index" + module.AddGlobalVariable ( + typeof(StructureInstance), + MethodReplacementIndexVariableName, + new StructureInstance (jniRemappingIndexTypeEntryStructureInfo, new JniRemappingIndexTypeEntry ()) { IsZeroInitialized = true }, + LlvmIrVariableOptions.GlobalConstant ); - return; } - generator.WriteStructureArray ( - jniRemappingTypeReplacementEntryStructureInfo, - typeReplacements, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_type_replacements", - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (TypeReplacementsVariableName, typeReplacements, LlvmIrVariableOptions.GlobalConstant); foreach (StructureInstance entry in methodIndexTypes) { - generator.WriteStructureArray ( - jniRemappingIndexMethodEntryStructureInfo, - entry.Obj.TypeMethods, - LlvmIrVariableOptions.LocalConstant, - entry.Obj.MethodsArraySymbolName, - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (entry.Instance.MethodsArraySymbolName, entry.Instance.TypeMethods, LlvmIrVariableOptions.LocalConstant); } - generator.WriteStructureArray ( - jniRemappingIndexTypeEntryStructureInfo, - methodIndexTypes, - LlvmIrVariableOptions.GlobalConstant, - "jni_remapping_method_replacement_index", - nestedStructureWriter: WriteNestedStructure - ); + module.AddGlobalVariable (MethodReplacementIndexVariableName, methodIndexTypes, LlvmIrVariableOptions.GlobalConstant); + } + + void MapStructures (LlvmIrModule module) + { + jniRemappingStringStructureInfo = module.MapStructure (); + jniRemappingReplacementMethodStructureInfo = module.MapStructure (); + jniRemappingIndexMethodEntryStructureInfo = module.MapStructure (); + jniRemappingIndexTypeEntryStructureInfo = module.MapStructure (); + jniRemappingTypeReplacementEntryStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs deleted file mode 100644 index 0f21891c92e..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; -using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; -using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; -using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class Arm32LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"; - public override int PointerSize => 4; - protected override string Triple => "armv7-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { - new FramePointerFunctionAttribute ("all"), - new TargetCpuFunctionAttribute ("generic"), - new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+thumb-mode,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"), - }; - - public Arm32LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs deleted file mode 100644 index 1dfed4f0a1b..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; -using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; -using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; -using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class Arm64LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"; - public override int PointerSize => 8; - protected override string Triple => "aarch64-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { - new FramePointerFunctionAttribute ("non-leaf"), - new TargetCpuFunctionAttribute ("generic"), - new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics"), - }; - - public Arm64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index f5d4b46677f..a64f6194655 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -2,7 +2,7 @@ using System.Text; using System.Globalization; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR { // Not all attributes are currently used throughout the code, but we define them call for potential future use. // Documentation can be found here: https://llvm.org/docs/LangRef.html#function-attributes diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs index 9fc85ab6277..137eb886a6d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Reflection; -namespace Xamarin.Android.Tasks.LLVM.IR; +namespace Xamarin.Android.Tasks.LLVMIR; partial class LlvmIrModule { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs deleted file mode 100644 index 07f45698b9d..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.New.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.IO; -using System.IO.Hashing; -using System.Text; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - abstract class LlvmIrComposer - { - bool constructed; - - protected abstract void Construct (LlvmIrModule module); - - public LlvmIrModule Construct () - { - var module = new LlvmIrModule (); - Construct (module); - module.AfterConstruction (); - constructed = true; - - return module; - } - - public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter output, string fileName) - { - if (!constructed) { - throw new InvalidOperationException ($"Internal error: module not constructed yet. Was Constrict () called?"); - } - - LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName); - generator.Generate (output, module); - output.Flush (); - } - - public static ulong GetXxHash (string str, bool is64Bit) - { - byte[] stringBytes = Encoding.UTF8.GetBytes (str); - if (is64Bit) { - return XxHash64.HashToUInt64 (stringBytes); - } - - return (ulong)XxHash32.HashToUInt32 (stringBytes); - } - - protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) - { - var gv = variable as LlvmIrGlobalVariable; - if (gv == null) { - throw new InvalidOperationException ("Internal error: global variable expected"); - } - - return gv; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 790b8d30668..8db94269f32 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -7,75 +7,51 @@ namespace Xamarin.Android.Tasks.LLVMIR { - /// - /// Base class for all classes which "compose" LLVM IR assembly. - /// abstract class LlvmIrComposer { - protected AndroidTargetArch TargetArch { get; } + bool constructed; - protected LlvmIrComposer () - {} + protected abstract void Construct (LlvmIrModule module); - public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) + public LlvmIrModule Construct () { - LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); + var module = new LlvmIrModule (); + Construct (module); + module.AfterConstruction (); + constructed = true; - InitGenerator (generator); - MapStructures (generator); - generator.WriteFileTop (); - generator.WriteStructureDeclarations (); - Write (generator); - generator.WriteFunctionDeclarations (); - generator.WriteFileEnd (); + return module; } - protected static string GetAbiName (AndroidTargetArch arch) + public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter output, string fileName) { - return arch switch { - AndroidTargetArch.Arm => "armeabi-v7a", - AndroidTargetArch.Arm64 => "arm64-v8a", - AndroidTargetArch.X86 => "x86", - AndroidTargetArch.X86_64 => "x86_64", - _ => throw new InvalidOperationException ($"Unsupported Android architecture: {arch}"), - }; + if (!constructed) { + throw new InvalidOperationException ($"Internal error: module not constructed yet. Was Constrict () called?"); + } + + LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName); + generator.Generate (output, module); + output.Flush (); } - protected ulong HashName (string name, bool is64Bit) + public static ulong GetXxHash (string str, bool is64Bit) { - byte[] nameBytes = Encoding.UTF8.GetBytes (name); + byte[] stringBytes = Encoding.UTF8.GetBytes (str); if (is64Bit) { - return XxHash64.HashToUInt64 (nameBytes); + return XxHash64.HashToUInt64 (stringBytes); } - return (ulong)XxHash32.HashToUInt32 (nameBytes); + return (ulong)XxHash32.HashToUInt32 (stringBytes); } - protected virtual void InitGenerator (LlvmIrGenerator generator) - {} - - /// - /// Initialize the composer. It needs to allocate and populate all the structures that - /// are used by the composer, before they can be mapped by the generator. The code here - /// should initialize only the architecture-independent fields of structures etc to - /// write. The composer is reused between architectures, and only the Write method is - /// aware of which architecture is targetted. - /// - public abstract void Init (); - - /// - /// Maps all the structures used to internal LLVM IR representation. Every structure MUST - /// be mapped. - /// - protected abstract void MapStructures (LlvmIrGenerator generator); + protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) + { + var gv = variable as LlvmIrGlobalVariable; + if (gv == null) { + throw new InvalidOperationException ("Internal error: global variable expected"); + } - /// - /// Generate LLVM IR code from data structures initialized by . This is - /// called once per ABI, with the appropriate for the target - /// ABI. If any ABI-specific initialization must be performed on the data structures to - /// be written, it has to be done here (applies to e.g. constructs that require to know the - /// native pointer size). - /// - protected abstract void Write (LlvmIrGenerator generator); + return gv; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs index f62d23332e4..6ab6033e36a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs @@ -3,352 +3,351 @@ using System.Globalization; using System.Text; -namespace Xamarin.Android.Tasks.LLVM.IR -{ - abstract class LlvmIrDataLayoutField - { - public const char Separator = ':'; +namespace Xamarin.Android.Tasks.LLVMIR; - public string Id { get; } +abstract class LlvmIrDataLayoutField +{ + public const char Separator = ':'; - protected LlvmIrDataLayoutField (string id) - { - if (String.IsNullOrEmpty (id)) { - throw new ArgumentException (nameof (id), "must not be null or empty"); - } + public string Id { get; } - Id = id; + protected LlvmIrDataLayoutField (string id) + { + if (String.IsNullOrEmpty (id)) { + throw new ArgumentException (nameof (id), "must not be null or empty"); } - public virtual void Render (StringBuilder sb) - { - sb.Append (Id); - } + Id = id; + } - public static string ConvertToString (uint v) - { - return v.ToString (CultureInfo.InvariantCulture); - } + public virtual void Render (StringBuilder sb) + { + sb.Append (Id); + } - protected void Append (StringBuilder sb, uint v, bool needSeparator = true) - { - if (needSeparator) { - sb.Append (Separator); - } + public static string ConvertToString (uint v) + { + return v.ToString (CultureInfo.InvariantCulture); + } - sb.Append (ConvertToString (v)); + protected void Append (StringBuilder sb, uint v, bool needSeparator = true) + { + if (needSeparator) { + sb.Append (Separator); } - protected void Append (StringBuilder sb, uint? v, bool needSeparator = true) - { - if (!v.HasValue) { - return; - } - - Append (sb, v.Value, needSeparator); - } + sb.Append (ConvertToString (v)); } - class LlvmIrDataLayoutPointerSize : LlvmIrDataLayoutField + protected void Append (StringBuilder sb, uint? v, bool needSeparator = true) { - public uint? AddressSpace { get; set; } - public uint Abi { get; } - public uint Size { get; } - public uint? Pref { get; set; } - public uint? Idx { get; set; } - - public LlvmIrDataLayoutPointerSize (uint size, uint abi) - : base ("p") - { - Size = size; - Abi = abi; + if (!v.HasValue) { + return; } - public override void Render (StringBuilder sb) - { - base.Render (sb); + Append (sb, v.Value, needSeparator); + } +} - if (AddressSpace.HasValue && AddressSpace.Value > 0) { - Append (sb, AddressSpace.Value, needSeparator: false); - } - Append (sb, Size); - Append (sb, Abi); - Append (sb, Pref); - Append (sb, Idx); - } +class LlvmIrDataLayoutPointerSize : LlvmIrDataLayoutField +{ + public uint? AddressSpace { get; set; } + public uint Abi { get; } + public uint Size { get; } + public uint? Pref { get; set; } + public uint? Idx { get; set; } + + public LlvmIrDataLayoutPointerSize (uint size, uint abi) + : base ("p") + { + Size = size; + Abi = abi; } - abstract class LlvmIrDataLayoutTypeAlignment : LlvmIrDataLayoutField + public override void Render (StringBuilder sb) { - public uint Size { get; } - public uint Abi { get; } - public uint? Pref { get; set; } + base.Render (sb); - protected LlvmIrDataLayoutTypeAlignment (string id, uint size, uint abi) - : base (id) - { - Size = size; - Abi = abi; + if (AddressSpace.HasValue && AddressSpace.Value > 0) { + Append (sb, AddressSpace.Value, needSeparator: false); } + Append (sb, Size); + Append (sb, Abi); + Append (sb, Pref); + Append (sb, Idx); + } +} - public override void Render (StringBuilder sb) - { - base.Render (sb); +abstract class LlvmIrDataLayoutTypeAlignment : LlvmIrDataLayoutField +{ + public uint Size { get; } + public uint Abi { get; } + public uint? Pref { get; set; } - Append (sb, Size, needSeparator: false); - Append (sb, Abi); - Append (sb, Pref); - } + protected LlvmIrDataLayoutTypeAlignment (string id, uint size, uint abi) + : base (id) + { + Size = size; + Abi = abi; } - class LlvmIrDataLayoutIntegerAlignment : LlvmIrDataLayoutTypeAlignment + public override void Render (StringBuilder sb) { - public LlvmIrDataLayoutIntegerAlignment (uint size, uint abi, uint? pref = null) - : base ("i", size, abi) - { - if (size == 8 && abi != 8) { - throw new ArgumentOutOfRangeException (nameof (abi), "Must equal 8 for i8"); - } + base.Render (sb); - Pref = pref; - } + Append (sb, Size, needSeparator: false); + Append (sb, Abi); + Append (sb, Pref); } +} - class LlvmIrDataLayoutVectorAlignment : LlvmIrDataLayoutTypeAlignment +class LlvmIrDataLayoutIntegerAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutIntegerAlignment (uint size, uint abi, uint? pref = null) + : base ("i", size, abi) { - public LlvmIrDataLayoutVectorAlignment (uint size, uint abi, uint? pref = null) - : base ("v", size, abi) - { - Pref = pref; + if (size == 8 && abi != 8) { + throw new ArgumentOutOfRangeException (nameof (abi), "Must equal 8 for i8"); } + + Pref = pref; } +} - class LlvmIrDataLayoutFloatAlignment : LlvmIrDataLayoutTypeAlignment +class LlvmIrDataLayoutVectorAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutVectorAlignment (uint size, uint abi, uint? pref = null) + : base ("v", size, abi) { - public LlvmIrDataLayoutFloatAlignment (uint size, uint abi, uint? pref = null) - : base ("f", size, abi) - { - Pref = pref; - } + Pref = pref; } +} - class LlvmIrDataLayoutAggregateObjectAlignment : LlvmIrDataLayoutField +class LlvmIrDataLayoutFloatAlignment : LlvmIrDataLayoutTypeAlignment +{ + public LlvmIrDataLayoutFloatAlignment (uint size, uint abi, uint? pref = null) + : base ("f", size, abi) { - public uint Abi { get; } - public uint? Pref { get; set; } - - public LlvmIrDataLayoutAggregateObjectAlignment (uint abi, uint? pref = null) - : base ("a") - { - Abi = abi; - Pref = pref; - } - - public override void Render (StringBuilder sb) - { - base.Render (sb); - - Append (sb, Abi); - Append (sb, Pref); - } + Pref = pref; } +} + +class LlvmIrDataLayoutAggregateObjectAlignment : LlvmIrDataLayoutField +{ + public uint Abi { get; } + public uint? Pref { get; set; } - enum LlvmIrDataLayoutFunctionPointerAlignmentType + public LlvmIrDataLayoutAggregateObjectAlignment (uint abi, uint? pref = null) + : base ("a") { - Independent, - Multiple, + Abi = abi; + Pref = pref; } - class LlvmIrDataLayoutFunctionPointerAlignment : LlvmIrDataLayoutField + public override void Render (StringBuilder sb) { - public uint Abi { get; } - public LlvmIrDataLayoutFunctionPointerAlignmentType Type { get; } + base.Render (sb); - public LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType type, uint abi) - : base ("F") - { - Type = type; - Abi = abi; - } - - public override void Render (StringBuilder sb) - { - base.Render (sb); - - char type = Type switch { - LlvmIrDataLayoutFunctionPointerAlignmentType.Independent => 'i', - LlvmIrDataLayoutFunctionPointerAlignmentType.Multiple => 'n', - _ => throw new InvalidOperationException ($"Unsupported function pointer alignment type '{Type}'") - }; - sb.Append (type); - Append (sb, Abi, needSeparator: false); - } + Append (sb, Abi); + Append (sb, Pref); } +} + +enum LlvmIrDataLayoutFunctionPointerAlignmentType +{ + Independent, + Multiple, +} - enum LlvmIrDataLayoutManglingOption +class LlvmIrDataLayoutFunctionPointerAlignment : LlvmIrDataLayoutField +{ + public uint Abi { get; } + public LlvmIrDataLayoutFunctionPointerAlignmentType Type { get; } + + public LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType type, uint abi) + : base ("F") { - ELF, - GOFF, - MIPS, - MachO, - WindowsX86COFF, - WindowsCOFF, - XCOFF + Type = type; + Abi = abi; } - class LlvmIrDataLayoutMangling : LlvmIrDataLayoutField + public override void Render (StringBuilder sb) { - public LlvmIrDataLayoutManglingOption Option { get; } - - public LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption option) - : base ("m") - { - Option = option; - } + base.Render (sb); + + char type = Type switch { + LlvmIrDataLayoutFunctionPointerAlignmentType.Independent => 'i', + LlvmIrDataLayoutFunctionPointerAlignmentType.Multiple => 'n', + _ => throw new InvalidOperationException ($"Unsupported function pointer alignment type '{Type}'") + }; + sb.Append (type); + Append (sb, Abi, needSeparator: false); + } +} - public override void Render (StringBuilder sb) - { - base.Render (sb); +enum LlvmIrDataLayoutManglingOption +{ + ELF, + GOFF, + MIPS, + MachO, + WindowsX86COFF, + WindowsCOFF, + XCOFF +} - sb.Append (Separator); +class LlvmIrDataLayoutMangling : LlvmIrDataLayoutField +{ + public LlvmIrDataLayoutManglingOption Option { get; } - char opt = Option switch { - LlvmIrDataLayoutManglingOption.ELF => 'e', - LlvmIrDataLayoutManglingOption.GOFF => 'l', - LlvmIrDataLayoutManglingOption.MIPS => 'm', - LlvmIrDataLayoutManglingOption.MachO => 'o', - LlvmIrDataLayoutManglingOption.WindowsX86COFF => 'x', - LlvmIrDataLayoutManglingOption.WindowsCOFF => 'w', - LlvmIrDataLayoutManglingOption.XCOFF => 'a', - _ => throw new InvalidOperationException ($"Unsupported mangling option '{Option}'") - }; - - sb.Append (opt); - } + public LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption option) + : base ("m") + { + Option = option; } - // See: https://llvm.org/docs/LangRef.html#data-layout - class LlvmIrDataLayout + public override void Render (StringBuilder sb) { - bool bigEndian; - bool littleEndian = true; - - public bool BigEndian { - get => bigEndian; - set { - bigEndian = value; - littleEndian = !bigEndian; - } - } + base.Render (sb); + + sb.Append (Separator); + + char opt = Option switch { + LlvmIrDataLayoutManglingOption.ELF => 'e', + LlvmIrDataLayoutManglingOption.GOFF => 'l', + LlvmIrDataLayoutManglingOption.MIPS => 'm', + LlvmIrDataLayoutManglingOption.MachO => 'o', + LlvmIrDataLayoutManglingOption.WindowsX86COFF => 'x', + LlvmIrDataLayoutManglingOption.WindowsCOFF => 'w', + LlvmIrDataLayoutManglingOption.XCOFF => 'a', + _ => throw new InvalidOperationException ($"Unsupported mangling option '{Option}'") + }; + + sb.Append (opt); + } +} - public bool LittleEndian { - get => littleEndian; - set { - littleEndian = value; - bigEndian = !littleEndian; - } +// See: https://llvm.org/docs/LangRef.html#data-layout +class LlvmIrDataLayout +{ + bool bigEndian; + bool littleEndian = true; + + public bool BigEndian { + get => bigEndian; + set { + bigEndian = value; + littleEndian = !bigEndian; } + } - public uint? AllocaAddressSpaceId { get; set; } - public uint? GlobalsAddressSpaceId { get; set; } - public LlvmIrDataLayoutMangling? Mangling { get; set; } - public uint? ProgramAddressSpaceId { get; set; } - public uint? StackAlignment { get; set; } + public bool LittleEndian { + get => littleEndian; + set { + littleEndian = value; + bigEndian = !littleEndian; + } + } - public LlvmIrDataLayoutAggregateObjectAlignment? AggregateObjectAlignment { get; set; } - public List? FloatAlignment { get; set; } - public LlvmIrDataLayoutFunctionPointerAlignment? FunctionPointerAlignment { get; set; } - public List? IntegerAlignment { get; set; } - public List? VectorAlignment { get; set; } - public List? PointerSize { get; set; } + public uint? AllocaAddressSpaceId { get; set; } + public uint? GlobalsAddressSpaceId { get; set; } + public LlvmIrDataLayoutMangling? Mangling { get; set; } + public uint? ProgramAddressSpaceId { get; set; } + public uint? StackAlignment { get; set; } - public List? NativeIntegerWidths { get; set; } - public List? NonIntegralPointerTypeAddressSpaces { get; set; } + public LlvmIrDataLayoutAggregateObjectAlignment? AggregateObjectAlignment { get; set; } + public List? FloatAlignment { get; set; } + public LlvmIrDataLayoutFunctionPointerAlignment? FunctionPointerAlignment { get; set; } + public List? IntegerAlignment { get; set; } + public List? VectorAlignment { get; set; } + public List? PointerSize { get; set; } - public string Render () - { - var sb = new StringBuilder (); + public List? NativeIntegerWidths { get; set; } + public List? NonIntegralPointerTypeAddressSpaces { get; set; } - sb.Append ("target datalayout = \""); + public string Render () + { + var sb = new StringBuilder (); - sb.Append (LittleEndian ? 'e' : 'E'); + sb.Append ("target datalayout = \""); - if (Mangling != null) { - sb.Append ('-'); - Mangling.Render (sb); - } + sb.Append (LittleEndian ? 'e' : 'E'); - AppendFieldList (PointerSize); + if (Mangling != null) { + sb.Append ('-'); + Mangling.Render (sb); + } - if (FunctionPointerAlignment != null) { - sb.Append ('-'); - FunctionPointerAlignment.Render (sb); - } + AppendFieldList (PointerSize); - AppendFieldList (IntegerAlignment); - AppendFieldList (FloatAlignment); - AppendFieldList (VectorAlignment); + if (FunctionPointerAlignment != null) { + sb.Append ('-'); + FunctionPointerAlignment.Render (sb); + } - Append ('P', ProgramAddressSpaceId); - Append ('G', GlobalsAddressSpaceId); - Append ('A', AllocaAddressSpaceId); + AppendFieldList (IntegerAlignment); + AppendFieldList (FloatAlignment); + AppendFieldList (VectorAlignment); - if (AggregateObjectAlignment != null) { - sb.Append ('-'); - AggregateObjectAlignment.Render (sb); - } + Append ('P', ProgramAddressSpaceId); + Append ('G', GlobalsAddressSpaceId); + Append ('A', AllocaAddressSpaceId); - AppendList ("n", NativeIntegerWidths); - AppendList ("ni", NonIntegralPointerTypeAddressSpaces); - Append ('S', StackAlignment); + if (AggregateObjectAlignment != null) { + sb.Append ('-'); + AggregateObjectAlignment.Render (sb); + } - sb.Append ('"'); + AppendList ("n", NativeIntegerWidths); + AppendList ("ni", NonIntegralPointerTypeAddressSpaces); + Append ('S', StackAlignment); - return sb.ToString (); + sb.Append ('"'); - void AppendFieldList (List? list) where T: LlvmIrDataLayoutField - { - if (list == null || list.Count == 0) { - return; - } + return sb.ToString (); - foreach (LlvmIrDataLayoutField field in list) { - sb.Append ('-'); - field.Render (sb); - } + void AppendFieldList (List? list) where T: LlvmIrDataLayoutField + { + if (list == null || list.Count == 0) { + return; } - void AppendList (string id, List? list) - { - if (list == null || list.Count == 0) { - return; - } - + foreach (LlvmIrDataLayoutField field in list) { sb.Append ('-'); - sb.Append (id); - - bool first = true; - foreach (uint v in list) { - if (first) { - first = false; - } else { - sb.Append (LlvmIrDataLayoutField.Separator); - } + field.Render (sb); + } + } - sb.Append (LlvmIrDataLayoutField.ConvertToString (v)); - } + void AppendList (string id, List? list) + { + if (list == null || list.Count == 0) { + return; } - void Append (char id, uint? v) - { - if (!v.HasValue) { - return; + sb.Append ('-'); + sb.Append (id); + + bool first = true; + foreach (uint v in list) { + if (first) { + first = false; + } else { + sb.Append (LlvmIrDataLayoutField.Separator); } - sb.Append ('-'); - sb.Append (id); - sb.Append (LlvmIrDataLayoutField.ConvertToString (v.Value)); + sb.Append (LlvmIrDataLayoutField.ConvertToString (v)); } } + + void Append (char id, uint? v) + { + if (!v.HasValue) { + return; + } + + sb.Append ('-'); + sb.Append (id); + sb.Append (LlvmIrDataLayoutField.ConvertToString (v.Value)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 87a705b3836..79f913c1496 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -4,14 +4,8 @@ using System.Linq; using System.Text; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR { - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; - using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; - using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - interface ILlvmIrSavedFunctionParameterState {} class LlvmIrFunctionParameter : LlvmIrLocalVariable @@ -507,295 +501,3 @@ public bool Equals (LlvmIrFunction other) } } } - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class LlvmIrFunctionLocalVariable : LlvmIrVariable - { - public LlvmIrFunctionLocalVariable (Type type, string? name = null, bool isNativePointer = false) - : base (type, name, signature: null, isNativePointer: isNativePointer) - {} - - public LlvmIrFunctionLocalVariable (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false) - : base (typeof(LlvmNativeFunctionSignature), name, nativeFunction, isNativePointer: isNativePointer) - { - if (nativeFunction == null) { - throw new ArgumentNullException(nameof (nativeFunction)); - } - } - - public LlvmIrFunctionLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) - : base (variable, name, variable.IsNativePointer || isNativePointer) - {} - } - - class LlvmIrFunctionParameter : LlvmIrFunctionLocalVariable - { - public bool Immarg { get; set; } - public bool IsCplusPlusReference { get; } - public bool IsVarargs { get; set; } - public bool NoCapture { get; set; } - public bool NoUndef { get; set; } - - public LlvmIrFunctionParameter (LlvmIrFunctionParameter other, string? name = null) - : this (other.Type, name, other.IsNativePointer, other.IsCplusPlusReference) - { - CopyProperties (other); - } - - // This is most decidedly weird... poor API design ;) - public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, LlvmIrFunctionParameter otherParam, string? name = null) - : this (nativeFunction, name, otherParam.IsNativePointer, otherParam.IsCplusPlusReference) - { - CopyProperties (otherParam); - } - - public LlvmIrFunctionParameter (Type type, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) - : base (type, name, isNativePointer) - { - IsCplusPlusReference = isCplusPlusReference; - } - - public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) - : base (nativeFunction, name, isNativePointer) - { - IsCplusPlusReference = isCplusPlusReference; - } - - void CopyProperties (LlvmIrFunctionParameter other) - { - Immarg = other.Immarg; - IsVarargs = other.IsVarargs; - NoCapture = other.NoCapture; - NoUndef = other.NoUndef; - } - } - - class LlvmIrFunctionArgument - { - public object Value { get; } - public Type Type { get; } - public bool IsNativePointer { get; } - public bool NonNull { get; set; } - public bool NoUndef { get; set; } - - public LlvmIrFunctionArgument (LlvmIrFunctionParameter parameter, object? value = null) - { - Type = parameter?.Type ?? throw new ArgumentNullException (nameof (parameter)); - IsNativePointer = parameter.IsNativePointer; - - if (value != null && value.GetType () != Type) { - throw new ArgumentException ($"value type '{value.GetType ()}' does not match the argument type '{Type}'"); - } - - Value = value; - } - - public LlvmIrFunctionArgument (LlvmIrGenerator.StringSymbolInfo symbol) - { - Type = typeof(LlvmIrGenerator.StringSymbolInfo); - Value = symbol; - } - - public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable, bool isNull = false) - { - Type = typeof(LlvmIrFunctionLocalVariable); - Value = isNull ? null : variable; - IsNativePointer = variable.IsNativePointer; - } - - public LlvmIrFunctionArgument (LlvmIrVariableReference variable, bool isNull = false) - { - Type = typeof(LlvmIrVariableReference); - Value = isNull ? null : variable; - IsNativePointer = variable.IsNativePointer; - } - } - - /// - /// Describes a native function to be emitted and keeps code emitting state between calls to various generator - /// methods. - /// - class LlvmIrFunction - { - const string Indent1 = LlvmIrGenerator.Indent; - const string Indent2 = LlvmIrGenerator.Indent + LlvmIrGenerator.Indent; - - // Function signature - public string Name { get; } - public Type ReturnType { get; } - public int AttributeSetID { get; } - public IList? Parameters { get; } - public string ImplicitFuncTopLabel { get; } - public IList? ParameterVariables { get; } - public string PreviousBlockStartLabel => previousBlockStartLabel; - public string PreviousBlockEndLabel => previousBlockEndLabel; - - // Function writing state - public string Indent { get; private set; } = LlvmIrGenerator.Indent; - - // Used for unnamed function parameters as well as unnamed local variables - uint localSlot = 0; - uint indentLevel = 1; - - // This is a hack to work around a requirement in LLVM IR compiler that we cannot meet with a forward-only, single-pass generator like ours. Namely: - // LLVM IR compiler uses a monotonically increasing counter for all the unnamed function parameters, local variables and labels and it expects them to be - // used in strict sequence in the generated code. However, branch instructions need to know their target labels, so in a generator like ours we'd have to allocate - // their names before outputting the branch instruction and the blocks that we refer to. However, if those blocks use unnamed (counted) variables themselves, then - // they would be allocated numbers out of sequence, resulting in code similar to: - // - // br i1 %7, label %8, label %9 - // 8: - // %11 = load i8*, i8** %func_params_render, align 8 - // br label %10 - // - // 9: - // store i8* null, i8** %func_params_render, align 8 - // br label %10 - // - // 10: - // - // In this instance, the LLVM IR compiler would complain about the line after `8:` as follows: - // - // error: instruction expected to be numbered '%9' - // - // Since we have no time to rewrite the generator in some manner that would support this scenario (e.g. two-pass generator with an AST and label/parameter/variable - // placeholders), we need to employ a different technique: named labels. They won't be subject to the samme restrictions, but they pose another problem - if a - // given block of code is output more than once and generates the same label names, we'd have another error on our hands. Thus this counter variable, which will - // generate a unique label name by appending a number to some prefix - uint labelCounter = 0; - - // Names of the previous basic code block's start and end delimiters (labels), needed when the `phi` instruction is emitted. The instruction needs to refer to the - // code block preceding the current one, which will be the two labels preceding the current one (including the implicit label pointing to the beginning of the - // function, before any code. - // - // See also https://llvm.org/docs/LangRef.html#phi-instruction - // - string previousBlockStartLabel; - string previousBlockEndLabel; - string currentBlockStartLabel; - - public LlvmIrFunction (string name, Type returnType, int attributeSetID, IList? parameters = null, bool skipParameterNames = false) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - Name = name; - ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); - AttributeSetID = attributeSetID; - Parameters = parameters?.Select (p => EnsureParameterName (p))?.ToList ()?.AsReadOnly (); - ParameterVariables = Parameters?.Select (p => new LlvmIrFunctionLocalVariable (p.Type, p.Name, isNativePointer: p.IsNativePointer))?.ToList ()?.AsReadOnly (); - - // Unnamed local variables need to start from the value which equals [number_of_unnamed_parameters] + 1, - // since there's an implicit label created for the top of the function whose name is `[number_of_unnamed_parameters]` - ImplicitFuncTopLabel = previousBlockStartLabel = previousBlockEndLabel = currentBlockStartLabel = localSlot.ToString (CultureInfo.InvariantCulture); - localSlot++; - - LlvmIrFunctionParameter EnsureParameterName (LlvmIrFunctionParameter parameter) - { - if (parameter == null) { - throw new InvalidOperationException ("null parameters aren't allowed"); - } - - if (skipParameterNames) { - return parameter; - } - - if (!String.IsNullOrEmpty (parameter.Name)) { - return parameter; - } - - string name = GetNextSlotName (); - if (parameter.NativeFunction != null) { - return new LlvmIrFunctionParameter (parameter.NativeFunction, parameter, name); - } - return new LlvmIrFunctionParameter (parameter, name); - } - } - - public LlvmIrFunctionLocalVariable MakeLocalVariable (Type type, string? name = null, bool isNativePointer = false) - { - if (String.IsNullOrEmpty (name)) { - name = GetNextSlotName (); - } - - return new LlvmIrFunctionLocalVariable (type, name, isNativePointer: isNativePointer); - } - - public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) - { - if (String.IsNullOrEmpty (name)) { - name = GetNextSlotName (); - } - - return new LlvmIrFunctionLocalVariable (variable, name, isNativePointer: isNativePointer); - } - - /// - /// Return name of a local label with unique name. - /// - public string MakeUniqueLabel (string? prefix = null) - { - string name = String.IsNullOrEmpty (prefix) ? "ll" : prefix; - return ShiftBlockLabels ($"{name}{labelCounter++}"); - } - - public string MakeLabel (string labelName) - { - return ShiftBlockLabels (labelName); - } - - string ShiftBlockLabels (string newCurrentLabel) - { - previousBlockStartLabel = previousBlockEndLabel; - previousBlockEndLabel = currentBlockStartLabel; - currentBlockStartLabel = newCurrentLabel; - - return currentBlockStartLabel; - } - - public void IncreaseIndent () - { - indentLevel++; - Indent = MakeIndent (indentLevel); - } - - public void DecreaseIndent () - { - if (indentLevel == 0) { - return; - } - - indentLevel--; - Indent = MakeIndent (indentLevel); - } - - string MakeIndent (uint level) - { - switch (level) { - case 0: - return String.Empty; - - case 1: - return Indent1; - - case 2: - return Indent2; - - default: - var sb = new StringBuilder (); - for (uint i = 0; i < level; i++) { - sb.Append (LlvmIrGenerator.Indent); - } - return sb.ToString (); - } - } - - string GetNextSlotName () - { - string name = $"{localSlot.ToString (CultureInfo.InvariantCulture)}"; - localSlot++; - return name; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index 36ae6591424..45cd213dd59 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -5,131 +5,130 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable { - class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable - { - public uint Number { get; set; } = 0; + public uint Number { get; set; } = 0; - HashSet attributes; - Dictionary>? privateTargetSpecificAttributes; + HashSet attributes; + Dictionary>? privateTargetSpecificAttributes; - public LlvmIrFunctionAttributeSet () - { - attributes = new HashSet (); - } - - public LlvmIrFunctionAttributeSet (LlvmIrFunctionAttributeSet other) - { - attributes = new HashSet (other); - Number = other.Number; - } + public LlvmIrFunctionAttributeSet () + { + attributes = new HashSet (); + } - public IList? GetPrivateTargetAttributes (AndroidTargetArch targetArch) - { - if (privateTargetSpecificAttributes == null || !privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { - return null; - } + public LlvmIrFunctionAttributeSet (LlvmIrFunctionAttributeSet other) + { + attributes = new HashSet (other); + Number = other.Number; + } - return list.AsReadOnly (); + public IList? GetPrivateTargetAttributes (AndroidTargetArch targetArch) + { + if (privateTargetSpecificAttributes == null || !privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { + return null; } - public void Add (LlvmIrFunctionAttribute attr) - { - if (attr == null) { - throw new ArgumentNullException (nameof (attr)); - } + return list.AsReadOnly (); + } - if (!attributes.Contains (attr)) { - attributes.Add (attr); - } + public void Add (LlvmIrFunctionAttribute attr) + { + if (attr == null) { + throw new ArgumentNullException (nameof (attr)); } - public void Add (IList attrList) - { - foreach (LlvmIrFunctionAttribute attr in attrList) { - Add (attr); - } + if (!attributes.Contains (attr)) { + attributes.Add (attr); } + } - /// - /// Add architecture-specific attributes, private to the module generator (as opposed to arch-specific attributes which are common - /// between all attribute sets. - /// - public void Add (AndroidTargetArch targetArch, LlvmIrFunctionAttribute attr) - { - if (privateTargetSpecificAttributes == null) { - privateTargetSpecificAttributes = new Dictionary> (); - } + public void Add (IList attrList) + { + foreach (LlvmIrFunctionAttribute attr in attrList) { + Add (attr); + } + } - if (!privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { - list = new List (); - } + /// + /// Add architecture-specific attributes, private to the module generator (as opposed to arch-specific attributes which are common + /// between all attribute sets. + /// + public void Add (AndroidTargetArch targetArch, LlvmIrFunctionAttribute attr) + { + if (privateTargetSpecificAttributes == null) { + privateTargetSpecificAttributes = new Dictionary> (); + } - list.Add (attr); + if (!privateTargetSpecificAttributes.TryGetValue (targetArch, out List list)) { + list = new List (); } - public void Add (LlvmIrFunctionAttributeSet sourceSet) - { - if (sourceSet == null) { - throw new ArgumentNullException (nameof (sourceSet)); - } + list.Add (attr); + } - foreach (LlvmIrFunctionAttribute attr in sourceSet) { - Add (attr); - } + public void Add (LlvmIrFunctionAttributeSet sourceSet) + { + if (sourceSet == null) { + throw new ArgumentNullException (nameof (sourceSet)); } - public string Render () - { - List list = attributes.ToList (); - list.Sort ((LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) => a.Name.CompareTo (b.Name)); - - return String.Join (" ", list.Select (a => a.Render ())); + foreach (LlvmIrFunctionAttribute attr in sourceSet) { + Add (attr); } + } - public IEnumerator GetEnumerator () => attributes.GetEnumerator (); + public string Render () + { + List list = attributes.ToList (); + list.Sort ((LlvmIrFunctionAttribute a, LlvmIrFunctionAttribute b) => a.Name.CompareTo (b.Name)); - IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + return String.Join (" ", list.Select (a => a.Render ())); + } - public bool Equals (LlvmIrFunctionAttributeSet other) - { - if (other == null) { - return false; - } + public IEnumerator GetEnumerator () => attributes.GetEnumerator (); - if (attributes.Count != other.attributes.Count) { - return false; - } + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - foreach (LlvmIrFunctionAttribute attr in attributes) { - if (!other.attributes.Contains (attr)) { - return false; - } - } + public bool Equals (LlvmIrFunctionAttributeSet other) + { + if (other == null) { + return false; + } - return true; + if (attributes.Count != other.attributes.Count) { + return false; } - public override bool Equals (object obj) - { - var attrSet = obj as LlvmIrFunctionAttributeSet; - if (attrSet == null) { + foreach (LlvmIrFunctionAttribute attr in attributes) { + if (!other.attributes.Contains (attr)) { return false; } + } + + return true; + } - return Equals (attrSet); + public override bool Equals (object obj) + { + var attrSet = obj as LlvmIrFunctionAttributeSet; + if (attrSet == null) { + return false; } - public override int GetHashCode() - { - int hc = 0; + return Equals (attrSet); + } - foreach (LlvmIrFunctionAttribute attr in attributes) { - hc ^= attr?.GetHashCode () ?? 0; - } + public override int GetHashCode() + { + int hc = 0; - return hc; + foreach (LlvmIrFunctionAttribute attr in attributes) { + hc ^= attr?.GetHashCode () ?? 0; } - } + + return hc; + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index 7e097a863cf..20046779b14 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -3,11 +3,7 @@ using System.Globalization; using System.Text; -namespace Xamarin.Android.Tasks.LLVM.IR; - -// TODO: remove these aliases once the refactoring is done -using LlvmIrIcmpCond = LLVMIR.LlvmIrIcmpCond; -using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; +namespace Xamarin.Android.Tasks.LLVMIR; /// /// Abstract class from which all of the items (labels, function parameters, @@ -116,6 +112,7 @@ protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator { context.DecreaseIndent (); + context.Output.WriteLine (); context.Output.Write (context.CurrentIndent); context.Output.Write (Name); context.Output.Write (':'); @@ -170,7 +167,7 @@ protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator List items; HashSet definedLabels; - LlvmIrFunction function; + LlvmIrFunction ownerFunction; LlvmIrFunction.FunctionState functionState; LlvmIrFunctionLabelItem implicitStartBlock; @@ -184,7 +181,7 @@ protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState functionState) { - function = func; + ownerFunction = func; this.functionState = functionState; definedLabels = new HashSet (StringComparer.Ordinal); items = new List (); @@ -205,9 +202,9 @@ public LlvmIrInstructions.Br Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem if return ret; } - public LlvmIrInstructions.Call Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null) + public LlvmIrInstructions.Call Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null, LlvmIrVariable? funcPointer = null) { - var ret = new LlvmIrInstructions.Call (function, result, arguments); + var ret = new LlvmIrInstructions.Call (function, result, arguments, funcPointer); Add (ret); return ret; } @@ -252,7 +249,7 @@ public void Add (LlvmIrFunctionLabelItem label) { label.WillAddToBody (this, functionState); if (definedLabels.Contains (label.Name)) { - throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{function.Signature.Name}' body"); + throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{ownerFunction.Signature.Name}' body"); } items.Add (label); definedLabels.Add (label.Name); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs deleted file mode 100644 index 8dc3184fe10..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ /dev/null @@ -1,927 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; - -using LlvmIrFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttribute; -using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; -using ArgmemonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.ArgmemonlyFunctionAttribute; -using InaccessiblememOrArgmemonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.InaccessiblememOrArgmemonlyFunctionAttribute; -using MinLegalVectorWidthFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.MinLegalVectorWidthFunctionAttribute; -using MustprogressFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.MustprogressFunctionAttribute; -using NoTrappingMathFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NoTrappingMathFunctionAttribute; -using NofreeFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NofreeFunctionAttribute; -using NorecurseFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NorecurseFunctionAttribute; -using NosyncFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NosyncFunctionAttribute; -using NounwindFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.NounwindFunctionAttribute; -using SspstrongFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.SspstrongFunctionAttribute; -using StackProtectorBufferSizeFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.StackProtectorBufferSizeFunctionAttribute; -using WillreturnFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.WillreturnFunctionAttribute; -using WriteonlyFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.WriteonlyFunctionAttribute; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - abstract partial class LlvmIrGenerator - { - // In code generated by clang, function attributes are determined based on the compiler optimization, - // security arguments, architecture specific flags and so on. For our needs we will have but a - // handful of such sets, based on what clang generates for our native runtime. As such, there is nothing - // "smart" about how we select the attributes, they must match the compiler output for XA runtime, that's all. - // - // Sets are initialized here with the options common to all architectures, the rest is added in the architecture - // specific derived classes. - // - public const int FunctionAttributesXamarinAppInit = 0; - public const int FunctionAttributesJniMethods = 1; - public const int FunctionAttributesCall = 2; - public const int FunctionAttributesLlvmLifetime = 3; - public const int FunctionAttributesLibcFree = 4; - - protected readonly Dictionary FunctionAttributes = new Dictionary (); - - bool codeOutputInitialized = false; - List? externalFunctions = null; - LlvmIrVariableReference? llvm_lifetime_start_p0i8_ref; - LlvmIrVariableReference? llvm_lifetime_end_p0i8_ref; - List? llvm_lifetime_start_end_params; - - public void WriteFunctionDeclarations () - { - if (externalFunctions == null || externalFunctions.Count == 0) { - return; - } - - WriteEOL (); - WriteCommentLine (); - WriteCommentLine ("External functions"); - WriteCommentLine (); - - foreach (LlvmIrFunction func in externalFunctions) { - WriteFunctionForwardDeclaration (func); - } - } - - public void AddExternalFunction (LlvmIrFunction func) - { - if (externalFunctions == null) { - externalFunctions = new List (); - } - - externalFunctions.Add (func); - } - - void ValidateFunction (LlvmIrFunction function, out LlvmIrFunctionAttributeSet? attributes) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - attributes = null; - if (function.AttributeSetID >= 0 && !FunctionAttributes.TryGetValue (function.AttributeSetID, out attributes)) { - throw new InvalidOperationException ($"Function '{function.Name}' refers to attribute set that does not exist (ID: {function.AttributeSetID})"); - } - } - - void WriteFunctionSignature (LlvmIrFunction function, string statementKindKeyword, LlvmIrFunctionAttributeSet? attributes, string? comment, bool writeParameterNames = true) - { - if (!String.IsNullOrEmpty (comment)) { - foreach (string line in comment.Split ('\n')) { - WriteCommentLine (line); - } - } - - if (attributes != null) { - WriteCommentLine ($"Function attributes: {attributes.Render ()}"); - } - - Output.Write ($"{statementKindKeyword} {GetKnownIRType (function.ReturnType)} @{function.Name} ("); - WriteFunctionParameters (function.Parameters, writeNames: writeParameterNames); - Output.Write(") local_unnamed_addr "); - if (attributes != null) { - Output.Write ($"#{function.AttributeSetID.ToString (CultureInfo.InvariantCulture)}"); - } - } - - /// - /// Writes function forward declaration - /// - public void WriteFunctionForwardDeclaration (LlvmIrFunction function, string? comment = null) - { - ValidateFunction (function, out LlvmIrFunctionAttributeSet? attributes); - - Output.WriteLine (); - WriteFunctionSignature (function, "declare", attributes, comment, writeParameterNames: false); - Output.WriteLine (";"); - } - - /// - /// Writes the function definition up to the opening curly brace - /// - public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) - { - ValidateFunction (function, out LlvmIrFunctionAttributeSet? attributes); - Output.WriteLine (); - WriteFunctionSignature (function, "define", attributes, comment); - Output.WriteLine (); - Output.WriteLine ("{"); - } - - void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null, bool ignoreNativePointer = false) - { - string extraPointer = !ignoreNativePointer && variable.IsNativePointer ? "*" : String.Empty; - - if (variable.NativeFunction != null) { - if (builder == null) { - WriteFunctionSignature (variable.NativeFunction); - if (extraPointer.Length > 0) { - Output.Write (extraPointer); - } - } else { - builder.Append (RenderFunctionSignature (variable.NativeFunction)); - if (extraPointer.Length > 0) { - builder.Append (extraPointer); - } - } - return; - } - - StringBuilder? flags = null; - if (variable is LlvmIrFunctionParameter fparam) { - if (fparam.IsVarargs) { - DoWrite ("..."); - return; - } - - flags = new StringBuilder (); - if (fparam.NoCapture) { - flags.Append (" nocapture"); - } - - if (fparam.Immarg) { - flags.Append (" immarg"); - } - - if (fparam.NoUndef) { - flags.Append (" noundef"); - } - } - - string irType = $"{GetKnownIRType (variable.Type)}{extraPointer}{flags?.ToString() ?? String.Empty}"; - DoWrite (irType); - - void DoWrite (string s) - { - if (builder == null) { - Output.Write (s); - } else { - builder.Append (s); - } - } - } - - void WriteFunctionParameters (IList? parameters, bool writeNames) - { - string rendered = RenderFunctionParameters (parameters, writeNames); - if (String.IsNullOrEmpty (rendered)) { - return; - } - - Output.Write (rendered); - } - - public string RenderFunctionParameters (IList? parameters, bool writeNames) - { - if (parameters == null || parameters.Count == 0) { - return String.Empty; - } - - var sb = new StringBuilder (); - bool first = true; - foreach (LlvmIrFunctionParameter p in parameters) { - if (!first) { - sb.Append (", "); - } else { - first = false; - } - - CodeRenderType (p, sb); - - if (writeNames) { - if (String.IsNullOrEmpty (p.Name)) { - throw new InvalidOperationException ("Parameter name is required"); - } - - sb.Append ($" %{p.Name}"); - } - } - - return sb.ToString (); - } - - public void WriteFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) - { - Output.Write (RenderFunctionSignature (sig, isPointer)); - } - - public string RenderFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) - { - if (sig == null) { - throw new ArgumentNullException (nameof (sig)); - } - - var sb = new StringBuilder (); - sb.Append (GetKnownIRType (sig.ReturnType)); - sb.Append (" ("); - sb.Append (RenderFunctionParameters (sig.Parameters, writeNames: false)); - sb.Append (")"); - if (isPointer) { - sb.Append ('*'); - } - - return sb.ToString (); - } - - /// - /// Writes the epilogue of a function, including the return statement if the function return - /// type is void. - /// - public void WriteFunctionEnd (LlvmIrFunction function) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (function.ReturnType == typeof (void)) { - EmitReturnInstruction (function); - } - - Output.WriteLine ("}"); - } - - /// - /// Emits the ret statement using as the returned value. If - /// is null, void is used as the return value. - /// - public void EmitReturnInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable? retVar = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - string ret = retVar != null ? $"{GetKnownIRType(retVar.Type)} %{retVar.Name}" : "void"; - Output.WriteLine ($"{function.Indent}ret {ret}"); - } - - /// - /// Emits the store instruction (https://llvm.org/docs/LangRef.html#store-instruction), which stores data from a local - /// variable into either local or global destination. If types of and - /// differ, is bitcast to the type of . It is responsibility of the - /// caller to make sure the two types are compatible and/or convertible to each other. - /// - public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable source, LlvmIrVariableReference destination) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - // TODO: implement bitcast, if necessary - Output.Write ($"{function.Indent}store "); - CodeRenderType (source); - Output.Write ($" %{source.Name}, "); - CodeRenderType (destination); - Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); - } - - public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrVariableReference destination, T? value) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (typeof(T) != destination.Type) { - throw new InvalidOperationException ($"Destination variable is of type '{destination.Type}', it cannot be assigned a value of type {typeof(T)}"); - } - - if (value == null && typeof(T) != typeof(string) && typeof(T) != typeof(IntPtr) && !destination.IsNativePointer) { - throw new InvalidOperationException ($"Cannot assign a NULL pointer value to variable of non-pointer type '{destination.Type}'"); - } - - Output.Write ($"{function.Indent}store "); - CodeRenderType (destination, ignoreNativePointer: true); - Output.Write (' '); - if (value == null) { - Output.Write ("null"); - } else { - Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); - } - Output.Write (", "); - CodeRenderType (destination); - Output.WriteLine ($" {destination.Reference}, align {GetTypeSize (destination.Type).ToString (CultureInfo.InvariantCulture)}"); - } - - /// - /// Emits the load instruction (https://llvm.org/docs/LangRef.html#load-instruction) - /// - public LlvmIrFunctionLocalVariable EmitLoadInstruction (LlvmIrFunction function, LlvmIrVariableReference source, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (source, resultVariableName); - Output.Write ($"{function.Indent}%{result.Name} = load "); - CodeRenderType (source, ignoreNativePointer: true); - Output.Write (", "); - CodeRenderType (source); - if (!source.IsNativePointer) { - Output.Write ('*'); - } - - Output.WriteLine ($" {source.Reference}, align {PointerSize.ToString (CultureInfo.InvariantCulture)}"); - - return result; - } - - /// - /// Emits the icmp comparison instruction (https://llvm.org/docs/LangRef.html#icmp-instruction) - /// - public LlvmIrFunctionLocalVariable EmitIcmpInstruction (LlvmIrFunction function, LlvmIrIcmpCond cond, LlvmIrVariableReference variable, string expectedValue, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - string condOp; - switch (cond) { - case LlvmIrIcmpCond.Equal: // equal - condOp = "eq"; - break; - - case LlvmIrIcmpCond.NotEqual: // not equal - condOp = "ne"; - break; - - case LlvmIrIcmpCond.UnsignedGreaterThan: // unsigned greater than - condOp = "ugt"; - break; - - case LlvmIrIcmpCond.UnsignedGreaterOrEqual: // unsigned greater or equal - condOp = "uge"; - break; - - case LlvmIrIcmpCond.UnsignedLessThan: // unsigned less than - condOp = "ult"; - break; - - case LlvmIrIcmpCond.UnsignedLessOrEqual: // unsigned less or equal - condOp = "ule"; - break; - - case LlvmIrIcmpCond.SignedGreaterThan: // signed greater than, - condOp = "sgt"; - break; - - case LlvmIrIcmpCond.SignedGreaterOrEqual: // signed greater or equal - condOp = "sge"; - break; - - case LlvmIrIcmpCond.SignedLessThan: // signed less than - condOp = "slt"; - break; - - case LlvmIrIcmpCond.SignedLessOrEqual: // signed less or equal - condOp = "sle"; - break; - - default: - throw new InvalidOperationException ($"Unsupported `icmp` conditional '{cond}'"); - } - - var sb = new StringBuilder (); - CodeRenderType (variable, sb, ignoreNativePointer: true); - - string variableType = sb.ToString (); - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (variable.Type, resultVariableName); - - Output.WriteLine ($"{function.Indent}%{result.Name} = icmp {condOp} {variableType} {variable.Reference}, {expectedValue}"); - - return result; - } - - public void EmitBrInstruction (LlvmIrFunction function, LlvmIrVariableReference condVariable, string labelTrue, string labelFalse) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - Output.WriteLine ($"{function.Indent}br i1 {condVariable.Reference}, label %{labelTrue}, label %{labelFalse}"); - } - - public void EmitBrInstruction (LlvmIrFunction function, string label) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - Output.WriteLine ($"{function.Indent}br label %{label}"); - } - - public void EmitLabel (LlvmIrFunction function, string labelName, bool insertNewlineBefore = true) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (insertNewlineBefore) { - Output.WriteLine (); - } - - Output.WriteLine ($"{function.MakeLabel (labelName)}:"); - } - - public LlvmIrFunctionLocalVariable EmitUpcast (LlvmIrFunction function, LlvmIrVariableReference sourceRef, Type targetType) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (sourceRef == null) { - throw new ArgumentNullException (nameof (sourceRef)); - } - - if (targetType == null) { - throw new ArgumentNullException (nameof (targetType)); - } - - string extendOp; - if (targetType == typeof(double)) { - extendOp = "fp"; - } else if (targetType == typeof(int)) { - extendOp = "s"; - } else if (targetType == typeof(uint)) { - extendOp = "z"; - } else { - throw new InvalidOperationException ($"Unsupported target type for upcasting: {targetType}"); - } - - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (targetType); - Output.WriteLine ($"{function.Indent}%{result.Name} = {extendOp}ext {GetKnownIRType (sourceRef.Type)} {sourceRef.Reference} to {GetKnownIRType (targetType)}"); - - return result; - } - - void WriteCallArgument (LlvmIrFunction function, LlvmIrFunctionArgument argument, LlvmIrFunctionParameter? parameter, bool variadicCountry, int argumentIndex) - { - string paramType; - if (!variadicCountry) { - AssertValidType (argumentIndex, parameter, argument); - - paramType = GetParameterType (parameter); - } else { - paramType = GetArgumentType (argument); - } - - Output.Write ($"{paramType} "); - - if (argument.NonNull) { - Output.Write ("nonnull "); - } - - if (argument.NoUndef) { - Output.Write ("noundef "); - } - - if (argument.Value is LlvmIrFunctionLocalVariable variable) { - Output.Write ($"%{variable.Name}"); - return; - } - - if (argument.Value is StringSymbolInfo stringSymbol) { - WriteGetStringPointer (stringSymbol.SymbolName, stringSymbol.Size, indent: false, detectBitness: true, skipPointerType: true); - return; - } - - if (argument.Type.IsNativePointer () || argument.IsNativePointer) { - if (parameter != null && parameter.IsCplusPlusReference) { - Output.Write ("nonnull "); - } - - if (argument.Type == typeof(LlvmIrVariableReference)) { - var variableRef = argument.Value as LlvmIrVariableReference; - bool needBitcast = parameter == null || variableRef == null ? false : parameter.Type != variableRef.Type; - - if (needBitcast) { - string ptrSize = PointerSize.ToString (CultureInfo.InvariantCulture); - Output.Write ($"align {ptrSize} dereferenceable({ptrSize}) bitcast ("); - CodeRenderType (variableRef); - Output.Write (' '); - } - - if (variableRef != null) { - Output.Write (variableRef.Reference); - } else { - Output.Write ("null"); - } - - if (needBitcast) { - Output.Write ($" to {paramType})"); - } - } else { - throw new InvalidOperationException ($"Unexpected pointer type in argument {argumentIndex}, '{argument.Type}'"); - } - return; - } - - if (argument.Value is string str) { - StringSymbolInfo info = StringManager.Add (str); - WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true, skipPointerType: true); - return; - - } - - Output.Write (MonoAndroidHelper.CultureInvariantToString (argument.Value)); - - string GetArgumentType (LlvmIrFunctionArgument argument) - { - string extra = argument.IsNativePointer ? "*" : String.Empty; - - Type type; - if (argument.Value is LlvmIrFunctionLocalVariable variable) { - type = variable.Type; - } else if (argument.Value is StringSymbolInfo stringSymbol) { - type = typeof(string); - } else { - type = argument.Type; - } - - return $"{GetKnownIRType (type)}{extra}"; - } - - static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) - { - if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { - return; - } - - if (parameter.Type != typeof(IntPtr)) { - if (argument.Type == typeof(StringSymbolInfo) && parameter.Type == typeof (string)) { - // Fine, we want to pass a pointer to string - return; - } - - if (argument.Type != parameter.Type) { - ThrowException (); - } - return; - } - - if (argument.Type.IsNativePointer ()) { - return; - } - - if (typeof(LlvmIrVariable).IsAssignableFrom (argument.Type) && - argument.Value is LlvmIrVariable variable && - (variable.IsNativePointer || variable.NativeFunction != null)) { - return; - } - - ThrowException (); - - void ThrowException () - { - throw new InvalidOperationException ($"Argument {index} type '{argument.Type}' does not match the expected function parameter type '{parameter.Type}'"); - } - } - } - - void WriteCallArguments (LlvmIrFunction function, LlvmNativeFunctionSignature targetSignature, List arguments) - { - Console.WriteLine ($"WCA for '{function.Name}'"); - bool variadicCountry = false; - for (int i = 0; i < arguments.Count; i++) { - Console.WriteLine ($" WCA: arg #{i}"); - LlvmIrFunctionParameter? parameter = null; - - if (!variadicCountry) { - if (i >= targetSignature.Parameters.Count) { - throw new InvalidOperationException ("Internal error: Exceeded number of declared parameters, expected a trailing variadic parameter at this point"); - } - - parameter = targetSignature.Parameters[i]; - - if (parameter.IsVarargs) { - variadicCountry = true; - } - } - - if (i > 0) { - Output.Write (", "); - } - - WriteCallArgument (function, arguments[i], parameter, variadicCountry, i); - } - } - - public LlvmIrFunctionLocalVariable? EmitCall (LlvmIrFunction function, LlvmIrVariableReference targetRef, List? arguments = null, - string? resultVariableName = null, LlvmIrCallMarker marker = LlvmIrCallMarker.Tail, int AttributeSetID = FunctionAttributesCall) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - if (targetRef == null) { - throw new ArgumentNullException (nameof (targetRef)); - } - - LlvmNativeFunctionSignature targetSignature = targetRef.NativeFunction; - if (targetSignature == null) { - throw new ArgumentException ("must be reference to native function", nameof (targetRef)); - } - - bool haveParameters = targetSignature.Parameters != null && targetSignature.Parameters.Count > 0; - if (haveParameters) { - if (arguments == null) { - throw new ArgumentNullException (nameof (arguments)); - } - - if ((targetSignature.IsVariadic && targetSignature.NumberOfRequiredArguments > arguments.Count) || (!targetSignature.IsVariadic && targetSignature.NumberOfRequiredArguments != arguments.Count)) { - throw new ArgumentException ($"number of passed parameters ({arguments.Count}) does not match number of required parameters in function signature ({targetSignature.NumberOfRequiredArguments})", nameof (arguments)); - } - } - - bool returnsValue = targetSignature.ReturnType != typeof(void); - LlvmIrFunctionLocalVariable? result = null; - - Output.Write (function.Indent); - if (returnsValue) { - result = function.MakeLocalVariable (targetSignature.ReturnType, resultVariableName); - Output.Write ($"%{result.Name} = "); - } - - switch (marker) { - case LlvmIrCallMarker.Tail: - Output.Write ("tail "); - break; - - case LlvmIrCallMarker.MustTail: - Output.Write ("musttail "); - break; - - case LlvmIrCallMarker.NoTail: - Output.Write ("notail "); - break; - - case LlvmIrCallMarker.None: - break; - - default: - throw new InvalidOperationException ($"Unsupported call marker '{marker}'"); - } - - Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} "); - if (targetSignature.IsVariadic) { - Output.Write ('('); - for (int i = 0; i < targetSignature.NumberOfRequiredArguments; i++) { - if (i > 0) { - Output.Write (", "); - } - LlvmIrFunctionParameter argument = targetSignature.Parameters[i]; - Output.Write (GetParameterType (argument)); - } - Output.Write (", ...) "); // We know the variadic parameter is last - } - Output.Write ($"{targetRef.Reference} ("); - - if (haveParameters) { - WriteCallArguments (function, targetSignature, arguments); - } - - Output.Write (")"); - - if (AttributeSetID >= 0) { - if (!FunctionAttributes.ContainsKey (AttributeSetID)) { - throw new InvalidOperationException ($"Unknown attribute set ID {AttributeSetID}"); - } - Output.Write ($" #{AttributeSetID.ToString (CultureInfo.InvariantCulture)}"); - } - Output.WriteLine (); - - return result; - } - - string GetParameterType (LlvmIrFunctionParameter parameter) - { - string extra = parameter.IsNativePointer ? "*" : String.Empty; - return $"{GetKnownIRType (parameter.Type)}{extra}"; - } - - /// - /// Emits the phi instruction (https://llvm.org/docs/LangRef.html#phi-instruction) for a function pointer type - /// - public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrVariableReference target, List<(LlvmIrVariableReference? variableRef, string label)> pairs, string? resultVariableName = null) - { - if (function == null) { - throw new ArgumentNullException (nameof (function)); - } - - LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (target, resultVariableName); - Output.Write ($"{function.Indent}%{result.Name} = phi "); - CodeRenderType (target, ignoreNativePointer: true); - - bool first = true; - foreach ((LlvmIrVariableReference variableRef, string label) in pairs) { - if (first) { - first = false; - Output.Write (' '); - } else { - Output.Write (", "); - } - - string value = variableRef?.Reference ?? "null"; - Output.Write ($"[{value}, %{label}]"); - } - Output.WriteLine (); - - return result; - } - - void RegisterLlvmLifetimeTrackerFunctions () - { - if (llvm_lifetime_start_p0i8_ref != null && llvm_lifetime_end_p0i8_ref != null) { - return; - } - - const string llvm_lifetime_start_p0i8_name = "llvm.lifetime.start.p0i8"; - const string llvm_lifetime_end_p0i8_name = "llvm.lifetime.end.p0i8"; - - llvm_lifetime_start_end_params = new List { - new LlvmIrFunctionParameter (typeof(long)) { - Immarg = true, - }, - new LlvmIrFunctionParameter (typeof(sbyte), isNativePointer: true) { - NoCapture = true, - }, - }; - - if (llvm_lifetime_start_p0i8_ref == null) { - var llvm_lifetime_start_p0i8_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: llvm_lifetime_start_end_params - ); - - llvm_lifetime_start_p0i8_ref = new LlvmIrVariableReference (llvm_lifetime_start_p0i8_sig, llvm_lifetime_start_p0i8_name, isGlobal: true); - AddFunctionDeclaration (llvm_lifetime_start_p0i8_name, llvm_lifetime_start_p0i8_sig, LlvmIrGenerator.FunctionAttributesLlvmLifetime); - } - - if (llvm_lifetime_end_p0i8_ref == null) { - var llvm_lifetime_end_p0i8_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: llvm_lifetime_start_end_params - ); - - llvm_lifetime_end_p0i8_ref = new LlvmIrVariableReference (llvm_lifetime_end_p0i8_sig, llvm_lifetime_end_p0i8_name, isGlobal: true); - AddFunctionDeclaration (llvm_lifetime_end_p0i8_name, llvm_lifetime_end_p0i8_sig, LlvmIrGenerator.FunctionAttributesLlvmLifetime); - } - - void AddFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) - { - var func = new LlvmIrFunction ( - name: name, - returnType: sig.ReturnType, - attributeSetID: attributeSetID, - parameters: sig.Parameters - ); - AddExternalFunction (func); - } - } - - public (LlvmIrFunctionLocalVariable variable, LlvmIrFunctionLocalVariable lifetimeTracker) EmitAllocStackVariable (LlvmIrFunction function, Type type, string? name = null) - { - RegisterLlvmLifetimeTrackerFunctions (); - - string alignment = PointerSize.ToString (CultureInfo.InvariantCulture); - LlvmIrFunctionLocalVariable localVariable = function.MakeLocalVariable (type, name, isNativePointer: true); - LlvmIrFunctionLocalVariable lifetimeTracker = function.MakeLocalVariable (localVariable.Type); - - string localVariableTypeName = GetKnownIRType (localVariable.Type); - Output.WriteLine ($"{function.Indent}%{localVariable.Name} = alloca {localVariableTypeName}, align {alignment}"); - Output.WriteLine ($"{function.Indent}%{lifetimeTracker.Name} = bitcast {GetKnownIRType (lifetimeTracker.Type)}* %{localVariable.Name} to {localVariableTypeName}"); - - var lifetimeStartArgs = new List { - new LlvmIrFunctionArgument (llvm_lifetime_start_end_params[0], (long)PointerSize), - new LlvmIrFunctionArgument (lifetimeTracker) { - NonNull = true, - }, - }; - - EmitCall (function, llvm_lifetime_start_p0i8_ref, lifetimeStartArgs, marker: LlvmIrCallMarker.None, AttributeSetID: FunctionAttributesLlvmLifetime); - return (localVariable, lifetimeTracker); - } - - public void EmitDeallocStackVariable (LlvmIrFunction function, LlvmIrFunctionLocalVariable lifetimeTracker) - { - RegisterLlvmLifetimeTrackerFunctions (); - - var lifetimeEndArgs = new List { - new LlvmIrFunctionArgument (llvm_lifetime_start_end_params[0], (long)PointerSize), - new LlvmIrFunctionArgument (lifetimeTracker) { - NonNull = true, - }, - }; - - EmitCall (function, llvm_lifetime_end_p0i8_ref, lifetimeEndArgs, marker: LlvmIrCallMarker.None, AttributeSetID: FunctionAttributesLlvmLifetime); - } - - public void InitCodeOutput () - { - if (codeOutputInitialized) { - return; - } - - InitFunctionAttributes (); - InitCodeMetadata (); - codeOutputInitialized = true; - } - - protected virtual void InitCodeMetadata () - { - MetadataManager.Add ("llvm.linker.options"); - } - - protected virtual void InitFunctionAttributes () - { - FunctionAttributes[FunctionAttributesXamarinAppInit] = new LlvmIrFunctionAttributeSet { - new MinLegalVectorWidthFunctionAttribute (0), - new MustprogressFunctionAttribute (), - new NofreeFunctionAttribute (), - new NorecurseFunctionAttribute (), - new NosyncFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new NounwindFunctionAttribute (), - new SspstrongFunctionAttribute (), - new StackProtectorBufferSizeFunctionAttribute (8), -// new UwtableFunctionAttribute (), - new WillreturnFunctionAttribute (), - new WriteonlyFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesJniMethods] = new LlvmIrFunctionAttributeSet { - new MinLegalVectorWidthFunctionAttribute (0), - new MustprogressFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new NounwindFunctionAttribute (), - new SspstrongFunctionAttribute (), - new StackProtectorBufferSizeFunctionAttribute (8), -// new UwtableFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesCall] = new LlvmIrFunctionAttributeSet { - new NounwindFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesLlvmLifetime] = new LlvmIrFunctionAttributeSet { - new ArgmemonlyFunctionAttribute (), - new MustprogressFunctionAttribute (), - new NofreeFunctionAttribute (), - new NosyncFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesLibcFree] = new LlvmIrFunctionAttributeSet { - new InaccessiblememOrArgmemonlyFunctionAttribute (), - new MustprogressFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - }; - - FunctionAttributes[FunctionAttributesLibcFree].Add (FunctionAttributes[FunctionAttributesJniMethods]); - } - - void WriteAttributeSets () - { - if (!codeOutputInitialized) { - return; - } - - WriteSet (FunctionAttributesXamarinAppInit, Output); - WriteSet (FunctionAttributesJniMethods, Output); - WriteSet (FunctionAttributesCall, Output); - WriteSet (FunctionAttributesLlvmLifetime, Output); - WriteSet (FunctionAttributesLibcFree, Output); - - Output.WriteLine (); - - void WriteSet (int id, TextWriter output) - { - output.Write ($"attributes #{id.ToString (CultureInfo.InvariantCulture)} = {{ "); - foreach (LlvmIrFunctionAttribute attr in FunctionAttributes[id]) { - output.Write (attr.Render ()); - output.Write (' '); - } - output.WriteLine ("}"); - } - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs similarity index 83% rename from src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs rename to src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs index d82dee21f60..9600dbc81e1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.Constants.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Constants.cs @@ -1,15 +1,8 @@ using System; using System.Collections.Generic; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR { - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; - using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; - using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - using LlvmIrWritability = LLVMIR.LlvmIrWritability; - partial class LlvmIrGenerator { // https://llvm.org/docs/LangRef.html#linkage-types diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs deleted file mode 100644 index 9cd5b6e4731..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.New.cs +++ /dev/null @@ -1,1349 +0,0 @@ -using System; -using System.Buffers; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - using LlvmIrLinkage = LLVMIR.LlvmIrLinkage; - using LlvmIrRuntimePreemption = LLVMIR.LlvmIrRuntimePreemption; - using LlvmIrVisibility = LLVMIR.LlvmIrVisibility; - using LlvmIrWritability = LLVMIR.LlvmIrWritability; - using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; - - sealed class GeneratorStructureInstance : StructureInstance - { - public GeneratorStructureInstance (StructureInfo info, object instance) - : base (info, instance) - {} - } - - sealed class GeneratorWriteContext - { - const char IndentChar = '\t'; - - int currentIndentLevel = 0; - - public readonly TextWriter Output; - public readonly LlvmIrModule Module; - public readonly LlvmIrModuleTarget Target; - public readonly LlvmIrMetadataManager MetadataManager; - public string CurrentIndent { get; private set; } = String.Empty; - public bool InVariableGroup { get; set; } - - public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager) - { - Output = writer; - Module = module; - Target = target; - MetadataManager = metadataManager; - } - - public void IncreaseIndent () - { - currentIndentLevel++; - CurrentIndent = MakeIndentString (); - } - - public void DecreaseIndent () - { - if (currentIndentLevel > 0) { - currentIndentLevel--; - } - CurrentIndent = MakeIndentString (); - } - - string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; - } - - partial class LlvmIrGenerator - { - sealed class LlvmTypeInfo - { - public readonly bool IsPointer; - public readonly bool IsAggregate; - public readonly bool IsStructure; - public readonly ulong Size; - public readonly ulong MaxFieldAlignment; - - public LlvmTypeInfo (bool isPointer, bool isAggregate, bool isStructure, ulong size, ulong maxFieldAlignment) - { - IsPointer = isPointer; - IsAggregate = isAggregate; - IsStructure = isStructure; - Size = size; - MaxFieldAlignment = maxFieldAlignment; - } - } - - sealed class BasicType - { - public readonly string Name; - public readonly ulong Size; - public readonly bool IsNumeric; - - public BasicType (string name, ulong size, bool isNumeric = true) - { - Name = name; - Size = size; - IsNumeric = isNumeric; - } - } - - public const string IRPointerType = "ptr"; - - static readonly Dictionary basicTypeMap = new Dictionary { - { typeof (bool), new ("i8", 1, isNumeric: false) }, - { typeof (byte), new ("i8", 1) }, - { typeof (char), new ("i16", 2) }, - { typeof (sbyte), new ("i8", 1) }, - { typeof (short), new ("i16", 2) }, - { typeof (ushort), new ("i16", 2) }, - { typeof (int), new ("i32", 4) }, - { typeof (uint), new ("i32", 4) }, - { typeof (long), new ("i64", 8) }, - { typeof (ulong), new ("i64", 8) }, - { typeof (float), new ("float", 4) }, - { typeof (double), new ("double", 8) }, - { typeof (void), new ("void", 0, isNumeric: false) }, - }; - - public string FilePath { get; } - public string FileName { get; } - - LlvmIrModuleTarget target; - - protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) - { - FilePath = Path.GetFullPath (filePath); - FileName = Path.GetFileName (filePath); - this.target = target; - } - - public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) - { - return arch switch { - AndroidTargetArch.Arm => new LlvmIrGenerator (fileName, new LlvmIrModuleArmV7a ()), - AndroidTargetArch.Arm64 => new LlvmIrGenerator (fileName, new LlvmIrModuleAArch64 ()), - AndroidTargetArch.X86 => new LlvmIrGenerator (fileName, new LlvmIrModuleX86 ()), - AndroidTargetArch.X86_64 => new LlvmIrGenerator (fileName, new LlvmIrModuleX64 ()), - _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") - }; - } - - public void Generate (TextWriter writer, LlvmIrModule module) - { - LlvmIrMetadataManager metadataManager = module.GetMetadataManagerCopy (); - target.AddTargetSpecificMetadata (metadataManager); - - var context = new GeneratorWriteContext (writer, module, target, metadataManager); - if (!String.IsNullOrEmpty (FilePath)) { - WriteCommentLine (context, $" ModuleID = '{FileName}'"); - context.Output.WriteLine ($"source_filename = \"{FileName}\""); - } - - context.Output.WriteLine (target.DataLayout.Render ()); - context.Output.WriteLine ($"target triple = \"{target.Triple}\""); - WriteStructureDeclarations (context); - WriteGlobalVariables (context); - WriteFunctions (context); - - // Bottom of the file - WriteStrings (context); - WriteExternalFunctionDeclarations (context); - WriteAttributeSets (context); - WriteMetadata (context); - } - - void WriteStrings (GeneratorWriteContext context) - { - if (context.Module.Strings == null || context.Module.Strings.Count == 0) { - return; - } - - context.Output.WriteLine (); - WriteComment (context, " Strings"); - - foreach (LlvmIrStringGroup group in context.Module.Strings) { - context.Output.WriteLine (); - - if (!String.IsNullOrEmpty (group.Comment)) { - WriteCommentLine (context, group.Comment); - } - - foreach (LlvmIrStringVariable info in group.Strings) { - string s = QuoteString ((string)info.Value, out ulong size); - - WriteGlobalVariableStart (context, info); - context.Output.Write ('['); - context.Output.Write (size.ToString (CultureInfo.InvariantCulture)); - context.Output.Write (" x i8] c"); - context.Output.Write (s); - context.Output.Write (", align "); - context.Output.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); - } - } - } - - void WriteGlobalVariables (GeneratorWriteContext context) - { - if (context.Module.GlobalVariables == null || context.Module.GlobalVariables.Count == 0) { - return; - } - - foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { - if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { - if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) { - context.Output.WriteLine (); - context.Output.Write (context.CurrentIndent); - WriteComment (context, groupDelimiter.Comment); - } - - context.InVariableGroup = !context.InVariableGroup; - if (context.InVariableGroup) { - context.Output.WriteLine (); - } - continue; - } - - if (gv.BeforeWriteCallback != null) { - gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); - } - WriteGlobalVariable (context, gv); - } - } - - void WriteGlobalVariableStart (GeneratorWriteContext context, LlvmIrGlobalVariable variable) - { - if (!String.IsNullOrEmpty (variable.Comment)) { - WriteCommentLine (context, variable.Comment); - } - context.Output.Write ('@'); - context.Output.Write (variable.Name); - context.Output.Write (" = "); - - LlvmIrVariableOptions options = variable.Options ?? LlvmIrGlobalVariable.DefaultOptions; - WriteLinkage (context, options.Linkage); - WritePreemptionSpecifier (context, options.RuntimePreemption); - WriteVisibility (context, options.Visibility); - WriteAddressSignificance (context, options.AddressSignificance); - WriteWritability (context, options.Writability); - } - - void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable variable) - { - if (!context.InVariableGroup) { - context.Output.WriteLine (); - } - - WriteGlobalVariableStart (context, variable); - WriteTypeAndValue (context, variable, out LlvmTypeInfo typeInfo); - context.Output.Write (", align "); - - ulong alignment; - if (typeInfo.IsAggregate) { - ulong count = GetAggregateValueElementCount (variable); - alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); - } else if (typeInfo.IsStructure) { - alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); - } else if (typeInfo.IsPointer) { - alignment = target.NativePointerSize; - } else { - alignment = typeInfo.Size; - } - - context.Output.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); - } - - void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) - { - WriteType (context, variable, out typeInfo); - context.Output.Write (' '); - - Type valueType; - if (variable.Value is LlvmIrVariable referencedVariable) { - valueType = referencedVariable.Type; - } else { - valueType = variable.Value?.GetType () ?? variable.Type; - } - - if (variable.Value == null) { - // Order of checks is important here. Aggregates can contain pointer types, in which case typeInfo.IsPointer - // will be `true` and the aggregate would be incorrectly initialized with `null` instead of the correct - // `zeroinitializer` - if (typeInfo.IsAggregate) { - WriteValue (context, valueType, variable); - return; - } - - if (typeInfo.IsPointer) { - context.Output.Write ("null"); - return; - } - - throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); - } - - if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { - throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); - } - - WriteValue (context, valueType, variable); - } - - ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable); - - ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) - { - if (!type.IsArray ()) { - throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); - } - - if (value == null) { - if (globalVariable != null) { - return globalVariable.ArrayItemCount; - } - return 0; - } - - // TODO: use caching here - if (type.ImplementsInterface (typeof(IDictionary))) { - return (uint)((IDictionary)value).Count * 2; - } - - if (type.ImplementsInterface (typeof(ICollection))) { - return (uint)((ICollection)value).Count; - } - - throw new InvalidOperationException ($"Internal error: should never get here"); - } - - void WriteType (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) - { - WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); - } - - void WriteType (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) - { - if (memberInfo.IsNativePointer) { - typeInfo = new LlvmTypeInfo ( - isPointer: true, - isAggregate: false, - isStructure: false, - size: target.NativePointerSize, - maxFieldAlignment: target.NativePointerSize - ); - - context.Output.Write (IRPointerType); - return; - } - - if (memberInfo.IsInlineArray) { - WriteArrayType (context, memberInfo.MemberType.GetArrayElementType (), memberInfo.ArrayElements, out typeInfo); - return; - } - - if (memberInfo.IsIRStruct ()) { - var sim = new GeneratorStructureInstance (context.Module.GetStructureInfo (memberInfo.MemberType), memberInfo.GetValue (si.Obj)); - WriteStructureType (context, sim, out typeInfo); - return; - } - - WriteType (context, memberInfo.MemberType, value: null, out typeInfo); - } - - void WriteStructureType (GeneratorWriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) - { - ulong alignment = GetStructureMaxFieldAlignment (si.Info); - - typeInfo = new LlvmTypeInfo ( - isPointer: false, - isAggregate: false, - isStructure: true, - size: si.Info.Size, - maxFieldAlignment: alignment - ); - - context.Output.Write ('%'); - context.Output.Write (si.Info.NativeTypeDesignator); - context.Output.Write ('.'); - context.Output.Write (si.Info.Name); - } - - void WriteType (GeneratorWriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) - { - if (IsStructureInstance (type)) { - if (value == null) { - throw new ArgumentException ("must not be null for structure instances", nameof (value)); - } - - WriteStructureType (context, (StructureInstance)value, out typeInfo); - return; - } - - string irType; - ulong size; - bool isPointer; - - if (type.IsArray ()) { - Type elementType = type.GetArrayElementType (); - ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable); - - WriteArrayType (context, elementType, elementCount, out typeInfo); - return; - } - - irType = GetIRType (type, out size, out isPointer); - typeInfo = new LlvmTypeInfo ( - isPointer: isPointer, - isAggregate: false, - isStructure: false, - size: size, - maxFieldAlignment: size - ); - context.Output.Write (irType); - } - - void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) - { - string irType; - ulong size; - ulong maxFieldAlignment; - bool isPointer; - - if (elementType.IsStructureInstance (out Type? structureType)) { - StructureInfo si = context.Module.GetStructureInfo (structureType); - - irType = $"%{si.NativeTypeDesignator}.{si.Name}"; - size = si.Size; - maxFieldAlignment = GetStructureMaxFieldAlignment (si); - isPointer = false; - } else { - irType = GetIRType (elementType, out size, out isPointer); - maxFieldAlignment = size; - } - typeInfo = new LlvmTypeInfo ( - isPointer: isPointer, - isAggregate: true, - isStructure: false, - size: size, - maxFieldAlignment: maxFieldAlignment - ); - - context.Output.Write ('['); - context.Output.Write (elementCount.ToString (CultureInfo.InvariantCulture)); - context.Output.Write (" x "); - context.Output.Write (irType); - context.Output.Write (']'); - } - - ulong GetStructureMaxFieldAlignment (StructureInfo si) - { - if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { - return target.NativePointerSize; - } - - return si.MaxFieldAlignment; - } - - bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); - - void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable) - { - if (variable.Type.IsArray ()) { - bool zeroInitialize = false; - if (variable is LlvmIrGlobalVariable gv) { - zeroInitialize = gv.ZeroInitializeArray || variable.Value == null; - } else { - zeroInitialize = GetAggregateValueElementCount (variable) == 0; - } - - if (zeroInitialize) { - context.Output.Write ("zeroinitializer"); - return; - } - - WriteArrayValue (context, variable); - return; - } - - WriteValue (context, valueType, variable.Value); - } - - void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong length, ulong expectedLength) - { - if (length == expectedLength) { - return; - } - - throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}"); - } - - void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) - { - if (smi.IsNativePointer) { - if (WriteNativePointerValue (context, structInstance, smi, value)) { - return; - } - } - - if (smi.IsInlineArray) { - Array a = (Array)value; - ulong length = smi.ArrayElements == 0 ? (ulong)a.Length : smi.ArrayElements; - - if (smi.MemberType == typeof(byte[])) { - var bytes = (byte[])value; - - // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte - AssertArraySize (structInstance, smi, length, smi.ArrayElements); - context.Output.Write ('c'); - context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); - return; - } - - throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point. Field {smi.Info.Name} in structure {structInstance.Info.Name}"); - } - - if (smi.IsIRStruct ()) { - StructureInfo si = context.Module.GetStructureInfo (smi.MemberType); - WriteValue (context, typeof(GeneratorStructureInstance), new GeneratorStructureInstance (si, value)); - return; - } - - if (smi.Info.IsNativePointerToPreallocatedBuffer (out _)) { - string bufferVariableName = context.Module.LookupRequiredBufferVariableName (structInstance, smi); - context.Output.Write ('@'); - context.Output.Write (bufferVariableName); - return; - } - - WriteValue (context, smi.MemberType, value); - } - - bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) - { - // Structure members decorated with the [NativePointer] attribute cannot have a - // value other than `null`, unless they are strings or references to symbols - - if (smi.Info.PointsToSymbol (out string? symbolName)) { - if (String.IsNullOrEmpty (symbolName) && smi.Info.UsesDataProvider ()) { - if (si.Info.DataProvider == null) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{si.Info.Name}' points to a symbol, but symbol name wasn't provided and there's no configured data context provider"); - } - symbolName = si.Info.DataProvider.GetPointedToSymbolName (si.Obj, smi.Info.Name); - } - - if (String.IsNullOrEmpty (symbolName)) { - context.Output.Write ("null"); - } else { - context.Output.Write ('@'); - context.Output.Write (symbolName); - } - return true; - } - - if (smi.MemberType != typeof(string)) { - context.Output.Write ("null"); - return true; - } - - return false; - } - - void WriteValue (GeneratorWriteContext context, Type type, object? value) - { - if (value is LlvmIrVariable variableRef) { - context.Output.Write (variableRef.Reference); - return; - } - - if (IsNumeric (type)) { - context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); - return; - } - - if (type == typeof(bool)) { - context.Output.Write ((bool)value ? '1' : '0'); - return; - } - - if (IsStructureInstance (type)) { - WriteStructureValue (context, (StructureInstance?)value); - return; - } - - if (type == typeof(IntPtr)) { - // Pointers can only be `null` or a reference to variable - context.Output.Write ("null"); - return; - } - - if (type == typeof(string)) { - if (value == null) { - context.Output.Write ("null"); - return; - } - - LlvmIrStringVariable sv = context.Module.LookupRequiredVariableForString ((string)value); - context.Output.Write (sv.Reference); - return; - } - - if (type.IsInlineArray ()) { - - } - - throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); - } - - void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance) - { - if (instance == null || instance.IsZeroInitialized) { - context.Output.Write ("zeroinitializer"); - return; - } - - context.Output.WriteLine ('{'); - context.IncreaseIndent (); - - StructureInfo info = instance.Info; - int lastMember = info.Members.Count - 1; - - for (int i = 0; i < info.Members.Count; i++) { - StructureMemberInfo smi = info.Members[i]; - - context.Output.Write (context.CurrentIndent); - WriteType (context, instance, smi, out _); - context.Output.Write (' '); - - object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); - WriteValue (context, instance, smi, value); - - if (i < lastMember) { - context.Output.Write (", "); - } - - string? comment = info.GetCommentFromProvider (smi, instance); - if (String.IsNullOrEmpty (comment)) { - var sb = new StringBuilder (" "); - sb.Append (MapManagedTypeToNative (smi)); - sb.Append (' '); - sb.Append (smi.Info.Name); - if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { - sb.Append ($" (0x{value:x})"); - } - comment = sb.ToString (); - } - WriteCommentLine (context, comment); - } - - context.DecreaseIndent (); - context.Output.Write (context.CurrentIndent); - context.Output.Write ('}'); - } - - void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) - { - context.Output.WriteLine ('['); - context.IncreaseIndent (); - - ICollection entries; - if (variable.Type.ImplementsInterface (typeof(IDictionary))) { - var list = new List (); - foreach (var kvp in (IDictionary)variable.Value) { - list.Add (kvp.Key); - list.Add (kvp.Value); - } - entries = list; - } else { - entries = (ICollection)variable.Value; - } - - Type elementType = variable.Type.GetArrayElementType (); - bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; - ulong counter = 0; - string? prevItemComment = null; - uint stride; - - if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) { - stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1; - } else { - stride = 1; - } - - bool first = true; - - // TODO: implement output in rows - foreach (object entry in entries) { - if (!first) { - context.Output.Write (','); - WritePrevItemCommentOrNewline (); - } else { - first = false; - } - - prevItemComment = null; - if (variable.GetArrayItemCommentCallback != null) { - prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); - } - - if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { - prevItemComment = $" {counter}"; - } - - counter++; - context.Output.Write (context.CurrentIndent); - WriteType (context, elementType, entry, out _); - - context.Output.Write (' '); - WriteValue (context, elementType, entry); - } - WritePrevItemCommentOrNewline (); - - context.DecreaseIndent (); - context.Output.Write (']'); - - void WritePrevItemCommentOrNewline () - { - if (!String.IsNullOrEmpty (prevItemComment)) { - context.Output.Write (' '); - WriteCommentLine (context, prevItemComment); - } else { - context.Output.WriteLine (); - } - } - } - - void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage) - { - if (linkage == LlvmIrLinkage.Default) { - return; - } - - try { - WriteAttribute (context, llvmLinkage[linkage]); - } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported writability '{linkage}'", ex); - } - } - - void WriteWritability (GeneratorWriteContext context, LlvmIrWritability writability) - { - try { - WriteAttribute (context, llvmWritability[writability]); - } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); - } - } - - void WriteAddressSignificance (GeneratorWriteContext context, LlvmIrAddressSignificance addressSignificance) - { - if (addressSignificance == LlvmIrAddressSignificance.Default) { - return; - } - - try { - WriteAttribute (context, llvmAddressSignificance[addressSignificance]); - } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported address significance '{addressSignificance}'", ex); - } - } - - void WriteVisibility (GeneratorWriteContext context, LlvmIrVisibility visibility) - { - if (visibility == LlvmIrVisibility.Default) { - return; - } - - try { - WriteAttribute (context, llvmVisibility[visibility]); - } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported visibility '{visibility}'", ex); - } - } - - void WritePreemptionSpecifier (GeneratorWriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) - { - if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { - return; - } - - try { - WriteAttribute (context, llvmRuntimePreemption[preemptionSpecifier]); - } catch (Exception ex) { - throw new InvalidOperationException ($"Internal error: unsupported preemption specifier '{preemptionSpecifier}'", ex); - } - } - - /// - /// Write attribute named in followed by a single space - /// - void WriteAttribute (GeneratorWriteContext context, string attr) - { - context.Output.Write (attr); - context.Output.Write (' '); - } - - void WriteStructureDeclarations (GeneratorWriteContext context) - { - if (context.Module.Structures == null || context.Module.Structures.Count == 0) { - return; - } - - foreach (StructureInfo si in context.Module.Structures) { - context.Output.WriteLine (); - WriteStructureDeclaration (context, si); - } - } - - void WriteStructureDeclaration (GeneratorWriteContext context, StructureInfo si) - { - // $"%{typeDesignator}.{name} = type " - context.Output.Write ('%'); - context.Output.Write (si.NativeTypeDesignator); - context.Output.Write ('.'); - context.Output.Write (si.Name); - context.Output.Write (" = type "); - - if (si.IsOpaque) { - context.Output.WriteLine ("opaque"); - } else { - context.Output.WriteLine ('{'); - } - - if (si.IsOpaque) { - return; - } - - context.IncreaseIndent (); - for (int i = 0; i < si.Members.Count; i++) { - StructureMemberInfo info = si.Members[i]; - string nativeType = MapManagedTypeToNative (info.MemberType); - - // TODO: nativeType can be an array, update to indicate that (and get the size) - string arraySize; - if (info.IsNativeArray) { - arraySize = $"[{info.ArrayElements}]"; - } else { - arraySize = String.Empty; - } - - var comment = $" {nativeType} {info.Info.Name}{arraySize}"; - WriteStructureDeclarationField (info.IRType, comment, i == si.Members.Count - 1); - } - context.DecreaseIndent (); - - context.Output.WriteLine ('}'); - - void WriteStructureDeclarationField (string typeName, string comment, bool last) - { - context.Output.Write (context.CurrentIndent); - context.Output.Write (typeName); - if (!last) { - context.Output.Write (", "); - } else { - context.Output.Write (' '); - } - - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (context, comment); - } else { - context.Output.WriteLine (); - } - } - } - - // - // Functions syntax: https://llvm.org/docs/LangRef.html#functions - // - void WriteFunctions (GeneratorWriteContext context) - { - if (context.Module.Functions == null || context.Module.Functions.Count == 0) { - return; - } - - context.Output.WriteLine (); - WriteComment (context, " Functions"); - - foreach (LlvmIrFunction function in context.Module.Functions) { - context.Output.WriteLine (); - WriteFunctionComment (context, function); - - // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags - ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); - WriteFunctionDefinitionLeadingDecorations (context, function); - WriteFunctionSignature (context, function, writeParameterNames: true); - WriteFunctionDefinitionTrailingDecorations (context, function); - WriteFunctionBody (context, function); - function.RestoreState (funcState); - } - } - - void WriteFunctionComment (GeneratorWriteContext context, LlvmIrFunction function) - { - if (String.IsNullOrEmpty (function.Comment)) { - return; - } - - foreach (string commentLine in function.Comment.Split ('\n')) { - context.Output.Write (context.CurrentIndent); - WriteCommentLine (context, commentLine); - } - } - - void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) - { - context.Output.WriteLine (); - context.Output.WriteLine ('{'); - context.IncreaseIndent (); - - foreach (LlvmIrFunctionBodyItem item in function.Body.Items) { - item.Write (context, this); - } - - context.DecreaseIndent (); - context.Output.WriteLine ('}'); - } - - ILlvmIrSavedFunctionState WriteFunctionPreamble (GeneratorWriteContext context, LlvmIrFunction function, string keyword) - { - ILlvmIrSavedFunctionState funcState = function.SaveState (); - - foreach (LlvmIrFunctionParameter parameter in function.Signature.Parameters) { - target.SetParameterFlags (parameter); - } - - WriteFunctionAttributesComment (context, function); - context.Output.Write (keyword); - context.Output.Write (' '); - - return funcState; - } - - void WriteExternalFunctionDeclarations (GeneratorWriteContext context) - { - if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { - return; - } - - context.Output.WriteLine (); - WriteComment (context, " External functions"); - foreach (LlvmIrFunction function in context.Module.ExternalFunctions) { - context.Output.WriteLine (); - - // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) - ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "declare"); - WriteFunctionDeclarationLeadingDecorations (context, function); - WriteFunctionSignature (context, function, writeParameterNames: false); - WriteFunctionDeclarationTrailingDecorations (context, function); - - function.RestoreState (funcState); - } - } - - void WriteFunctionAttributesComment (GeneratorWriteContext context, LlvmIrFunction func) - { - if (func.AttributeSet == null) { - return; - } - - if (String.IsNullOrEmpty (func.Comment)) { - context.Output.WriteLine (); - } - WriteCommentLine (context, $" Function attributes: {func.AttributeSet.Render ()}"); - } - - void WriteFunctionDeclarationLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) - { - WriteFunctionLeadingDecorations (context, func, declaration: true); - } - - void WriteFunctionDefinitionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) - { - WriteFunctionLeadingDecorations (context, func, declaration: false); - } - - void WriteFunctionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) - { - if (func.Linkage != LlvmIrLinkage.Default) { - context.Output.Write (llvmLinkage[func.Linkage]); - context.Output.Write (' '); - } - - if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { - context.Output.Write (llvmRuntimePreemption[func.RuntimePreemption]); - context.Output.Write (' '); - } - - if (func.Visibility != LlvmIrVisibility.Default) { - context.Output.Write (llvmVisibility[func.Visibility]); - context.Output.Write (' '); - } - } - - void WriteFunctionDeclarationTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) - { - WriteFunctionTrailingDecorations (context, func, declaration: true); - } - - void WriteFunctionDefinitionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) - { - WriteFunctionTrailingDecorations (context, func, declaration: false); - } - - void WriteFunctionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) - { - if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { - context.Output.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); - } - - if (func.AttributeSet != null) { - context.Output.Write ($" #{func.AttributeSet.Number}"); - } - } - - public static void WriteReturnAttributes (GeneratorWriteContext context, LlvmIrFunctionSignature.ReturnTypeAttributes returnAttrs) - { - if (AttributeIsSet (returnAttrs.NoUndef)) { - context.Output.Write ("noundef "); - } - - if (AttributeIsSet (returnAttrs.SignExt)) { - context.Output.Write ("signext "); - } - - if (AttributeIsSet (returnAttrs.ZeroExt)) { - context.Output.Write ("zeroext "); - } - } - - void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, bool writeParameterNames) - { - if (func.ReturnsValue) { - WriteReturnAttributes (context, func.Signature.ReturnAttributes); - } - - context.Output.Write (MapToIRType (func.Signature.ReturnType)); - context.Output.Write (" @"); - context.Output.Write (func.Signature.Name); - context.Output.Write ('('); - - bool first = true; - foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { - if (!first) { - context.Output.Write (", "); - } else { - first = false; - } - - context.Output.Write (MapToIRType (parameter.Type)); - WriteParameterAttributes (context, parameter); - if (writeParameterNames) { - if (String.IsNullOrEmpty (parameter.Name)) { - throw new InvalidOperationException ($"Internal error: parameter must have a name"); - } - context.Output.Write (" %"); // Function arguments are always local variables - context.Output.Write (parameter.Name); - } - } - - context.Output.Write (')'); - } - - public static void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) - { - var attributes = new List (); - if (AttributeIsSet (parameter.ImmArg)) { - attributes.Add ("immarg"); - } - - if (AttributeIsSet (parameter.AllocPtr)) { - attributes.Add ("allocptr"); - } - - if (AttributeIsSet (parameter.NoCapture)) { - attributes.Add ("nocapture"); - } - - if (AttributeIsSet (parameter.NonNull)) { - attributes.Add ("nonnull"); - } - - if (AttributeIsSet (parameter.NoUndef)) { - attributes.Add ("noundef"); - } - - if (AttributeIsSet (parameter.ReadNone)) { - attributes.Add ("readnone"); - } - - if (AttributeIsSet (parameter.SignExt)) { - attributes.Add ("signext"); - } - - if (AttributeIsSet (parameter.ZeroExt)) { - attributes.Add ("zeroext"); - } - - if (parameter.Align.HasValue) { - attributes.Add ($"align({ValueOrPointerSize (parameter.Align.Value)})"); - } - - if (parameter.Dereferenceable.HasValue) { - attributes.Add ($"dereferenceable({ValueOrPointerSize (parameter.Dereferenceable.Value)})"); - } - - if (attributes.Count == 0) { - return; - } - - context.Output.Write (' '); - context.Output.Write (String.Join (" ", attributes)); - - uint ValueOrPointerSize (uint? value) - { - if (value.Value == 0) { - return context.Target.NativePointerSize; - } - - return value.Value; - } - } - - static bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; - - void WriteAttributeSets (GeneratorWriteContext context) - { - if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { - return; - } - - context.Output.WriteLine (); - foreach (LlvmIrFunctionAttributeSet attrSet in context.Module.AttributeSets) { - // Must not modify the original set, it is shared with other targets. - var targetSet = new LlvmIrFunctionAttributeSet (attrSet); - target.AddTargetSpecificAttributes (targetSet); - - IList? privateTargetSet = attrSet.GetPrivateTargetAttributes (target.TargetArch); - if (privateTargetSet != null) { - targetSet.Add (privateTargetSet); - } - - context.Output.WriteLine ($"attributes #{targetSet.Number} = {{ {targetSet.Render ()} }}"); - } - } - - void WriteMetadata (GeneratorWriteContext context) - { - if (context.MetadataManager.Items.Count == 0) { - return; - } - - context.Output.WriteLine (); - WriteCommentLine (context, " Metadata"); - foreach (LlvmIrMetadataItem metadata in context.MetadataManager.Items) { - context.Output.WriteLine (metadata.Render ()); - } - } - - public void WriteComment (GeneratorWriteContext context, string comment) - { - context.Output.Write (';'); - context.Output.Write (comment); - } - - public void WriteCommentLine (GeneratorWriteContext context, string comment) - { - WriteComment (context, comment); - context.Output.WriteLine (); - } - - 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 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 (); - } - - static string MapManagedTypeToNative (StructureMemberInfo smi) - { - string nativeType = MapManagedTypeToNative (smi.MemberType); - // Silly, but effective - if (nativeType[nativeType.Length - 1] == '*') { - return nativeType; - } - - if (!smi.IsNativePointer) { - return nativeType; - } - - return $"{nativeType}*"; - } - - static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; - - object? GetTypedMemberValue (GeneratorWriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) - { - object? value = smi.GetValue (instance.Obj); - if (value == null) { - return defaultValue; - } - - Type valueType = value.GetType (); - if (valueType != expectedType) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should have a value of '{expectedType}' type, instead it had a '{value.GetType ()}'"); - } - - if (valueType == typeof(string)) { - return context.Module.LookupRequiredVariableForString ((string)value); - } - - return value; - } - - public static string MapToIRType (Type type) - { - return MapToIRType (type, out _, out _); - } - - public static string MapToIRType (Type type, out ulong size) - { - return MapToIRType (type, out size, out _); - } - - public static string MapToIRType (Type type, out bool isPointer) - { - return MapToIRType (type, out _, out isPointer); - } - - /// - /// Maps managed type to equivalent IR type. Puts type size in and whether or not the type - /// is a pointer in . When a type is determined to be a pointer, - /// will be set to 0, because this method doesn't have access to the generator target. In order to adjust pointer - /// size, the instance method must be called (private to the generator as other classes should not - /// have any need to know the pointer size). - /// - public static string MapToIRType (Type type, out ulong size, out bool isPointer) - { - type = GetActualType (type); - if (!type.IsNativePointer () && basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { - size = typeDesc.Size; - isPointer = false; - return typeDesc.Name; - } - - // if it's not a basic type, then it's an opaque pointer - size = 0; // Will be determined by the specific target architecture class - isPointer = true; - return IRPointerType; - } - - string GetIRType (Type type, out ulong size, out bool isPointer) - { - string ret = MapToIRType (type, out size, out isPointer); - if (isPointer && size == 0) { - size = target.NativePointerSize; - } - - return ret; - } - - public static bool IsFirstClassNonPointerType (Type type) - { - if (type == typeof(void)) { - return false; - } - - return basicTypeMap.ContainsKey (type); - } - - 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, bytes.Length, out _, nullTerminated: false); - } - - public static string QuoteString (string value, out ulong stringSize, bool nullTerminated = true) - { - var encoding = Encoding.UTF8; - int byteCount = encoding.GetByteCount (value); - var bytes = ArrayPool.Shared.Rent (byteCount); - try { - encoding.GetBytes (value, 0, value.Length, bytes, 0); - return QuoteString (bytes, byteCount, out stringSize, nullTerminated); - } finally { - ArrayPool.Shared.Return (bytes); - } - } - - public static string QuoteString (byte[] bytes, int byteCount, out ulong stringSize, bool nullTerminated = true) - { - var sb = new StringBuilder (byteCount * 2); // rough estimate of capacity - - byte b; - for (int i = 0; i < byteCount; i++) { - b = bytes [i]; - if (b != '"' && b != '\\' && b >= 32 && b < 127) { - sb.Append ((char)b); - continue; - } - - sb.Append ('\\'); - sb.Append ($"{b:X2}"); - } - - stringSize = (ulong) byteCount; - if (nullTerminated) { - stringSize++; - sb.Append ("\\00"); - } - - return QuoteStringNoEscape (sb.ToString ()); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 139e079c4e7..202d04a48c3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -9,1481 +10,1284 @@ namespace Xamarin.Android.Tasks.LLVMIR { - /// - /// Base class for all classes which implement architecture-specific code generators. - /// - abstract partial class LlvmIrGenerator + sealed class GeneratorStructureInstance : StructureInstance { - internal sealed class StructureBodyWriterOptions + public GeneratorStructureInstance (StructureInfo info, object instance) + : base (info, instance) + {} + } + + sealed class GeneratorWriteContext + { + const char IndentChar = '\t'; + + int currentIndentLevel = 0; + + public readonly TextWriter Output; + public readonly LlvmIrModule Module; + public readonly LlvmIrModuleTarget Target; + public readonly LlvmIrMetadataManager MetadataManager; + public string CurrentIndent { get; private set; } = String.Empty; + public bool InVariableGroup { get; set; } + + public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager) + { + Output = writer; + Module = module; + Target = target; + MetadataManager = metadataManager; + } + + public void IncreaseIndent () + { + currentIndentLevel++; + CurrentIndent = MakeIndentString (); + } + + public void DecreaseIndent () { - public readonly bool WriteFieldComment; - public readonly string FieldIndent; - public readonly string StructIndent; - public readonly TextWriter? StructureOutput; - public readonly TextWriter? StringsOutput; - public readonly TextWriter? BuffersOutput; + if (currentIndentLevel > 0) { + currentIndentLevel--; + } + CurrentIndent = MakeIndentString (); + } + + string MakeIndentString () => currentIndentLevel > 0 ? new String (IndentChar, currentIndentLevel) : String.Empty; + } - public StructureBodyWriterOptions (bool writeFieldComment, string fieldIndent = "", string structIndent = "", - TextWriter? structureOutput = null, TextWriter? stringsOutput = null, TextWriter? buffersOutput = null) + partial class LlvmIrGenerator + { + sealed class LlvmTypeInfo + { + public readonly bool IsPointer; + public readonly bool IsAggregate; + public readonly bool IsStructure; + public readonly ulong Size; + public readonly ulong MaxFieldAlignment; + + public LlvmTypeInfo (bool isPointer, bool isAggregate, bool isStructure, ulong size, ulong maxFieldAlignment) { - WriteFieldComment = writeFieldComment; - FieldIndent = fieldIndent; - StructIndent = structIndent; - StructureOutput = structureOutput; - StringsOutput = stringsOutput; - BuffersOutput = buffersOutput; + IsPointer = isPointer; + IsAggregate = isAggregate; + IsStructure = isStructure; + Size = size; + MaxFieldAlignment = maxFieldAlignment; } } - sealed class PackedStructureMember + sealed class BasicType { - public readonly string ValueIRType; - public readonly string? PaddingIRType; - public readonly object? Value; - public readonly bool IsPadded; - public readonly StructureMemberInfo MemberInfo; + public readonly string Name; + public readonly ulong Size; + public readonly bool IsNumeric; - public PackedStructureMember (StructureMemberInfo memberInfo, object? value, string? valueIRType = null, string? paddingIRType = null) + public BasicType (string name, ulong size, bool isNumeric = true) { - ValueIRType = valueIRType ?? memberInfo.IRType; - Value = value; - MemberInfo = memberInfo; - PaddingIRType = paddingIRType; - IsPadded = !String.IsNullOrEmpty (paddingIRType); - } - } - - static readonly Dictionary typeMap = new Dictionary { - { typeof (bool), "i8" }, - { typeof (byte), "i8" }, - { typeof (char), "i16" }, - { typeof (sbyte), "i8" }, - { typeof (short), "i16" }, - { typeof (ushort), "i16" }, - { typeof (int), "i32" }, - { typeof (uint), "i32" }, - { typeof (long), "i64" }, - { typeof (ulong), "i64" }, - { typeof (float), "float" }, - { typeof (double), "double" }, - { typeof (string), "i8*" }, - { typeof (IntPtr), "i8*" }, - { typeof (void), "void" }, + Name = name; + Size = size; + IsNumeric = isNumeric; + } + } + + public const string IRPointerType = "ptr"; + + static readonly Dictionary basicTypeMap = new Dictionary { + { typeof (bool), new ("i8", 1, isNumeric: false) }, + { typeof (byte), new ("i8", 1) }, + { typeof (char), new ("i16", 2) }, + { typeof (sbyte), new ("i8", 1) }, + { typeof (short), new ("i16", 2) }, + { typeof (ushort), new ("i16", 2) }, + { typeof (int), new ("i32", 4) }, + { typeof (uint), new ("i32", 4) }, + { typeof (long), new ("i64", 8) }, + { typeof (ulong), new ("i64", 8) }, + { typeof (float), new ("float", 4) }, + { typeof (double), new ("double", 8) }, + { typeof (void), new ("void", 0, isNumeric: false) }, }; - // https://llvm.org/docs/LangRef.html#single-value-types - static readonly Dictionary typeSizes = new Dictionary { - { typeof (bool), 1 }, - { typeof (byte), 1 }, - { typeof (char), 2 }, - { 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 - }; + public string FilePath { get; } + public string FileName { get; } - // 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" }, - }; + LlvmIrModuleTarget target; - // 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" }, - }; + protected LlvmIrGenerator (string filePath, LlvmIrModuleTarget target) + { + FilePath = Path.GetFullPath (filePath); + FileName = Path.GetFileName (filePath); + this.target = target; + } - // https://llvm.org/docs/LangRef.html#visibility-styles - static readonly Dictionary llvmVisibility = new Dictionary { - { LlvmIrVisibility.Default, "default" }, - { LlvmIrVisibility.Hidden, "hidden" }, - { LlvmIrVisibility.Protected, "protected" }, - }; + public static LlvmIrGenerator Create (AndroidTargetArch arch, string fileName) + { + return arch switch { + AndroidTargetArch.Arm => new LlvmIrGenerator (fileName, new LlvmIrModuleArmV7a ()), + AndroidTargetArch.Arm64 => new LlvmIrGenerator (fileName, new LlvmIrModuleAArch64 ()), + AndroidTargetArch.X86 => new LlvmIrGenerator (fileName, new LlvmIrModuleX86 ()), + AndroidTargetArch.X86_64 => new LlvmIrGenerator (fileName, new LlvmIrModuleX64 ()), + _ => throw new InvalidOperationException ($"Unsupported Android target ABI {arch}") + }; + } - // 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" }, - }; + public void Generate (TextWriter writer, LlvmIrModule module) + { + LlvmIrMetadataManager metadataManager = module.GetMetadataManagerCopy (); + target.AddTargetSpecificMetadata (metadataManager); - // https://llvm.org/docs/LangRef.html#global-variables - static readonly Dictionary llvmWritability = new Dictionary { - { LlvmIrWritability.Constant, "constant" }, - { LlvmIrWritability.Writable, "global" }, - }; + var context = new GeneratorWriteContext (writer, module, target, metadataManager); + if (!String.IsNullOrEmpty (FilePath)) { + WriteCommentLine (context, $" ModuleID = '{FileName}'"); + context.Output.WriteLine ($"source_filename = \"{FileName}\""); + } - static readonly LlvmIrVariableOptions preAllocatedBufferVariableOptions = new LlvmIrVariableOptions { - Writability = LlvmIrWritability.Writable, - Linkage = LlvmIrLinkage.Internal, - }; + context.Output.WriteLine (target.DataLayout.Render ()); + context.Output.WriteLine ($"target triple = \"{target.Triple}\""); + WriteStructureDeclarations (context); + WriteGlobalVariables (context); + WriteFunctions (context); - string fileName; - ulong stringCounter = 0; - ulong structStringCounter = 0; - ulong structBufferCounter = 0; + // Bottom of the file + WriteStrings (context); + WriteExternalFunctionDeclarations (context); + WriteAttributeSets (context); + WriteMetadata (context); + } - List structures = new List (); - Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); - LlvmIrMetadataItem llvmModuleFlags; + void WriteStrings (GeneratorWriteContext context) + { + if (context.Module.Strings == null || context.Module.Strings.Count == 0) { + return; + } - public const string Indent = "\t"; + context.Output.WriteLine (); + WriteComment (context, " Strings"); - protected abstract string DataLayout { get; } - public abstract int PointerSize { get; } - protected abstract string Triple { get; } + foreach (LlvmIrStringGroup group in context.Module.Strings) { + context.Output.WriteLine (); - public bool Is64Bit { get; } - public TextWriter Output { get; } - public AndroidTargetArch TargetArch { get; } + if (!String.IsNullOrEmpty (group.Comment)) { + WriteCommentLine (context, group.Comment); + } - protected LlvmIrMetadataManager MetadataManager { get; } - protected LlvmIrStringManager StringManager { get; } + foreach (LlvmIrStringVariable info in group.Strings) { + string s = QuoteString ((string)info.Value, out ulong size); - protected LlvmIrGenerator (AndroidTargetArch arch, TextWriter output, string fileName) - { - Output = output; - MetadataManager = new LlvmIrMetadataManager (); - StringManager = new LlvmIrStringManager (); - TargetArch = arch; - Is64Bit = arch == AndroidTargetArch.X86_64 || arch == AndroidTargetArch.Arm64; - this.fileName = fileName; + WriteGlobalVariableStart (context, info); + context.Output.Write ('['); + context.Output.Write (size.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x i8] c"); + context.Output.Write (s); + context.Output.Write (", align "); + context.Output.WriteLine (target.GetAggregateAlignment (1, size).ToString (CultureInfo.InvariantCulture)); + } + } } - /// - /// 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) + void WriteGlobalVariables (GeneratorWriteContext context) { - LlvmIrGenerator ret = Instantiate (); - ret.Init (); - return ret; + if (context.Module.GlobalVariables == null || context.Module.GlobalVariables.Count == 0) { + return; + } - 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}") - }; + foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { + if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { + if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) { + context.Output.WriteLine (); + context.Output.Write (context.CurrentIndent); + WriteComment (context, groupDelimiter.Comment); + } + + context.InVariableGroup = !context.InVariableGroup; + if (context.InVariableGroup) { + context.Output.WriteLine (); + } + continue; + } + + if (gv.BeforeWriteCallback != null) { + gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); + } + WriteGlobalVariable (context, gv); } } - static string EnsureIrType (Type type) + void WriteGlobalVariableStart (GeneratorWriteContext context, LlvmIrGlobalVariable variable) { - if (!typeMap.TryGetValue (type, out string irType)) { - throw new InvalidOperationException ($"Unsupported managed type {type}"); + if (!String.IsNullOrEmpty (variable.Comment)) { + WriteCommentLine (context, variable.Comment); } + context.Output.Write ('@'); + context.Output.Write (variable.Name); + context.Output.Write (" = "); - return irType; + LlvmIrVariableOptions options = variable.Options ?? LlvmIrGlobalVariable.DefaultOptions; + WriteLinkage (context, options.Linkage); + WritePreemptionSpecifier (context, options.RuntimePreemption); + WriteVisibility (context, options.Visibility); + WriteAddressSignificance (context, options.AddressSignificance); + WriteWritability (context, options.Writability); } - static Type GetActualType (Type type) + void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable variable) { - // Arrays of types are handled elsewhere, so we obtain the array base type here - if (type.IsArray) { - return type.GetElementType (); + if (!context.InVariableGroup) { + context.Output.WriteLine (); } - return type; - } + WriteGlobalVariableStart (context, variable); + WriteTypeAndValue (context, variable, out LlvmTypeInfo typeInfo); + context.Output.Write (", align "); - /// - /// 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)); + ulong alignment; + if (typeInfo.IsAggregate) { + ulong count = GetAggregateValueElementCount (variable); + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); + } else if (typeInfo.IsStructure) { + alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); + } else if (typeInfo.IsPointer) { + alignment = target.NativePointerSize; + } else { + alignment = typeInfo.Size; + } + + context.Output.WriteLine (alignment.ToString (CultureInfo.InvariantCulture)); } - /// - /// 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) + void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - Type actualType = GetActualType (type); - string irType = EnsureIrType (actualType); - size = GetTypeSize (actualType); + WriteType (context, variable, out typeInfo); + context.Output.Write (' '); + + Type valueType; + if (variable.Value is LlvmIrVariable referencedVariable) { + valueType = referencedVariable.Type; + } else { + valueType = variable.Value?.GetType () ?? variable.Type; + } + + if (variable.Value == null) { + // Order of checks is important here. Aggregates can contain pointer types, in which case typeInfo.IsPointer + // will be `true` and the aggregate would be incorrectly initialized with `null` instead of the correct + // `zeroinitializer` + if (typeInfo.IsAggregate) { + WriteValue (context, valueType, variable); + return; + } + + if (typeInfo.IsPointer) { + context.Output.Write ("null"); + return; + } + + throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); + } + + if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { + throw new InvalidOperationException ($"Internal error: variable type '{variable.Type}' is different to its value type, '{valueType}'"); + } - return irType; + WriteValue (context, valueType, variable); } - ulong GetTypeSize (Type actualType) + ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable); + + ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) { - if (!typeSizes.TryGetValue (actualType, out ulong size)) { - if (actualType == typeof (string) || actualType == typeof (IntPtr) || actualType == typeof (LlvmNativeFunctionSignature)) { - size = (ulong)PointerSize; - } else { - throw new InvalidOperationException ($"Unsupported managed type {actualType}"); + if (!type.IsArray ()) { + throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); + } + + if (value == null) { + if (globalVariable != null) { + return globalVariable.ArrayItemCount; } + return 0; + } + + // TODO: use caching here + if (type.ImplementsInterface (typeof(IDictionary))) { + return (uint)((IDictionary)value).Count * 2; } - return size; + if (type.ImplementsInterface (typeof(ICollection))) { + return (uint)((ICollection)value).Count; + } + + throw new InvalidOperationException ($"Internal error: should never get here"); } - /// - /// 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) + void WriteType (GeneratorWriteContext context, LlvmIrVariable variable, out LlvmTypeInfo typeInfo) { - return MapManagedTypeToIR (typeof(T), out size); + WriteType (context, variable.Type, variable.Value, out typeInfo, variable as LlvmIrGlobalVariable); } - /// - /// Map a managed to its C++ counterpart. Only primitive types, - /// string and IntPtr are supported. - /// - public static string MapManagedTypeToNative (Type type) + void WriteType (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo memberInfo, out LlvmTypeInfo typeInfo) { - 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*"; + if (memberInfo.IsNativePointer) { + typeInfo = new LlvmTypeInfo ( + isPointer: true, + isAggregate: false, + isStructure: false, + size: target.NativePointerSize, + maxFieldAlignment: target.NativePointerSize + ); - return type.GetShortName (); - } + context.Output.Write (IRPointerType); + return; + } - public string GetIRType (out ulong size, T? value = default) - { - if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { - if (value == null) { - throw new ArgumentNullException (nameof (value)); - } + if (memberInfo.IsInlineArray) { + WriteArrayType (context, memberInfo.MemberType.GetArrayElementType (), memberInfo.ArrayElements, out typeInfo); + return; + } - size = (ulong)PointerSize; - return RenderFunctionSignature ((LlvmNativeFunctionSignature)(object)value); + if (memberInfo.IsIRStruct ()) { + var sim = new GeneratorStructureInstance (context.Module.GetStructureInfo (memberInfo.MemberType), memberInfo.GetValue (si.Obj)); + WriteStructureType (context, sim, out typeInfo); + return; } - return MapManagedTypeToIR (out size); + WriteType (context, memberInfo.MemberType, value: null, out typeInfo); } - public string GetKnownIRType (Type type) + void WriteStructureType (GeneratorWriteContext context, StructureInstance si, out LlvmTypeInfo typeInfo) { - if (type == null) { - throw new ArgumentNullException (nameof (type)); - } + ulong alignment = GetStructureMaxFieldAlignment (si.Info); - if (type.IsNativeClass ()) { - IStructureInfo si = GetStructureInfo (type); - return $"%{si.NativeTypeDesignator}.{si.Name}"; - } + typeInfo = new LlvmTypeInfo ( + isPointer: false, + isAggregate: false, + isStructure: true, + size: si.Info.Size, + maxFieldAlignment: alignment + ); - return MapManagedTypeToIR (type); + context.Output.Write ('%'); + context.Output.Write (si.Info.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Info.Name); } - public string GetValue (T value) + void WriteType (GeneratorWriteContext context, Type type, object? value, out LlvmTypeInfo typeInfo, LlvmIrGlobalVariable? globalVariable = null) { - if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { + if (IsStructureInstance (type)) { if (value == null) { - throw new ArgumentNullException (nameof (value)); - } - - var v = (LlvmNativeFunctionSignature)(object)value; - if (v.FieldValue != null) { - return MonoAndroidHelper.CultureInvariantToString (v.FieldValue); + throw new ArgumentException ("must not be null for structure instances", nameof (value)); } - return MonoAndroidHelper.CultureInvariantToString (v); + WriteStructureType (context, (StructureInstance)value, out typeInfo); + return; } - return MonoAndroidHelper.CultureInvariantToString (value) ?? String.Empty; - } - - /// - /// Initialize the generator. It involves adding required LLVM IR module metadata (such as data model specification, - /// code generation flags etc) - /// - protected virtual void Init () - { - llvmModuleFlags = MetadataManager.Add ("llvm.module.flags"); - LlvmIrMetadataItem ident = MetadataManager.Add ("llvm.ident"); + string irType; + ulong size; + bool isPointer; - var flagsFields = new List (); - AddModuleFlagsMetadata (flagsFields); + if (type.IsArray ()) { + Type elementType = type.GetArrayElementType (); + ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable); - foreach (LlvmIrMetadataItem item in flagsFields) { - llvmModuleFlags.AddReferenceField (item.Name); + WriteArrayType (context, elementType, elementCount, out typeInfo); + return; } - LlvmIrMetadataItem identValue = MetadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); - ident.AddReferenceField (identValue.Name); + irType = GetIRType (type, out size, out isPointer); + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: false, + isStructure: false, + size: size, + maxFieldAlignment: size + ); + context.Output.Write (irType); } - protected void AddLlvmModuleFlag (LlvmIrMetadataItem flag) + void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) { - llvmModuleFlags.AddReferenceField (flag.Name); + string irType; + ulong size; + ulong maxFieldAlignment; + bool isPointer; + + if (elementType.IsStructureInstance (out Type? structureType)) { + StructureInfo si = context.Module.GetStructureInfo (structureType); + + irType = $"%{si.NativeTypeDesignator}.{si.Name}"; + size = si.Size; + maxFieldAlignment = GetStructureMaxFieldAlignment (si); + isPointer = false; + } else { + irType = GetIRType (elementType, out size, out isPointer); + maxFieldAlignment = size; + } + typeInfo = new LlvmTypeInfo ( + isPointer: isPointer, + isAggregate: true, + isStructure: false, + size: size, + maxFieldAlignment: maxFieldAlignment + ); + + context.Output.Write ('['); + context.Output.Write (elementCount.ToString (CultureInfo.InvariantCulture)); + context.Output.Write (" x "); + context.Output.Write (irType); + context.Output.Write (']'); } - /// - /// Since LLVM IR is strongly typed, it requires each structure to be properly declared before it is - /// used throughout the code. This method uses reflection to scan the managed type - /// and record the information for future use. The returned structure contains - /// the description. It is used later on not only to declare the structure in output code, but also to generate - /// data from instances of . This method is typically called from the - /// method. - /// - public StructureInfo MapStructure () + ulong GetStructureMaxFieldAlignment (StructureInfo si) { - Type t = typeof(T); - if (!t.IsClass && !t.IsValueType) { - throw new InvalidOperationException ($"{t} must be a class or a struct"); + if (si.HasPointers && target.NativePointerSize > si.MaxFieldAlignment) { + return target.NativePointerSize; } - var ret = new StructureInfo (this); - structures.Add (ret); - - return ret; + return si.MaxFieldAlignment; } - internal IStructureInfo GetStructureInfo (Type type) - { - IStructureInfo? ret = null; + bool IsStructureInstance (Type t) => typeof(StructureInstance).IsAssignableFrom (t); - foreach (IStructureInfo si in structures) { - if (si.Type != type) { - continue; + void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable) + { + if (variable.Type.IsArray ()) { + bool zeroInitialize = false; + if (variable is LlvmIrGlobalVariable gv) { + zeroInitialize = gv.ZeroInitializeArray || variable.Value == null; + } else { + zeroInitialize = GetAggregateValueElementCount (variable) == 0; } - ret = si; - break; - } + if (zeroInitialize) { + context.Output.Write ("zeroinitializer"); + return; + } - if (ret == null) { - throw new InvalidOperationException ($"Unmapped structure {type}"); + WriteArrayValue (context, variable); + return; } - return ret; + WriteValue (context, valueType, variable.Value); } - TextWriter EnsureOutput (TextWriter? output) + void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong length, ulong expectedLength) { - return output ?? Output; + if (length == expectedLength) { + return; + } + + throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}"); } - void WriteGlobalSymbolStart (string symbolName, LlvmIrVariableOptions options, TextWriter? output = null) + void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) { - output = EnsureOutput (output); + if (smi.IsNativePointer) { + if (WriteNativePointerValue (context, structInstance, smi, value)) { + return; + } + } - output.Write ('@'); - output.Write (symbolName); - output.Write (" = "); + if (smi.IsInlineArray) { + Array a = (Array)value; + ulong length = smi.ArrayElements == 0 ? (ulong)a.Length : smi.ArrayElements; - string linkage = llvmLinkage [options.Linkage]; - if (!string.IsNullOrEmpty (linkage)) { - output.Write (linkage); - output.Write (' '); - } - if (options.AddressSignificance != LlvmIrAddressSignificance.Default) { - output.Write (llvmAddressSignificance[options.AddressSignificance]); - output.Write (' '); - } + if (smi.MemberType == typeof(byte[])) { + var bytes = (byte[])value; - output.Write (llvmWritability[options.Writability]); - output.Write (' '); - } + // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte + AssertArraySize (structInstance, smi, length, smi.ArrayElements); + context.Output.Write ('c'); + context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + return; + } - object? GetTypedMemberValue (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) - { - object? value = smi.GetValue (instance.Obj); - if (value == null) { - return defaultValue; + throw new NotSupportedException ($"Internal error: inline arrays of type {smi.MemberType} aren't supported at this point. Field {smi.Info.Name} in structure {structInstance.Info.Name}"); } - if (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 (smi.IsIRStruct ()) { + StructureInfo si = context.Module.GetStructureInfo (smi.MemberType); + WriteValue (context, typeof(GeneratorStructureInstance), new GeneratorStructureInstance (si, value)); + return; } - if (expectedType == typeof(bool)) { - return (bool)value ? 1 : 0; + if (smi.Info.IsNativePointerToPreallocatedBuffer (out _)) { + string bufferVariableName = context.Module.LookupRequiredBufferVariableName (structInstance, smi); + context.Output.Write ('@'); + context.Output.Write (bufferVariableName); + return; } - return value; + WriteValue (context, smi.MemberType, value); } - bool MaybeWriteStructureString (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance si, StructureMemberInfo smi, object? value) { - if (smi.MemberType != typeof(string)) { - return false; - } + // Structure members decorated with the [NativePointer] attribute cannot have a + // value other than `null`, unless they are strings or references to symbols - output = EnsureOutput (output); - string? str = (string?)GetTypedMemberValue (info, smi, instance, typeof(string), null); - if (str == null) { - instance.AddPointerData (smi, null, 0); - return false; + if (smi.Info.PointsToSymbol (out string? symbolName)) { + if (String.IsNullOrEmpty (symbolName) && smi.Info.UsesDataProvider ()) { + if (si.Info.DataProvider == null) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{si.Info.Name}' points to a symbol, but symbol name wasn't provided and there's no configured data context provider"); + } + symbolName = si.Info.DataProvider.GetPointedToSymbolName (si.Obj, smi.Info.Name); + } + + if (String.IsNullOrEmpty (symbolName)) { + context.Output.Write ("null"); + } else { + context.Output.Write ('@'); + context.Output.Write (symbolName); + } + return true; } - StringSymbolInfo stringSymbol = StringManager.Add (str, groupName: info.Name, symbolSuffix: smi.Info.Name);//WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter); - instance.AddPointerData (smi, stringSymbol.SymbolName, stringSymbol.Size); + if (smi.MemberType != typeof(string)) { + context.Output.Write ("null"); + return true; + } - return true; + return false; } - bool MaybeWritePreAllocatedBuffer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter? output = null) + void WriteValue (GeneratorWriteContext context, Type type, object? value) { - if (!smi.Info.IsNativePointerToPreallocatedBuffer (out ulong bufferSize)) { - return false; + if (value is LlvmIrVariable variableRef) { + context.Output.Write (variableRef.Reference); + return; } - if (smi.Info.UsesDataProvider ()) { - bufferSize = info.GetBufferSizeFromProvider (smi, instance); + if (IsNumeric (type)) { + context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + return; } - output = EnsureOutput (output); - string irType = MapManagedTypeToIR (smi.MemberType); - string variableName = $"__{info.Name}_{smi.Info.Name}_{structBufferCounter.ToString (CultureInfo.InvariantCulture)}"; - structBufferCounter++; + if (type == typeof(bool)) { + context.Output.Write ((bool)value ? '1' : '0'); + return; + } - WriteGlobalSymbolStart (variableName, preAllocatedBufferVariableOptions, output); - ulong size = bufferSize * smi.BaseTypeSize; + if (IsStructureInstance (type)) { + WriteStructureValue (context, (StructureInstance?)value); + return; + } - // WriteLine $"[{bufferSize} x {irType}] zeroinitializer, align {GetAggregateAlignment ((int)smi.BaseTypeSize, size)}" - output.Write ('['); - output.Write (bufferSize.ToString (CultureInfo.InvariantCulture)); - output.Write (" x "); - output.Write (irType); - output.Write ("] zeroinitializer, align "); - output.WriteLine (GetAggregateAlignment ((int) smi.BaseTypeSize, size).ToString (CultureInfo.InvariantCulture)); + if (type == typeof(IntPtr)) { + // Pointers can only be `null` or a reference to variable + context.Output.Write ("null"); + return; + } - instance.AddPointerData (smi, variableName, size); - return true; - } + if (type == typeof(string)) { + if (value == null) { + context.Output.Write ("null"); + return; + } - 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)); + LlvmIrStringVariable sv = context.Module.LookupRequiredVariableForString ((string)value); + context.Output.Write (sv.Reference); + return; } - bool named = !String.IsNullOrEmpty (symbolName); - if (named || !String.IsNullOrEmpty (initialComment)) { - WriteEOL (output: output); - WriteEOL (initialComment ?? symbolName, output); - } + if (type.IsInlineArray ()) { - if (named) { - WriteGlobalSymbolStart (symbolName, options, output); } - return named; + throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } - void WriteStructureArrayEnd (StructureInfo info, string? symbolName, ulong count, bool named, bool skipFinalComment = false, TextWriter? output = null, bool isArrayOfPointers = false) + void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance) { - output = EnsureOutput (output); - - int alignment = isArrayOfPointers ? PointerSize : GetAggregateAlignment (info.MaxFieldAlignment, info.Size * count); - output.Write (", align "); - output.Write (alignment.ToString (CultureInfo.InvariantCulture)); - if (named && !skipFinalComment) { - WriteEOL ($"end of '{symbolName!}' array", output); - } else { - WriteEOL (output: output); + if (instance == null || instance.IsZeroInitialized) { + context.Output.Write ("zeroinitializer"); + return; } - } - /// - /// Writes an array of zero-initialized entries. specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructureArray (StructureInfo info, ulong count, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, bool isArrayOfPointers = false) - { - bool named = WriteStructureArrayStart (info, null, options, symbolName, initialComment); + context.Output.WriteLine ('{'); + context.IncreaseIndent (); - // $"[{count} x %{info.NativeTypeDesignator}.{info.Name}{pointerAsterisk}] zeroinitializer" - Output.Write ('['); - Output.Write (count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x %"); - Output.Write (info.NativeTypeDesignator); - Output.Write ('.'); - Output.Write (info.Name); - if (isArrayOfPointers) - Output.Write ('*'); - Output.Write ("] zeroinitializer"); + StructureInfo info = instance.Info; + int lastMember = info.Members.Count - 1; - WriteStructureArrayEnd (info, symbolName, (ulong)count, named, skipFinalComment: true, isArrayOfPointers: isArrayOfPointers); - } + for (int i = 0; i < info.Members.Count; i++) { + StructureMemberInfo smi = info.Members[i]; - /// - /// Writes an array of zero-initialized entries. The array will be generated as a local, writable symbol. - /// - public void WriteStructureArray (StructureInfo info, ulong count, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, bool isArrayOfPointers = false) - { - WriteStructureArray (info, count, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment, isArrayOfPointers); - } + context.Output.Write (context.CurrentIndent); + WriteType (context, instance, smi, out _); + context.Output.Write (' '); - /// - /// Writes an array of managed type , with data optionally specified in (if it's null, the array - /// will be zero-initialized). specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructureArray (StructureInfo info, IList>? instances, LlvmIrVariableOptions options, - string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, - Action? nestedStructureWriter = null) - { - var arrayOutput = new StringWriter (); - bool named = WriteStructureArrayStart (info, instances, options, symbolName, initialComment, arrayOutput); - int count = instances != null ? instances.Count : 0; - - // $"[{count} x %{info.NativeTypeDesignator}.{info.Name}] " - arrayOutput.Write ('['); - arrayOutput.Write (count.ToString (CultureInfo.InvariantCulture)); - arrayOutput.Write (" x %"); - arrayOutput.Write (info.NativeTypeDesignator); - arrayOutput.Write ('.'); - arrayOutput.Write (info.Name); - arrayOutput.Write ("] "); - - if (instances != null) { - var bodyWriterOptions = new StructureBodyWriterOptions ( - writeFieldComment: true, - fieldIndent: $"{Indent}{Indent}", - structIndent: Indent, - structureOutput: arrayOutput, - stringsOutput: info.HasStrings ? new StringWriter () : null, - buffersOutput: info.HasPreAllocatedBuffers ? new StringWriter () : null - ); + object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); + WriteValue (context, instance, smi, value); - arrayOutput.WriteLine ('['); - for (int i = 0; i < count; i++) { - StructureInstance instance = instances[i]; + if (i < lastMember) { + context.Output.Write (", "); + } - arrayOutput.Write (Indent); - arrayOutput.Write ("; "); - arrayOutput.WriteLine (i.ToString (CultureInfo.InvariantCulture)); - WriteStructureBody (info, instance, bodyWriterOptions, nestedStructureWriter); - if (i < count - 1) { - arrayOutput.Write (", "); + string? comment = info.GetCommentFromProvider (smi, instance); + if (String.IsNullOrEmpty (comment)) { + var sb = new StringBuilder (" "); + sb.Append (MapManagedTypeToNative (smi)); + sb.Append (' '); + sb.Append (smi.Info.Name); + if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { + sb.Append ($" (0x{value:x})"); } - WriteEOL (output: arrayOutput); + comment = sb.ToString (); } - arrayOutput.Write (']'); - - WriteBufferToOutput (bodyWriterOptions.StringsOutput); - WriteBufferToOutput (bodyWriterOptions.BuffersOutput); - } else { - arrayOutput.Write ("zeroinitializer"); + WriteCommentLine (context, comment); } - 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); + context.DecreaseIndent (); + context.Output.Write (context.CurrentIndent); + context.Output.Write ('}'); } - public void WriteArray (IList values, string symbolName, string? initialComment = null) + void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) { - WriteEOL (); - WriteEOL (initialComment ?? symbolName); - - var strings = new List (); - foreach (string s in values) { - StringSymbolInfo symbol = StringManager.Add (s, groupName: symbolName); - strings.Add (symbol); + ICollection entries; + if (variable.Type.ImplementsInterface (typeof(IDictionary))) { + var list = new List (); + foreach (var kvp in (IDictionary)variable.Value) { + list.Add (kvp.Key); + list.Add (kvp.Value); + } + entries = list; + } else { + entries = (ICollection)variable.Value; } - if (strings.Count > 0) { - Output.WriteLine (); + if (entries.Count == 0) { + context.Output.Write ("zeroinitializer"); + return; } - WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); - } + context.Output.WriteLine ('['); + context.IncreaseIndent (); - public void WriteArray (IList values, LlvmIrVariableOptions options, string symbolName, Func? commentProvider = null) where T: struct - { - bool optimizeOutput = commentProvider == null; + Type elementType = variable.Type.GetArrayElementType (); + bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + ulong counter = 0; + string? prevItemComment = null; + uint stride; - WriteGlobalSymbolStart (symbolName, options); - string elementType = MapManagedTypeToIR (typeof (T), out ulong size); + if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) { + stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1; + } else { + stride = 1; + } - // WriteLine $"[{values.Count} x {elementType}] [" - Output.Write ('['); - Output.Write (values.Count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x "); - Output.Write (elementType); - Output.WriteLine ("] ["); + bool first = true; - Output.Write (Indent); - for (int i = 0; i < values.Count; i++) { - if (i != 0) { - if (optimizeOutput) { - Output.Write (','); - if (i % 8 == 0) { - Output.Write (" ; "); - Output.Write (i - 8); - Output.Write (".."); - Output.WriteLine (i - 1); + // TODO: implement output in rows + foreach (object entry in entries) { + if (!first) { + context.Output.Write (','); + WritePrevItemCommentOrNewline (); + } else { + first = false; + } - Output.Write (Indent); - } else { - Output.Write (' '); - } - } else { - Output.Write (Indent); - } + prevItemComment = null; + if (variable.GetArrayItemCommentCallback != null) { + prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); } - Output.Write (elementType); - Output.Write (' '); - Output.Write (MonoAndroidHelper.CultureInvariantToString (values [i])); + if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { + prevItemComment = $" {counter}"; + } - if (!optimizeOutput) { - bool last = i == values.Count - 1; - if (!last) { - Output.Write (','); - } + counter++; + context.Output.Write (context.CurrentIndent); + WriteType (context, elementType, entry, out _); - string? comment = commentProvider (i, values[i]); - if (!String.IsNullOrEmpty (comment)) { - Output.Write (" ; "); - Output.Write (comment); - } + context.Output.Write (' '); + WriteValue (context, elementType, entry); + } + WritePrevItemCommentOrNewline (); - if (!last) { - Output.WriteLine (); - } + context.DecreaseIndent (); + context.Output.Write (']'); + + void WritePrevItemCommentOrNewline () + { + if (!String.IsNullOrEmpty (prevItemComment)) { + context.Output.Write (' '); + WriteCommentLine (context, prevItemComment); + } else { + context.Output.WriteLine (); } } - if (optimizeOutput && values.Count / 8 != 0) { - int idx = values.Count - (values.Count % 8); - Output.Write (" ; "); - Output.Write (idx); - Output.Write (".."); - Output.Write (values.Count - 1); - } - - Output.WriteLine (); - Output.Write ("], align "); - Output.WriteLine (GetAggregateAlignment ((int) size, size * (ulong) values.Count).ToString (CultureInfo.InvariantCulture)); } - void AssertArraySize (StructureInfo info, StructureMemberInfo smi, ulong length, ulong expectedLength) + void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage) { - if (length == expectedLength) { + if (linkage == LlvmIrLinkage.Default) { return; } - throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{info.Name}', expected {expectedLength}, found {length}"); + try { + WriteAttribute (context, llvmLinkage[linkage]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{linkage}'", ex); + } } - void RenderArray (StructureInfo info, StructureMemberInfo smi, byte[] bytes, TextWriter output, ulong? expectedArraySize = null) + void WriteWritability (GeneratorWriteContext context, LlvmIrWritability writability) { - // Byte arrays are represented in the same way as strings, without the explicit NUL termination byte - AssertArraySize (info, smi, expectedArraySize ?? (ulong)bytes.Length, smi.ArrayElements); - output.Write ('c'); - output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + try { + WriteAttribute (context, llvmWritability[writability]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported writability '{writability}'", ex); + } } - void MaybeWriteStructureStringsAndBuffers (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options) + void WriteAddressSignificance (GeneratorWriteContext context, LlvmIrAddressSignificance addressSignificance) { - if (options.StringsOutput != null) { - MaybeWriteStructureString (info, smi, instance, options.StringsOutput); + if (addressSignificance == LlvmIrAddressSignificance.Default) { + return; } - if (options.BuffersOutput != null) { - MaybeWritePreAllocatedBuffer (info, smi, instance, options.BuffersOutput); + try { + WriteAttribute (context, llvmAddressSignificance[addressSignificance]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported address significance '{addressSignificance}'", ex); } } - void WriteStructureField (StructureInfo info, StructureInstance instance, StructureMemberInfo smi, int fieldIndex, - StructureBodyWriterOptions options, TextWriter output, object? valueOverride = null, ulong? expectedArraySize = null, - Action? nestedStructureWriter = null) + void WriteVisibility (GeneratorWriteContext context, LlvmIrVisibility visibility) { - object? value = null; - - if (smi.IsIRStruct ()) { - if (nestedStructureWriter == null) { - throw new InvalidOperationException ($"Nested structure found in type {typeof(T)}, field {smi.Info.Name} but no nested structure writer provided"); - } - nestedStructureWriter (this, options, smi.MemberType, valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType)); - } else if (smi.IsNativePointer) { - output.Write (options.FieldIndent); - WritePointer (info, smi, instance, output); - } else if (smi.IsNativeArray) { - if (!smi.IsInlineArray) { - throw new InvalidOperationException ($"Out of line arrays aren't supported at this time (structure '{info.Name}', field '{smi.Info.Name}')"); - } - - output.Write (options.FieldIndent); - output.Write (smi.IRType); - output.Write (" "); - 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; - output.Write (options.FieldIndent); - WritePrimitiveField (info, smi, instance, output); + if (visibility == LlvmIrVisibility.Default) { + return; } - FinishStructureField (info, smi, instance, options, fieldIndex, value, output); - } - - void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options, Action? nestedStructureWriter = null) - { - TextWriter structureOutput = EnsureOutput (options.StructureOutput); - - // $"{options.StructIndent}%{info.NativeTypeDesignator}.{info.Name} " - structureOutput.Write (options.StructIndent); - structureOutput.Write ('%'); - structureOutput.Write (info.NativeTypeDesignator); - structureOutput.Write ('.'); - structureOutput.Write (info.Name); - structureOutput.Write (' '); - - if (instance != null) { - structureOutput.WriteLine ('{'); - for (int i = 0; i < info.Members.Count; i++) { - StructureMemberInfo smi = info.Members[i]; - - MaybeWriteStructureStringsAndBuffers (info, smi, instance, options); - WriteStructureField (info, instance, smi, i, options, structureOutput, nestedStructureWriter: nestedStructureWriter); - } - - structureOutput.Write (options.StructIndent); - structureOutput.Write ('}'); - } else { - structureOutput.Write ("zeroinitializer"); + try { + WriteAttribute (context, llvmVisibility[visibility]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported visibility '{visibility}'", ex); } } - void MaybeWriteFieldComment (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options, object? value, TextWriter output) + void WritePreemptionSpecifier (GeneratorWriteContext context, LlvmIrRuntimePreemption preemptionSpecifier) { - if (!options.WriteFieldComment) { + if (preemptionSpecifier == LlvmIrRuntimePreemption.Default) { return; } - string? comment = info.GetCommentFromProvider (smi, instance); - if (String.IsNullOrEmpty (comment)) { - var sb = new StringBuilder (smi.Info.Name); - if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) { - sb.Append (" (0x"); - sb.Append ($"{value:x}"); - sb.Append (')'); - } - comment = sb.ToString (); + try { + WriteAttribute (context, llvmRuntimePreemption[preemptionSpecifier]); + } catch (Exception ex) { + throw new InvalidOperationException ($"Internal error: unsupported preemption specifier '{preemptionSpecifier}'", ex); } - WriteComment (output, comment); } - void FinishStructureField (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, StructureBodyWriterOptions options, int fieldIndex, object? value, TextWriter output) + /// + /// Write attribute named in followed by a single space + /// + void WriteAttribute (GeneratorWriteContext context, string attr) { - if (fieldIndex < info.Members.Count - 1) { - output.Write (", "); - } - MaybeWriteFieldComment (info, smi, instance, options, value, output); - WriteEOL (output); + context.Output.Write (attr); + context.Output.Write (' '); } - void WritePrimitiveField (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter output, object? overrideValue = null) + void WriteStructureDeclarations (GeneratorWriteContext context) { - object? value = overrideValue ?? GetTypedMemberValue (info, smi, instance, smi.MemberType); - output.Write (smi.IRType); - output.Write (' '); - output.Write (MonoAndroidHelper.CultureInvariantToString (value)); + if (context.Module.Structures == null || context.Module.Structures.Count == 0) { + return; + } + + foreach (StructureInfo si in context.Module.Structures) { + context.Output.WriteLine (); + WriteStructureDeclaration (context, si); + } } - void WritePointer (StructureInfo info, StructureMemberInfo smi, StructureInstance instance, TextWriter output, object? overrideValue = null) + void WriteStructureDeclaration (GeneratorWriteContext context, StructureInfo si) { - if (info.HasStrings) { - StructurePointerData? spd = instance.GetPointerData (smi); - if (spd != null) { - WriteGetStringPointer (spd.VariableName, spd.Size, indent: false, output: output); - return; - } + // $"%{typeDesignator}.{name} = type " + context.Output.Write ('%'); + context.Output.Write (si.NativeTypeDesignator); + context.Output.Write ('.'); + context.Output.Write (si.Name); + context.Output.Write (" = type "); + + if (si.IsOpaque) { + context.Output.WriteLine ("opaque"); + } else { + context.Output.WriteLine ('{'); } - if (info.HasPreAllocatedBuffers) { - StructurePointerData? spd = instance.GetPointerData (smi); - if (spd != null) { - WriteGetBufferPointer (spd.VariableName, smi.IRType, spd.Size, indent: false, output: output); - return; - } + if (si.IsOpaque) { + 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); - } + context.IncreaseIndent (); + for (int i = 0; i < si.Members.Count; i++) { + StructureMemberInfo info = si.Members[i]; + string nativeType = MapManagedTypeToNative (info.MemberType); - if (String.IsNullOrEmpty (symbolName)) { - WriteNullPointer (smi, output); - return; + // 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; } - ulong bufferSize = info.GetBufferSizeFromProvider (smi, instance); - WriteGetBufferPointer (symbolName, smi.IRType, bufferSize, indent: false, output: output); - return; + var comment = $" {nativeType} {info.Info.Name}{arraySize}"; + WriteStructureDeclarationField (info.IRType, comment, i == si.Members.Count - 1); } + context.DecreaseIndent (); - object? value = overrideValue ?? smi.GetValue (instance.Obj); - if (value == null || ((value is IntPtr) && (IntPtr)value == IntPtr.Zero)) { - WriteNullPointer (smi, output); - return; - } + context.Output.WriteLine ('}'); - if (value.GetType ().IsPrimitive) { - ulong v = Convert.ToUInt64 (value); - if (v == 0) { - WriteNullPointer (smi, output); - return; + void WriteStructureDeclarationField (string typeName, string comment, bool last) + { + context.Output.Write (context.CurrentIndent); + context.Output.Write (typeName); + if (!last) { + context.Output.Write (", "); + } else { + context.Output.Write (' '); } - } - - throw new InvalidOperationException ($"While processing field '{smi.Info.Name}' of type '{info.Name}': non-null pointers to objects of managed type '{smi.MemberType}' (IR type '{smi.IRType}') currently not supported (value: {value})"); - } - void WriteNullPointer (StructureMemberInfo smi, TextWriter output) - { - output.Write (smi.IRType); - output.Write (" null"); + if (!String.IsNullOrEmpty (comment)) { + WriteCommentLine (context, comment); + } else { + context.Output.WriteLine (); + } + } } - // 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) + // Functions syntax: https://llvm.org/docs/LangRef.html#functions // - 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}')"); - } + void WriteFunctions (GeneratorWriteContext context) + { + if (context.Module.Functions == null || context.Module.Functions.Count == 0) { + return; + } - var array = (byte[])value; - var arrayLength = (ulong)array.Length; + context.Output.WriteLine (); + WriteComment (context, " Functions"); - if (arrayLength > smi.ArrayElements) { - throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should not have more than {smi.ArrayElements} elements"); - } + foreach (LlvmIrFunction function in context.Module.Functions) { + context.Output.WriteLine (); + WriteFunctionComment (context, function); - ulong padding = smi.ArrayElements - arrayLength; - if (padding == 0) { - members.Add (new PackedStructureMember (smi, value)); - continue; - } + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "define"); + WriteFunctionDefinitionLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: true); + WriteFunctionDefinitionTrailingDecorations (context, function); + WriteFunctionBody (context, function); + function.RestoreState (funcState); + } + } - if (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; - } + void WriteFunctionComment (GeneratorWriteContext context, LlvmIrFunction function) + { + if (String.IsNullOrEmpty (function.Comment)) { + return; + } - members.Add (new PackedStructureMember (smi, value, valueIRType: $"[{arrayLength} x i8]", paddingIRType: $"[{padding} x i8]")); - hasPaddedFields = true; - } + foreach (string commentLine in function.Comment.Split ('\n')) { + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, commentLine); + } + } - bool firstField; - instanceType.Clear (); - if (!hasPaddedFields) { - instanceType.Append ("\t%"); - instanceType.Append (info.NativeTypeDesignator); - instanceType.Append ('.'); - instanceType.Append (info.Name); - } else { - instanceType.Append ("\t{ "); - - firstField = true; - foreach (PackedStructureMember psm in members) { - if (!firstField) { - instanceType.Append (", "); - } else { - firstField = false; - } - - if (!psm.IsPadded) { - instanceType.Append (psm.ValueIRType); - continue; - } - - // $"<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}>" - instanceType.Append ("<{ "); - instanceType.Append (psm.ValueIRType); - instanceType.Append (", "); - instanceType.Append (psm.PaddingIRType); - instanceType.Append (" }>"); - } + void WriteFunctionBody (GeneratorWriteContext context, LlvmIrFunction function) + { + context.Output.WriteLine (); + context.Output.WriteLine ('{'); + context.IncreaseIndent (); - instanceType.Append (" }"); - } - structureTypeOutput.Write (instanceType.ToString ()); + foreach (LlvmIrFunctionBodyItem item in function.Body.Items) { + item.Write (context, this); + } - structureBodyOutput.Write (instanceType.ToString ()); - structureBodyOutput.WriteLine (" {"); + context.DecreaseIndent (); + context.Output.WriteLine ('}'); + } - firstField = true; - bool previousFieldWasPadded = false; - for (int i = 0; i < members.Count; i++) { - PackedStructureMember psm = members[i]; + ILlvmIrSavedFunctionState WriteFunctionPreamble (GeneratorWriteContext context, LlvmIrFunction function, string keyword) + { + ILlvmIrSavedFunctionState funcState = function.SaveState (); - if (firstField) { - firstField = false; - } + foreach (LlvmIrFunctionParameter parameter in function.Signature.Parameters) { + target.SetParameterFlags (parameter); + } - 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; - } + WriteFunctionAttributesComment (context, function); + context.Output.Write (keyword); + context.Output.Write (' '); - if (!firstField && previousFieldWasPadded) { - structureBodyOutput.Write (", "); - } + return funcState; + } - // $"{bodyWriterOptions.FieldIndent}<{{ {psm.ValueIRType}, {psm.PaddingIRType} }}> <{{ {psm.ValueIRType} c{QuoteString ((byte[])psm.Value)}, {psm.PaddingIRType} zeroinitializer }}> " - structureBodyOutput.Write (bodyWriterOptions.FieldIndent); - structureBodyOutput.Write ("<{ "); - structureBodyOutput.Write (psm.ValueIRType); - structureBodyOutput.Write (", "); - structureBodyOutput.Write (psm.PaddingIRType); - structureBodyOutput.Write (" }> <{ "); - structureBodyOutput.Write (psm.ValueIRType); - structureBodyOutput.Write (" c"); - structureBodyOutput.Write (QuoteString ((byte []) psm.Value)); - structureBodyOutput.Write (", "); - structureBodyOutput.Write (psm.PaddingIRType); - structureBodyOutput.Write (" zeroinitializer }> "); - - MaybeWriteFieldComment (info, psm.MemberInfo, instance, bodyWriterOptions, value: null, output: structureBodyOutput); - previousFieldWasPadded = true; - } - structureBodyOutput.WriteLine (); - structureBodyOutput.Write (Indent); - structureBodyOutput.Write ('}'); + void WriteExternalFunctionDeclarations (GeneratorWriteContext context) + { + if (context.Module.ExternalFunctions == null || context.Module.ExternalFunctions.Count == 0) { + return; } - structureOutput.WriteLine ("<{"); - structureOutput.Write (structureTypeOutput); - structureOutput.WriteLine (); - structureOutput.WriteLine ("}>"); + context.Output.WriteLine (); + WriteComment (context, " External functions"); + foreach (LlvmIrFunction function in context.Module.ExternalFunctions) { + context.Output.WriteLine (); - structureOutput.WriteLine ("<{"); - structureOutput.Write (structureBodyOutput); - structureOutput.WriteLine (); - structureOutput.Write ("}>"); + // Must preserve state between calls, different targets may modify function state differently (e.g. set different parameter flags) + ILlvmIrSavedFunctionState funcState = WriteFunctionPreamble (context, function, "declare"); + WriteFunctionDeclarationLeadingDecorations (context, function); + WriteFunctionSignature (context, function, writeParameterNames: false); + WriteFunctionDeclarationTrailingDecorations (context, function); - FinishStructureWrite (info, bodyWriterOptions); + function.RestoreState (funcState); + } } - StructureBodyWriterOptions InitStructureWrite (StructureInfo info, LlvmIrVariableOptions options, string? symbolName, bool writeFieldComment, string? fieldIndent = null) + void WriteFunctionAttributesComment (GeneratorWriteContext context, LlvmIrFunction func) { - if (options.IsGlobal && String.IsNullOrEmpty (symbolName)) { - throw new ArgumentException ("must not be null or empty for global symbols", nameof (symbolName)); + if (func.AttributeSet == null) { + return; } - var structureOutput = new StringWriter (); - bool named = !String.IsNullOrEmpty (symbolName); - if (named) { - WriteEOL (output: structureOutput); - WriteEOL (symbolName, structureOutput); - - WriteGlobalSymbolStart (symbolName, options, structureOutput); + if (String.IsNullOrEmpty (func.Comment)) { + context.Output.WriteLine (); } - - return new StructureBodyWriterOptions ( - writeFieldComment: writeFieldComment, - fieldIndent: fieldIndent ?? Indent, - structureOutput: structureOutput, - stringsOutput: info.HasStrings ? new StringWriter () : null, - buffersOutput: info.HasPreAllocatedBuffers ? new StringWriter () : null - ); + WriteCommentLine (context, $" Function attributes: {func.AttributeSet.Render ()}"); } - void FinishStructureWrite (StructureInfo info, StructureBodyWriterOptions bodyWriterOptions) + void WriteFunctionDeclarationLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - bodyWriterOptions.StructureOutput.Write (", align "); - bodyWriterOptions.StructureOutput.WriteLine (info.MaxFieldAlignment.ToString (CultureInfo.InvariantCulture)); - - WriteBufferToOutput (bodyWriterOptions.StringsOutput); - WriteBufferToOutput (bodyWriterOptions.BuffersOutput); - WriteBufferToOutput (bodyWriterOptions.StructureOutput); + WriteFunctionLeadingDecorations (context, func, declaration: true); } - public void WriteStructure (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions bodyWriterOptions, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) + void WriteFunctionDefinitionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - WriteStructureBody (info, instance, bodyWriterOptions); - FinishStructureWrite (info, bodyWriterOptions); + WriteFunctionLeadingDecorations (context, func, declaration: false); } - public void WriteNestedStructure (StructureInfo info, StructureInstance instance, StructureBodyWriterOptions bodyWriterOptions) + void WriteFunctionLeadingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { - var options = new StructureBodyWriterOptions ( - bodyWriterOptions.WriteFieldComment, - bodyWriterOptions.FieldIndent + Indent, - bodyWriterOptions.FieldIndent, // structure indent should start at the original struct's field column - bodyWriterOptions.StructureOutput, - bodyWriterOptions.StringsOutput, - bodyWriterOptions.BuffersOutput - ); - WriteStructureBody (info, instance, options); + if (func.Linkage != LlvmIrLinkage.Default) { + context.Output.Write (llvmLinkage[func.Linkage]); + context.Output.Write (' '); + } + + if (!declaration && func.RuntimePreemption != LlvmIrRuntimePreemption.Default) { + context.Output.Write (llvmRuntimePreemption[func.RuntimePreemption]); + context.Output.Write (' '); + } + + if (func.Visibility != LlvmIrVisibility.Default) { + context.Output.Write (llvmVisibility[func.Visibility]); + context.Output.Write (' '); + } } - /// - /// Write a structure represented by managed type , with optional data passed in (if null, the structure - /// is zero-initialized). specifies the symbol attributes (visibility, writeability etc) - /// - public void WriteStructure (StructureInfo info, StructureInstance? instance, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) + void WriteFunctionDeclarationTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - StructureBodyWriterOptions bodyWriterOptions = InitStructureWrite (info, options, symbolName, writeFieldComment); - WriteStructure (info, instance, bodyWriterOptions, options, symbolName, writeFieldComment); + WriteFunctionTrailingDecorations (context, func, declaration: true); } - /// - /// 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) + void WriteFunctionDefinitionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func) { - WriteStructure (info, instance, LlvmIrVariableOptions.Default, symbolName, writeFieldComment); + WriteFunctionTrailingDecorations (context, func, declaration: false); } - void WriteBufferToOutput (TextWriter? writer) + void WriteFunctionTrailingDecorations (GeneratorWriteContext context, LlvmIrFunction func, bool declaration) { - if (writer == null) { - return; + if (func.AddressSignificance != LlvmIrAddressSignificance.Default) { + context.Output.Write ($" {llvmAddressSignificance[func.AddressSignificance]}"); } - writer.Flush (); - string text = writer.ToString (); - if (text.Length > 0) { - Output.WriteLine (text); + if (func.AttributeSet != null) { + context.Output.Write ($" #{func.AttributeSet.Number}"); } } - void WriteGetStringPointer (string? variableName, ulong size, bool indent = true, TextWriter? output = null, bool detectBitness = false, bool skipPointerType = false) + public static void WriteReturnAttributes (GeneratorWriteContext context, LlvmIrFunctionSignature.ReturnTypeAttributes returnAttrs) { - WriteGetBufferPointer (variableName, "i8*", size, indent, output, detectBitness, skipPointerType); + if (AttributeIsSet (returnAttrs.NoUndef)) { + context.Output.Write ("noundef "); + } + + if (AttributeIsSet (returnAttrs.SignExt)) { + context.Output.Write ("signext "); + } + + if (AttributeIsSet (returnAttrs.ZeroExt)) { + context.Output.Write ("zeroext "); + } } - void WriteGetBufferPointer (string? variableName, string irType, ulong size, bool indent = true, TextWriter? output = null, bool detectBitness = false, bool skipPointerType = false) + void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, bool writeParameterNames) { - output = EnsureOutput (output); - if (indent) { - output.Write (Indent); + if (func.ReturnsValue) { + WriteReturnAttributes (context, func.Signature.ReturnAttributes); } - if (String.IsNullOrEmpty (variableName)) { - output.Write (irType); - output.Write (" null"); - } else { - string irBaseType; - if (irType[irType.Length - 1] == '*') { - irBaseType = irType.Substring (0, irType.Length - 1); + context.Output.Write (MapToIRType (func.Signature.ReturnType)); + context.Output.Write (" @"); + context.Output.Write (func.Signature.Name); + context.Output.Write ('('); + + bool first = true; + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + if (!first) { + context.Output.Write (", "); } else { - irBaseType = irType; + first = false; } - string indexType = detectBitness && Is64Bit ? "i64" : "i32"; - // $"{irType} getelementptr inbounds ([{size} x {irBaseType}], [{size} x {irBaseType}]* @{variableName}, i32 0, i32 0)" - if (!skipPointerType) { - output.Write (irType); + context.Output.Write (MapToIRType (parameter.Type)); + WriteParameterAttributes (context, parameter); + if (writeParameterNames) { + if (String.IsNullOrEmpty (parameter.Name)) { + throw new InvalidOperationException ($"Internal error: parameter must have a name"); + } + context.Output.Write (" %"); // Function arguments are always local variables + context.Output.Write (parameter.Name); } - - string sizeStr = size.ToString (CultureInfo.InvariantCulture); - output.Write (" getelementptr inbounds (["); - output.Write (sizeStr); - output.Write (" x "); - output.Write (irBaseType); - output.Write ("], ["); - output.Write (sizeStr); - output.Write (" x "); - output.Write (irBaseType); - output.Write ("]* @"); - output.Write (variableName); - output.Write (", "); - output.Write (indexType); - output.Write (" 0, "); - output.Write (indexType); - output.Write (" 0)"); } + + context.Output.Write (')'); } - /// - /// Write an array of name/value pairs. The array symbol will be global and non-writable. - /// - public void WriteNameValueArray (string symbolName, IDictionary arrayContents) + public static void WriteParameterAttributes (GeneratorWriteContext context, LlvmIrFunctionParameter parameter) { - WriteEOL (); - WriteEOL (symbolName); + var attributes = new List (); + if (AttributeIsSet (parameter.ImmArg)) { + attributes.Add ("immarg"); + } - var strings = new List (); - long i = 0; + if (AttributeIsSet (parameter.AllocPtr)) { + attributes.Add ("allocptr"); + } - foreach (var kvp in arrayContents) { - string name = kvp.Key; - string value = kvp.Value; - string iStr = i.ToString (CultureInfo.InvariantCulture); + if (AttributeIsSet (parameter.NoCapture)) { + attributes.Add ("nocapture"); + } - WriteArrayString (name, $"n_{iStr}"); - WriteArrayString (value, $"v_{iStr}"); - i++; + if (AttributeIsSet (parameter.NonNull)) { + attributes.Add ("nonnull"); } - if (strings.Count > 0) { - Output.WriteLine (); + if (AttributeIsSet (parameter.NoUndef)) { + attributes.Add ("noundef"); } - WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); + if (AttributeIsSet (parameter.ReadNone)) { + attributes.Add ("readnone"); + } - void WriteArrayString (string str, string symbolSuffix) - { - StringSymbolInfo symbol = StringManager.Add (str, groupName: symbolName, symbolSuffix: symbolSuffix); - strings.Add (symbol); + if (AttributeIsSet (parameter.SignExt)) { + attributes.Add ("signext"); } - } - void WriteStringArray (string symbolName, LlvmIrVariableOptions options, List strings) - { - WriteGlobalSymbolStart (symbolName, options); + if (AttributeIsSet (parameter.ZeroExt)) { + attributes.Add ("zeroext"); + } - // $"[{strings.Count} x i8*]" - Output.Write ('['); - Output.Write (strings.Count.ToString (CultureInfo.InvariantCulture)); - Output.Write (" x i8*]"); + if (parameter.Align.HasValue) { + attributes.Add ($"align({ValueOrPointerSize (parameter.Align.Value)})"); + } - if (strings.Count > 0) { - Output.WriteLine (" ["); + if (parameter.Dereferenceable.HasValue) { + attributes.Add ($"dereferenceable({ValueOrPointerSize (parameter.Dereferenceable.Value)})"); + } - for (int j = 0; j < strings.Count; j++) { - ulong size = strings[j].Size; - string varName = strings[j].SymbolName; + if (attributes.Count == 0) { + return; + } - // - // 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 (','); - } + context.Output.Write (' '); + context.Output.Write (String.Join (" ", attributes)); + + uint ValueOrPointerSize (uint? value) + { + if (value.Value == 0) { + return context.Target.NativePointerSize; } - WriteEOL (); - } else { - Output.Write (" zeroinitializer"); - } - var arraySize = (ulong)(strings.Count * PointerSize); - if (strings.Count > 0) { - Output.Write (']'); + return value.Value; } - Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (PointerSize, arraySize).ToString (CultureInfo.InvariantCulture)); } - /// - /// Wries a global, constant variable - /// - public void WriteVariable (string symbolName, T value) - { - WriteVariable (symbolName, value, LlvmIrVariableOptions.GlobalConstant); - } + static bool AttributeIsSet (bool? attr) => attr.HasValue && attr.Value; - public void WriteVariable (string symbolName, T value, LlvmIrVariableOptions options) + void WriteAttributeSets (GeneratorWriteContext context) { - if (typeof(T) == typeof(string)) { - WriteString (symbolName, (string)(object)value, options); + if (context.Module.AttributeSets == null || context.Module.AttributeSets.Count == 0) { return; } - WriteEOL (); - string irType = GetIRType (out ulong size, value); - WriteGlobalSymbolStart (symbolName, options); + context.Output.WriteLine (); + foreach (LlvmIrFunctionAttributeSet attrSet in context.Module.AttributeSets) { + // Must not modify the original set, it is shared with other targets. + var targetSet = new LlvmIrFunctionAttributeSet (attrSet); + target.AddTargetSpecificAttributes (targetSet); - Output.Write (irType); - Output.Write (' '); - Output.Write (GetValue (value)); - Output.Write (", align "); - Output.WriteLine (size); - } - - /// - /// Writes a global, C++ constexpr style string - /// - public string WriteString (string symbolName, string value) - { - return WriteString (symbolName, value, LlvmIrVariableOptions.GlobalConstexprString); - } + IList? privateTargetSet = attrSet.GetPrivateTargetAttributes (target.TargetArch); + if (privateTargetSet != null) { + targetSet.Add (privateTargetSet); + } - /// - /// - /// Writes a string with symbol options (writeability, visibility) options specified in the parameter. - /// - /// - /// If symbol is local, as per , then is used as a common prefix for a group of strings, with unique symbol - /// names assigned to each string added to the group. - /// - /// - public string WriteString (string symbolName, string value, LlvmIrVariableOptions options) - { - return WriteString (symbolName, value, options, out _); + context.Output.WriteLine ($"attributes #{targetSet.Number} = {{ {targetSet.Render ()} }}"); + } } - /// - /// - /// Writes a string with specified , and symbol options (writeability, visibility etc) specified in the - /// parameter. Places string size (in bytes) in . - /// - /// - /// If symbol is local, as per , then is used as a common prefix for a group of strings, with unique symbol - /// names assigned to each string added to the group. - /// - /// - /// Name of the native symbol which refers to the written string. - public string WriteString (string symbolName, string value, LlvmIrVariableOptions options, out ulong stringSize) + void WriteMetadata (GeneratorWriteContext context) { - StringSymbolInfo info = AddString (value, groupName: symbolName); - stringSize = info.Size; - if (!options.IsGlobal) { - return info.SymbolName; + if (context.MetadataManager.Items.Count == 0) { + return; } - string indexType = Is64Bit ? "i64" : "i32"; - WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); - WriteGetStringPointer (info.SymbolName, info.Size, indent: false, detectBitness: true); - Output.Write (", align "); - Output.WriteLine (GetAggregateAlignment (PointerSize, stringSize).ToString (CultureInfo.InvariantCulture)); - - return symbolName; - } - - public StringSymbolInfo AddString (string value, string? groupName = null) - { - return StringManager.Add (value, groupName: groupName); + context.Output.WriteLine (); + WriteCommentLine (context, " Metadata"); + foreach (LlvmIrMetadataItem metadata in context.MetadataManager.Items) { + context.Output.WriteLine (metadata.Render ()); + } } - public void WriteFileTop () + public void WriteComment (GeneratorWriteContext context, string comment) { - WriteCommentLine ($"ModuleID = '{fileName}'"); - WriteDirective ("source_filename", QuoteStringNoEscape (fileName)); - WriteDirective ("target datalayout", QuoteStringNoEscape (DataLayout)); - WriteDirective ("target triple", QuoteStringNoEscape (Triple)); + context.Output.Write (';'); + context.Output.Write (comment); } - public virtual void WriteFileEnd () + public void WriteCommentLine (GeneratorWriteContext context, string comment) { - Output.WriteLine (); - StringManager.Flush (this); - - Output.WriteLine (); - WriteAttributeSets (); - - foreach (LlvmIrMetadataItem metadata in MetadataManager.Items) { - Output.WriteLine (metadata.Render ()); - } + WriteComment (context, comment); + context.Output.WriteLine (); } - public void WriteStructureDeclarations () + static Type GetActualType (Type type) { - if (structures.Count == 0) { - return; + // Arrays of types are handled elsewhere, so we obtain the array base type here + if (type.IsArray) { + return type.GetElementType (); } - Output.WriteLine (); - foreach (IStructureInfo si in structures) { - si.RenderDeclaration (this); - } + return type; } - public void WriteStructureDeclarationStart (string typeDesignator, string name, bool forOpaqueType = false) + /// + /// Map a managed to its C++ counterpart. Only primitive types, + /// string and IntPtr are supported. + /// + public static string MapManagedTypeToNative (Type type) { - WriteEOL (); + Type baseType = GetActualType (type); - // $"%{typeDesignator}.{name} = type " - Output.Write ('%'); - Output.Write (typeDesignator); - Output.Write ('.'); - Output.Write (name); - Output.Write (" = type "); - - if (forOpaqueType) { - Output.WriteLine ("opaque"); - } else { - Output.WriteLine ("{"); - } - } + if (baseType == typeof (bool)) return "bool"; + if (baseType == typeof (byte)) return "uint8_t"; + if (baseType == typeof (char)) return "char"; + if (baseType == typeof (sbyte)) return "int8_t"; + if (baseType == typeof (short)) return "int16_t"; + if (baseType == typeof (ushort)) return "uint16_t"; + if (baseType == typeof (int)) return "int32_t"; + if (baseType == typeof (uint)) return "uint32_t"; + if (baseType == typeof (long)) return "int64_t"; + if (baseType == typeof (ulong)) return "uint64_t"; + if (baseType == typeof (float)) return "float"; + if (baseType == typeof (double)) return "double"; + if (baseType == typeof (string)) return "char*"; + if (baseType == typeof (IntPtr)) return "void*"; - public void WriteStructureDeclarationEnd () - { - Output.WriteLine ('}'); + return type.GetShortName (); } - public void WriteStructureDeclarationField (string typeName, string comment, bool last) + static string MapManagedTypeToNative (StructureMemberInfo smi) { - Output.Write (Indent); - Output.Write (typeName); - if (!last) { - Output.Write (","); + string nativeType = MapManagedTypeToNative (smi.MemberType); + // Silly, but effective + if (nativeType[nativeType.Length - 1] == '*') { + return nativeType; } - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (comment); - } else { - WriteEOL (); + if (!smi.IsNativePointer) { + return nativeType; } - } - protected virtual void AddModuleFlagsMetadata (List flagsFields) - { - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "wchar_size", 4)); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Max, "PIC Level", 2)); + return $"{nativeType}*"; } - // Alignment for arrays, structures and unions - protected virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) - { - return maxFieldAlignment; - } + static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric; - public void WriteCommentLine (string? comment = null, bool indent = false) + object? GetTypedMemberValue (GeneratorWriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null) { - WriteCommentLine (Output, comment, indent); - } - - public void WriteComment (TextWriter writer, string? comment = null, bool indent = false) - { - if (indent) { - writer.Write (Indent); + object? value = smi.GetValue (instance.Obj); + if (value == null) { + return defaultValue; } - writer.Write (';'); + Type valueType = value.GetType (); + if (valueType != expectedType) { + throw new InvalidOperationException ($"Field '{smi.Info.Name}' of structure '{info.Name}' should have a value of '{expectedType}' type, instead it had a '{value.GetType ()}'"); + } - if (!String.IsNullOrEmpty (comment)) { - writer.Write (' '); - writer.Write (comment); + if (valueType == typeof(string)) { + return context.Module.LookupRequiredVariableForString ((string)value); } + + return value; } - public void WriteCommentLine (TextWriter writer, string? comment = null, bool indent = false) + public static string MapToIRType (Type type) { - WriteComment (writer, comment, indent); - writer.WriteLine (); + return MapToIRType (type, out _, out _); } - public void WriteEOL (string? comment = null, TextWriter? output = null) + public static string MapToIRType (Type type, out ulong size) { - WriteEOL (EnsureOutput (output), comment); + return MapToIRType (type, out size, out _); } - public void WriteEOL (TextWriter writer, string? comment = null) + public static string MapToIRType (Type type, out bool isPointer) { - if (!String.IsNullOrEmpty (comment)) { - WriteCommentLine (writer, comment); - return; - } - writer.WriteLine (); + return MapToIRType (type, out _, out isPointer); } - public void WriteDirectiveWithComment (TextWriter writer, string name, string? comment, string? value) + /// + /// Maps managed type to equivalent IR type. Puts type size in and whether or not the type + /// is a pointer in . When a type is determined to be a pointer, + /// will be set to 0, because this method doesn't have access to the generator target. In order to adjust pointer + /// size, the instance method must be called (private to the generator as other classes should not + /// have any need to know the pointer size). + /// + public static string MapToIRType (Type type, out ulong size, out bool isPointer) { - writer.Write (name); - - if (!String.IsNullOrEmpty (value)) { - writer.Write (" = "); - writer.Write (value); + type = GetActualType (type); + if (!type.IsNativePointer () && basicTypeMap.TryGetValue (type, out BasicType typeDesc)) { + size = typeDesc.Size; + isPointer = false; + return typeDesc.Name; } - WriteEOL (writer, comment); + // if it's not a basic type, then it's an opaque pointer + size = 0; // Will be determined by the specific target architecture class + isPointer = true; + return IRPointerType; } - public void WriteDirectiveWithComment (string name, string? comment, string? value) + string GetIRType (Type type, out ulong size, out bool isPointer) { - WriteDirectiveWithComment (name, comment, value); - } + string ret = MapToIRType (type, out size, out isPointer); + if (isPointer && size == 0) { + size = target.NativePointerSize; + } - public void WriteDirective (TextWriter writer, string name, string? value) - { - WriteDirectiveWithComment (writer, name, comment: null, value: value); + return ret; } - public void WriteDirective (string name, string value) + public static bool IsFirstClassNonPointerType (Type type) { - WriteDirective (Output, name, value); + if (type == typeof(void)) { + return false; + } + + return basicTypeMap.ContainsKey (type); } public static string QuoteStringNoEscape (string s) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index f5a9f1d655c..aeecad461f4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -2,11 +2,7 @@ using System.Collections.Generic; using System.Globalization; -namespace Xamarin.Android.Tasks.LLVM.IR; - -// TODO: remove these aliases once the refactoring is done -using LlvmIrIcmpCond = LLVMIR.LlvmIrIcmpCond; -using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; +namespace Xamarin.Android.Tasks.LLVMIR; abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem { @@ -169,13 +165,20 @@ public class Call : LlvmIrInstruction LlvmIrVariable? result; public LlvmIrCallMarker CallMarker { get; set; } = LlvmIrCallMarker.None; + + /// + /// This needs to be set if we want to call a function via a local or global variable. passed + /// to the constructor is then used only to generate a type safe call, while function address comes from the variable assigned + /// to this property. + /// public LlvmIrVariable? FuncPointer { get; set; } - public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null) + public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection? arguments = null, LlvmIrVariable? funcPointer = null) : base ("call") { this.function = function; this.result = result; + this.FuncPointer = funcPointer; if (function.Signature.ReturnType != typeof(void)) { if (result == null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs index c77b6451ec4..dbd6ce0f69c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrKnownMetadata.cs @@ -1,4 +1,4 @@ -namespace Xamarin.Android.Tasks.LLVM.IR; +namespace Xamarin.Android.Tasks.LLVMIR; sealed class LlvmIrKnownMetadata { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs deleted file mode 100644 index 78bcbdf5e6f..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.New.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - class LlvmIrMetadataField - { - public string Contents { get; } - public bool IsReference { get; } - - public LlvmIrMetadataField (LlvmIrMetadataField other) - { - Contents = other.Contents; - IsReference = other.IsReference; - } - - public LlvmIrMetadataField (string value, bool isReference = false) - { - if (isReference) { - 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.MapToIRType (vt); - return $"{irType} {MonoAndroidHelper.CultureInvariantToString (value)}"; - } - - string QuoteString (string value) - { - return $"!{LlvmIrGenerator.QuoteStringNoEscape (value)}"; - } - } - - class LlvmIrMetadataItem - { - List fields; - - public string Name { get; } - - public LlvmIrMetadataItem (LlvmIrMetadataItem other) - { - Name = other.Name; - fields = new List (); - foreach (LlvmIrMetadataField field in other.fields) { - fields.Add (new LlvmIrMetadataField (field)); - } - } - - public LlvmIrMetadataItem (string name) - { - if (name.Length == 0) { - 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 AddReferenceField (LlvmIrMetadataItem referencedItem) - { - AddReferenceField (referencedItem.Name); - } - - public void AddField (object value) - { - AddField (new LlvmIrMetadataField (value)); - } - - public void AddField (LlvmIrMetadataField field) - { - fields.Add (field); - } - - 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 (); - Dictionary nameToItem = new Dictionary (StringComparer.Ordinal); - - public List Items => items; - - public LlvmIrMetadataManager () - {} - - public LlvmIrMetadataManager (LlvmIrMetadataManager other) - { - foreach (LlvmIrMetadataItem item in other.items) { - var newItem = new LlvmIrMetadataItem (item); - items.Add (newItem); - nameToItem.Add (newItem.Name, newItem); - } - counter = other.counter; - } - - public LlvmIrMetadataItem Add (string name, params object[]? values) - { - if (nameToItem.ContainsKey (name)) { - throw new InvalidOperationException ($"Internal error: metadata item '{name}' has already been added"); - } - - var ret = new LlvmIrMetadataItem (name); - - if (values != null && values.Length > 0) { - foreach (object v in values) { - ret.AddField (v); - } - } - items.Add (ret); - - nameToItem.Add (name, ret); - return ret; - } - - public LlvmIrMetadataItem AddNumbered (params object[]? values) - { - string name = counter.ToString (CultureInfo.InvariantCulture); - counter++; - return Add (name, values); - } - - public LlvmIrMetadataItem? GetItem (string name) - { - if (nameToItem.TryGetValue (name, out LlvmIrMetadataItem? item)) { - return item; - } - - return null; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs index b2d8bf7379a..37acb17ce44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrMetadataManager.cs @@ -10,6 +10,12 @@ class LlvmIrMetadataField public string Contents { get; } public bool IsReference { get; } + public LlvmIrMetadataField (LlvmIrMetadataField other) + { + Contents = other.Contents; + IsReference = other.IsReference; + } + public LlvmIrMetadataField (string value, bool isReference = false) { if (isReference) { @@ -35,7 +41,7 @@ string FormatValue (object value) return QuoteString ((string)value); } - string irType = LlvmIrGenerator.MapManagedTypeToIR (vt); + string irType = LlvmIrGenerator.MapToIRType (vt); return $"{irType} {MonoAndroidHelper.CultureInvariantToString (value)}"; } @@ -51,6 +57,15 @@ class LlvmIrMetadataItem public string Name { get; } + public LlvmIrMetadataItem (LlvmIrMetadataItem other) + { + Name = other.Name; + fields = new List (); + foreach (LlvmIrMetadataField field in other.fields) { + fields.Add (new LlvmIrMetadataField (field)); + } + } + public LlvmIrMetadataItem (string name) { if (name.Length == 0) { @@ -66,9 +81,19 @@ public void AddReferenceField (string referenceName) fields.Add (new LlvmIrMetadataField (referenceName, isReference: true)); } + public void AddReferenceField (LlvmIrMetadataItem referencedItem) + { + AddReferenceField (referencedItem.Name); + } + public void AddField (object value) { - fields.Add (new LlvmIrMetadataField (value)); + AddField (new LlvmIrMetadataField (value)); + } + + public void AddField (LlvmIrMetadataField field) + { + fields.Add (field); } public string Render () @@ -96,11 +121,29 @@ class LlvmIrMetadataManager { ulong counter = 0; List items = new List (); + Dictionary nameToItem = new Dictionary (StringComparer.Ordinal); public List Items => items; + public LlvmIrMetadataManager () + {} + + public LlvmIrMetadataManager (LlvmIrMetadataManager other) + { + foreach (LlvmIrMetadataItem item in other.items) { + var newItem = new LlvmIrMetadataItem (item); + items.Add (newItem); + nameToItem.Add (newItem.Name, newItem); + } + counter = other.counter; + } + public LlvmIrMetadataItem Add (string name, params object[]? values) { + if (nameToItem.ContainsKey (name)) { + throw new InvalidOperationException ($"Internal error: metadata item '{name}' has already been added"); + } + var ret = new LlvmIrMetadataItem (name); if (values != null && values.Length > 0) { @@ -110,6 +153,7 @@ public LlvmIrMetadataItem Add (string name, params object[]? values) } items.Add (ret); + nameToItem.Add (name, ret); return ret; } @@ -119,5 +163,14 @@ public LlvmIrMetadataItem AddNumbered (params object[]? values) counter++; return Add (name, values); } + + public LlvmIrMetadataItem? GetItem (string name) + { + if (nameToItem.TryGetValue (name, out LlvmIrMetadataItem? item)) { + return item; + } + + return null; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 8a125c0f287..d560b924bce 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -5,12 +5,8 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR { - // TODO: remove these aliases once the refactoring is done - using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; - using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; - partial class LlvmIrModule { /// @@ -257,7 +253,7 @@ void AddAutomaticBuffer (StructureInstance structure, StructureMemberInfo smi, u } string bufferName = bufferManager.Allocate (structure, smi, bufferSize); - var buffer = new LlvmIrGlobalVariable (typeof(List), bufferName, LLVMIR.LlvmIrVariableOptions.LocalWritable) { + var buffer = new LlvmIrGlobalVariable (typeof(List), bufferName, LlvmIrVariableOptions.LocalWritable) { ZeroInitializeArray = true, ArrayItemCount = bufferSize, }; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index 865c5591bd5..88a9b4140cb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -3,55 +3,51 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleAArch64 : LlvmIrModuleTarget { - // TODO: remove these aliases once the refactoring is done - using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "aarch64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; + public override uint NativePointerSize => 8; + public override bool Is64Bit => true; + + public LlvmIrModuleAArch64 () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 8, abi: 8, pref: 32), // i8 + new LlvmIrDataLayoutIntegerAlignment (size: 16, abi: 16, pref: 32), // i16 + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + new LlvmIrDataLayoutIntegerAlignment (size: 128, abi: 128), // i128 + }, + + NativeIntegerWidths = new List { 32, 64}, + StackAlignment = 128, + }; + } - class LlvmIrModuleAArch64 : LlvmIrModuleTarget + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) { - public override LlvmIrDataLayout DataLayout { get; } - public override string Triple => "aarch64-unknown-linux-android21"; - public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm64; - public override uint NativePointerSize => 8; - public override bool Is64Bit => true; - - public LlvmIrModuleAArch64 () - { - // - // As per Android NDK: - // target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" - // - DataLayout = new LlvmIrDataLayout { - LittleEndian = true, - Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), - - IntegerAlignment = new List { - new LlvmIrDataLayoutIntegerAlignment (size: 8, abi: 8, pref: 32), // i8 - new LlvmIrDataLayoutIntegerAlignment (size: 16, abi: 16, pref: 32), // i16 - new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 - new LlvmIrDataLayoutIntegerAlignment (size: 128, abi: 128), // i128 - }, - - NativeIntegerWidths = new List { 32, 64}, - StackAlignment = 128, - }; - } - - public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) - { - attrSet.Add (new TargetCpuFunctionAttribute ("generic")); - attrSet.Add (new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics,+v8a")); - } - - public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) - { - LlvmIrMetadataItem flags = GetFlagsMetadata (manager); - - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); - } + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+fix-cortex-a53-835769,+neon,+outline-atomics,+v8a")); + } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "branch-target-enforcement", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index 35506303c0f..a2419e1b6d8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -3,66 +3,62 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleArmV7a : LlvmIrModuleTarget { - // TODO: remove these aliases once the refactoring is done - using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "armv7-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; + public override uint NativePointerSize => 4; + public override bool Is64Bit => false; - class LlvmIrModuleArmV7a : LlvmIrModuleTarget + public LlvmIrModuleArmV7a () { - public override LlvmIrDataLayout DataLayout { get; } - public override string Triple => "armv7-unknown-linux-android21"; - public override AndroidTargetArch TargetArch => AndroidTargetArch.Arm; - public override uint NativePointerSize => 4; - public override bool Is64Bit => false; - - public LlvmIrModuleArmV7a () - { - // - // As per Android NDK: - // target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" - // - DataLayout = new LlvmIrDataLayout { - LittleEndian = true, - Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), - PointerSize = new List { - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), - }, + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + }, - FunctionPointerAlignment = new LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType.Independent, abi: 8), + FunctionPointerAlignment = new LlvmIrDataLayoutFunctionPointerAlignment (LlvmIrDataLayoutFunctionPointerAlignmentType.Independent, abi: 8), - IntegerAlignment = new List { - new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 - }, + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, - VectorAlignment = new List { - new LlvmIrDataLayoutVectorAlignment (size: 128, abi: 64, pref: 128), // v128 - }, + VectorAlignment = new List { + new LlvmIrDataLayoutVectorAlignment (size: 128, abi: 64, pref: 128), // v128 + }, - AggregateObjectAlignment = new LlvmIrDataLayoutAggregateObjectAlignment (abi: 0, pref: 32), - NativeIntegerWidths = new List { 32 }, - StackAlignment = 64, - }; - } + AggregateObjectAlignment = new LlvmIrDataLayoutAggregateObjectAlignment (abi: 0, pref: 32), + NativeIntegerWidths = new List { 32 }, + StackAlignment = 64, + }; + } - public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) - { - attrSet.Add (new TargetCpuFunctionAttribute ("generic")); - attrSet.Add (new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp")); - } + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("generic")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp")); + } - public override void SetParameterFlags (LlvmIrFunctionParameter parameter) - { - base.SetParameterFlags (parameter); - SetIntegerParameterUpcastFlags (parameter); - } + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } - public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) - { - LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); - } + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs index b58a6372017..ad9a1d9c604 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleTarget.cs @@ -1,74 +1,73 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +abstract class LlvmIrModuleTarget { - abstract class LlvmIrModuleTarget - { - public abstract LlvmIrDataLayout DataLayout { get; } - public abstract string Triple { get; } - public abstract AndroidTargetArch TargetArch { get; } - public abstract uint NativePointerSize { get; } - public abstract bool Is64Bit { get; } + public abstract LlvmIrDataLayout DataLayout { get; } + public abstract string Triple { get; } + public abstract AndroidTargetArch TargetArch { get; } + public abstract uint NativePointerSize { get; } + public abstract bool Is64Bit { get; } - /// - /// Adds target-specific attributes which are common to many attribute sets. Usually this specifies CPU type, tuning and - /// features. - /// - public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) - {} + /// + /// Adds target-specific attributes which are common to many attribute sets. Usually this specifies CPU type, tuning and + /// features. + /// + public virtual void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + {} - public virtual void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) - {} + public virtual void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + {} - public virtual void SetParameterFlags (LlvmIrFunctionParameter parameter) - { - if (!parameter.NoUndef.HasValue) { - parameter.NoUndef = true; - } + public virtual void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + if (!parameter.NoUndef.HasValue) { + parameter.NoUndef = true; } + } - /// - /// Sets the zeroext or signext attributes on the parameter, if not set previously and if - /// the parameter is a small integral type. Out of our supported architectures, all except AArch64 set - /// the flags, thus the reason to put this method in the base class. - /// - protected void SetIntegerParameterUpcastFlags (LlvmIrFunctionParameter parameter) + /// + /// Sets the zeroext or signext attributes on the parameter, if not set previously and if + /// the parameter is a small integral type. Out of our supported architectures, all except AArch64 set + /// the flags, thus the reason to put this method in the base class. + /// + protected void SetIntegerParameterUpcastFlags (LlvmIrFunctionParameter parameter) + { + if (parameter.Type == typeof(bool) || + parameter.Type == typeof(byte) || + parameter.Type == typeof(char) || + parameter.Type == typeof(ushort)) { - if (parameter.Type == typeof(bool) || - parameter.Type == typeof(byte) || - parameter.Type == typeof(char) || - parameter.Type == typeof(ushort)) - { - if (!parameter.ZeroExt.HasValue) { - parameter.ZeroExt = true; - parameter.SignExt = false; - } - return; - } - - if (parameter.Type == typeof(sbyte) || - parameter.Type == typeof(short)) - { - if (!parameter.SignExt.HasValue) { - parameter.SignExt = true; - parameter.ZeroExt = false; - } + if (!parameter.ZeroExt.HasValue) { + parameter.ZeroExt = true; + parameter.SignExt = false; } + return; } - public virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + if (parameter.Type == typeof(sbyte) || + parameter.Type == typeof(short)) { - return maxFieldAlignment; + if (!parameter.SignExt.HasValue) { + parameter.SignExt = true; + parameter.ZeroExt = false; + } } + } - protected LlvmIrMetadataItem GetFlagsMetadata (LlvmIrMetadataManager manager) - { - LlvmIrMetadataItem? flags = manager.GetItem (LlvmIrKnownMetadata.LlvmModuleFlags); - if (flags == null) { - flags = manager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); - } + public virtual int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + return maxFieldAlignment; + } - return flags; + protected LlvmIrMetadataItem GetFlagsMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem? flags = manager.GetItem (LlvmIrKnownMetadata.LlvmModuleFlags); + if (flags == null) { + flags = manager.Add (LlvmIrKnownMetadata.LlvmModuleFlags); } + + return flags; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index e1673600aea..8b36d2a01d9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -3,76 +3,75 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleX64 : LlvmIrModuleTarget { - class LlvmIrModuleX64 : LlvmIrModuleTarget - { - public override LlvmIrDataLayout DataLayout { get; } - public override string Triple => "x86_64-unknown-linux-android21"; - public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; - public override uint NativePointerSize => 8; - public override bool Is64Bit => true; + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "x86_64-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86_64; + public override uint NativePointerSize => 8; + public override bool Is64Bit => true; - public LlvmIrModuleX64 () - { - // - // As per Android NDK: - // target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" - // - DataLayout = new LlvmIrDataLayout { - LittleEndian = true, - Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + public LlvmIrModuleX64 () + { + // + // As per Android NDK: + // target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), - PointerSize = new List { - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { - AddressSpace = 270, - }, - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { - AddressSpace = 271, - }, - new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { - AddressSpace = 272, - }, + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, }, - - IntegerAlignment = new List { - new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, }, - - FloatAlignment = new List { - new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 128), // f80 + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, }, + }, - NativeIntegerWidths = new List { 8, 16, 32, 64 }, - StackAlignment = 128, - }; - } + IntegerAlignment = new List { + new LlvmIrDataLayoutIntegerAlignment (size: 64, abi: 64), // i64 + }, - public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) - { - attrSet.Add (new TargetCpuFunctionAttribute ("x86-64")); - attrSet.Add (new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87")); - attrSet.Add (new TuneCpuFunctionAttribute ("generic")); - } + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 128), // f80 + }, - public override void SetParameterFlags (LlvmIrFunctionParameter parameter) - { - base.SetParameterFlags (parameter); - SetIntegerParameterUpcastFlags (parameter); - } + NativeIntegerWidths = new List { 8, 16, 32, 64 }, + StackAlignment = 128, + }; + } + + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("x86-64")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + } - public override 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; - } + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } - return maxFieldAlignment; + public override int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) + { + // System V ABI for x86_64 mandates that any aggregates 16 bytes or more long will + // be aligned at at least 16 bytes + // + // See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf (Section '3.1.2 Data Representation', "Aggregates and Unions") + // + if (dataSize >= 16 && maxFieldAlignment < 16) { + return 16; } + + return maxFieldAlignment; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index 3f532daa606..51f86cbf9ff 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -3,71 +3,67 @@ using Xamarin.Android.Tools; -namespace Xamarin.Android.Tasks.LLVM.IR +namespace Xamarin.Android.Tasks.LLVMIR; + +class LlvmIrModuleX86 : LlvmIrModuleTarget { - // TODO: remove these aliases once the refactoring is done - using LlvmIrModuleMergeBehavior = LLVMIR.LlvmIrModuleMergeBehavior; + public override LlvmIrDataLayout DataLayout { get; } + public override string Triple => "i686-unknown-linux-android21"; + public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; + public override uint NativePointerSize => 4; + public override bool Is64Bit => false; - class LlvmIrModuleX86 : LlvmIrModuleTarget + public LlvmIrModuleX86 () { - public override LlvmIrDataLayout DataLayout { get; } - public override string Triple => "i686-unknown-linux-android21"; - public override AndroidTargetArch TargetArch => AndroidTargetArch.X86; - public override uint NativePointerSize => 4; - public override bool Is64Bit => false; - - public LlvmIrModuleX86 () - { - // - // As per Android NDK: - // target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" - // - DataLayout = new LlvmIrDataLayout { - LittleEndian = true, - Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), + // + // As per Android NDK: + // target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" + // + DataLayout = new LlvmIrDataLayout { + LittleEndian = true, + Mangling = new LlvmIrDataLayoutMangling (LlvmIrDataLayoutManglingOption.ELF), - PointerSize = new List { - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { - AddressSpace = 270, - }, - new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { - AddressSpace = 271, - }, - new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { - AddressSpace = 272, - }, + PointerSize = new List { + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32), + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 270, }, - - FloatAlignment = new List { - new LlvmIrDataLayoutFloatAlignment (size: 64, abi: 32, pref: 64), // f64 - new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 32), // f80 + new LlvmIrDataLayoutPointerSize (size: 32, abi: 32) { + AddressSpace = 271, }, + new LlvmIrDataLayoutPointerSize (size: 64, abi: 64) { + AddressSpace = 272, + }, + }, - NativeIntegerWidths = new List { 8, 16, 32 }, - StackAlignment = 128, - }; - } + FloatAlignment = new List { + new LlvmIrDataLayoutFloatAlignment (size: 64, abi: 32, pref: 64), // f64 + new LlvmIrDataLayoutFloatAlignment (size: 80, abi: 32), // f80 + }, - public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) - { - attrSet.Add (new TargetCpuFunctionAttribute ("i686")); - attrSet.Add (new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87")); - attrSet.Add (new TuneCpuFunctionAttribute ("generic")); - attrSet.Add (new StackrealignFunctionAttribute ()); - } + NativeIntegerWidths = new List { 8, 16, 32 }, + StackAlignment = 128, + }; + } - public override void SetParameterFlags (LlvmIrFunctionParameter parameter) - { - base.SetParameterFlags (parameter); - SetIntegerParameterUpcastFlags (parameter); - } + public override void AddTargetSpecificAttributes (LlvmIrFunctionAttributeSet attrSet) + { + attrSet.Add (new TargetCpuFunctionAttribute ("i686")); + attrSet.Add (new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87")); + attrSet.Add (new TuneCpuFunctionAttribute ("generic")); + attrSet.Add (new StackrealignFunctionAttribute ()); + } - public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) - { - LlvmIrMetadataItem flags = GetFlagsMetadata (manager); + public override void SetParameterFlags (LlvmIrFunctionParameter parameter) + { + base.SetParameterFlags (parameter); + SetIntegerParameterUpcastFlags (parameter); + } + + public override void AddTargetSpecificMetadata (LlvmIrMetadataManager manager) + { + LlvmIrMetadataItem flags = GetFlagsMetadata (manager); - flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); - } + flags.AddReferenceField (manager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs index 4d0bcdd8182..a20cae23a8b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringGroup.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Xamarin.Android.Tasks.LLVM.IR; +namespace Xamarin.Android.Tasks.LLVMIR; sealed class LlvmIrStringGroup { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs deleted file mode 100644 index bad4d99c565..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.New.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Tasks.LLVM.IR; - -partial class LlvmIrModule -{ - protected class LlvmIrStringManager - { - Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); - Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); - List stringGroups = new List (); - - LlvmIrStringGroup defaultGroup; - - public List StringGroups => stringGroups; - - public LlvmIrStringManager () - { - defaultGroup = new LlvmIrStringGroup (); - stringGroupCache.Add (String.Empty, defaultGroup); - stringGroups.Add (defaultGroup); - } - - public LlvmIrStringVariable Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) - { - if (value == null) { - throw new ArgumentNullException (nameof (value)); - } - - LlvmIrStringVariable? stringVar; - if (stringSymbolCache.TryGetValue (value, out stringVar) && stringVar != null) { - return stringVar; - } - - LlvmIrStringGroup? group; - string groupPrefix; - if (String.IsNullOrEmpty (groupName) || String.Compare ("str", groupName, StringComparison.Ordinal) == 0) { - group = defaultGroup; - groupPrefix = ".str"; - } else if (!stringGroupCache.TryGetValue (groupName, out group) || group == null) { - group = new LlvmIrStringGroup (groupComment ?? groupName); - stringGroups.Add (group); - stringGroupCache[groupName] = group; - groupPrefix = $".{groupName}"; - } else { - groupPrefix = $".{groupName}"; - } - - string symbolName = $"{groupPrefix}.{group.Count++}"; - if (!String.IsNullOrEmpty (symbolSuffix)) { - symbolName = $"{symbolName}_{symbolSuffix}"; - } - - stringVar = new LlvmIrStringVariable (symbolName, value); - group.Strings.Add (stringVar); - stringSymbolCache.Add (value, stringVar); - - return stringVar; - } - - public LlvmIrStringVariable? Lookup (string value) - { - if (stringSymbolCache.TryGetValue (value, out LlvmIrStringVariable? sv)) { - return sv; - } - - return null; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs index de7b107af24..dcf62b40851 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs @@ -1,116 +1,72 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; namespace Xamarin.Android.Tasks.LLVMIR; -partial class LlvmIrGenerator +partial class LlvmIrModule { - public sealed class StringSymbolInfo - { - public readonly string SymbolName; - public readonly ulong Size; - public readonly string Value; - - public StringSymbolInfo (string symbolName, string value, ulong size) - { - SymbolName = symbolName; - Value = value; - Size = size; - } - } - - sealed class StringGroup - { - public ulong Count; - public readonly string? Comment; - public readonly List Strings = new List (); - - public StringGroup (string? comment = null) - { - Comment = comment; - Count = 0; - } - } - protected class LlvmIrStringManager { - Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); - Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); - List stringGroups = new List (); + Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + Dictionary stringGroupCache = new Dictionary (StringComparer.Ordinal); + List stringGroups = new List (); + + LlvmIrStringGroup defaultGroup; - StringGroup defaultGroup; + public List StringGroups => stringGroups; public LlvmIrStringManager () { - defaultGroup = new StringGroup (); + defaultGroup = new LlvmIrStringGroup (); stringGroupCache.Add (String.Empty, defaultGroup); stringGroups.Add (defaultGroup); } - public StringSymbolInfo Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) + public LlvmIrStringVariable Add (string value, string? groupName = null, string? groupComment = null, string? symbolSuffix = null) { if (value == null) { throw new ArgumentNullException (nameof (value)); } - StringSymbolInfo? info; - if (stringSymbolCache.TryGetValue (value, out info) && info != null) { - return info; + LlvmIrStringVariable? stringVar; + if (stringSymbolCache.TryGetValue (value, out stringVar) && stringVar != null) { + return stringVar; } - StringGroup? group; + LlvmIrStringGroup? group; string groupPrefix; if (String.IsNullOrEmpty (groupName) || String.Compare ("str", groupName, StringComparison.Ordinal) == 0) { group = defaultGroup; - groupPrefix = "__str"; + groupPrefix = ".str"; } else if (!stringGroupCache.TryGetValue (groupName, out group) || group == null) { - group = new StringGroup (groupComment ?? groupName); + group = new LlvmIrStringGroup (groupComment ?? groupName); stringGroups.Add (group); stringGroupCache[groupName] = group; - groupPrefix = $"__{groupName}"; + groupPrefix = $".{groupName}"; } else { - groupPrefix = $"__{groupName}"; + groupPrefix = $".{groupName}"; } - string quotedString = QuoteString (value, out ulong stringSize); string symbolName = $"{groupPrefix}.{group.Count++}"; if (!String.IsNullOrEmpty (symbolSuffix)) { symbolName = $"{symbolName}_{symbolSuffix}"; } - info = new StringSymbolInfo (symbolName, quotedString, stringSize); - group.Strings.Add (info); - stringSymbolCache.Add (value, info); + stringVar = new LlvmIrStringVariable (symbolName, value); + group.Strings.Add (stringVar); + stringSymbolCache.Add (value, stringVar); - return info; + return stringVar; } - public void Flush (LlvmIrGenerator generator) + public LlvmIrStringVariable? Lookup (string value) { - TextWriter output = generator.Output; - - generator.WriteEOL ("Strings"); - foreach (StringGroup group in stringGroups) { - if (group != defaultGroup) { - output.WriteLine (); - } - - if (!String.IsNullOrEmpty (group.Comment)) { - generator.WriteCommentLine (output, group.Comment); - } - - foreach (StringSymbolInfo info in group.Strings) { - generator.WriteGlobalSymbolStart (info.SymbolName, LlvmIrVariableOptions.LocalConstexprString); - output.Write ('['); - output.Write (info.Size.ToString (CultureInfo.InvariantCulture)); - output.Write (" x i8] c"); - output.Write (info.Value); - output.Write (", align "); - output.WriteLine (generator.GetAggregateAlignment (1, info.Size).ToString (CultureInfo.InvariantCulture)); - } + if (stringSymbolCache.TryGetValue (value, out LlvmIrStringVariable? sv)) { + return sv; } + + return null; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs deleted file mode 100644 index 2488bc7585a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.New.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System; -using System.Globalization; - -namespace Xamarin.Android.Tasks.LLVM.IR; - -// TODO: remove these aliases once the refactoring is done -using LlvmIrVariableOptions = LLVMIR.LlvmIrVariableOptions; - -[Flags] -enum LlvmIrVariableWriteOptions -{ - None = 0x0000, - ArrayWriteIndexComments = 0x0001, - ArrayFormatInRows = 0x0002, -} - -abstract class LlvmIrVariable : IEquatable -{ - public abstract bool Global { get; } - public abstract string NamePrefix { get; } - - public string? Name { get; protected set; } - public Type Type { get; protected set; } - public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; - - /// - /// Number of columns an array that is written in rows should have. By default, arrays are written one item in a line, but - /// when the flag is set in , then - /// the value of this property dictates how many items are to be placed in a single row. - /// - public uint ArrayStride { get; set; } = 8; - public object? Value { get; set; } - public string? Comment { get; set; } - - /// - /// Both global and local variables will want their names to matter in equality checks, but function - /// parameters must not take it into account, thus this property. If set to false, - /// will ignore name when checking for equality. - protected bool NameMatters { get; set; } = true; - - /// - /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global - /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are - /// referenced. - /// - public virtual string Reference { - get { - if (String.IsNullOrEmpty (Name)) { - throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); - } - - return $"{NamePrefix}{Name}"; - } - } - - /// - /// - /// Certain data must be calculated when the target architecture is known, because it may depend on certain aspects of - /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output - /// stream, allowing updating of any such data as described above. - /// - /// - /// First parameter passed to the callback is the variable itself, second parameter is the current - /// and the third is the value previously assigned to - /// - /// - public Action? BeforeWriteCallback { get; set; } - - /// - /// Object passed to the method, if any, as the caller state. - /// - public object? BeforeWriteCallbackCallerState { get; set; } - - /// - /// - /// Callback used when processing array variables, called for each item of the array in order to obtain the item's comment, if any. - /// - /// - /// The first argument is the variable which contains the array, second is the item index, third is the item value and fourth is - /// the caller state object, previously assigned to the property. The callback - /// can return an empty string or null, in which case no comment is written. - /// - /// - public Func? GetArrayItemCommentCallback { get; set; } - - /// - /// Object passed to the method, if any, as the caller state. - /// - public object? GetArrayItemCommentCallbackCallerState { get; set; } - - /// - /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. - /// - protected LlvmIrVariable (Type type, string? name = null) - { - Type = type; - Name = name; - } - - public override int GetHashCode () - { - return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); - } - - public override bool Equals (object obj) - { - var irVar = obj as LlvmIrVariable; - if (irVar == null) { - return false; - } - - return Equals (irVar); - } - - public virtual bool Equals (LlvmIrVariable other) - { - if (other == null) { - return false; - } - - return - Global == other.Global && - Type == other.Type && - String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && - (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); - } -} - -class LlvmIrLocalVariable : LlvmIrVariable -{ - public override bool Global => false; - public override string NamePrefix => "%"; - - /// - /// Constructs a local variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in - /// which case they will be assigned a sequential number when function code is generated. - /// - public LlvmIrLocalVariable (Type type, string? name = null) - : base (type, name) - {} - - public void AssignNumber (ulong n) - { - Name = n.ToString (CultureInfo.InvariantCulture); - } -} - -class LlvmIrGlobalVariable : LlvmIrVariable -{ - /// - /// By default a global variable is constant and exported. - /// - public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; - - public override bool Global => true; - public override string NamePrefix => "@"; - - /// - /// Specify variable options. If omitted, it defaults to . - /// - /// - public virtual LlvmIrVariableOptions? Options { get; set; } - - public bool ZeroInitializeArray { get; set; } - public ulong ArrayItemCount { get; set; } - - /// - /// Constructs a local variable. is translated to one of the LLVM IR first class types (see - /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it - /// is treated as an opaque pointer type. is required because global variables must be named. - /// - public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) - : base (type, name) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - - Options = options; - } - - /// - /// Constructs a local variable and sets the property to and - /// property to its type. For that reason, **must not** be null. is - /// required because global variables must be named. - /// - public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? options = null) - : this ((value ?? throw new ArgumentNullException (nameof (value))).GetType (), name, options) - { - Value = value; - } - - /// - /// This is, unfortunately, needed to be able to address scenarios when a single symbol can have a different type when - /// generating output for a specific target (e.g. 32-bit vs 64-bit integer variables). If the variable requires such - /// type changes, this should be done at generation time from within the method. - /// - public void OverrideValueAndType (Type newType, object? newValue) - { - Type = newType; - Value = newValue; - } -} - -class LlvmIrStringVariable : LlvmIrGlobalVariable -{ - public LlvmIrStringVariable (string name, string value) - : base (typeof(string), name, LlvmIrVariableOptions.LocalString) - { - Value = value; - } -} - -/// -/// This is to address my dislike to have single-line variables separated by empty lines :P. -/// When an instance of this "variable" is first encountered, it enables variable grouping, that is -/// they will be followed by just a single newline. The next instance of this "variable" turns -/// grouping off, meaning the following variables will be followed by two newlines. -/// -class LlvmIrGroupDelimiterVariable : LlvmIrGlobalVariable -{ - public LlvmIrGroupDelimiterVariable () - : base (typeof(void), ".:!GroupDelimiter!:.", LlvmIrVariableOptions.LocalConstant) - {} -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 0abda63bfdd..0f7f28ea74d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -1,34 +1,226 @@ using System; +using System.Globalization; -namespace Xamarin.Android.Tasks.LLVMIR +namespace Xamarin.Android.Tasks.LLVMIR; + +[Flags] +enum LlvmIrVariableWriteOptions +{ + None = 0x0000, + ArrayWriteIndexComments = 0x0001, + ArrayFormatInRows = 0x0002, +} + +abstract class LlvmIrVariable : IEquatable { + public abstract bool Global { get; } + public abstract string NamePrefix { get; } + + public string? Name { get; protected set; } + public Type Type { get; protected set; } + public LlvmIrVariableWriteOptions WriteOptions { get; set; } = LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + + /// + /// Number of columns an array that is written in rows should have. By default, arrays are written one item in a line, but + /// when the flag is set in , then + /// the value of this property dictates how many items are to be placed in a single row. + /// + public uint ArrayStride { get; set; } = 8; + public object? Value { get; set; } + public string? Comment { get; set; } + + /// + /// Both global and local variables will want their names to matter in equality checks, but function + /// parameters must not take it into account, thus this property. If set to false, + /// will ignore name when checking for equality. + protected bool NameMatters { get; set; } = true; + /// - /// Base class for all the variable (local and global) as well as function parameter classes. + /// Returns a string which constitutes a reference to a local (using the % prefix character) or a global + /// (using the @ prefix character) variable, ready for use in the generated code wherever variables are + /// referenced. /// - abstract class LlvmIrVariable + public virtual string Reference { + get { + if (String.IsNullOrEmpty (Name)) { + throw new InvalidOperationException ("Variable doesn't have a name, it cannot be referenced"); + } + + return $"{NamePrefix}{Name}"; + } + } + + /// + /// + /// Certain data must be calculated when the target architecture is known, because it may depend on certain aspects of + /// the target (e.g. its bitness). This callback, if set, will be invoked before the variable is written to the output + /// stream, allowing updating of any such data as described above. + /// + /// + /// First parameter passed to the callback is the variable itself, second parameter is the current + /// and the third is the value previously assigned to + /// + /// + public Action? BeforeWriteCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? BeforeWriteCallbackCallerState { get; set; } + + /// + /// + /// Callback used when processing array variables, called for each item of the array in order to obtain the item's comment, if any. + /// + /// + /// The first argument is the variable which contains the array, second is the item index, third is the item value and fourth is + /// the caller state object, previously assigned to the property. The callback + /// can return an empty string or null, in which case no comment is written. + /// + /// + public Func? GetArrayItemCommentCallback { get; set; } + + /// + /// Object passed to the method, if any, as the caller state. + /// + public object? GetArrayItemCommentCallbackCallerState { get; set; } + + /// + /// Constructs an abstract variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. + /// + protected LlvmIrVariable (Type type, string? name = null) { - public LlvmNativeFunctionSignature? NativeFunction { get; } - public string? Name { get; } - public Type Type { get; } + Type = type; + Name = name; + } - // Used when we need a pointer to pointer (etc) or when the type itself is not a pointer but we need one - // in a given context (e.g. function parameters) - public bool IsNativePointer { get; } + public override int GetHashCode () + { + return Type.GetHashCode () ^ (Name?.GetHashCode () ?? 0); + } - protected LlvmIrVariable (Type type, string name, LlvmNativeFunctionSignature? signature, bool isNativePointer) - { - Type = type ?? throw new ArgumentNullException (nameof (type)); - Name = name; - NativeFunction = signature; - IsNativePointer = isNativePointer; + public override bool Equals (object obj) + { + var irVar = obj as LlvmIrVariable; + if (irVar == null) { + return false; } - protected LlvmIrVariable (LlvmIrVariable variable, string name, bool isNativePointer) - { - Type = variable?.Type ?? throw new ArgumentNullException (nameof (variable)); - Name = name; - NativeFunction = variable.NativeFunction; - IsNativePointer = isNativePointer; + return Equals (irVar); + } + + public virtual bool Equals (LlvmIrVariable other) + { + if (other == null) { + return false; } + + return + Global == other.Global && + Type == other.Type && + String.Compare (NamePrefix, other.NamePrefix, StringComparison.Ordinal) == 0 && + (!NameMatters || String.Compare (Name, other.Name, StringComparison.Ordinal) == 0); } } + +class LlvmIrLocalVariable : LlvmIrVariable +{ + public override bool Global => false; + public override string NamePrefix => "%"; + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is optional because local variables can be unnamed, in + /// which case they will be assigned a sequential number when function code is generated. + /// + public LlvmIrLocalVariable (Type type, string? name = null) + : base (type, name) + {} + + public void AssignNumber (ulong n) + { + Name = n.ToString (CultureInfo.InvariantCulture); + } +} + +class LlvmIrGlobalVariable : LlvmIrVariable +{ + /// + /// By default a global variable is constant and exported. + /// + public static readonly LlvmIrVariableOptions DefaultOptions = LlvmIrVariableOptions.GlobalConstant; + + public override bool Global => true; + public override string NamePrefix => "@"; + + /// + /// Specify variable options. If omitted, it defaults to . + /// + /// + public virtual LlvmIrVariableOptions? Options { get; set; } + + public bool ZeroInitializeArray { get; set; } + public ulong ArrayItemCount { get; set; } + + /// + /// Constructs a local variable. is translated to one of the LLVM IR first class types (see + /// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it + /// is treated as an opaque pointer type. is required because global variables must be named. + /// + public LlvmIrGlobalVariable (Type type, string name, LlvmIrVariableOptions? options = null) + : base (type, name) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Options = options; + } + + /// + /// Constructs a local variable and sets the property to and + /// property to its type. For that reason, **must not** be null. is + /// required because global variables must be named. + /// + public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? options = null) + : this ((value ?? throw new ArgumentNullException (nameof (value))).GetType (), name, options) + { + Value = value; + } + + /// + /// This is, unfortunately, needed to be able to address scenarios when a single symbol can have a different type when + /// generating output for a specific target (e.g. 32-bit vs 64-bit integer variables). If the variable requires such + /// type changes, this should be done at generation time from within the method. + /// + public void OverrideValueAndType (Type newType, object? newValue) + { + Type = newType; + Value = newValue; + } +} + +class LlvmIrStringVariable : LlvmIrGlobalVariable +{ + public LlvmIrStringVariable (string name, string value) + : base (typeof(string), name, LlvmIrVariableOptions.LocalString) + { + Value = value; + } +} + +/// +/// This is to address my dislike to have single-line variables separated by empty lines :P. +/// When an instance of this "variable" is first encountered, it enables variable grouping, that is +/// they will be followed by just a single newline. The next instance of this "variable" turns +/// grouping off, meaning the following variables will be followed by two newlines. +/// +class LlvmIrGroupDelimiterVariable : LlvmIrGlobalVariable +{ + public LlvmIrGroupDelimiterVariable () + : base (typeof(void), ".:!GroupDelimiter!:.", LlvmIrVariableOptions.LocalConstant) + {} +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs deleted file mode 100644 index e6838807877..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - /// - /// References either a local or global variable. - /// - class LlvmIrVariableReference : LlvmIrVariable - { - public string Reference { get; } - - public LlvmIrVariableReference (Type type, string name, bool isGlobal, bool isNativePointer = false) - : base (type, name, signature: null, isNativePointer: isNativePointer) - { - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - Reference = MakeReference (isGlobal, name); - } - - public LlvmIrVariableReference (LlvmNativeFunctionSignature signature, string name, bool isGlobal, bool isNativePointer = false) - : base (typeof(LlvmNativeFunctionSignature), name, signature, isNativePointer) - { - if (signature == null) { - throw new ArgumentNullException (nameof (signature)); - } - - if (String.IsNullOrEmpty (name)) { - throw new ArgumentException ("must not be null or empty", nameof (name)); - } - - Reference = MakeReference (isGlobal, name); - } - - public LlvmIrVariableReference (LlvmIrVariable variable, bool isGlobal, bool isNativePointer = false) - : base (variable, variable?.Name, isNativePointer || variable.IsNativePointer) - { - if (String.IsNullOrEmpty (variable?.Name)) { - throw new ArgumentException ("variable name must not be null or empty", nameof (variable)); - } - - Reference = MakeReference (isGlobal, variable?.Name); - } - - string MakeReference (bool isGlobal, string name) - { - return $"{(isGlobal ? '@' : '%')}{Name}"; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs deleted file mode 100644 index 5a92931d5da..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - /// - /// Contains signature/description of a native function. All the types used for parameters or return value must - /// be mappable to LLVM IR types. This class can be used to describe pointers to functions which have no corresponding - /// managed method (e.g. `xamarin_app_init` used by marshal methods). Additionally, an optional default value can be - /// specified, to be used whenever a variable of this type is emitted (e.g. - class LlvmNativeFunctionSignature - { - int requiredArgsCount; - - public Type ReturnType { get; } - public IList? Parameters { get; } - public object? FieldValue { get; set; } - public bool IsVariadic { get; set; } - public int NumberOfRequiredArguments => requiredArgsCount; - - public LlvmNativeFunctionSignature (Type returnType, List? parameters = null) - { - requiredArgsCount = 0; - ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); - Parameters = parameters?.Select (p => EnsureValidParameter (p))?.ToList ()?.AsReadOnly (); - - LlvmIrFunctionParameter EnsureValidParameter (LlvmIrFunctionParameter parameter) - { - if (parameter == null) { - throw new InvalidOperationException ("null parameters aren't allowed"); - } - - if (IsVariadic) { - throw new InvalidOperationException ("Variadic argument must be the last one"); - } - - if (parameter.IsVarargs) { - IsVariadic = true; - } else { - requiredArgsCount++; - } - - return parameter; - } - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs deleted file mode 100644 index c7742aa7444..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.New.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Reflection; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - // TODO: remove these aliases once the refactoring is done - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - 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 || attr.PointsToSymbol == 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/MemberInfoUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs index 832a34c0fae..431d92b6229 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs @@ -25,7 +25,7 @@ public static bool IsNativePointerToPreallocatedBuffer (this MemberInfo mi, out public static bool PointsToSymbol (this MemberInfo mi, out string? symbolName) { var attr = mi.GetCustomAttribute (); - if (attr == null) { + if (attr == null || attr.PointsToSymbol == null) { symbolName = null; return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs deleted file mode 100644 index aafa57ad53a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.New.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - // TODO: add cache for members and data provider info - sealed class StructureInfo - { - Type type; - - public Type Type => type; - public string Name { get; } = String.Empty; - public ulong Size { get; } - public IList Members { get; } = new List (); - public NativeAssemblerStructContextDataProvider? DataProvider { get; } - public ulong MaxFieldAlignment { get; private set; } = 0; - public bool HasStrings { get; private set; } - public bool HasPreAllocatedBuffers { get; private set; } - public bool HasPointers { get; private set; } - - public bool IsOpaque => Members.Count == 0; - public string NativeTypeDesignator { get; } - - public StructureInfo (LlvmIrModule module, Type type) - { - this.type = type; - Name = type.GetShortName (); - Size = GatherMembers (type, module); - DataProvider = type.GetDataProvider (); - NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; - } - - 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, LlvmIrModule module, bool storeMembers = true) - { - ulong size = 0; - foreach (MemberInfo mi in type.GetMembers ()) { - if (mi.ShouldBeIgnored () || (!(mi is FieldInfo) && !(mi is PropertyInfo))) { - continue; - } - - var info = new StructureMemberInfo (mi, module); - if (info.IsNativePointer) { - HasPointers = true; - } - - if (storeMembers) { - Members.Add (info); - size += info.Size; - - if (info.Alignment > MaxFieldAlignment) { - MaxFieldAlignment = info.Alignment; - } - } - - if (!HasStrings && info.MemberType == typeof (string)) { - HasStrings = true; - } - - if (!HasPreAllocatedBuffers && info.Info.IsNativePointerToPreallocatedBuffer (out ulong _)) { - HasPreAllocatedBuffers = true; - } - - // If we encounter an embedded struct (as opposed to a pointer), we need to descend and check if that struct contains any strings or buffers, but we - // do NOT want to store any members while doing that, as the struct should have been mapped by the composer previously. - // The presence of strings/buffers is important at the generation time as it is used to decide whether we need separate stream writers for them and - // if the owning structure does **not** have any of those, the generated code would be invalid - if (info.IsIRStruct ()) { - GatherMembers (info.MemberType, module, storeMembers: false); - } - } - - return size; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs index 5cc1dfa6589..066c788c93e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs @@ -1,68 +1,37 @@ using System; using System.Collections.Generic; -using System.IO; using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR { // TODO: add cache for members and data provider info - sealed class StructureInfo : IStructureInfo + sealed class StructureInfo { Type type; public Type Type => type; - public string Name { get; } = String.Empty; - public ulong Size { get; } - public List> Members { get; } = new List> (); + public string Name { get; } = String.Empty; + public ulong Size { get; } + public IList Members { get; } = new List (); public NativeAssemblerStructContextDataProvider? DataProvider { get; } - public int MaxFieldAlignment { get; private set; } = 0; - public bool HasStrings { get; private set; } - public bool HasPreAllocatedBuffers { get; private set; } + public ulong MaxFieldAlignment { get; private set; } = 0; + public bool HasStrings { get; private set; } + public bool HasPreAllocatedBuffers { get; private set; } + public bool HasPointers { get; private set; } - public bool IsOpaque => Members.Count == 0; - public string NativeTypeDesignator { get; } + public bool IsOpaque => Members.Count == 0; + public string NativeTypeDesignator { get; } - public StructureInfo (LlvmIrGenerator generator) + public StructureInfo (LlvmIrModule module, Type type) { - type = typeof(T); + this.type = type; Name = type.GetShortName (); - Size = GatherMembers (type, generator); + Size = GatherMembers (type, module); DataProvider = type.GetDataProvider (); NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; } - public void RenderDeclaration (LlvmIrGenerator generator) - { - TextWriter output = generator.Output; - generator.WriteStructureDeclarationStart (NativeTypeDesignator, Name, forOpaqueType: IsOpaque); - - if (IsOpaque) { - return; - } - - for (int i = 0; i < Members.Count; i++) { - StructureMemberInfo info = Members[i]; - string nativeType = LlvmIrGenerator.MapManagedTypeToNative (info.MemberType); - if (info.Info.IsNativePointer ()) { - nativeType += "*"; - } - - // TODO: nativeType can be an array, update to indicate that (and get the size) - string arraySize; - if (info.IsNativeArray) { - arraySize = $"[{info.ArrayElements}]"; - } else { - arraySize = String.Empty; - } - - var comment = $"{nativeType} {info.Info.Name}{arraySize}"; - generator.WriteStructureDeclarationField (info.IRType, comment, i == Members.Count - 1); - } - - generator.WriteStructureDeclarationEnd (); - } - - public string? GetCommentFromProvider (StructureMemberInfo smi, StructureInstance instance) + public string? GetCommentFromProvider (StructureMemberInfo smi, StructureInstance instance) { if (DataProvider == null || !smi.Info.UsesDataProvider ()) { return null; @@ -76,7 +45,7 @@ public void RenderDeclaration (LlvmIrGenerator generator) return ret; } - public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureInstance instance) + public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureInstance instance) { if (DataProvider == null) { return 0; @@ -85,7 +54,7 @@ public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureIns return DataProvider.GetBufferSize (instance.Obj, smi.Info.Name); } - ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = true) + ulong GatherMembers (Type type, LlvmIrModule module, bool storeMembers = true) { ulong size = 0; foreach (MemberInfo mi in type.GetMembers ()) { @@ -93,13 +62,17 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = t continue; } - var info = new StructureMemberInfo (mi, generator); + var info = new StructureMemberInfo (mi, module); + if (info.IsNativePointer) { + HasPointers = true; + } + if (storeMembers) { Members.Add (info); size += info.Size; - if ((int)info.Alignment > MaxFieldAlignment) { - MaxFieldAlignment = (int)info.Alignment; + if (info.Alignment > MaxFieldAlignment) { + MaxFieldAlignment = info.Alignment; } } @@ -116,7 +89,7 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = t // The presence of strings/buffers is important at the generation time as it is used to decide whether we need separate stream writers for them and // if the owning structure does **not** have any of those, the generated code would be invalid if (info.IsIRStruct ()) { - GatherMembers (info.MemberType, generator, storeMembers: false); + GatherMembers (info.MemberType, module, storeMembers: false); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs deleted file mode 100644 index dc8f8f8ce99..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.New.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - // TODO: remove these aliases once the refactoring is done - using StructurePointerData = LLVMIR.StructurePointerData; - - abstract class StructureInstance - { - Dictionary? pointees; - StructureInfo info; - - public object? Obj { get; } - public Type Type => info.Type; - public StructureInfo Info => info; - - /// - /// Do **not** set this property, it is used internally by , - /// and when dealing with arrays of objects where each - /// array index contains the same object instance - /// - internal ulong IndexInArray { get; set; } - - /// - /// This is a cludge to support zero-initialized structures. In order to output proper variable type - /// when a structure is used, the generator must be able to read the structure descrption, which is - /// provided in the property and, thus, it requires a variable of structural type to - /// **always** have a non-null value. To support zero initialization of such structures, this property - /// can be set to true - /// - public bool IsZeroInitialized { get; set; } - - protected StructureInstance (StructureInfo info, object instance) - { - if (instance == null) { - throw new ArgumentNullException (nameof (instance)); - } - - if (!info.Type.IsAssignableFrom (instance.GetType ())) { - throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance)); - } - - this.info = info; - Obj = instance; - } - - public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) - { - if (pointees == null) { - pointees = new Dictionary (); - } - - 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; - } - } - - /// - /// Represents a typed structure instance, derived from the class. The slightly weird - /// approach is because on one hand we need to operate on a heterogenous set of structures (in which generic types would - /// only get in the way), but on the other hand we need to be able to get the structure type (whose instance is in - /// and ) only by looking at the **type**. This is needed in situations when we have - /// an array of some structures that is empty - we wouldn't be able to gleam the structure type from any instance and we still - /// need to output a stronly typed LLVM IR declaration of the structure array. With this class, most of the code will use the - /// abstract type, and knowing we have only one non-abstract implementation of the class allows - /// us to use StructureInstance<T> in a cast, to get T via reflection. - /// - sealed class StructureInstance : StructureInstance - { - public T? Instance => (T)Obj; - - public StructureInstance (StructureInfo info, T instance) - : base (info, instance) - {} - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs index 3b171485f5e..ef4d7759d9f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs @@ -1,31 +1,57 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR { - class StructureInstance + abstract class StructureInstance { - Dictionary, StructurePointerData>? pointees; + Dictionary? pointees; + StructureInfo info; - public T Obj { get; } + public object? Obj { get; } + public Type Type => info.Type; + public StructureInfo Info => info; - public StructureInstance (T instance) + /// + /// Do **not** set this property, it is used internally by , + /// and when dealing with arrays of objects where each + /// array index contains the same object instance + /// + internal ulong IndexInArray { get; set; } + + /// + /// This is a cludge to support zero-initialized structures. In order to output proper variable type + /// when a structure is used, the generator must be able to read the structure descrption, which is + /// provided in the property and, thus, it requires a variable of structural type to + /// **always** have a non-null value. To support zero initialization of such structures, this property + /// can be set to true + /// + public bool IsZeroInitialized { get; set; } + + protected StructureInstance (StructureInfo info, object instance) { + if (instance == null) { + throw new ArgumentNullException (nameof (instance)); + } + + if (!info.Type.IsAssignableFrom (instance.GetType ())) { + throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance)); + } + + this.info = info; Obj = instance; } - public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) + public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) { if (pointees == null) { - pointees = new Dictionary, StructurePointerData> (); + pointees = new Dictionary (); } pointees.Add (smi, new StructurePointerData (variableName, dataSize)); } - public StructurePointerData? GetPointerData (StructureMemberInfo smi) + public StructurePointerData? GetPointerData (StructureMemberInfo smi) { if (pointees != null && pointees.TryGetValue (smi, out StructurePointerData ssd)) { return ssd; @@ -34,4 +60,23 @@ public void AddPointerData (StructureMemberInfo smi, string? variableName, ul return null; } } + + /// + /// Represents a typed structure instance, derived from the class. The slightly weird + /// approach is because on one hand we need to operate on a heterogenous set of structures (in which generic types would + /// only get in the way), but on the other hand we need to be able to get the structure type (whose instance is in + /// and ) only by looking at the **type**. This is needed in situations when we have + /// an array of some structures that is empty - we wouldn't be able to gleam the structure type from any instance and we still + /// need to output a stronly typed LLVM IR declaration of the structure array. With this class, most of the code will use the + /// abstract type, and knowing we have only one non-abstract implementation of the class allows + /// us to use StructureInstance<T> in a cast, to get T via reflection. + /// + sealed class StructureInstance : StructureInstance + { + public T? Instance => (T)Obj; + + public StructureInstance (StructureInfo info, T instance) + : base (info, instance) + {} + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs deleted file mode 100644 index c67af949525..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.New.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Reflection; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - 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 or a struct - /// - public ulong Size { get; } - public ulong Alignment { 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, LlvmIrModule module) - { - Info = mi; - - MemberType = mi switch { - FieldInfo fi => fi.FieldType, - PropertyInfo pi => pi.PropertyType, - _ => throw new InvalidOperationException ($"Unsupported member type {mi}") - }; - - ulong size = 0; - bool isPointer = false; - if (MemberType != typeof(string) && !MemberType.IsArray && (MemberType.IsStructure () || MemberType.IsClass)) { - IRType = $"%struct.{MemberType.GetShortName ()}"; - // TODO: figure out how to get structure size if it isn't a pointer - } else { - IRType = LlvmIrGenerator.MapToIRType (MemberType, out size, out isPointer); - } - IsNativePointer = isPointer; - - if (!IsNativePointer) { - IsNativePointer = mi.IsNativePointer (); - if (IsNativePointer) { - IRType = LlvmIrGenerator.IRPointerType; - } - } - - BaseTypeSize = size; - ArrayElements = 0; - IsInlineArray = false; - NeedsPadding = false; - Alignment = 0; - - if (IsNativePointer) { - size = 0; // Real size will be determined when code is generated and we know the target architecture - } else if (mi.IsInlineArray ()) { - if (!MemberType.IsArray) { - throw new InvalidOperationException ($"Internal error: member {mi.Name} of structure {mi.DeclaringType.Name} is marked as inline array, but is not of an array type."); - } - - IsInlineArray = true; - IsNativeArray = true; - NeedsPadding = mi.InlineArrayNeedsPadding (); - int arrayElements = mi.GetInlineArraySize (); - if (arrayElements < 0) { - arrayElements = GetArraySizeFromProvider (MemberType.GetDataProvider (), mi.Name); - } - - if (arrayElements < 0) { - throw new InvalidOperationException ($"Array cannot have negative size (got {arrayElements})"); - } - - IRType = $"[{arrayElements} x {IRType}]"; - ArrayElements = (ulong)arrayElements; - } else if (this.IsIRStruct ()) { - StructureInfo si = module.GetStructureInfo (MemberType); - size = si.Size; - Alignment = (ulong)si.MaxFieldAlignment; - } - - if (MemberType.IsArray && !IsInlineArray) { - throw new InvalidOperationException ("Out of line arrays in structures aren't currently supported"); - } - - Size = size; - if (Alignment == 0) { - Alignment = size; - } - } - - public object? GetValue (object 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/StructureMemberInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs index 63b6f111a48..1f313a34f31 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs @@ -3,7 +3,7 @@ namespace Xamarin.Android.Tasks.LLVMIR { - sealed class StructureMemberInfo + sealed class StructureMemberInfo { public string IRType { get; } public MemberInfo Info { get; } @@ -27,7 +27,7 @@ sealed class StructureMemberInfo public bool IsInlineArray { get; } public bool NeedsPadding { get; } - public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) + public StructureMemberInfo (MemberInfo mi, LlvmIrModule module) { Info = mi; @@ -38,18 +38,19 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) }; ulong size = 0; + bool isPointer = false; if (MemberType != typeof(string) && !MemberType.IsArray && (MemberType.IsStructure () || MemberType.IsClass)) { IRType = $"%struct.{MemberType.GetShortName ()}"; // TODO: figure out how to get structure size if it isn't a pointer } else { - IRType = generator.MapManagedTypeToIR (MemberType, out size); + IRType = LlvmIrGenerator.MapToIRType (MemberType, out size, out isPointer); } - IsNativePointer = IRType[IRType.Length - 1] == '*'; + IsNativePointer = isPointer; if (!IsNativePointer) { IsNativePointer = mi.IsNativePointer (); if (IsNativePointer) { - IRType += "*"; + IRType = LlvmIrGenerator.IRPointerType; } } @@ -60,14 +61,18 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) Alignment = 0; if (IsNativePointer) { - size = (ulong)generator.PointerSize; + size = 0; // Real size will be determined when code is generated and we know the target architecture } else if (mi.IsInlineArray ()) { + if (!MemberType.IsArray) { + throw new InvalidOperationException ($"Internal error: member {mi.Name} of structure {mi.DeclaringType.Name} is marked as inline array, but is not of an array type."); + } + IsInlineArray = true; IsNativeArray = true; NeedsPadding = mi.InlineArrayNeedsPadding (); int arrayElements = mi.GetInlineArraySize (); if (arrayElements < 0) { - arrayElements = GetArraySizeFromProvider (typeof(T).GetDataProvider (), mi.Name); + arrayElements = GetArraySizeFromProvider (MemberType.GetDataProvider (), mi.Name); } if (arrayElements < 0) { @@ -77,7 +82,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) IRType = $"[{arrayElements} x {IRType}]"; ArrayElements = (ulong)arrayElements; } else if (this.IsIRStruct ()) { - IStructureInfo si = generator.GetStructureInfo (MemberType); + StructureInfo si = module.GetStructureInfo (MemberType); size = si.Size; Alignment = (ulong)si.MaxFieldAlignment; } @@ -92,7 +97,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) } } - public object? GetValue (T instance) + public object? GetValue (object instance) { if (Info is FieldInfo fi) { return fi.GetValue (instance); @@ -110,11 +115,5 @@ int GetArraySizeFromProvider (NativeAssemblerStructContextDataProvider? provider return (int)provider.GetMaxInlineWidth (null, fieldName); } - - public override int GetHashCode () - { - return base.GetHashCode () ^ Info.GetHashCode () ^ MemberType.GetHashCode (); - } - } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs deleted file mode 100644 index a67a3c41859..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.New.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; - -namespace Xamarin.Android.Tasks.LLVM.IR -{ - 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.IsArray && - type != typeof (decimal) && - type != typeof (DateTime) && - type != typeof (object); - } - - public static bool IsIRStruct (this StructureMemberInfo smi) - { - Type type = smi.MemberType; - - // type.IsStructure() handles checks for primitive types, enums etc - return - type != typeof(string) && - !smi.Info.IsInlineArray () && - !smi.Info.IsNativePointer () && - (type.IsStructure () || type.IsClass); - } - - public static NativeAssemblerStructContextDataProvider? GetDataProvider (this Type t) - { - var attr = t.GetCustomAttribute (); - if (attr == null) { - return null; - } - - return Activator.CreateInstance (attr.Type) as NativeAssemblerStructContextDataProvider; - } - - public static bool IsNativeClass (this Type t) - { - var attr = t.GetCustomAttribute (); - return attr != null; - } - - public static bool ImplementsInterface (this Type type, Type requiredIfaceType) - { - if (type == null || requiredIfaceType == null) { - return false; - } - - if (type == requiredIfaceType) { - return true; - } - - bool generic = requiredIfaceType.IsGenericType; - foreach (Type iface in type.GetInterfaces ()) { - if (iface == requiredIfaceType) { - return true; - } - - if (generic) { - if (!iface.IsGenericType) { - continue; - } - - if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { - return true; - } - } - } - - return false; - } - - public static bool IsStructureInstance (this Type type, out Type? structureType) - { - structureType = null; - if (!type.IsGenericType) { - return false; - } - - if (type.GetGenericTypeDefinition () != typeof(StructureInstance<>)) { - return false; - } - - structureType = type.GetGenericArguments ()[0]; - return true; - } - - /// - /// Return element type of a single-dimensional (with one exception, see below) array. Parameter **MUST** - /// correspond to one of the following array types: T[], ICollection<T> or IDictionary<string, string>. The latter is - /// used to comfortably represent name:value arrays, which are output as single dimensional arrays in the native code. - /// - /// - /// Thrown when is not one of the array types listed above. - /// - public static Type GetArrayElementType (this Type type) - { - if (type.IsArray) { - return type.GetElementType (); - } - - if (!type.IsGenericType) { - throw WrongTypeException (); - } - - Type genericType = type.GetGenericTypeDefinition (); - if (genericType.ImplementsInterface (typeof(ICollection<>))) { - Type[] genericArgs = type.GetGenericArguments (); - return genericArgs[0]; - } - - if (!genericType.ImplementsInterface (typeof(IDictionary))) { - throw WrongTypeException (); - } - - return typeof(string); - - // Dictionary - Exception WrongTypeException () => new InvalidOperationException ($"Internal error: type '{type}' is not an array, ICollection or IDictionary"); - } - - /// - /// Determine whether type represents an array, in our understanding. That means the type has to be - /// a standard single-dimensional language array (i.e. T[]), implement ICollection<T> together with ICollection or, - /// as a special case for name:value pair collections, implement IDictionary<string, string> - /// - public static bool IsArray (this Type t) - { - if (t.IsPrimitive) { - return false; - } - - if (t == typeof(string)) { - return false; - } - - if (t.IsArray) { - if (t.GetArrayRank () > 1) { - throw new NotSupportedException ("Internal error: multi-dimensional arrays aren't supported"); - } - - return true; - } - - // TODO: cache results here - // IDictionary is a special case for name:value string arrays which we use for some constructs. - return (t.ImplementsInterface (typeof(ICollection<>)) && t.ImplementsInterface (typeof(ICollection))) || - t.ImplementsInterface (typeof(IDictionary)); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs index fd41c1790e8..b50b7290d90 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs @@ -1,8 +1,8 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Reflection; -using Xamarin.Android.Tasks; - namespace Xamarin.Android.Tasks.LLVMIR { static class TypeUtilities @@ -46,7 +46,7 @@ public static bool IsStructure (this Type type) type != typeof (object); } - public static bool IsIRStruct (this StructureMemberInfo smi) + public static bool IsIRStruct (this StructureMemberInfo smi) { Type type = smi.MemberType; @@ -73,5 +73,113 @@ public static bool IsNativeClass (this Type t) var attr = t.GetCustomAttribute (); return attr != null; } + + public static bool ImplementsInterface (this Type type, Type requiredIfaceType) + { + if (type == null || requiredIfaceType == null) { + return false; + } + + if (type == requiredIfaceType) { + return true; + } + + bool generic = requiredIfaceType.IsGenericType; + foreach (Type iface in type.GetInterfaces ()) { + if (iface == requiredIfaceType) { + return true; + } + + if (generic) { + if (!iface.IsGenericType) { + continue; + } + + if (iface.GetGenericTypeDefinition () == requiredIfaceType.GetGenericTypeDefinition ()) { + return true; + } + } + } + + return false; + } + + public static bool IsStructureInstance (this Type type, out Type? structureType) + { + structureType = null; + if (!type.IsGenericType) { + return false; + } + + if (type.GetGenericTypeDefinition () != typeof(StructureInstance<>)) { + return false; + } + + structureType = type.GetGenericArguments ()[0]; + return true; + } + + /// + /// Return element type of a single-dimensional (with one exception, see below) array. Parameter **MUST** + /// correspond to one of the following array types: T[], ICollection<T> or IDictionary<string, string>. The latter is + /// used to comfortably represent name:value arrays, which are output as single dimensional arrays in the native code. + /// + /// + /// Thrown when is not one of the array types listed above. + /// + public static Type GetArrayElementType (this Type type) + { + if (type.IsArray) { + return type.GetElementType (); + } + + if (!type.IsGenericType) { + throw WrongTypeException (); + } + + Type genericType = type.GetGenericTypeDefinition (); + if (genericType.ImplementsInterface (typeof(ICollection<>))) { + Type[] genericArgs = type.GetGenericArguments (); + return genericArgs[0]; + } + + if (!genericType.ImplementsInterface (typeof(IDictionary))) { + throw WrongTypeException (); + } + + return typeof(string); + + // Dictionary + Exception WrongTypeException () => new InvalidOperationException ($"Internal error: type '{type}' is not an array, ICollection or IDictionary"); + } + + /// + /// Determine whether type represents an array, in our understanding. That means the type has to be + /// a standard single-dimensional language array (i.e. T[]), implement ICollection<T> together with ICollection or, + /// as a special case for name:value pair collections, implement IDictionary<string, string> + /// + public static bool IsArray (this Type t) + { + if (t.IsPrimitive) { + return false; + } + + if (t == typeof(string)) { + return false; + } + + if (t.IsArray) { + if (t.GetArrayRank () > 1) { + throw new NotSupportedException ("Internal error: multi-dimensional arrays aren't supported"); + } + + return true; + } + + // TODO: cache results here + // IDictionary is a special case for name:value string arrays which we use for some constructs. + return (t.ImplementsInterface (typeof(ICollection<>)) && t.ImplementsInterface (typeof(ICollection))) || + t.ImplementsInterface (typeof(IDictionary)); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs deleted file mode 100644 index 039b231cecd..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; -using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; -using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; -using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; -using TuneCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TuneCpuFunctionAttribute; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class X64LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"; - public override int PointerSize => 8; - protected override string Triple => "x86_64-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { - new FramePointerFunctionAttribute ("none"), - new TargetCpuFunctionAttribute ("x86-64"), - new TargetFeaturesFunctionAttribute ("+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), - new TuneCpuFunctionAttribute ("generic"), - }; - - public X64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override int GetAggregateAlignment (int maxFieldAlignment, ulong dataSize) - { - // System V ABI for x86_64 mandates that any aggregates 16 bytes or more long will - // be aligned at at least 16 bytes - // - // See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf (Section '3.1.2 Data Representation', "Aggregates and Unions") - // - if (dataSize >= 16 && maxFieldAlignment < 16) { - return 16; - } - - return maxFieldAlignment; - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs deleted file mode 100644 index 284314fdb5a..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.Tools; - -using LlvmIrFunctionAttributeSet = Xamarin.Android.Tasks.LLVM.IR.LlvmIrFunctionAttributeSet; -using FramePointerFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.FramePointerFunctionAttribute; -using TargetCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetCpuFunctionAttribute; -using TargetFeaturesFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TargetFeaturesFunctionAttribute; -using TuneCpuFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.TuneCpuFunctionAttribute; -using StackrealignFunctionAttribute = Xamarin.Android.Tasks.LLVM.IR.StackrealignFunctionAttribute; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - class X86LlvmIrGenerator : LlvmIrGenerator - { - // See https://llvm.org/docs/LangRef.html#data-layout - // - // Value as used by Android NDK's clang++ - // - protected override string DataLayout => "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"; - public override int PointerSize => 4; - protected override string Triple => "i686-unknown-linux-android"; // NDK appends API level, we don't need that - - static readonly LlvmIrFunctionAttributeSet commonAttributes = new LlvmIrFunctionAttributeSet { - new FramePointerFunctionAttribute ("none"), - new TargetCpuFunctionAttribute ("i686"), - new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87"), - new TuneCpuFunctionAttribute ("generic"), - new StackrealignFunctionAttribute (), - }; - - public X86LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) - : base (arch, output, fileName) - {} - - protected override void AddModuleFlagsMetadata (List flagsFields) - { - base.AddModuleFlagsMetadata (flagsFields); - flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); - } - - protected override void InitFunctionAttributes () - { - base.InitFunctionAttributes (); - - FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); - FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); - FunctionAttributes[FunctionAttributesLibcFree].Add (commonAttributes); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs deleted file mode 100644 index f665834367b..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.New.cs +++ /dev/null @@ -1,1088 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Utilities; - -using Xamarin.Android.Tasks.LLVM.IR; - -using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; -using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - using LlvmIrCallMarker = LLVMIR.LlvmIrCallMarker; - - partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer - { - const string GetFunctionPointerVariableName = "get_function_pointer"; - - // This is here only to generate strongly-typed IR - internal sealed class MonoClass - {} - - [NativeClass] - sealed class _JNIEnv - {} - - // Empty class must have at least one member so that the class address can be obtained - [NativeClass] - class _jobject - { - public byte b; - } - - sealed class _jclass : _jobject - {} - - sealed class _jstring : _jobject - {} - - sealed class _jthrowable : _jobject - {} - - class _jarray : _jobject - {} - - sealed class _jobjectArray : _jarray - {} - - sealed class _jbooleanArray : _jarray - {} - - sealed class _jbyteArray : _jarray - {} - - sealed class _jcharArray : _jarray - {} - - sealed class _jshortArray : _jarray - {} - - sealed class _jintArray : _jarray - {} - - sealed class _jlongArray : _jarray - {} - - sealed class _jfloatArray : _jarray - {} - - sealed class _jdoubleArray : _jarray - {} - - sealed class MarshalMethodInfo - { - public MarshalMethodEntry Method { get; } - public string NativeSymbolName { get; set; } - public List Parameters { get; } - public Type ReturnType { get; } - public uint ClassCacheIndex { get; } - - // This one isn't known until the generation time, which happens after we instantiate the class - // in Init and it may be different between architectures/ABIs, hence it needs to be settable from - // the outside. - public uint AssemblyCacheIndex { get; set; } - - public MarshalMethodInfo (MarshalMethodEntry method, Type returnType, string nativeSymbolName, int classCacheIndex) - { - Method = method ?? throw new ArgumentNullException (nameof (method)); - ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); - if (String.IsNullOrEmpty (nativeSymbolName)) { - throw new ArgumentException ("must not be null or empty", nameof (nativeSymbolName)); - } - NativeSymbolName = nativeSymbolName; - Parameters = new List { - new LlvmIrFunctionParameter (typeof (_JNIEnv), "env"), // JNIEnv *env - new LlvmIrFunctionParameter (typeof (_jclass), "klass"), // jclass klass - }; - ClassCacheIndex = (uint)classCacheIndex; - } - } - - sealed class MarshalMethodsManagedClassDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var klass = EnsureType (data); - - if (String.Compare ("token", fieldName, StringComparison.Ordinal) == 0) { - return $" token 0x{klass.token:x}; class name: {klass.ClassName}"; - } - - return String.Empty; - } - } - - [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodsManagedClassDataProvider))] - sealed class MarshalMethodsManagedClass - { - [NativeAssembler (UsesDataProvider = true)] - public uint token; - - [NativePointer (IsNull = true)] - public MonoClass klass; - - [NativeAssembler (Ignore = true)] - public string ClassName; - }; - - sealed class MarshalMethodNameDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var methodName = EnsureType (data); - - if (String.Compare ("id", fieldName, StringComparison.Ordinal) == 0) { - return $" id 0x{methodName.id:x}; name: {methodName.name}"; - } - - return String.Empty; - } - } - - [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodNameDataProvider))] - sealed class MarshalMethodName - { - [NativeAssembler (Ignore = true)] - public ulong Id32; - - [NativeAssembler (Ignore = true)] - public ulong Id64; - - [NativeAssembler (UsesDataProvider = true)] - public ulong id; - public string name; - } - - sealed class AssemblyCacheState - { - public Dictionary? AsmNameToIndexData32; - public Dictionary Hashes32; - public List Keys32; - public List Indices32; - - public Dictionary? AsmNameToIndexData64; - public Dictionary Hashes64; - public List Keys64; - public List Indices64; - } - - sealed class MarshalMethodsWriteState - { - public AssemblyCacheState AssemblyCacheState; - public LlvmIrFunctionAttributeSet AttributeSet; - public Dictionary UniqueAssemblyId; - public Dictionary UsedBackingFields; - public LlvmIrVariable GetFunctionPtrVariable; - public LlvmIrFunction GetFunctionPtrFunction; - } - - sealed class MarshalMethodAssemblyIndexValuePlaceholder : LlvmIrInstructionArgumentValuePlaceholder - { - MarshalMethodInfo mmi; - AssemblyCacheState acs; - - public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, AssemblyCacheState acs) - { - this.mmi = mmi; - this.acs = acs; - } - - public override object? GetValue (LlvmIrModuleTarget target) - { - // What a monstrosity... - string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; - Dictionary asmNameToIndex = target.Is64Bit ? acs.AsmNameToIndexData64 : acs.AsmNameToIndexData32; - if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { - throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); - } - return asmIndex; - } - } - - static readonly Dictionary jniSimpleTypeMap = new Dictionary { - { 'Z', typeof(bool) }, - { 'B', typeof(byte) }, - { 'C', typeof(char) }, - { 'S', typeof(short) }, - { 'I', typeof(int) }, - { 'J', typeof(long) }, - { 'F', typeof(float) }, - { 'D', typeof(double) }, - }; - - static readonly Dictionary jniArrayTypeMap = new Dictionary { - { 'Z', typeof(_jbooleanArray) }, - { 'B', typeof(_jbyteArray) }, - { 'C', typeof(_jcharArray) }, - { 'S', typeof(_jshortArray) }, - { 'I', typeof(_jintArray) }, - { 'J', typeof(_jlongArray) }, - { 'F', typeof(_jfloatArray) }, - { 'D', typeof(_jdoubleArray) }, - { 'L', typeof(_jobjectArray) }, - }; - - ICollection uniqueAssemblyNames; - int numberOfAssembliesInApk; - IDictionary> marshalMethods; - TaskLoggingHelper logger; - - StructureInfo marshalMethodsManagedClassStructureInfo; - StructureInfo marshalMethodNameStructureInfo; - - List methods; - List> classes = new List> (); - - LlvmIrCallMarker defaultCallMarker; - - readonly bool generateEmptyCode; - - /// - /// Constructor to be used ONLY when marshal methods are DISABLED - /// - public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames) - { - this.numberOfAssembliesInApk = numberOfAssembliesInApk; - this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); - generateEmptyCode = true; - defaultCallMarker = LlvmIrCallMarker.Tail; - } - - /// - /// Constructor to be used ONLY when marshal methods are ENABLED - /// - public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger, MarshalMethodsTracingMode tracingMode) - { - this.numberOfAssembliesInApk = numberOfAssembliesInApk; - this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); - this.marshalMethods = marshalMethods; - this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); - - generateEmptyCode = false; - this.tracingMode = tracingMode; - defaultCallMarker = tracingMode != MarshalMethodsTracingMode.None ? LlvmIrCallMarker.None : LlvmIrCallMarker.Tail; - } - - void Init () - { - if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { - return; - } - - var seenClasses = new Dictionary (StringComparer.Ordinal); - var allMethods = new List (); - - // It's possible that several otherwise different methods (from different classes, but with the same - // names and similar signatures) will actually share the same **short** native symbol name. In this case we must - // ensure that they all use long symbol names. This has to be done as a post-processing step, after we - // have already iterated over the entire method collection. - // - // A handful of examples from the Hello World MAUI app: - // - // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnActionItemClicked(Android.Views.ActionMode,Android.Views.IMenuItem)) - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnActionItemClicked(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenuItem)) - // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked__Landroidx_appcompat_view_ActionMode_2Landroid_view_MenuItem_2 - // - // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnCreateActionMode(Android.Views.ActionMode,Android.Views.IMenu)) - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnCreateActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) - // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 - // - // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void Android.Views.ActionMode/ICallback::OnDestroyActionMode(Android.Views.ActionMode)) - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void AndroidX.AppCompat.View.ActionMode/ICallback::OnDestroyActionMode(AndroidX.AppCompat.View.ActionMode)) - // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode__Landroidx_appcompat_view_ActionMode_2 - // - // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnPrepareActionMode(Android.Views.ActionMode,Android.Views.IMenu)) - // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnPrepareActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) - // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 - // - var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); - foreach (IList entryList in marshalMethods.Values) { - bool useFullNativeSignature = entryList.Count > 1; - foreach (MarshalMethodEntry entry in entryList) { - ProcessAndAddMethod (allMethods, entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); - } - } - - foreach (List mmiList in overloadedNativeSymbolNames.Values) { - if (mmiList.Count <= 1) { - continue; - } - - foreach (MarshalMethodInfo overloadedMethod in mmiList) { - overloadedMethod.NativeSymbolName = MakeNativeSymbolName (overloadedMethod.Method, useFullNativeSignature: true); - } - } - - // In some cases it's possible that a single type implements two different interfaces which have methods with the same native signature: - // - // Microsoft.Maui.Controls.Handlers.TabbedPageManager/Listeners - // System.Void AndroidX.ViewPager.Widget.ViewPager/IOnPageChangeListener::OnPageSelected(System.Int32) - // System.Void AndroidX.ViewPager2.Widget.ViewPager2/OnPageChangeCallback::OnPageSelected(System.Int32) - // - // Both of the above methods will have the same native implementation and symbol name. e.g. (Java type name being `crc649ff77a65592e7d55/TabbedPageManager_Listeners`): - // Java_crc649ff77a65592e7d55_TabbedPageManager_1Listeners_n_1onPageSelected__I - // - // We need to de-duplicate the entries or the generated native code will fail to build. - var seenNativeSymbols = new HashSet (StringComparer.Ordinal); - methods = new List (); - - foreach (MarshalMethodInfo method in allMethods) { - if (seenNativeSymbols.Contains (method.NativeSymbolName)) { - logger.LogDebugMessage ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}"); - continue; - } - - seenNativeSymbols.Add (method.NativeSymbolName); - methods.Add (method); - } - } - - string MakeNativeSymbolName (MarshalMethodEntry entry, bool useFullNativeSignature) - { - var sb = new StringBuilder ("Java_"); - sb.Append (MangleForJni (entry.JniTypeName)); - sb.Append ('_'); - sb.Append (MangleForJni ($"n_{entry.JniMethodName}")); - - if (useFullNativeSignature) { - string signature = entry.JniMethodSignature; - if (signature.Length < 2) { - ThrowInvalidSignature (signature, "must be at least two characters long"); - } - - if (signature[0] != '(') { - ThrowInvalidSignature (signature, "must start with '('"); - } - - int sigEndIdx = signature.LastIndexOf (')'); - if (sigEndIdx < 1) { // the first position where ')' can appear is 1, for a method without parameters - ThrowInvalidSignature (signature, "missing closing parenthesis"); - } - - string sigParams = signature.Substring (1, sigEndIdx - 1); - if (sigParams.Length > 0) { - sb.Append ("__"); - sb.Append (MangleForJni (sigParams)); - } - } - - return sb.ToString (); - - void ThrowInvalidSignature (string signature, string reason) - { - throw new InvalidOperationException ($"Invalid JNI signature '{signature}': {reason}"); - } - } - - void ProcessAndAddMethod (List allMethods, MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) - { - CecilMethodDefinition nativeCallback = entry.NativeCallback; - string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); - string klass = $"{nativeCallback.DeclaringType.FullName}, {nativeCallback.Module.Assembly.FullName}"; - - if (!seenClasses.TryGetValue (klass, out int classIndex)) { - classIndex = classes.Count; - seenClasses.Add (klass, classIndex); - - var mc = new MarshalMethodsManagedClass { - token = nativeCallback.DeclaringType.MetadataToken.ToUInt32 (), - ClassName = klass, - }; - - classes.Add (new StructureInstance (marshalMethodsManagedClassStructureInfo, mc)); - } - - // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference - (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.IsSpecial ? entry.NativeCallback : entry.ImplementedMethod); - - var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: nativeSymbolName, classIndex); - if (parameters != null && parameters.Count > 0) { - method.Parameters.AddRange (parameters); - } - - if (!overloadedNativeSymbolNames.TryGetValue (method.NativeSymbolName, out List overloadedMethods)) { - overloadedMethods = new List (); - overloadedNativeSymbolNames.Add (method.NativeSymbolName, overloadedMethods); - } - overloadedMethods.Add (method); - - allMethods.Add (method); - } - - string MangleForJni (string name) - { - var sb = new StringBuilder (); - - foreach (char ch in name) { - switch (ch) { - case '/': - case '.': - sb.Append ('_'); - break; - - case '_': - sb.Append ("_1"); - break; - - case ';': - sb.Append ("_2"); - break; - - case '[': - sb.Append ("_3"); - break; - - default: - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { - sb.Append (ch); - } else { - sb.Append ("_0"); - sb.Append (((int)ch).ToString ("x04")); - } - break; - } - } - - return sb.ToString (); - } - - (Type returnType, List? functionParams) ParseJniSignature (string signature, Mono.Cecil.MethodDefinition implementedMethod) - { - Type returnType = null; - List? parameters = null; - bool paramsDone = false; - int idx = 0; - while (!paramsDone && idx < signature.Length) { - char jniType = signature[idx]; - - if (jniType == '(') { - idx++; - continue; - } - - if (jniType == ')') { - paramsDone = true; - continue; - } - - Type? managedType = JniTypeToManaged (jniType); - if (managedType != null) { - AddParameter (managedType); - continue; - } - - throw new InvalidOperationException ($"Unsupported JNI type '{jniType}' at position {idx} of signature '{signature}'"); - } - - if (!paramsDone || idx >= signature.Length || signature[idx] != ')') { - throw new InvalidOperationException ($"Missing closing arguments parenthesis: '{signature}'"); - } - - idx++; - if (signature[idx] == 'V') { - returnType = typeof(void); - } else { - returnType = JniTypeToManaged (signature[idx]); - } - - return (returnType, parameters); - - Type? JniTypeToManaged (char jniType) - { - if (jniSimpleTypeMap.TryGetValue (jniType, out Type managedType)) { - idx++; - return managedType; - } - - if (jniType == 'L') { - return JavaClassToManaged (justSkip: false); - } - - if (jniType == '[') { - // Arrays of arrays (any rank) are bound as a simple pointer, which makes the generated code much simpler (no need to generate pointers to - // pointers to pointers etc), especially that we don't need to dereference these pointers in generated code, we simply pass them along to - // the managed land after all. - while (signature[idx] == '[') { - idx++; - } - - jniType = signature[idx]; - if (jniArrayTypeMap.TryGetValue (jniType, out managedType)) { - if (jniType == 'L') { - JavaClassToManaged (justSkip: true); - } else { - idx++; - } - - return managedType; - } - - throw new InvalidOperationException ($"Unsupported JNI array type '{jniType}' at index {idx} of signature '{signature}'"); - } - - return null; - } - - Type? JavaClassToManaged (bool justSkip) - { - idx++; - StringBuilder sb = null; - if (!justSkip) { - sb = new StringBuilder (); - } - - while (idx < signature.Length) { - if (signature[idx] == ')') { - throw new InvalidOperationException ($"Syntax error: unterminated class type (missing ';' before closing parenthesis) in signature '{signature}'"); - } - - if (signature[idx] == ';') { - idx++; - break; - } - - sb?.Append (signature[idx]); - idx++; - } - - if (justSkip) { - return null; - } - - string typeName = sb.ToString (); - if (String.Compare (typeName, "java/lang/Class", StringComparison.Ordinal) == 0) { - return typeof(_jclass); - } - - if (String.Compare (typeName, "java/lang/String", StringComparison.Ordinal) == 0) { - return typeof(_jstring); - } - - if (String.Compare (typeName, "java/lang/Throwable", StringComparison.Ordinal) == 0) { - return typeof(_jthrowable); - } - - return typeof(_jobject); - } - - void AddParameter (Type type) - { - if (parameters == null) { - parameters = new List (); - } - - if (implementedMethod.Parameters.Count <= parameters.Count) { - throw new InvalidOperationException ($"Method {implementedMethod.FullName} managed signature doesn't match its JNI signature '{signature}' (not enough parameters)"); - } - - // Every parameter which isn't a primitive type becomes a pointer - parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name)); - } - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - - Init (); - // InitTracing (module); - AddAssemblyImageCache (module, out AssemblyCacheState acs); - - // class cache - module.AddGlobalVariable ("marshal_methods_number_of_classes", (uint)classes.Count, LLVMIR.LlvmIrVariableOptions.GlobalConstant); - module.AddGlobalVariable ("marshal_methods_class_cache", classes, LLVMIR.LlvmIrVariableOptions.GlobalWritable); - - // Marshal methods class names - var mm_class_names = new List (); - foreach (StructureInstance klass in classes) { - mm_class_names.Add (klass.Instance.ClassName); - } - module.AddGlobalVariable ("mm_class_names", mm_class_names, LLVMIR.LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); - - AddMarshalMethodNames (module, acs); - (LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) = AddXamarinAppInitFunction (module); - - AddMarshalMethods (module, acs, getFunctionPtrVariable, getFunctionPtrFunction); - } - - void MapStructures (LlvmIrModule module) - { - marshalMethodsManagedClassStructureInfo = module.MapStructure (); - marshalMethodNameStructureInfo = module.MapStructure (); - } - - void AddMarshalMethods (LlvmIrModule module, AssemblyCacheState acs, LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) - { - if (generateEmptyCode || methods == null || methods.Count == 0) { - return; - } - - // This will make all the backing fields to appear in a block without empty lines separating them. - module.Add ( - new LlvmIrGroupDelimiterVariable () { - Comment = " Marshal methods backing fields, pointers to native functions" - } - ); - - var writeState = new MarshalMethodsWriteState { - AssemblyCacheState = acs, - AttributeSet = MakeMarshalMethodAttributeSet (module), - UsedBackingFields = new Dictionary (StringComparer.Ordinal), - UniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase), - GetFunctionPtrVariable = getFunctionPtrVariable, - GetFunctionPtrFunction = getFunctionPtrFunction, - }; - foreach (MarshalMethodInfo mmi in methods) { - CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; - string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; - - if (!writeState.UniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { - asmId = (ulong)writeState.UniqueAssemblyId.Count; - writeState.UniqueAssemblyId.Add (asmName, asmId); - } - - AddMarshalMethod (module, mmi, asmId, writeState); - } - - module.Add (new LlvmIrGroupDelimiterVariable ()); - } - - void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, MarshalMethodsWriteState writeState) - { - CecilMethodDefinition nativeCallback = method.Method.NativeCallback; - string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{asmId}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; - - if (!writeState.UsedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { - backingField = module.AddGlobalVariable (typeof(IntPtr), backingFieldName, null, LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr); - writeState.UsedBackingFields.Add (backingFieldName, backingField); - } - - var funcComment = new StringBuilder (" Method: "); - funcComment.AppendLine (nativeCallback.FullName); - funcComment.Append (" Assembly: "); - funcComment.AppendLine (nativeCallback.Module.Assembly.Name.FullName); - funcComment.Append (" Registered: "); - funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); - - var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, writeState.AttributeSet) { - Comment = funcComment.ToString (), - }; - - WriteBody (func.Body); - module.Add (func); - - void WriteBody (LlvmIrFunctionBody body) - { - LlvmIrLocalVariable callback = func.CreateLocalVariable (typeof(IntPtr)); - body.Load (backingField, callback, tbaa: module.TbaaAnyPointer); - - LlvmIrLocalVariable callbackIsNullResult = func.CreateLocalVariable (typeof(bool)); - body.Icmp (LLVMIR.LlvmIrIcmpCond.Equal, callback, null, callbackIsNullResult); - - var callbackIsNullLabel = new LlvmIrFunctionLabelItem (); - var callbackNotNullLabel = new LlvmIrFunctionLabelItem (); - body.Br (callbackIsNullResult, callbackIsNullLabel, callbackNotNullLabel); - - // Callback variable was null - body.Add (callbackIsNullLabel); - - LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr)); - body.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult, tbaa: module.TbaaAnyPointer); - - var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); - LlvmIrInstructions.Call call = body.Call ( - writeState.GetFunctionPtrFunction, - arguments: new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken.ToUInt32 (), backingField } - ); - call.FuncPointer = getFuncPtrResult; - - LlvmIrLocalVariable newlySetCallback = func.CreateLocalVariable (typeof(IntPtr)); - body.Load (backingField, newlySetCallback, tbaa: module.TbaaAnyPointer); - body.Br (callbackNotNullLabel); - - // Callback variable has just been set or it wasn't null - body.Add (callbackNotNullLabel); - LlvmIrLocalVariable selectedCallback = func.CreateLocalVariable (typeof(IntPtr)); - - // Preceding blocks are ordered from the newest to the oldest, so we need to pass the variables referring to our callback in "reverse" order - body.Phi (selectedCallback, newlySetCallback, body.PrecedingBlock1, callback, body.PrecedingBlock2); - - var nativeFunc = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters); - nativeFunc.Signature.ReturnAttributes.NoUndef = true; - - var arguments = new List (); - foreach (LlvmIrFunctionParameter parameter in nativeFunc.Signature.Parameters) { - arguments.Add (new LlvmIrLocalVariable (parameter.Type, parameter.Name)); - } - LlvmIrLocalVariable? result = nativeFunc.ReturnsValue ? func.CreateLocalVariable (nativeFunc.Signature.ReturnType) : null; - call = body.Call (nativeFunc, result, arguments); - call.CallMarker = LlvmIrCallMarker.Tail; - - body.Ret (nativeFunc.Signature.ReturnType, result); - } - } - - LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) - { - var attrSet = new LlvmIrFunctionAttributeSet { - new MustprogressFunctionAttribute (), - new UwtableFunctionAttribute (), - new MinLegalVectorWidthFunctionAttribute (0), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), - }; - - return module.AddAttributeSet (attrSet); - } - - (LlvmIrVariable getFuncPtrVariable, LlvmIrFunction getFuncPtrFunction) AddXamarinAppInitFunction (LlvmIrModule module) - { - var getFunctionPtrParams = new List { - new (typeof(uint), "mono_image_index") { - NoUndef = true, - }, - new (typeof(uint), "class_index") { - NoUndef = true, - }, - new (typeof(uint), "method_token") { - NoUndef = true, - }, - new (typeof(IntPtr), "target_ptr") { - NoUndef = true, - NonNull = true, - Align = 0, // 0 means use natural pointer alignment - Dereferenceable = 0, // ditto 👆 - IsCplusPlusReference = true, - }, - }; - - var getFunctionPtrComment = new StringBuilder (" "); - getFunctionPtrComment.Append (GetFunctionPointerVariableName); - getFunctionPtrComment.Append (" ("); - for (int i = 0; i < getFunctionPtrParams.Count; i++) { - if (i > 0) { - getFunctionPtrComment.Append (", "); - } - LlvmIrFunctionParameter parameter = getFunctionPtrParams[i]; - getFunctionPtrComment.Append (LlvmIrGenerator.MapManagedTypeToNative (parameter.Type)); - if (parameter.IsCplusPlusReference.HasValue && parameter.IsCplusPlusReference.Value) { - getFunctionPtrComment.Append ('&'); - } - getFunctionPtrComment.Append (' '); - getFunctionPtrComment.Append (parameter.Name); - } - getFunctionPtrComment.Append (')'); - - LlvmIrFunction getFunctionPtrFunc = new LlvmIrFunction ( - name: GetFunctionPointerVariableName, - returnType: typeof(void), - parameters: getFunctionPtrParams - ); - - LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable ( - typeof(IntPtr), - GetFunctionPointerVariableName, - null, - LLVMIR.LlvmIrVariableOptions.LocalWritableInsignificantAddr, - getFunctionPtrComment.ToString () - ); - - var init_params = new List { - new (typeof(_JNIEnv), "env") { - NoCapture = true, - NoUndef = true, - ReadNone = true, - }, - new (typeof(IntPtr), "fn") { - NoUndef = true, - }, - }; - - var init_signature = new LlvmIrFunctionSignature ( - name: "xamarin_app_init", - returnType: typeof(void), - parameters: init_params - ); - - LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); - var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); - xamarin_app_init.Body.Add ( - new LlvmIrInstructions.Store (init_params[1], getFunctionPtrVariable) { - TBAA = module.TbaaAnyPointer, - } - ); - xamarin_app_init.Body.Add (new LlvmIrInstructions.Ret (typeof(void))); - - module.Add (xamarin_app_init); - - return (getFunctionPtrVariable, getFunctionPtrFunc); - } - - LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) - { - var attrSet = new LlvmIrFunctionAttributeSet { - new MustprogressFunctionAttribute (), - new NofreeFunctionAttribute (), - new NorecurseFunctionAttribute (), - new NosyncFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - // TODO: LLVM 16+ feature, enable when we switch to this version - // new MemoryFunctionAttribute { - // Default = MemoryAttributeAccessKind.Write, - // Argmem = MemoryAttributeAccessKind.None, - // InaccessibleMem = MemoryAttributeAccessKind.None, - // }, - new UwtableFunctionAttribute (), - new MinLegalVectorWidthFunctionAttribute (0), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), - }; - - return module.AddAttributeSet (attrSet); - } - - void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) - { - var uniqueMethods = new Dictionary (); - - if (!generateEmptyCode && methods != null) { - foreach (MarshalMethodInfo mmi in methods) { - string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - - if (!acs.AsmNameToIndexData32.TryGetValue (asmName, out uint idx32)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 32-bit cache array index"); - } - - if (!acs.AsmNameToIndexData64.TryGetValue (asmName, out uint idx64)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 64-bit cache array index"); - } - - ulong methodToken = (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); - ulong id32 = ((ulong)idx32 << 32) | methodToken; - if (uniqueMethods.ContainsKey (id32)) { - continue; - } - - ulong id64 = ((ulong)idx64 << 32) | methodToken; - uniqueMethods.Add (id32, (mmi, id32, id64)); - } - } - - MarshalMethodName name; - var methodName = new StringBuilder (); - var mm_method_names = new List> (); - foreach (var kvp in uniqueMethods) { - ulong id = kvp.Key; - (MarshalMethodInfo mmi, ulong id32, ulong id64) = kvp.Value; - - RenderMethodNameWithParams (mmi.Method.NativeCallback, methodName); - name = new MarshalMethodName { - Id32 = id32, - Id64 = id64, - - // Tokens are unique per assembly - id = 0, - name = methodName.ToString (), - }; - mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); - } - - // Must terminate with an "invalid" entry - name = new MarshalMethodName { - Id32 = 0, - Id64 = 0, - - id = 0, - name = String.Empty, - }; - mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); - - var mm_method_names_variable = new LlvmIrGlobalVariable (mm_method_names, "mm_method_names", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { - BeforeWriteCallback = UpdateMarshalMethodNameIds, - BeforeWriteCallbackCallerState = acs, - }; - module.Add (mm_method_names_variable); - - void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) - { - buffer.Clear (); - buffer.Append (md.Name); - buffer.Append ('('); - - if (md.HasParameters) { - bool first = true; - foreach (CecilParameterDefinition pd in md.Parameters) { - if (!first) { - buffer.Append (','); - } else { - first = false; - } - - buffer.Append (pd.ParameterType.Name); - } - } - - buffer.Append (')'); - } - } - - void UpdateMarshalMethodNameIds (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) - { - var mm_method_names = (List>)variable.Value; - bool is64Bit = target.Is64Bit; - - foreach (StructureInstance mmn in mm_method_names) { - mmn.Instance.id = is64Bit ? mmn.Instance.Id64 : mmn.Instance.Id32; - } - } - - // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache - void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) - { - var assembly_image_cache = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - ZeroInitializeArray = true, - ArrayItemCount = (ulong)numberOfAssembliesInApk, - }; - module.Add (assembly_image_cache); - - acs = new AssemblyCacheState { - AsmNameToIndexData32 = new Dictionary (StringComparer.Ordinal), - Indices32 = new List (), - - AsmNameToIndexData64 = new Dictionary (StringComparer.Ordinal), - Indices64 = new List (), - }; - - acs.Hashes32 = new Dictionary (); - acs.Hashes64 = new Dictionary (); - uint index = 0; - - foreach (string name in uniqueAssemblyNames) { - // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here - string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); - ulong hashFull32 = GetXxHash (name, is64Bit: false); - ulong hashClipped32 = GetXxHash (clippedName, is64Bit: false); - ulong hashFull64 = GetXxHash (name, is64Bit: true); - ulong hashClipped64 = GetXxHash (clippedName, is64Bit: true); - - // - // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the - // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. - // - acs.Hashes32.Add ((uint)Convert.ChangeType (hashFull32, typeof(uint)), (name, index)); - acs.Hashes32.Add ((uint)Convert.ChangeType (hashClipped32, typeof(uint)), (clippedName, index)); - acs.Hashes64.Add (hashFull64, (name, index)); - acs.Hashes64.Add (hashClipped64, (clippedName, index)); - - index++; - } - - acs.Keys32 = acs.Hashes32.Keys.ToList (); - acs.Keys32.Sort (); - for (int i = 0; i < acs.Keys32.Count; i++) { - (string name, uint idx) = acs.Hashes32[acs.Keys32[i]]; - acs.Indices32.Add (idx); - acs.AsmNameToIndexData32.Add (name, idx); - } - - acs.Keys64 = acs.Hashes64.Keys.ToList (); - acs.Keys64.Sort (); - for (int i = 0; i < acs.Keys64.Count; i++) { - (string name, uint idx) = acs.Hashes64[acs.Keys64[i]]; - acs.Indices64.Add (idx); - acs.AsmNameToIndexData64.Add (name, idx); - } - - var assembly_image_cache_hashes = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_hashes", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { - Comment = " Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array", - BeforeWriteCallback = UpdateAssemblyImageCacheHashes, - BeforeWriteCallbackCallerState = acs, - GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment, - GetArrayItemCommentCallbackCallerState = acs, - }; - module.Add (assembly_image_cache_hashes); - - var assembly_image_cache_indices = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_indices", LLVMIR.LlvmIrVariableOptions.GlobalConstant) { - WriteOptions = LlvmIrVariableWriteOptions.ArrayWriteIndexComments | LlvmIrVariableWriteOptions.ArrayFormatInRows, - BeforeWriteCallback = UpdateAssemblyImageCacheIndices, - BeforeWriteCallbackCallerState = acs, - }; - module.Add (assembly_image_cache_indices); - } - - void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) - { - AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); - object value; - Type type; - - if (target.Is64Bit) { - value = acs.Keys64; - type = typeof(List); - } else { - value = acs.Keys32; - type = typeof(List); - } - - LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - gv.OverrideValueAndType (type, value); - } - - string? GetAssemblyImageCacheItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) - { - AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); - - string name; - uint i; - if (target.Is64Bit) { - var v64 = (ulong)value; - name = acs.Hashes64[v64].name; - i = acs.Hashes64[v64].index; - } else { - var v32 = (uint)value; - name = acs.Hashes32[v32].name; - i = acs.Hashes32[v32].index; - } - - return $" {index}: {name} => 0x{value:x} => {i}"; - } - - void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) - { - AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); - object value; - - if (target.Is64Bit) { - value = acs.Indices64; - } else { - value = acs.Indices32; - } - - LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - gv.OverrideValueAndType (variable.Type, value); - } - - AssemblyCacheState EnsureAssemblyCacheState (object? callerState) - { - var acs = callerState as AssemblyCacheState; - if (acs == null) { - throw new InvalidOperationException ("Internal error: construction state expected but not found"); - } - - return acs; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs deleted file mode 100644 index 2b7ec72cea2..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.New.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; - -using Xamarin.Android.Tools; -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once everything is migrated to the LLVM.IR namespace - using LlvmIrAddressSignificance = LLVMIR.LlvmIrAddressSignificance; - - partial class MarshalMethodsNativeAssemblyGenerator - { - readonly MarshalMethodsTracingMode tracingMode; - - LlvmIrFunction? mm_trace_func_enter; - LlvmIrFunction? mm_trace_func_leave; - LlvmIrFunction? llvm_lifetime_start; - LlvmIrFunction? llvm_lifetime_end; - - void InitTracing (LlvmIrModule module) - { - var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); - var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); - - var llvm_lifetime_params = new List { - new (typeof(ulong), "size"), - new (typeof(IntPtr), "pointer"), - }; - - var lifetime_sig = new LlvmIrFunctionSignature ( - name: "llvm.lifetime.start", - returnType: typeof(void), - parameters: llvm_lifetime_params - ); - - llvm_lifetime_start = module.DeclareExternalFunction ( - new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); - llvm_lifetime_start = module.DeclareExternalFunction ( - new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); - - // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - var mm_trace_func_enter_or_leave_params = new List { - new (typeof(IntPtr), "env"), // JNIEnv *env - new (typeof(int), "tracing_mode"), - new (typeof(uint), "mono_image_index"), - new (typeof(uint), "class_index"), - new (typeof(uint), "method_token"), - new (typeof(string), "native_method_name"), - new (typeof(string), "method_extra_info"), - }; - - var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_func_enter", - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - ); - - mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); - } - - LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) - { - return new LlvmIrFunctionAttributeSet { - new ArgmemonlyFunctionAttribute (), - new MustprogressFunctionAttribute (), - new NocallbackFunctionAttribute (), - new NofreeFunctionAttribute (), - new NosyncFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - }; - } - - LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) - { - var ret = new LlvmIrFunctionAttributeSet { - new NounwindFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), - }; - - ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); - ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); - ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); - ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); - - return ret; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 962c5db27bd..2e733561000 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -1,658 +1,95 @@ using System; -using System.Text; using System.Collections.Generic; +using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVMIR; -using System.Collections; namespace Xamarin.Android.Tasks { - using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; - using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; - partial class MarshalMethodsNativeAssemblyGenerator { - const string mm_trace_init_name = "_mm_trace_init"; - const string mm_trace_func_enter_name = "_mm_trace_func_enter"; - const string mm_trace_func_leave_name = "_mm_trace_func_leave"; - const string mm_trace_get_class_name_name = "_mm_trace_get_class_name"; - const string mm_trace_get_object_class_name_name = "_mm_trace_get_object_class_name"; - const string mm_trace_get_c_string_name = "_mm_trace_get_c_string"; - const string mm_trace_get_boolean_string_name = "_mm_trace_get_boolean_string"; - const string asprintf_name = "asprintf"; - const string free_name = "free"; - - enum TracingRenderArgumentFunction - { - None, - GetClassName, - GetObjectClassname, - GetCString, - GetBooleanString, - } - - sealed class AsprintfParameterOperation - { - public readonly Type? Upcast; - public readonly TracingRenderArgumentFunction RenderFunction = TracingRenderArgumentFunction.None; - public readonly bool MustBeFreed; - - public AsprintfParameterOperation (Type? upcast, TracingRenderArgumentFunction renderFunction) - { - Upcast = upcast; - RenderFunction = renderFunction; - } - - public AsprintfParameterOperation (Type upcast) - : this (upcast, TracingRenderArgumentFunction.None) - {} - - public AsprintfParameterOperation (TracingRenderArgumentFunction renderFunction, bool mustBeFreed) - : this (null, renderFunction) - { - MustBeFreed = mustBeFreed; - } - - public AsprintfParameterOperation () - : this (null, TracingRenderArgumentFunction.None) - {} - } - - sealed class AsprintfParameterTransform : IEnumerable - { - public List Operations { get; } = new List (); - - public void Add (AsprintfParameterOperation parameterOp) - { - Operations.Add (parameterOp); - } - - public IEnumerator GetEnumerator () - { - return ((IEnumerable)Operations).GetEnumerator (); - } - - IEnumerator IEnumerable.GetEnumerator () - { - return ((IEnumerable)Operations).GetEnumerator (); - } - } - - sealed class AsprintfCallState - { - public readonly string Format; - public readonly List ParameterTransforms; - public readonly List VariablesToFree = new List (); - public readonly List VariadicArgsVariables = new List (); + readonly MarshalMethodsTracingMode tracingMode; - public AsprintfCallState (string format, List parameterTransforms) - { - Format = format; - ParameterTransforms = parameterTransforms; - } - } + LlvmIrFunction? mm_trace_func_enter; + LlvmIrFunction? mm_trace_func_leave; + LlvmIrFunction? llvm_lifetime_start; + LlvmIrFunction? llvm_lifetime_end; - sealed class TracingState + void InitTracing (LlvmIrModule module) { - public List? trace_enter_leave_args = null; - public LlvmIrFunctionLocalVariable? tracingParamsStringLifetimeTracker = null; - public List? asprintfVariadicArgs = null; - public LlvmIrVariableReference? asprintfAllocatedStringAccessorRef = null; - public LlvmIrVariableReference? asprintfAllocatedStringVarRef = null; - } + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); + var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); - List? mm_trace_func_enter_or_leave_params; - int mm_trace_func_enter_leave_extra_info_param_index = -1; - List? get_function_pointer_params; - LlvmIrVariableReference? mm_trace_init_ref; - LlvmIrVariableReference? mm_trace_func_enter_ref; - LlvmIrVariableReference? mm_trace_func_leave_ref; - LlvmIrVariableReference? mm_trace_get_class_name_ref; - LlvmIrVariableReference? mm_trace_get_object_class_name_ref; - LlvmIrVariableReference? mm_trace_get_c_string_ref; - LlvmIrVariableReference? mm_trace_get_boolean_string_ref; - LlvmIrVariableReference? asprintf_ref; - LlvmIrVariableReference? free_ref; - - void InitializeTracing (LlvmIrGenerator generator) - { - if (tracingMode == MarshalMethodsTracingMode.None) { - return; - } - - // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - mm_trace_func_enter_or_leave_params = new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - new LlvmIrFunctionParameter (typeof(int), "tracing_mode"), - new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), - new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token"), - new LlvmIrFunctionParameter (typeof(string), "native_method_name"), - new LlvmIrFunctionParameter (typeof(string), "method_extra_info"), + var llvm_lifetime_params = new List { + new (typeof(ulong), "size"), + new (typeof(IntPtr), "pointer"), }; - mm_trace_func_enter_leave_extra_info_param_index = mm_trace_func_enter_or_leave_params.Count - 1; - - var mm_trace_func_enter_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - ); - mm_trace_func_enter_ref = new LlvmIrVariableReference (mm_trace_func_enter_sig, mm_trace_func_enter_name, isGlobal: true); - - var mm_trace_func_leave_sig = new LlvmNativeFunctionSignature ( + var lifetime_sig = new LlvmIrFunctionSignature ( + name: "llvm.lifetime.start", returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params + parameters: llvm_lifetime_params ); - mm_trace_func_leave_ref = new LlvmIrVariableReference (mm_trace_func_leave_sig, mm_trace_func_leave_name, isGlobal: true); - var asprintf_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(int), - parameters: new List { - new LlvmIrFunctionParameter (typeof(string), isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(string)) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(void)) { - IsVarargs = true, - } + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default } ); - asprintf_ref = new LlvmIrVariableReference (asprintf_sig, asprintf_name, isGlobal: true); - - var free_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(string)) { - NoCapture = true, - NoUndef = true, - }, - } - ); - free_ref = new LlvmIrVariableReference (free_sig, free_name, isGlobal: true); - - var mm_trace_init_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { - NoUndef = true, - }, + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default } ); - mm_trace_init_ref = new LlvmIrVariableReference (mm_trace_init_sig, mm_trace_init_name, isGlobal: true); - var mm_trace_get_class_name_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(string), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(_jclass), "v") { - NoUndef = true, - }, - } - ); - mm_trace_get_class_name_ref = new LlvmIrVariableReference (mm_trace_get_class_name_sig, mm_trace_get_class_name_name, isGlobal: true); - - var mm_trace_get_object_class_name_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(string), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(_jobject), "v") { - NoUndef = true, - }, - } - ); - mm_trace_get_object_class_name_ref = new LlvmIrVariableReference (mm_trace_get_object_class_name_sig, mm_trace_get_object_class_name_name, isGlobal: true); - - var mm_trace_get_c_string_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(string), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(_jstring), "v") { - NoUndef = true, - }, - } - ); - mm_trace_get_c_string_ref = new LlvmIrVariableReference (mm_trace_get_c_string_sig, mm_trace_get_c_string_name, isGlobal: true); - - var mm_trace_get_boolean_string_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(string), - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true) { - NoUndef = true, - }, - new LlvmIrFunctionParameter (typeof(byte), "v") { - NoUndef = true, - }, - } - ); - mm_trace_get_boolean_string_ref = new LlvmIrVariableReference (mm_trace_get_boolean_string_sig, mm_trace_get_boolean_string_name, isGlobal: true); - - AddTraceFunctionDeclaration (asprintf_name, asprintf_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (free_name, free_sig, LlvmIrGenerator.FunctionAttributesLibcFree); - AddTraceFunctionDeclaration (mm_trace_init_name, mm_trace_init_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_func_enter_name, mm_trace_func_enter_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_func_leave_name, mm_trace_func_leave_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_get_class_name_name, mm_trace_get_class_name_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_get_object_class_name_name, mm_trace_get_object_class_name_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_get_c_string_name, mm_trace_get_c_string_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - AddTraceFunctionDeclaration (mm_trace_get_boolean_string_name, mm_trace_get_boolean_string_sig, LlvmIrGenerator.FunctionAttributesJniMethods); - - void AddTraceFunctionDeclaration (string name, LlvmNativeFunctionSignature sig, int attributeSetID) - { - var func = new LlvmIrFunction ( - name: name, - returnType: sig.ReturnType, - attributeSetID: attributeSetID, - parameters: sig.Parameters - ); - generator.AddExternalFunction (func); - } - } - - void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms, out bool isNativePointer) - { - string format; - AsprintfParameterTransform? transform = null; - isNativePointer = false; - - if (type == typeof(string)) { - format = "\"%s\""; - isNativePointer = true; - } else if (type == typeof(_jclass)) { - format = "%s @%p"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetClassName, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(_jobject)) { - format = "%s @%p"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetObjectClassname, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(_jstring)) { - format = "\"%s\""; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetCString, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { - format = "%p"; - isNativePointer = true; - } else if (type == typeof(bool)) { - format = "%s"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetBooleanString, mustBeFreed: false), - }; - isNativePointer = true; - } else if (type == typeof(byte) || type == typeof(ushort)) { - format = "%u"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(uint)), - }; - } else if (type == typeof(sbyte) || type == typeof(short)) { - format = "%d"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(int)), - }; - } else if (type == typeof(char)) { - format = "'\\%x'"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(uint)), - }; - } else if (type == typeof(int)) { - format = "%d"; - } else if (type == typeof(uint)) { - format = "%u"; - } else if (type == typeof(long)) { - format = "%ld"; - } else if (type == typeof(ulong)) { - format = "%lu"; - } else if (type == typeof(float)) { - format = "%g"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(double)), - }; - } else if (type == typeof(double)) { - format = "%g"; - } else { - throw new InvalidOperationException ($"Unsupported type '{type}'"); + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_func_enter_or_leave_params = new List { + new (typeof(IntPtr), "env"), // JNIEnv *env + new (typeof(int), "tracing_mode"), + new (typeof(uint), "mono_image_index"), + new (typeof(uint), "class_index"), + new (typeof(uint), "method_token"), + new (typeof(string), "native_method_name"), + new (typeof(string), "method_extra_info"), }; - parameterTransforms.Add (transform); - sb.Append (format); - } - - (StringBuilder sb, List parameterOps) InitPrintfState (string startChars = "(") - { - return (new StringBuilder (startChars), new List ()); - } - - AsprintfCallState FinishPrintfState (StringBuilder sb, List parameterTransforms, string endChars = ")") - { - sb.Append (endChars); - return new AsprintfCallState (sb.ToString (), parameterTransforms); - } - - AsprintfCallState GetPrintfStateForFunctionParams (MarshalMethodInfo method, LlvmIrFunction func) - { - (StringBuilder ret, List parameterOps) = InitPrintfState (); - bool first = true; - - List nativeMethodParameters = method.Parameters; - Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters; - - int expectedRegisteredParamCount = nativeMethodParameters.Count - 2; - if (managedMethodParameters != null && managedMethodParameters.Count != expectedRegisteredParamCount) { - throw new InvalidOperationException ($"Internal error: unexpected number of registered method parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); - } - - if (nativeMethodParameters.Count != func.Parameters.Count) { - throw new InvalidOperationException ($"Internal error: number of native method parameter ({nativeMethodParameters.Count}) doesn't match the number of marshal method parameters ({func.Parameters.Count})"); - } - - var variadicArgs = new List (); - for (int i = 0; i < nativeMethodParameters.Count; i++) { - LlvmIrFunctionParameter parameter = nativeMethodParameters[i]; - if (!first) { - ret.Append (", "); - } else { - first = false; - } - - // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two - // parameters, so we start looking at the managed parameters only once the first two are out of the way - AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, i >= 2 ? managedMethodParameters[i - 2] : null), parameterOps, out bool isNativePointer); - variadicArgs.Add (new LlvmIrVariableReference (func.ParameterVariables[i], isGlobal: false, isNativePointer: isNativePointer)); - } - - AsprintfCallState state = FinishPrintfState (ret, parameterOps); - state.VariadicArgsVariables.AddRange (variadicArgs); - return state; - - Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) - { - if (managedParameter == null) { - return nativeParameter.Type; - } - - if (nativeParameter.Type == typeof(byte) && String.Compare ("System.Boolean", managedParameter.Name, StringComparison.Ordinal) == 0) { - // `bool`, as a non-blittable type, is mapped to `byte` by the marshal method rewriter - return typeof(bool); - } - - return nativeParameter.Type; - } - } - - AsprintfCallState GetPrintfStateForReturnValue (LlvmIrFunctionLocalVariable localVariable) - { - (StringBuilder ret, List parameterTransforms) = InitPrintfState ("=>["); - - AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms, out bool isNativePointer); - - return FinishPrintfState (ret, parameterTransforms, "]"); - } - - LlvmIrVariableReference DoWriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, List variadicArgs, AsprintfCallState asprintfState, TracingState tracingState) - { - LlvmIrGenerator.StringSymbolInfo asprintfFormatSym = generator.AddString (asprintfState.Format, $"asprintf_fmt_{func.Name}"); - - var asprintfArgs = new List { - new LlvmIrFunctionArgument (tracingState.asprintfAllocatedStringVarRef) { - NonNull = true, - NoUndef = true, - }, - new LlvmIrFunctionArgument (asprintfFormatSym) { - NoUndef = true, - }, - }; - asprintfArgs.AddRange (variadicArgs); - - generator.WriteEOL (); - generator.WriteCommentLine ($"Format: {asprintfState.Format}", indent: true); - LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, asprintf_ref, asprintfArgs, marker: defaultCallMarker, AttributeSetID: -1); - LlvmIrVariableReference? resultRef = new LlvmIrVariableReference (result, isGlobal: false); - - // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) - LlvmIrFunctionLocalVariable asprintfResultVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.SignedLessThan, resultRef, "0"); - var asprintfResultVariableRef = new LlvmIrVariableReference (asprintfResultVariable, isGlobal: false); - - string asprintfIfThenLabel = func.MakeUniqueLabel ("if.then"); - string asprintfIfElseLabel = func.MakeUniqueLabel ("if.else"); - string ifElseDoneLabel = func.MakeUniqueLabel ("if.done"); - - generator.EmitBrInstruction (func, asprintfResultVariableRef, asprintfIfThenLabel, asprintfIfElseLabel); - - // Condition is true if asprintf **failed** - generator.EmitLabel (func, asprintfIfThenLabel); - generator.EmitStoreInstruction (func, tracingState.asprintfAllocatedStringVarRef, null); - generator.EmitBrInstruction (func, ifElseDoneLabel); - - generator.EmitLabel (func, asprintfIfElseLabel); - LlvmIrFunctionLocalVariable bufferPointerVar = generator.EmitLoadInstruction (func, tracingState.asprintfAllocatedStringVarRef); - LlvmIrVariableReference bufferPointerVarRef = new LlvmIrVariableReference (bufferPointerVar, isGlobal: false); - generator.EmitBrInstruction (func, ifElseDoneLabel); - - generator.EmitLabel (func, ifElseDoneLabel); - LlvmIrFunctionLocalVariable allocatedStringValueVar = generator.EmitPhiInstruction ( - func, - tracingState.asprintfAllocatedStringVarRef, - new List<(LlvmIrVariableReference? variableRef, string label)> { - (null, func.PreviousBlockStartLabel), - (bufferPointerVarRef, func.PreviousBlockEndLabel), - } + var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_func_enter", + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params ); - return new LlvmIrVariableReference (allocatedStringValueVar, isGlobal: false, isNativePointer: true); + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); } - LlvmIrVariableReference? WriteTransformFunctionCall (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, AsprintfParameterOperation paramOp, LlvmIrVariableReference paramVar) + LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) { - if (paramOp.RenderFunction == TracingRenderArgumentFunction.None) { - return null; - } - - var transformerArgs = new List (); - LlvmIrVariableReference transformerFunc; - - switch (paramOp.RenderFunction) { - case TracingRenderArgumentFunction.GetClassName: - transformerFunc = mm_trace_get_class_name_ref; - AddJNIEnvArgument (); - break; - - case TracingRenderArgumentFunction.GetObjectClassname: - transformerFunc = mm_trace_get_object_class_name_ref; - AddJNIEnvArgument (); - break; - - case TracingRenderArgumentFunction.GetCString: - transformerFunc = mm_trace_get_c_string_ref; - AddJNIEnvArgument (); - break; - - case TracingRenderArgumentFunction.GetBooleanString: - transformerFunc = mm_trace_get_boolean_string_ref; - break; - - default: - throw new InvalidOperationException ($"Internal error: unsupported transformer function {paramOp.RenderFunction}"); + return new LlvmIrFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NocallbackFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), }; - transformerArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); - - LlvmIrFunctionLocalVariable? result = generator.EmitCall (func, transformerFunc, transformerArgs, marker: defaultCallMarker, AttributeSetID: -1); - if (result == null) { - return null; - } - - if (paramOp.MustBeFreed) { - asprintfState.VariablesToFree.Add (new LlvmIrVariableReference (result, isGlobal: false)); - } - - return new LlvmIrVariableReference (result, isGlobal: false, isNativePointer: true); - - void AddJNIEnvArgument () - { - transformerArgs.Add (new LlvmIrFunctionArgument (func.ParameterVariables[0]) { NoUndef = true }); - } - } - - void AddAsprintfArgument (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, List asprintfArgs, List? paramOps, LlvmIrVariableReference paramVar) - { - if (paramOps == null || paramOps.Count == 0) { - asprintfArgs.Add (new LlvmIrFunctionArgument (paramVar) { NoUndef = true }); - return; - } - - foreach (AsprintfParameterOperation paramOp in paramOps) { - LlvmIrVariableReference? paramRef = WriteTransformFunctionCall (generator, func, asprintfState, paramOp, paramVar); - - if (paramOp.Upcast != null) { - LlvmIrFunctionLocalVariable upcastVar = generator.EmitUpcast (func, paramRef == null ? paramVar : paramRef, paramOp.Upcast); - paramRef = new LlvmIrVariableReference (upcastVar, isGlobal: false); - } - - if (paramRef == null) { - paramRef = paramVar; - } - - asprintfArgs.Add ( - new LlvmIrFunctionArgument (paramRef) { - NoUndef = true, - } - ); - } - } - - LlvmIrVariableReference WriteAsprintfCall (LlvmIrGenerator generator, LlvmIrFunction func, AsprintfCallState asprintfState, TracingState tracingState) - { - if (asprintfState.VariadicArgsVariables.Count != asprintfState.ParameterTransforms.Count) { - throw new ArgumentException (nameof (asprintfState), $"Number of transforms ({asprintfState.ParameterTransforms.Count}) is not equal to the number of variadic arguments ({asprintfState.VariadicArgsVariables.Count})"); - } - - var asprintfArgs = new List (); - - for (int i = 0; i < asprintfState.VariadicArgsVariables.Count; i++) { - AddAsprintfArgument (generator, func, asprintfState, asprintfArgs, asprintfState.ParameterTransforms[i]?.Operations, asprintfState.VariadicArgsVariables[i]); - } - - return DoWriteAsprintfCall (generator, func, asprintfArgs, asprintfState, tracingState); } - TracingState? WriteMarshalMethodTracingTop (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrFunction func) + LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) { - if (tracingMode == MarshalMethodsTracingMode.None) { - return null; - } - - CecilMethodDefinition nativeCallback = method.Method.NativeCallback; - const string paramsLocalVarName = "func_params_render"; - - var state = new TracingState (); - generator.WriteCommentLine ("Tracing code start", indent: true); - (LlvmIrFunctionLocalVariable asprintfAllocatedStringVar, state.tracingParamsStringLifetimeTracker) = generator.EmitAllocStackVariable (func, typeof(string), paramsLocalVarName); - state.asprintfAllocatedStringVarRef = new LlvmIrVariableReference (asprintfAllocatedStringVar, isGlobal: false); - generator.EmitStoreInstruction (func, state.asprintfAllocatedStringVarRef, null); - - state.asprintfVariadicArgs = new List (); - foreach (LlvmIrFunctionLocalVariable lfv in func.ParameterVariables) { - state.asprintfVariadicArgs.Add ( - new LlvmIrFunctionArgument (lfv) { - NoUndef = true, - } - ); - } - - AsprintfCallState asprintfState = GetPrintfStateForFunctionParams (method, func); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state); - - state.trace_enter_leave_args = new List { - new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[1], (int)tracingMode), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[2], method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[3], method.ClassCacheIndex), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[4], nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (mm_trace_func_enter_or_leave_params[5], method.NativeSymbolName), - new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef), + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), }; - generator.EmitCall (func, mm_trace_func_enter_ref, state.trace_enter_leave_args, marker: defaultCallMarker); - asprintfAllocatedStringVar = generator.EmitLoadInstruction (func, state.asprintfAllocatedStringVarRef); - - generator.EmitCall ( - func, - free_ref, - new List { - new LlvmIrFunctionArgument (asprintfAllocatedStringVar) { - NoUndef = true, - }, - }, - marker: defaultCallMarker - ); - generator.WriteCommentLine ("Tracing code end", indent: true); - generator.WriteEOL (); - - return state; - } - - void WriteMarshalMethodTracingBottom (TracingState? state, LlvmIrGenerator generator, LlvmIrFunction func, LlvmIrFunctionLocalVariable? result) - { - if (tracingMode == MarshalMethodsTracingMode.None) { - return; - } - - if (state == null) { - throw new InvalidOperationException ("Internal error: tracing state is required."); - } - - generator.WriteCommentLine ("Tracing code start", indent: true); - - LlvmIrFunctionArgument extraInfoArg; - if (result != null) { - AsprintfCallState asprintfState = GetPrintfStateForReturnValue (result); - state.asprintfAllocatedStringAccessorRef = WriteAsprintfCall (generator, func, asprintfState, state); - extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringAccessorRef) { - NoUndef = true, - }; - } else { - extraInfoArg = new LlvmIrFunctionArgument (state.asprintfAllocatedStringVarRef, isNull: true) { - NoUndef = true, - }; - } - - if (mm_trace_func_enter_leave_extra_info_param_index < 0) { - throw new InvalidOperationException ("Internal error: index of the extra info parameter is unknown"); - } - state.trace_enter_leave_args[mm_trace_func_enter_leave_extra_info_param_index] = extraInfoArg; - - generator.EmitCall (func, mm_trace_func_leave_ref, state.trace_enter_leave_args, marker: defaultCallMarker); - generator.EmitDeallocStackVariable (func, state.tracingParamsStringLifetimeTracker); - - generator.WriteCommentLine ("Tracing code end", indent: true); - } - - void WriteTracingInit (LlvmIrGenerator generator, LlvmIrFunction func) - { - if (tracingMode == MarshalMethodsTracingMode.None) { - return; - } - - var trace_init_args = new List { - new LlvmIrFunctionArgument (func.ParameterVariables[0]), // JNIEnv* env - }; + ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); + ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); + ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); + ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); - generator.EmitCall (func, mm_trace_init_ref, trace_init_args, marker: defaultCallMarker); + return ret; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index d0f031b59ca..854e8ee6860 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -12,13 +12,12 @@ using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; -// TODO: generate code to check for pending Java exceptions (maybe?) -// TODO: check whether delegates not converted to marshal methods work correctly. It's possible something isn't called when it should be and that's -// why Blazor hangs. namespace Xamarin.Android.Tasks { partial class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { + const string GetFunctionPointerVariableName = "get_function_pointer"; + // This is here only to generate strongly-typed IR internal sealed class MonoClass {} @@ -95,8 +94,8 @@ public MarshalMethodInfo (MarshalMethodEntry method, Type returnType, string nat } NativeSymbolName = nativeSymbolName; Parameters = new List { - new LlvmIrFunctionParameter (typeof (_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - new LlvmIrFunctionParameter (typeof (_jclass), "klass", isNativePointer: true), // jclass klass + new LlvmIrFunctionParameter (typeof (_JNIEnv), "env"), // JNIEnv *env + new LlvmIrFunctionParameter (typeof (_jclass), "klass"), // jclass klass }; ClassCacheIndex = (uint)classCacheIndex; } @@ -109,7 +108,7 @@ public override string GetComment (object data, string fieldName) var klass = EnsureType (data); if (String.Compare ("token", fieldName, StringComparison.Ordinal) == 0) { - return $"token 0x{klass.token:x}; class name: {klass.ClassName}"; + return $" token 0x{klass.token:x}; class name: {klass.ClassName}"; } return String.Empty; @@ -136,7 +135,7 @@ public override string GetComment (object data, string fieldName) var methodName = EnsureType (data); if (String.Compare ("id", fieldName, StringComparison.Ordinal) == 0) { - return $"id 0x{methodName.id:x}; name: {methodName.name}"; + return $" id 0x{methodName.id:x}; name: {methodName.name}"; } return String.Empty; @@ -146,11 +145,63 @@ public override string GetComment (object data, string fieldName) [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodNameDataProvider))] sealed class MarshalMethodName { + [NativeAssembler (Ignore = true)] + public ulong Id32; + + [NativeAssembler (Ignore = true)] + public ulong Id64; + [NativeAssembler (UsesDataProvider = true)] public ulong id; public string name; } + sealed class AssemblyCacheState + { + public Dictionary? AsmNameToIndexData32; + public Dictionary Hashes32; + public List Keys32; + public List Indices32; + + public Dictionary? AsmNameToIndexData64; + public Dictionary Hashes64; + public List Keys64; + public List Indices64; + } + + sealed class MarshalMethodsWriteState + { + public AssemblyCacheState AssemblyCacheState; + public LlvmIrFunctionAttributeSet AttributeSet; + public Dictionary UniqueAssemblyId; + public Dictionary UsedBackingFields; + public LlvmIrVariable GetFunctionPtrVariable; + public LlvmIrFunction GetFunctionPtrFunction; + } + + sealed class MarshalMethodAssemblyIndexValuePlaceholder : LlvmIrInstructionArgumentValuePlaceholder + { + MarshalMethodInfo mmi; + AssemblyCacheState acs; + + public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, AssemblyCacheState acs) + { + this.mmi = mmi; + this.acs = acs; + } + + public override object? GetValue (LlvmIrModuleTarget target) + { + // What a monstrosity... + string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; + Dictionary asmNameToIndex = target.Is64Bit ? acs.AsmNameToIndexData64 : acs.AsmNameToIndexData32; + if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { + throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); + } + return asmIndex; + } + } + static readonly Dictionary jniSimpleTypeMap = new Dictionary { { 'Z', typeof(bool) }, { 'B', typeof(byte) }, @@ -179,25 +230,8 @@ sealed class MarshalMethodName IDictionary> marshalMethods; TaskLoggingHelper logger; - StructureInfo monoImage; - StructureInfo marshalMethodsClass; - StructureInfo marshalMethodName; - StructureInfo monoClass; - StructureInfo<_JNIEnv> _jniEnvSI; - StructureInfo<_jobject> _jobjectSI; - StructureInfo<_jclass> _jclassSI; - StructureInfo<_jstring> _jstringSI; - StructureInfo<_jthrowable> _jthrowableSI; - StructureInfo<_jarray> _jarraySI; - StructureInfo<_jobjectArray> _jobjectArraySI; - StructureInfo<_jbooleanArray> _jbooleanArraySI; - StructureInfo<_jbyteArray> _jbyteArraySI; - StructureInfo<_jcharArray> _jcharArraySI; - StructureInfo<_jshortArray> _jshortArraySI; - StructureInfo<_jintArray> _jintArraySI; - StructureInfo<_jlongArray> _jlongArraySI; - StructureInfo<_jfloatArray> _jfloatArraySI; - StructureInfo<_jdoubleArray> _jdoubleArraySI; + StructureInfo marshalMethodsManagedClassStructureInfo; + StructureInfo marshalMethodNameStructureInfo; List methods; List> classes = new List> (); @@ -205,7 +239,6 @@ sealed class MarshalMethodName LlvmIrCallMarker defaultCallMarker; readonly bool generateEmptyCode; - readonly MarshalMethodsTracingMode tracingMode; /// /// Constructor to be used ONLY when marshal methods are DISABLED @@ -233,7 +266,7 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl defaultCallMarker = tracingMode != MarshalMethodsTracingMode.None ? LlvmIrCallMarker.None : LlvmIrCallMarker.Tail; } - public override void Init () + void Init () { if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { return; @@ -363,7 +396,7 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry ClassName = klass, }; - classes.Add (new StructureInstance (mc)); + classes.Add (new StructureInstance (marshalMethodsManagedClassStructureInfo, mc)); } // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference @@ -550,65 +583,296 @@ void AddParameter (Type type) } // Every parameter which isn't a primitive type becomes a pointer - parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name, isNativePointer: type.IsNativeClass ())); + parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name)); } } - protected override void InitGenerator (LlvmIrGenerator generator) + protected override void Construct (LlvmIrModule module) { - generator.InitCodeOutput (); + MapStructures (module); + + Init (); + // InitTracing (module); + AddAssemblyImageCache (module, out AssemblyCacheState acs); + + // class cache + module.AddGlobalVariable ("marshal_methods_number_of_classes", (uint)classes.Count, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable ("marshal_methods_class_cache", classes, LlvmIrVariableOptions.GlobalWritable); + + // Marshal methods class names + var mm_class_names = new List (); + foreach (StructureInstance klass in classes) { + mm_class_names.Add (klass.Instance.ClassName); + } + module.AddGlobalVariable ("mm_class_names", mm_class_names, LlvmIrVariableOptions.GlobalConstant, comment: " Names of classes in which marshal methods reside"); + + AddMarshalMethodNames (module, acs); + (LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) = AddXamarinAppInitFunction (module); + + AddMarshalMethods (module, acs, getFunctionPtrVariable, getFunctionPtrFunction); } - protected override void MapStructures (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - monoImage = generator.MapStructure (); - monoClass = generator.MapStructure (); - marshalMethodsClass = generator.MapStructure (); - marshalMethodName = generator.MapStructure (); - _jniEnvSI = generator.MapStructure<_JNIEnv> (); - _jobjectSI = generator.MapStructure<_jobject> (); - _jclassSI = generator.MapStructure<_jclass> (); - _jstringSI = generator.MapStructure<_jstring> (); - _jthrowableSI = generator.MapStructure<_jthrowable> (); - _jarraySI = generator.MapStructure<_jarray> (); - _jobjectArraySI = generator.MapStructure<_jobjectArray> (); - _jbooleanArraySI = generator.MapStructure<_jbooleanArray> (); - _jbyteArraySI = generator.MapStructure<_jbyteArray> (); - _jcharArraySI = generator.MapStructure<_jcharArray> (); - _jshortArraySI = generator.MapStructure<_jshortArray> (); - _jintArraySI = generator.MapStructure<_jintArray> (); - _jlongArraySI = generator.MapStructure<_jlongArray> (); - _jfloatArraySI = generator.MapStructure<_jfloatArray> (); - _jdoubleArraySI = generator.MapStructure<_jdoubleArray> (); + marshalMethodsManagedClassStructureInfo = module.MapStructure (); + marshalMethodNameStructureInfo = module.MapStructure (); } - protected override void Write (LlvmIrGenerator generator) + void AddMarshalMethods (LlvmIrModule module, AssemblyCacheState acs, LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction) { - WriteAssemblyImageCache (generator, out Dictionary asmNameToIndex); - WriteClassCache (generator); - InitializeTracing (generator); - LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); - WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); + if (generateEmptyCode || methods == null || methods.Count == 0) { + return; + } - var mm_class_names = new List (); - foreach (StructureInstance klass in classes) { - mm_class_names.Add (klass.Obj.ClassName); + // This will make all the backing fields to appear in a block without empty lines separating them. + module.Add ( + new LlvmIrGroupDelimiterVariable () { + Comment = " Marshal methods backing fields, pointers to native functions" + } + ); + + var writeState = new MarshalMethodsWriteState { + AssemblyCacheState = acs, + AttributeSet = MakeMarshalMethodAttributeSet (module), + UsedBackingFields = new Dictionary (StringComparer.Ordinal), + UniqueAssemblyId = new Dictionary (StringComparer.OrdinalIgnoreCase), + GetFunctionPtrVariable = getFunctionPtrVariable, + GetFunctionPtrFunction = getFunctionPtrFunction, + }; + foreach (MarshalMethodInfo mmi in methods) { + CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; + string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; + + if (!writeState.UniqueAssemblyId.TryGetValue (asmName, out ulong asmId)) { + asmId = (ulong)writeState.UniqueAssemblyId.Count; + writeState.UniqueAssemblyId.Add (asmName, asmId); + } + + AddMarshalMethod (module, mmi, asmId, writeState); + } + + module.Add (new LlvmIrGroupDelimiterVariable ()); + } + + void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmId, MarshalMethodsWriteState writeState) + { + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{asmId}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; + + if (!writeState.UsedBackingFields.TryGetValue (backingFieldName, out LlvmIrVariable backingField)) { + backingField = module.AddGlobalVariable (typeof(IntPtr), backingFieldName, null, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + writeState.UsedBackingFields.Add (backingFieldName, backingField); + } + + var funcComment = new StringBuilder (" Method: "); + funcComment.AppendLine (nativeCallback.FullName); + funcComment.Append (" Assembly: "); + funcComment.AppendLine (nativeCallback.Module.Assembly.Name.FullName); + funcComment.Append (" Registered: "); + funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); + + var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, writeState.AttributeSet) { + Comment = funcComment.ToString (), + }; + + WriteBody (func.Body); + module.Add (func); + + void WriteBody (LlvmIrFunctionBody body) + { + LlvmIrLocalVariable cb1 = func.CreateLocalVariable (typeof(IntPtr), "cb1"); + body.Load (backingField, cb1, tbaa: module.TbaaAnyPointer); + + LlvmIrLocalVariable isNullResult = func.CreateLocalVariable (typeof(bool), "isNull"); + body.Icmp (LlvmIrIcmpCond.Equal, cb1, null, isNullResult); + + var loadCallbackLabel = new LlvmIrFunctionLabelItem ("loadCallback"); + var callbackLoadedLabel = new LlvmIrFunctionLabelItem ("callbackLoaded"); + body.Br (isNullResult, loadCallbackLabel, callbackLoadedLabel); + + // Callback variable was null + body.Add (loadCallbackLabel); + + LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr), "get_func_ptr"); + body.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult, tbaa: module.TbaaAnyPointer); + + var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + LlvmIrInstructions.Call call = body.Call ( + writeState.GetFunctionPtrFunction, + arguments: new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken.ToUInt32 (), backingField }, + funcPointer: getFuncPtrResult + ); + + LlvmIrLocalVariable cb2 = func.CreateLocalVariable (typeof(IntPtr), "cb2"); + body.Load (backingField, cb2, tbaa: module.TbaaAnyPointer); + body.Br (callbackLoadedLabel); + + // Callback variable has just been set or it wasn't null + body.Add (callbackLoadedLabel); + LlvmIrLocalVariable fn = func.CreateLocalVariable (typeof(IntPtr), "fn"); + + // Preceding blocks are ordered from the newest to the oldest, so we need to pass the variables referring to our callback in "reverse" order + body.Phi (fn, cb2, body.PrecedingBlock1, cb1, body.PrecedingBlock2); + + var nativeFunc = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters); + nativeFunc.Signature.ReturnAttributes.NoUndef = true; + + var arguments = new List (); + foreach (LlvmIrFunctionParameter parameter in nativeFunc.Signature.Parameters) { + arguments.Add (new LlvmIrLocalVariable (parameter.Type, parameter.Name)); + } + LlvmIrLocalVariable? result = nativeFunc.ReturnsValue ? func.CreateLocalVariable (nativeFunc.Signature.ReturnType) : null; + call = body.Call (nativeFunc, result, arguments, funcPointer: fn); + call.CallMarker = LlvmIrCallMarker.Tail; + + body.Ret (nativeFunc.Signature.ReturnType, result); + } + } + + LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) + { + var attrSet = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (attrSet); + } + + (LlvmIrVariable getFuncPtrVariable, LlvmIrFunction getFuncPtrFunction) AddXamarinAppInitFunction (LlvmIrModule module) + { + var getFunctionPtrParams = new List { + new (typeof(uint), "mono_image_index") { + NoUndef = true, + }, + new (typeof(uint), "class_index") { + NoUndef = true, + }, + new (typeof(uint), "method_token") { + NoUndef = true, + }, + new (typeof(IntPtr), "target_ptr") { + NoUndef = true, + NonNull = true, + Align = 0, // 0 means use natural pointer alignment + Dereferenceable = 0, // ditto 👆 + IsCplusPlusReference = true, + }, + }; + + var getFunctionPtrComment = new StringBuilder (" "); + getFunctionPtrComment.Append (GetFunctionPointerVariableName); + getFunctionPtrComment.Append (" ("); + for (int i = 0; i < getFunctionPtrParams.Count; i++) { + if (i > 0) { + getFunctionPtrComment.Append (", "); + } + LlvmIrFunctionParameter parameter = getFunctionPtrParams[i]; + getFunctionPtrComment.Append (LlvmIrGenerator.MapManagedTypeToNative (parameter.Type)); + if (parameter.IsCplusPlusReference.HasValue && parameter.IsCplusPlusReference.Value) { + getFunctionPtrComment.Append ('&'); + } + getFunctionPtrComment.Append (' '); + getFunctionPtrComment.Append (parameter.Name); } - generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); + getFunctionPtrComment.Append (')'); + + LlvmIrFunction getFunctionPtrFunc = new LlvmIrFunction ( + name: GetFunctionPointerVariableName, + returnType: typeof(void), + parameters: getFunctionPtrParams + ); + + LlvmIrVariable getFunctionPtrVariable = module.AddGlobalVariable ( + typeof(IntPtr), + GetFunctionPointerVariableName, + null, + LlvmIrVariableOptions.LocalWritableInsignificantAddr, + getFunctionPtrComment.ToString () + ); + + var init_params = new List { + new (typeof(_JNIEnv), "env") { + NoCapture = true, + NoUndef = true, + ReadNone = true, + }, + new (typeof(IntPtr), "fn") { + NoUndef = true, + }, + }; + + var init_signature = new LlvmIrFunctionSignature ( + name: "xamarin_app_init", + returnType: typeof(void), + parameters: init_params + ); + + LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); + var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); + xamarin_app_init.Body.Add ( + new LlvmIrInstructions.Store (init_params[1], getFunctionPtrVariable) { + TBAA = module.TbaaAnyPointer, + } + ); + xamarin_app_init.Body.Add (new LlvmIrInstructions.Ret (typeof(void))); + + module.Add (xamarin_app_init); + + return (getFunctionPtrVariable, getFunctionPtrFunc); + } + + LlvmIrFunctionAttributeSet MakeXamarinAppInitAttributeSet (LlvmIrModule module) + { + var attrSet = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NofreeFunctionAttribute (), + new NorecurseFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, + new UwtableFunctionAttribute (), + new MinLegalVectorWidthFunctionAttribute (0), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (attrSet); + } + + void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs) + { + var uniqueMethods = new Dictionary (); - var uniqueMethods = new Dictionary (); if (!generateEmptyCode && methods != null) { foreach (MarshalMethodInfo mmi in methods) { string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + + if (!acs.AsmNameToIndexData32.TryGetValue (asmName, out uint idx32)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 32-bit cache array index"); + } + + if (!acs.AsmNameToIndexData64.TryGetValue (asmName, out uint idx64)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to 64-bit cache array index"); } - ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); - if (uniqueMethods.ContainsKey (id)) { + ulong methodToken = (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + ulong id32 = ((ulong)idx32 << 32) | methodToken; + if (uniqueMethods.ContainsKey (id32)) { continue; } - uniqueMethods.Add (id, mmi); + + ulong id64 = ((ulong)idx64 << 32) | methodToken; + uniqueMethods.Add (id32, (mmi, id32, id64)); } } @@ -617,25 +881,35 @@ protected override void Write (LlvmIrGenerator generator) var mm_method_names = new List> (); foreach (var kvp in uniqueMethods) { ulong id = kvp.Key; - MarshalMethodInfo mmi = kvp.Value; + (MarshalMethodInfo mmi, ulong id32, ulong id64) = kvp.Value; RenderMethodNameWithParams (mmi.Method.NativeCallback, methodName); name = new MarshalMethodName { + Id32 = id32, + Id64 = id64, + // Tokens are unique per assembly - id = id, + id = 0, name = methodName.ToString (), }; - mm_method_names.Add (new StructureInstance (name)); + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); } // Must terminate with an "invalid" entry name = new MarshalMethodName { + Id32 = 0, + Id64 = 0, + id = 0, name = String.Empty, }; - mm_method_names.Add (new StructureInstance (name)); + mm_method_names.Add (new StructureInstance (marshalMethodNameStructureInfo, name)); - generator.WriteStructureArray (marshalMethodName, mm_method_names, LlvmIrVariableOptions.GlobalConstant, "mm_method_names"); + var mm_method_names_variable = new LlvmIrGlobalVariable (mm_method_names, "mm_method_names", LlvmIrVariableOptions.GlobalConstant) { + BeforeWriteCallback = UpdateMarshalMethodNameIds, + BeforeWriteCallbackCallerState = acs, + }; + module.Add (mm_method_names_variable); void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) { @@ -660,217 +934,150 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) } } - void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) + void UpdateMarshalMethodNameIds (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { - if (generateEmptyCode || methods == null || methods.Count == 0) { - return; - } + var mm_method_names = (List>)variable.Value; + bool is64Bit = target.Is64Bit; - var usedBackingFields = new HashSet (StringComparer.Ordinal); - foreach (MarshalMethodInfo mmi in methods) { - CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; - string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; - if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { - throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); - } - mmi.AssemblyCacheIndex = asmIndex; - WriteMarshalMethod (generator, mmi, get_function_pointer_ref, usedBackingFields); + foreach (StructureInstance mmn in mm_method_names) { + mmn.Instance.id = is64Bit ? mmn.Instance.Id64 : mmn.Instance.Id32; } } - void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) + // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache + void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) { - var backingFieldSignature = new LlvmNativeFunctionSignature ( - returnType: method.ReturnType, - parameters: method.Parameters - ) { - FieldValue = "null", + var assembly_image_cache = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache", LlvmIrVariableOptions.GlobalWritable) { + ZeroInitializeArray = true, + ArrayItemCount = (ulong)numberOfAssembliesInApk, }; + module.Add (assembly_image_cache); - CecilMethodDefinition nativeCallback = method.Method.NativeCallback; - string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; - var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true, isNativePointer: true); + acs = new AssemblyCacheState { + AsmNameToIndexData32 = new Dictionary (StringComparer.Ordinal), + Indices32 = new List (), - if (!usedBackingFields.Contains (backingFieldName)) { - generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); - usedBackingFields.Add (backingFieldName); - } - - var func = new LlvmIrFunction ( - name: method.NativeSymbolName, - returnType: method.ReturnType, - attributeSetID: LlvmIrGenerator.FunctionAttributesJniMethods, - parameters: method.Parameters - ); - - generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}\nRegistered: {method.Method.RegisteredMethod?.FullName}"); - - TracingState? tracingState = WriteMarshalMethodTracingTop (generator, method, func); - LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); - var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); - - LlvmIrFunctionLocalVariable isNullVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.Equal, callbackVariable1Ref, expectedValue: "null", resultVariableName: "isNull"); - var isNullVariableRef = new LlvmIrVariableReference (isNullVariable, isGlobal: false); - - const string loadCallbackLabel = "loadCallback"; - const string callbackLoadedLabel = "callbackLoaded"; - - generator.EmitBrInstruction (func, isNullVariableRef, loadCallbackLabel, callbackLoadedLabel); - generator.EmitLabel (func, loadCallbackLabel); - - LlvmIrFunctionLocalVariable getFunctionPointerVariable = generator.EmitLoadInstruction (func, get_function_pointer_ref, "get_func_ptr"); - var getFunctionPtrRef = new LlvmIrVariableReference (getFunctionPointerVariable, isGlobal: false); - - generator.EmitCall ( - func, - getFunctionPtrRef, - new List { - new LlvmIrFunctionArgument (get_function_pointer_params[0], method.AssemblyCacheIndex), - new LlvmIrFunctionArgument (get_function_pointer_params[1], method.ClassCacheIndex), - new LlvmIrFunctionArgument (get_function_pointer_params[2], nativeCallback.MetadataToken.ToUInt32 ()), - new LlvmIrFunctionArgument (backingFieldRef), - }, - marker: defaultCallMarker - ); - - LlvmIrFunctionLocalVariable callbackVariable2 = generator.EmitLoadInstruction (func, backingFieldRef, "cb2"); - var callbackVariable2Ref = new LlvmIrVariableReference (callbackVariable2, isGlobal: false); - - generator.EmitBrInstruction (func, callbackLoadedLabel); - generator.EmitLabel (func, callbackLoadedLabel); - - LlvmIrFunctionLocalVariable fnVariable = generator.EmitPhiInstruction ( - func, - backingFieldRef, - new List<(LlvmIrVariableReference variableRef, string label)> { - (callbackVariable1Ref, func.PreviousBlockStartLabel), - (callbackVariable2Ref, func.PreviousBlockEndLabel), - }, - resultVariableName: "fn" - ); - var fnVariableRef = new LlvmIrVariableReference (fnVariable, isGlobal: false); + AsmNameToIndexData64 = new Dictionary (StringComparer.Ordinal), + Indices64 = new List (), + }; - LlvmIrFunctionLocalVariable? result = generator.EmitCall ( - func, - fnVariableRef, - func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList (), - marker: defaultCallMarker - ); + acs.Hashes32 = new Dictionary (); + acs.Hashes64 = new Dictionary (); + uint index = 0; + + foreach (string name in uniqueAssemblyNames) { + // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here + string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); + ulong hashFull32 = GetXxHash (name, is64Bit: false); + ulong hashClipped32 = GetXxHash (clippedName, is64Bit: false); + ulong hashFull64 = GetXxHash (name, is64Bit: true); + ulong hashClipped64 = GetXxHash (clippedName, is64Bit: true); + + // + // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the + // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. + // + acs.Hashes32.Add ((uint)Convert.ChangeType (hashFull32, typeof(uint)), (name, index)); + acs.Hashes32.Add ((uint)Convert.ChangeType (hashClipped32, typeof(uint)), (clippedName, index)); + acs.Hashes64.Add (hashFull64, (name, index)); + acs.Hashes64.Add (hashClipped64, (clippedName, index)); + + index++; + } - WriteMarshalMethodTracingBottom (tracingState, generator, func, result); - if (result != null) { - generator.EmitReturnInstruction (func, result); + acs.Keys32 = acs.Hashes32.Keys.ToList (); + acs.Keys32.Sort (); + for (int i = 0; i < acs.Keys32.Count; i++) { + (string name, uint idx) = acs.Hashes32[acs.Keys32[i]]; + acs.Indices32.Add (idx); + acs.AsmNameToIndexData32.Add (name, idx); } - generator.WriteFunctionEnd (func); - } + acs.Keys64 = acs.Hashes64.Keys.ToList (); + acs.Keys64.Sort (); + for (int i = 0; i < acs.Keys64.Count; i++) { + (string name, uint idx) = acs.Hashes64[acs.Keys64[i]]; + acs.Indices64.Add (idx); + acs.AsmNameToIndexData64.Add (name, idx); + } - LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) - { - get_function_pointer_params = new List { - new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), - new LlvmIrFunctionParameter (typeof(uint), "class_index"), - new LlvmIrFunctionParameter (typeof(uint), "method_token"), - new LlvmIrFunctionParameter (typeof(IntPtr), "target_ptr", isNativePointer: true, isCplusPlusReference: true) + var assembly_image_cache_hashes = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_hashes", LlvmIrVariableOptions.GlobalConstant) { + Comment = " Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array", + BeforeWriteCallback = UpdateAssemblyImageCacheHashes, + BeforeWriteCallbackCallerState = acs, + GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment, + GetArrayItemCommentCallbackCallerState = acs, }; + module.Add (assembly_image_cache_hashes); - var get_function_pointer_sig = new LlvmNativeFunctionSignature ( - returnType: typeof(void), - parameters: get_function_pointer_params - ) { - FieldValue = "null", + var assembly_image_cache_indices = new LlvmIrGlobalVariable (typeof(List), "assembly_image_cache_indices", LlvmIrVariableOptions.GlobalConstant) { + WriteOptions = LlvmIrVariableWriteOptions.ArrayWriteIndexComments | LlvmIrVariableWriteOptions.ArrayFormatInRows, + BeforeWriteCallback = UpdateAssemblyImageCacheIndices, + BeforeWriteCallbackCallerState = acs, }; + module.Add (assembly_image_cache_indices); + } - const string GetFunctionPointerFieldName = "get_function_pointer"; - generator.WriteVariable (GetFunctionPointerFieldName, get_function_pointer_sig, LlvmIrVariableOptions.LocalWritableInsignificantAddr); - - var fnParameter = new LlvmIrFunctionParameter (get_function_pointer_sig, "fn"); - var func = new LlvmIrFunction ( - name: "xamarin_app_init", - returnType: typeof (void), - attributeSetID: LlvmIrGenerator.FunctionAttributesXamarinAppInit, - parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), // JNIEnv *env - fnParameter, - } - ); - - generator.WriteFunctionStart (func); - generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); - - WriteTracingInit (generator, func); + void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; + Type type; - generator.WriteFunctionEnd (func); + if (target.Is64Bit) { + value = acs.Keys64; + type = typeof(List); + } else { + value = acs.Keys32; + type = typeof(List); + } - return new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (type, value); } - void WriteClassCache (LlvmIrGenerator generator) + string? GetAssemblyImageCacheItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) { - uint marshal_methods_number_of_classes = (uint)classes.Count; + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + + string name; + uint i; + if (target.Is64Bit) { + var v64 = (ulong)value; + name = acs.Hashes64[v64].name; + i = acs.Hashes64[v64].index; + } else { + var v32 = (uint)value; + name = acs.Hashes32[v32].name; + i = acs.Hashes32[v32].index; + } - generator.WriteVariable (nameof (marshal_methods_number_of_classes), marshal_methods_number_of_classes); - generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); + return $" {index}: {name} => 0x{value:x} => {i}"; } - // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache - void WriteAssemblyImageCache (LlvmIrGenerator generator, out Dictionary asmNameToIndex) + void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) { - bool is64Bit = generator.Is64Bit; - generator.WriteStructureArray (monoImage, (ulong)numberOfAssembliesInApk, "assembly_image_cache", isArrayOfPointers: true); + AssemblyCacheState acs = EnsureAssemblyCacheState (callerState); + object value; - var asmNameToIndexData = new Dictionary (StringComparer.Ordinal); - if (is64Bit) { - WriteHashes (); + if (target.Is64Bit) { + value = acs.Indices64; } else { - WriteHashes (); + value = acs.Indices32; } - asmNameToIndex = asmNameToIndexData; - - void WriteHashes () where T: struct - { - var hashes = new Dictionary (); - uint index = 0; - - foreach (string name in uniqueAssemblyNames) { - // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here - string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); - ulong hashFull = HashName (name, is64Bit); - ulong hashClipped = HashName (clippedName, is64Bit); - - // - // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the - // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. - // - hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); - hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); - - index++; - } - List keys = hashes.Keys.ToList (); - keys.Sort (); - - generator.WriteCommentLine ("Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array"); - generator.WriteArray ( - keys, - LlvmIrVariableOptions.GlobalConstant, - "assembly_image_cache_hashes", - (int idx, T value) => $"{idx}: {hashes[value].name} => 0x{value:x} => {hashes[value].index}" - ); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + gv.OverrideValueAndType (variable.Type, value); + } - var indices = new List (); - for (int i = 0; i < keys.Count; i++) { - (string name, uint idx) = hashes[keys[i]]; - indices.Add (idx); - asmNameToIndexData.Add (name, idx); - } - generator.WriteArray ( - indices, - LlvmIrVariableOptions.GlobalConstant, - "assembly_image_cache_indices" - ); + AssemblyCacheState EnsureAssemblyCacheState (object? callerState) + { + var acs = callerState as AssemblyCacheState; + if (acs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); } + + return acs; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 7abb701f62e..31a501c1d94 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -211,7 +211,7 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L } GeneratedBinaryTypeMaps.Add (typeMapIndexPath); - var composer = new New.TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); + var composer = new TypeMappingDebugNativeAssemblyGenerator (new ModuleDebugData ()); GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; @@ -242,7 +242,7 @@ bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttribu PrepareDebugMaps (data); - var composer = new New.TypeMappingDebugNativeAssemblyGenerator (data); + var composer = new TypeMappingDebugNativeAssemblyGenerator (data); GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; @@ -412,7 +412,7 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List NativeTypeMappingData data; data = new NativeTypeMappingData (logger, modules); - var composer = new New.TypeMappingReleaseNativeAssemblyGenerator (data); + var composer = new TypeMappingReleaseNativeAssemblyGenerator (data); GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); return true; @@ -423,7 +423,7 @@ bool ShouldSkipInJavaToManaged (TypeDefinition td) return td.IsInterface || td.HasGenericParameters; } - void GenerateNativeAssembly (LLVM.IR.LlvmIrComposer composer, LLVM.IR.LlvmIrModule typeMapModule, string baseFileName) + void GenerateNativeAssembly (LLVMIR.LlvmIrComposer composer, LLVMIR.LlvmIrModule typeMapModule, string baseFileName) { AndroidTargetArch arch; foreach (string abi in supportedAbis) { @@ -443,40 +443,6 @@ void GenerateNativeAssembly (LLVM.IR.LlvmIrComposer composer, LLVM.IR.LlvmIrModu } } - void GenerateNativeAssembly (TypeMappingAssemblyGenerator generator, string baseFileName) - { - AndroidTargetArch arch; - foreach (string abi in supportedAbis) { - switch (abi.Trim ()) { - case "armeabi-v7a": - arch = AndroidTargetArch.Arm; - break; - - case "arm64-v8a": - arch = AndroidTargetArch.Arm64; - break; - - case "x86": - arch = AndroidTargetArch.X86; - break; - - case "x86_64": - arch = AndroidTargetArch.X86_64; - break; - - default: - throw new InvalidOperationException ($"Unknown ABI {abi}"); - } - - string outputFile = $"{baseFileName}.{abi}.ll"; - using (var sw = MemoryStreamPool.Shared.CreateStreamWriter (outputEncoding)) { - generator.Write (arch, sw, outputFile); - sw.Flush (); - Files.CopyIfStreamChanged (sw.BaseStream, outputFile); - } - } - } - // Binary index file format, all data is little-endian: // // [Magic string] # XATI diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs deleted file mode 100644 index 85bca8f93b9..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.New.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; - -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once the refactoring is done - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer - { - 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 - - sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var map_module = EnsureType (data); - - if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { - return "assembly_name (unused in this mode)"; - } - - if (String.Compare ("data", fieldName, StringComparison.Ordinal) == 0) { - return "data (unused in this mode)"; - } - - 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 - { - public string from; - public string to; - }; - - // Order of fields and their type must correspond *exactly* to that in - // src/monodroid/jni/xamarin-app.hh TypeMap structure - [NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))] - sealed class TypeMap - { - [NativeAssembler (Ignore = true)] - public int JavaToManagedCount; - - [NativeAssembler (Ignore = true)] - public int ManagedToJavaCount; - - public uint entry_count; - - [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] - public string? assembly_name = null; // unused in Debug mode - - [NativeAssembler (UsesDataProvider = true), NativePointer (IsNull = true)] - public byte data = 0; // unused in Debug mode - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - public TypeMapEntry? java_to_managed = null; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - public TypeMapEntry? managed_to_java = null; - }; - - readonly TypeMapGenerator.ModuleDebugData data; - - StructureInfo typeMapEntryStructureInfo; - StructureInfo typeMapStructureInfo; - List> javaToManagedMap; - List> managedToJavaMap; - StructureInstance type_map; - - public TypeMappingDebugNativeAssemblyGenerator (TypeMapGenerator.ModuleDebugData data) - { - this.data = data; - - javaToManagedMap = new List> (); - managedToJavaMap = new List> (); - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - - if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { - foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { - var m2j = new TypeMapEntry { - from = entry.ManagedName, - to = entry.JavaName, - }; - managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); - } - } - - if (data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0) { - foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { - TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; - - var j2m = new TypeMapEntry { - from = entry.JavaName, - to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, - }; - javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); - } - } - - var map = new TypeMap { - JavaToManagedCount = data.JavaToManagedMap == null ? 0 : data.JavaToManagedMap.Count, - ManagedToJavaCount = data.ManagedToJavaMap == null ? 0 : data.ManagedToJavaMap.Count, - - entry_count = data.EntryCount, - }; - type_map = new StructureInstance (typeMapStructureInfo, map); - module.AddGlobalVariable (TypeMapSymbol, type_map, LLVMIR.LlvmIrVariableOptions.GlobalConstant); - - if (managedToJavaMap.Count > 0) { - module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LLVMIR.LlvmIrVariableOptions.LocalConstant); - } - - if (javaToManagedMap.Count > 0) { - module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LLVMIR.LlvmIrVariableOptions.LocalConstant); - } - } - - void MapStructures (LlvmIrModule module) - { - typeMapEntryStructureInfo = module.MapStructure (); - typeMapStructureInfo = module.MapStructure (); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs index 9e0246ed06c..2d9e3388475 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -5,7 +5,7 @@ namespace Xamarin.Android.Tasks { - partial class TypeMappingDebugNativeAssemblyGenerator : TypeMappingAssemblyGenerator + class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer { const string JavaToManagedSymbol = "map_java_to_managed"; const string ManagedToJavaSymbol = "map_managed_to_java"; @@ -110,8 +110,8 @@ sealed class TypeMap readonly TypeMapGenerator.ModuleDebugData data; - StructureInfo typeMapEntryStructureInfo; - StructureInfo typeMapStructureInfo; + StructureInfo typeMapEntryStructureInfo; + StructureInfo typeMapStructureInfo; List> javaToManagedMap; List> managedToJavaMap; StructureInstance type_map; @@ -124,15 +124,17 @@ public TypeMappingDebugNativeAssemblyGenerator (TypeMapGenerator.ModuleDebugData managedToJavaMap = new List> (); } - public override void Init () + protected override void Construct (LlvmIrModule module) { + MapStructures (module); + if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { var m2j = new TypeMapEntry { from = entry.ManagedName, to = entry.JavaName, }; - managedToJavaMap.Add (new StructureInstance (m2j)); + managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); } } @@ -144,7 +146,7 @@ public override void Init () from = entry.JavaName, to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, }; - javaToManagedMap.Add (new StructureInstance (j2m)); + javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); } } @@ -154,26 +156,22 @@ public override void Init () entry_count = data.EntryCount, }; - type_map = new StructureInstance (map); - } - - protected override void MapStructures (LlvmIrGenerator generator) - { - typeMapEntryStructureInfo = generator.MapStructure (); - typeMapStructureInfo = generator.MapStructure (); - } + type_map = new StructureInstance (typeMapStructureInfo, map); + module.AddGlobalVariable (TypeMapSymbol, type_map, LlvmIrVariableOptions.GlobalConstant); - protected override void Write (LlvmIrGenerator generator) - { if (managedToJavaMap.Count > 0) { - generator.WriteStructureArray (typeMapEntryStructureInfo, managedToJavaMap, LlvmIrVariableOptions.LocalConstant, ManagedToJavaSymbol); + module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LlvmIrVariableOptions.LocalConstant); } if (javaToManagedMap.Count > 0) { - generator.WriteStructureArray (typeMapEntryStructureInfo, javaToManagedMap, LlvmIrVariableOptions.LocalConstant, JavaToManagedSymbol); + module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LlvmIrVariableOptions.LocalConstant); } + } - generator.WriteStructure (typeMapStructureInfo, type_map, LlvmIrVariableOptions.GlobalConstant, TypeMapSymbol); + void MapStructures (LlvmIrModule module) + { + typeMapEntryStructureInfo = module.MapStructure (); + typeMapStructureInfo = module.MapStructure (); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs deleted file mode 100644 index 13f4ab235e1..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.New.cs +++ /dev/null @@ -1,434 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO.Hashing; -using System.Text; - -using Xamarin.Android.Tasks.LLVM.IR; - -namespace Xamarin.Android.Tasks.New -{ - // TODO: remove these aliases once the refactoring is done - using NativePointerAttribute = LLVMIR.NativePointerAttribute; - - partial class TypeMappingReleaseNativeAssemblyGenerator : LlvmIrComposer - { - sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var map_module = EnsureType (data); - - if (String.Compare ("module_uuid", fieldName, StringComparison.Ordinal) == 0) { - return $" module_uuid: {map_module.MVID}"; - } - - if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { - 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); - } - } - - // This is here only to generate strongly-typed IR - internal 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 - { - [NativeAssembler (Ignore = true)] - public TypeMapJava JavaTypeMapEntry; - - public uint type_token_id; - public uint java_map_index; - } - - // Order of fields and their type must correspond *exactly* to that in - // src/monodroid/jni/xamarin-app.hh TypeMapModule structure - [NativeAssemblerStructContextDataProvider (typeof (TypeMapModuleContextDataProvider))] - sealed class TypeMapModule - { - [NativeAssembler (Ignore = true)] - public Guid MVID; - - [NativeAssembler (Ignore = true)] - public string? MapSymbolName; - - [NativeAssembler (Ignore = true)] - public string? DuplicateMapSymbolName; - - [NativeAssembler (Ignore = true)] - public TypeMapGenerator.ModuleReleaseData Data; - - [NativeAssembler (UsesDataProvider = true, InlineArray = true, InlineArraySize = 16)] - public byte[] module_uuid; - public uint entry_count; - public uint duplicate_count; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - public TypeMapModuleEntry map; - - [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] - public TypeMapModuleEntry duplicate_map; - - [NativeAssembler (UsesDataProvider = true)] - public string assembly_name; - - [NativePointer (IsNull = true)] - public MonoImage image; - public uint java_name_width; - - [NativePointer (IsNull = true)] - public byte java_map; - } - - // Order of fields and their type must correspond *exactly* to that in - // src/monodroid/jni/xamarin-app.hh TypeMapJava structure - sealed class TypeMapJava - { - [NativeAssembler (Ignore = true)] - public string JavaName; - - [NativeAssembler (Ignore = true)] - public uint JavaNameHash32; - - [NativeAssembler (Ignore = true)] - public ulong JavaNameHash64; - - public uint module_index; - public uint type_token_id; - public uint java_name_index; - } - - sealed class ModuleMapData - { - public string SymbolLabel { get; } - public List> Entries { get; } - - public ModuleMapData (string symbolLabel, List> entries) - { - SymbolLabel = symbolLabel; - Entries = entries; - } - } - - sealed class JavaNameHash32Comparer : IComparer> - { - public int Compare (StructureInstance a, StructureInstance b) - { - return a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32); - } - } - - sealed class JavaNameHash64Comparer : IComparer> - { - public int Compare (StructureInstance a, StructureInstance b) - { - return a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64); - } - } - - sealed class ConstructionState - { - public List> MapModules; - public Dictionary JavaTypesByName; - public List JavaNames; - public List> JavaMap; - public List AllModulesData; - } - - readonly NativeTypeMappingData mappingData; - StructureInfo typeMapJavaStructureInfo; - StructureInfo typeMapModuleStructureInfo; - StructureInfo typeMapModuleEntryStructureInfo; - JavaNameHash32Comparer javaNameHash32Comparer; - JavaNameHash64Comparer javaNameHash64Comparer; - - ulong moduleCounter = 0; - - public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) - { - this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - javaNameHash32Comparer = new JavaNameHash32Comparer (); - javaNameHash64Comparer = new JavaNameHash64Comparer (); - } - - protected override void Construct (LlvmIrModule module) - { - MapStructures (module); - - var cs = new ConstructionState (); - cs.JavaTypesByName = new Dictionary (StringComparer.Ordinal); - cs.JavaNames = new List (); - InitJavaMap (cs); - InitMapModules (cs); - HashJavaNames (cs); - PrepareModules (cs); - - module.AddGlobalVariable ("map_module_count", mappingData.MapModuleCount); - module.AddGlobalVariable ("java_type_count", cs.JavaMap.Count); - - var map_modules = new LlvmIrGlobalVariable (cs.MapModules, "map_modules", LLVMIR.LlvmIrVariableOptions.GlobalWritable) { - Comment = " Managed modules map", - }; - module.Add (map_modules); - - // Java hashes are output bafore Java type map **and** managed modules, because they will also sort the Java map for us. - // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save - // time sorting only once. - var map_java_hashes = new LlvmIrGlobalVariable (typeof(List), "map_java_hashes") { - Comment = " Java types name hashes", - BeforeWriteCallback = GenerateAndSortJavaHashes, - BeforeWriteCallbackCallerState = cs, - GetArrayItemCommentCallback = GetJavaHashesItemComment, - GetArrayItemCommentCallbackCallerState = cs, - }; - map_java_hashes.WriteOptions &= ~LlvmIrVariableWriteOptions.ArrayWriteIndexComments; - module.Add (map_java_hashes); - - foreach (ModuleMapData mmd in cs.AllModulesData) { - var mmdVar = new LlvmIrGlobalVariable (mmd.Entries, mmd.SymbolLabel, LLVMIR.LlvmIrVariableOptions.LocalConstant) { - BeforeWriteCallback = UpdateJavaIndexes, - BeforeWriteCallbackCallerState = cs, - }; - module.Add (mmdVar); - } - - module.AddGlobalVariable ("map_java", cs.JavaMap, LLVMIR.LlvmIrVariableOptions.GlobalConstant, " Java to managed map"); - module.AddGlobalVariable ("java_type_names", cs.JavaNames, LLVMIR.LlvmIrVariableOptions.GlobalConstant, " Java type names"); - } - - void UpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) - { - ConstructionState cs = EnsureConstructionState (callerState); - LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - IComparer> hashComparer = target.Is64Bit ? javaNameHash64Comparer : javaNameHash32Comparer; - - var entries = (List>)variable.Value; - foreach (StructureInstance entry in entries) { - entry.Instance.java_map_index = GetJavaEntryIndex (entry.Instance.JavaTypeMapEntry); - } - - uint GetJavaEntryIndex (TypeMapJava javaEntry) - { - var key = new StructureInstance (typeMapJavaStructureInfo, javaEntry); - int idx = cs.JavaMap.BinarySearch (key, hashComparer); - if (idx < 0) { - throw new InvalidOperationException ($"Could not map entry '{javaEntry.JavaName}' to array index"); - } - - return (uint)idx; - } - } - - string? GetJavaHashesItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) - { - var cs = callerState as ConstructionState; - if (cs == null) { - throw new InvalidOperationException ("Internal error: construction state expected but not found"); - } - - return $" {index}: 0x{value:x} => {cs.JavaMap[(int)index].Instance.JavaName}"; - } - - void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) - { - ConstructionState cs = EnsureConstructionState (callerState); - LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - Type listType; - IList hashes; - if (target.Is64Bit) { - listType = typeof(List); - cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64)); - - var list = new List (); - foreach (StructureInstance si in cs.JavaMap) { - list.Add (si.Instance.JavaNameHash64); - } - hashes = list; - } else { - listType = typeof(List); - cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32)); - - var list = new List (); - foreach (StructureInstance si in cs.JavaMap) { - list.Add (si.Instance.JavaNameHash32); - } - hashes = list; - } - - gv.OverrideValueAndType (listType, hashes); - } - - ConstructionState EnsureConstructionState (object? callerState) - { - var cs = callerState as ConstructionState; - if (cs == null) { - throw new InvalidOperationException ("Internal error: construction state expected but not found"); - } - - return cs; - } - - void InitJavaMap (ConstructionState cs) - { - cs.JavaMap = new List> (); - TypeMapJava map_entry; - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { - cs.JavaNames.Add (entry.JavaName); - - map_entry = new TypeMapJava { - module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, - type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, - java_name_index = (uint)(cs.JavaNames.Count - 1), - JavaName = entry.JavaName, - }; - - cs.JavaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); - cs.JavaTypesByName.Add (map_entry.JavaName, map_entry); - } - } - - void InitMapModules (ConstructionState cs) - { - cs.MapModules = new List> (); - foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { - string mapName = $"module{moduleCounter++}_managed_to_java"; - string duplicateMapName; - - if (data.DuplicateTypes.Count == 0) { - duplicateMapName = String.Empty; - } else { - duplicateMapName = $"{mapName}_duplicates"; - } - - var map_module = new TypeMapModule { - MVID = data.Mvid, - MapSymbolName = mapName, - DuplicateMapSymbolName = duplicateMapName.Length == 0 ? null : duplicateMapName, - Data = data, - - module_uuid = data.MvidBytes, - entry_count = (uint)data.Types.Length, - duplicate_count = (uint)data.DuplicateTypes.Count, - assembly_name = data.AssemblyName, - java_name_width = 0, - }; - - cs.MapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); - } - } - - void MapStructures (LlvmIrModule module) - { - typeMapJavaStructureInfo = module.MapStructure (); - typeMapModuleStructureInfo = module.MapStructure (); - typeMapModuleEntryStructureInfo = module.MapStructure (); - } - - void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, ConstructionState cs) - { - var mapModuleEntries = new List> (); - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { - if (!cs.JavaTypesByName.TryGetValue (entry.JavaName, out TypeMapJava javaType)) { - throw new InvalidOperationException ($"Internal error: Java type '{entry.JavaName}' not found in cache"); - } - - var map_entry = new TypeMapModuleEntry { - JavaTypeMapEntry = javaType, - type_token_id = entry.Token, - java_map_index = UInt32.MaxValue, // will be set later, when the target is known - }; - mapModuleEntries.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); - } - - mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Instance.type_token_id.CompareTo (b.Instance.type_token_id)); - cs.AllModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); - } - - void PrepareModules (ConstructionState cs) - { - cs.AllModulesData = new List (); - foreach (StructureInstance moduleInstance in cs.MapModules) { - TypeMapModule module = moduleInstance.Instance; - PrepareMapModuleData (module.MapSymbolName, module.Data.Types, cs); - if (module.Data.DuplicateTypes.Count > 0) { - PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, cs); - } - } - } - - void HashJavaNames (ConstructionState cs) - { - // We generate both 32-bit and 64-bit hashes at the construction time. Which set will be used depends on the target. - // Java map list will also be sorted when the target is known - var hashes32 = new HashSet (); - var hashes64 = new HashSet (); - - // Generate Java type name hashes... - for (int i = 0; i < cs.JavaMap.Count; i++) { - TypeMapJava entry = cs.JavaMap[i].Instance; - - // The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit - entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false); - hashes32.Add (entry.JavaNameHash32); - - entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true); - hashes64.Add (entry.JavaNameHash64); - } - - ulong HashName (string name, bool is64Bit) - { - if (name.Length == 0) { - return UInt64.MaxValue; - } - - // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do - // the same - return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit); - } - - ulong HashBytes (byte[] bytes, bool is64Bit) - { - if (is64Bit) { - return XxHash64.HashToUInt64 (bytes); - } - - return (ulong)XxHash32.HashToUInt32 (bytes); - } - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index 7618962ac5e..a6e4fd465df 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -1,14 +1,14 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO.Hashing; -using System.Linq; using System.Text; using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks { - partial class TypeMappingReleaseNativeAssemblyGenerator : TypeMappingAssemblyGenerator + partial class TypeMappingReleaseNativeAssemblyGenerator : LlvmIrComposer { sealed class TypeMapModuleContextDataProvider : NativeAssemblerStructContextDataProvider { @@ -17,11 +17,11 @@ public override string GetComment (object data, string fieldName) var map_module = EnsureType (data); if (String.Compare ("module_uuid", fieldName, StringComparison.Ordinal) == 0) { - return $"module_uuid: {map_module.MVID}"; + return $" module_uuid: {map_module.MVID}"; } if (String.Compare ("assembly_name", fieldName, StringComparison.Ordinal) == 0) { - return $"assembly_name: {map_module.assembly_name}"; + return $" assembly_name: {map_module.assembly_name}"; } return String.Empty; @@ -58,14 +58,6 @@ public override ulong GetBufferSize (object data, string fieldName) } } - sealed class JavaNameHashComparer : IComparer> - { - public int Compare (StructureInstance a, StructureInstance b) - { - return a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash); - } - } - // This is here only to generate strongly-typed IR internal sealed class MonoImage {} @@ -74,6 +66,9 @@ internal sealed class MonoImage // src/monodroid/jni/xamarin-app.hh TypeMapModuleEntry structure sealed class TypeMapModuleEntry { + [NativeAssembler (Ignore = true)] + public TypeMapJava JavaTypeMapEntry; + public uint type_token_id; public uint java_map_index; } @@ -125,7 +120,10 @@ sealed class TypeMapJava public string JavaName; [NativeAssembler (Ignore = true)] - public ulong JavaNameHash; + public uint JavaNameHash32; + + [NativeAssembler (Ignore = true)] + public ulong JavaNameHash64; public uint module_index; public uint type_token_id; @@ -144,62 +142,195 @@ public ModuleMapData (string symbolLabel, List> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32); + } + } + + sealed class JavaNameHash64Comparer : IComparer> + { + public int Compare (StructureInstance a, StructureInstance b) + { + return a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64); + } + } + + sealed class ConstructionState + { + public List> MapModules; + public Dictionary JavaTypesByName; + public List JavaNames; + public List> JavaMap; + public List AllModulesData; + } + readonly NativeTypeMappingData mappingData; - StructureInfo typeMapJavaStructureInfo; - StructureInfo typeMapModuleStructureInfo; - StructureInfo typeMapModuleEntryStructureInfo; - List> mapModules; - List> javaMap; - Dictionary javaTypesByName; - List javaNames; - JavaNameHashComparer javaNameHashComparer; + StructureInfo typeMapJavaStructureInfo; + StructureInfo typeMapModuleStructureInfo; + StructureInfo typeMapModuleEntryStructureInfo; + JavaNameHash32Comparer javaNameHash32Comparer; + JavaNameHash64Comparer javaNameHash64Comparer; ulong moduleCounter = 0; public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) { this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - mapModules = new List> (); - javaMap = new List> (); - javaTypesByName = new Dictionary (StringComparer.Ordinal); - javaNameHashComparer = new JavaNameHashComparer (); - javaNames = new List (); + javaNameHash32Comparer = new JavaNameHash32Comparer (); + javaNameHash64Comparer = new JavaNameHash64Comparer (); + } + + protected override void Construct (LlvmIrModule module) + { + MapStructures (module); + + var cs = new ConstructionState (); + cs.JavaTypesByName = new Dictionary (StringComparer.Ordinal); + cs.JavaNames = new List (); + InitJavaMap (cs); + InitMapModules (cs); + HashJavaNames (cs); + PrepareModules (cs); + + module.AddGlobalVariable ("map_module_count", mappingData.MapModuleCount); + module.AddGlobalVariable ("java_type_count", cs.JavaMap.Count); + + var map_modules = new LlvmIrGlobalVariable (cs.MapModules, "map_modules", LlvmIrVariableOptions.GlobalWritable) { + Comment = " Managed modules map", + }; + module.Add (map_modules); + + // Java hashes are output bafore Java type map **and** managed modules, because they will also sort the Java map for us. + // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save + // time sorting only once. + var map_java_hashes = new LlvmIrGlobalVariable (typeof(List), "map_java_hashes") { + Comment = " Java types name hashes", + BeforeWriteCallback = GenerateAndSortJavaHashes, + BeforeWriteCallbackCallerState = cs, + GetArrayItemCommentCallback = GetJavaHashesItemComment, + GetArrayItemCommentCallbackCallerState = cs, + }; + map_java_hashes.WriteOptions &= ~LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + module.Add (map_java_hashes); + + foreach (ModuleMapData mmd in cs.AllModulesData) { + var mmdVar = new LlvmIrGlobalVariable (mmd.Entries, mmd.SymbolLabel, LlvmIrVariableOptions.LocalConstant) { + BeforeWriteCallback = UpdateJavaIndexes, + BeforeWriteCallbackCallerState = cs, + }; + module.Add (mmdVar); + } + + module.AddGlobalVariable ("map_java", cs.JavaMap, LlvmIrVariableOptions.GlobalConstant, " Java to managed map"); + module.AddGlobalVariable ("java_type_names", cs.JavaNames, LlvmIrVariableOptions.GlobalConstant, " Java type names"); + } + + void UpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + IComparer> hashComparer = target.Is64Bit ? javaNameHash64Comparer : javaNameHash32Comparer; + + var entries = (List>)variable.Value; + foreach (StructureInstance entry in entries) { + entry.Instance.java_map_index = GetJavaEntryIndex (entry.Instance.JavaTypeMapEntry); + } + + uint GetJavaEntryIndex (TypeMapJava javaEntry) + { + var key = new StructureInstance (typeMapJavaStructureInfo, javaEntry); + int idx = cs.JavaMap.BinarySearch (key, hashComparer); + if (idx < 0) { + throw new InvalidOperationException ($"Could not map entry '{javaEntry.JavaName}' to array index"); + } + + return (uint)idx; + } + } + + string? GetJavaHashesItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) + { + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } + + return $" {index}: 0x{value:x} => {cs.JavaMap[(int)index].Instance.JavaName}"; + } + + void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) + { + ConstructionState cs = EnsureConstructionState (callerState); + LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); + Type listType; + IList hashes; + if (target.Is64Bit) { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash64.CompareTo (b.Instance.JavaNameHash64)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash64); + } + hashes = list; + } else { + listType = typeof(List); + cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash32.CompareTo (b.Instance.JavaNameHash32)); + + var list = new List (); + foreach (StructureInstance si in cs.JavaMap) { + list.Add (si.Instance.JavaNameHash32); + } + hashes = list; + } + + gv.OverrideValueAndType (listType, hashes); } - public override void Init () + ConstructionState EnsureConstructionState (object? callerState) { - InitMapModules (); - InitJavaMap (); + var cs = callerState as ConstructionState; + if (cs == null) { + throw new InvalidOperationException ("Internal error: construction state expected but not found"); + } + + return cs; } - void InitJavaMap () + void InitJavaMap (ConstructionState cs) { + cs.JavaMap = new List> (); TypeMapJava map_entry; foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { - javaNames.Add (entry.JavaName); + cs.JavaNames.Add (entry.JavaName); map_entry = new TypeMapJava { module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, - java_name_index = (uint)(javaNames.Count - 1), + java_name_index = (uint)(cs.JavaNames.Count - 1), JavaName = entry.JavaName, }; - javaMap.Add (new StructureInstance (map_entry)); - javaTypesByName.Add (map_entry.JavaName, map_entry); + cs.JavaMap.Add (new StructureInstance (typeMapJavaStructureInfo, map_entry)); + cs.JavaTypesByName.Add (map_entry.JavaName, map_entry); } } - void InitMapModules () + void InitMapModules (ConstructionState cs) { + cs.MapModules = new List> (); foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { string mapName = $"module{moduleCounter++}_managed_to_java"; string duplicateMapName; - if (data.DuplicateTypes.Count == 0) + if (data.DuplicateTypes.Count == 0) { duplicateMapName = String.Empty; - else + } else { duplicateMapName = $"{mapName}_duplicates"; + } var map_module = new TypeMapModule { MVID = data.Mvid, @@ -214,85 +345,69 @@ void InitMapModules () java_name_width = 0, }; - mapModules.Add (new StructureInstance (map_module)); + cs.MapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); } } - protected override void MapStructures (LlvmIrGenerator generator) + void MapStructures (LlvmIrModule module) { - generator.MapStructure (); - typeMapJavaStructureInfo = generator.MapStructure (); - typeMapModuleStructureInfo = generator.MapStructure (); - typeMapModuleEntryStructureInfo = generator.MapStructure (); + typeMapJavaStructureInfo = module.MapStructure (); + typeMapModuleStructureInfo = module.MapStructure (); + typeMapModuleEntryStructureInfo = module.MapStructure (); } - // Prepare module map entries by sorting them on the managed token, and then mapping each entry to its corresponding Java type map index. - // Requires that `javaMap` is sorted on the type name hash. - void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData) + void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, ConstructionState cs) { var mapModuleEntries = new List> (); foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { + if (!cs.JavaTypesByName.TryGetValue (entry.JavaName, out TypeMapJava javaType)) { + throw new InvalidOperationException ($"Internal error: Java type '{entry.JavaName}' not found in cache"); + } + var map_entry = new TypeMapModuleEntry { + JavaTypeMapEntry = javaType, type_token_id = entry.Token, - java_map_index = GetJavaEntryIndex (entry.JavaName), + java_map_index = UInt32.MaxValue, // will be set later, when the target is known }; - mapModuleEntries.Add (new StructureInstance (map_entry)); + mapModuleEntries.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); } - mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Obj.type_token_id.CompareTo (b.Obj.type_token_id)); - allModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); - - uint GetJavaEntryIndex (string javaTypeName) - { - if (!javaTypesByName.TryGetValue (javaTypeName, out TypeMapJava javaType)) { - throw new InvalidOperationException ($"INTERNAL ERROR: Java type '{javaTypeName}' not found in cache"); - } + mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Instance.type_token_id.CompareTo (b.Instance.type_token_id)); + cs.AllModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); + } - var key = new StructureInstance (javaType); - int idx = javaMap.BinarySearch (key, javaNameHashComparer); - if (idx < 0) { - throw new InvalidOperationException ($"Could not map entry '{javaTypeName}' to array index"); + void PrepareModules (ConstructionState cs) + { + cs.AllModulesData = new List (); + foreach (StructureInstance moduleInstance in cs.MapModules) { + TypeMapModule module = moduleInstance.Instance; + PrepareMapModuleData (module.MapSymbolName, module.Data.Types, cs); + if (module.Data.DuplicateTypes.Count > 0) { + PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, cs); } - - return (uint)idx; } } - // Generate hashes for all Java type names, then sort javaMap on the name hash. This has to be done in the writing phase because hashes - // will depend on architecture (or, actually, on its bitness) and may differ between architectures (they will be the same for all architectures - // with the same bitness) - (List allMapModulesData, List javaMapHashes) PrepareMapsForWriting (LlvmIrGenerator generator) + void HashJavaNames (ConstructionState cs) { - bool is64Bit = generator.Is64Bit; + // We generate both 32-bit and 64-bit hashes at the construction time. Which set will be used depends on the target. + // Java map list will also be sorted when the target is known + var hashes32 = new HashSet (); + var hashes64 = new HashSet (); // Generate Java type name hashes... - for (int i = 0; i < javaMap.Count; i++) { - TypeMapJava entry = javaMap[i].Obj; - entry.JavaNameHash = HashName (entry.JavaName); - } + for (int i = 0; i < cs.JavaMap.Count; i++) { + TypeMapJava entry = cs.JavaMap[i].Instance; - // ...sort them... - javaMap.Sort ((StructureInstance a, StructureInstance b) => a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash)); - - var allMapModulesData = new List (); - - // ...and match managed types to Java... - foreach (StructureInstance moduleInstance in mapModules) { - TypeMapModule module = moduleInstance.Obj; - PrepareMapModuleData (module.MapSymbolName, module.Data.Types, allMapModulesData); - if (module.Data.DuplicateTypes.Count > 0) { - PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes, allMapModulesData); - } - } + // The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit + entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false); + hashes32.Add (entry.JavaNameHash32); - var javaMapHashes = new HashSet (); - foreach (StructureInstance entry in javaMap) { - javaMapHashes.Add (entry.Obj.JavaNameHash); + entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true); + hashes64.Add (entry.JavaNameHash64); } - return (allMapModulesData, javaMapHashes.ToList ()); - - ulong HashName (string name) + ulong HashName (string name, bool is64Bit) { if (name.Length == 0) { return UInt64.MaxValue; @@ -300,10 +415,10 @@ ulong HashName (string name) // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do // the same - return HashBytes (Encoding.Unicode.GetBytes (name)); + return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit); } - ulong HashBytes (byte[] bytes) + ulong HashBytes (byte[] bytes, bool is64Bit) { if (is64Bit) { return XxHash64.HashToUInt64 (bytes); @@ -312,79 +427,5 @@ ulong HashBytes (byte[] bytes) return (ulong)XxHash32.HashToUInt32 (bytes); } } - - protected override void Write (LlvmIrGenerator generator) - { - generator.WriteVariable ("map_module_count", mappingData.MapModuleCount); - generator.WriteVariable ("java_type_count", javaMap.Count); // must include the padding item, if any - - (List allMapModulesData, List javaMapHashes) = PrepareMapsForWriting (generator); - WriteMapModules (generator, allMapModulesData); - WriteJavaMap (generator, javaMapHashes); - } - - void WriteJavaMap (LlvmIrGenerator generator, List javaMapHashes) - { - generator.WriteEOL (); - generator.WriteEOL ("Java to managed map"); - - generator.WriteStructureArray ( - typeMapJavaStructureInfo, - javaMap, - LlvmIrVariableOptions.GlobalConstant, - "map_java" - ); - - if (generator.Is64Bit) { - WriteHashes (javaMapHashes); - } else { - // A bit ugly, but simple. We know that hashes are really 32-bit, so we can cast without - // worrying. - var hashes = new List (javaMapHashes.Count); - foreach (ulong hash in javaMapHashes) { - hashes.Add ((uint)hash); - } - WriteHashes (hashes); - } - - generator.WriteArray (javaNames, "java_type_names"); - - void WriteHashes (List hashes) where T: struct - { - generator.WriteArray ( - hashes, - LlvmIrVariableOptions.GlobalConstant, - "map_java_hashes", - (int idx, T value) => $"{idx}: 0x{value:x} => {javaMap[idx].Obj.JavaName}" - ); - } - } - - void WriteMapModules (LlvmIrGenerator generator, List mapModulesData) - { - if (mapModules.Count == 0) { - return; - } - - generator.WriteEOL (); - generator.WriteEOL ("Map modules data"); - - foreach (ModuleMapData mmd in mapModulesData) { - generator.WriteStructureArray ( - typeMapModuleEntryStructureInfo, - mmd.Entries, - LlvmIrVariableOptions.LocalConstant, - mmd.SymbolLabel - ); - } - - generator.WriteEOL ("Map modules"); - generator.WriteStructureArray ( - typeMapModuleStructureInfo, - mapModules, - LlvmIrVariableOptions.GlobalWritable, - "map_modules" - ); - } } } From 6799989811d23102822c8eb3bc4024d62853e623 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 21 Jun 2023 21:32:02 +0200 Subject: [PATCH 53/60] A bit more cleanup --- .../LlvmIrGenerator/IStructureInfo.cs | 15 -------------- .../LlvmIrGenerator/LlvmIrBufferManager.cs | 1 - .../LlvmIrGenerator/LlvmIrDataLayout.cs | 8 ++++---- .../LlvmIrGenerator/LlvmIrFunction.cs | 3 --- .../LlvmIrFunctionAttributeSet.cs | 11 ---------- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 17 ---------------- .../LlvmIrGenerator/LlvmIrInstructions.cs | 1 - .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 11 ---------- .../LlvmIrGenerator/LlvmIrModuleAArch64.cs | 1 - .../LlvmIrGenerator/LlvmIrModuleArmV7a.cs | 1 - .../LlvmIrGenerator/LlvmIrModuleX64.cs | 1 - .../LlvmIrGenerator/LlvmIrModuleX86.cs | 1 - .../LlvmIrGenerator/LlvmIrStringManager.cs | 1 - .../LlvmIrGenerator/LlvmIrVariable.cs | 4 ++-- .../LlvmIrGenerator/StructureInstance.cs | 20 ------------------- .../LlvmIrGenerator/StructureStringData.cs | 14 ------------- 16 files changed, 6 insertions(+), 104 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs deleted file mode 100644 index afd17fefda5..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Xamarin.Android.Tasks.LLVMIR -{ - interface IStructureInfo - { - Type Type { get; } - ulong Size { get; } - int MaxFieldAlignment { get; } - string Name { get; } - string NativeTypeDesignator { get; } - - void RenderDeclaration (LlvmIrGenerator generator); - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs index 137eb886a6d..8cb8c2910e4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrBufferManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; namespace Xamarin.Android.Tasks.LLVMIR; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs index 6ab6033e36a..33da1be9a57 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrDataLayout.cs @@ -256,11 +256,11 @@ public bool LittleEndian { public uint? StackAlignment { get; set; } public LlvmIrDataLayoutAggregateObjectAlignment? AggregateObjectAlignment { get; set; } - public List? FloatAlignment { get; set; } + public List? FloatAlignment { get; set; } public LlvmIrDataLayoutFunctionPointerAlignment? FunctionPointerAlignment { get; set; } - public List? IntegerAlignment { get; set; } - public List? VectorAlignment { get; set; } - public List? PointerSize { get; set; } + public List? IntegerAlignment { get; set; } + public List? VectorAlignment { get; set; } + public List? PointerSize { get; set; } public List? NativeIntegerWidths { get; set; } public List? NonIntegralPointerTypeAddressSpaces { get; set; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 79f913c1496..d65a8fef5a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; namespace Xamarin.Android.Tasks.LLVMIR { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index 45cd213dd59..8b8dadbf0fd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -69,17 +69,6 @@ public void Add (AndroidTargetArch targetArch, LlvmIrFunctionAttribute attr) list.Add (attr); } - public void Add (LlvmIrFunctionAttributeSet sourceSet) - { - if (sourceSet == null) { - throw new ArgumentNullException (nameof (sourceSet)); - } - - foreach (LlvmIrFunctionAttribute attr in sourceSet) { - Add (attr); - } - } - public string Render () { List list = attributes.ToList (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index 20046779b14..3336b5d0b7d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -148,23 +148,6 @@ public LlvmIrFunctionImplicitStartLabel (ulong num) } } - sealed class LlvmIrFunctionParameterItem : LlvmIrFunctionLocalItem - { - public LlvmIrFunctionParameter Parameter { get; } - - public LlvmIrFunctionParameterItem (LlvmIrFunction.FunctionState state, LlvmIrFunctionParameter parameter) - : base (state, parameter.Name) - { - Parameter = parameter; - SkipInOutput = true; - } - - protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator generator) - { - throw new NotSupportedException ("Internal error: writing not supported for this item"); - } - } - List items; HashSet definedLabels; LlvmIrFunction ownerFunction; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index aeecad461f4..2efa5e45db8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -6,7 +6,6 @@ namespace Xamarin.Android.Tasks.LLVMIR; abstract class LlvmIrInstruction : LlvmIrFunctionBodyItem { - // TODO: add support for metadata public string Mnemonic { get; } public LlvmIrFunctionAttributeSet? AttributeSet { get; set; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index d560b924bce..f7094dbbd42 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Xamarin.Android.Tools; @@ -421,16 +420,6 @@ bool IsStructureArrayVariable (LlvmIrGlobalVariable variable) return typeof(StructureInstance).IsAssignableFrom (elementType); } - bool IsPointerArrayVariable (LlvmIrGlobalVariable variable) - { - if (!variable.Type.IsArray ()) { - return false; - } - - Type elementType = variable.Type.GetArrayElementType (); - return elementType == typeof(IntPtr) || elementType == typeof(UIntPtr); - } - bool IsStructureVariable (LlvmIrGlobalVariable variable) { if (!typeof(StructureInstance).IsAssignableFrom (variable.Type)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs index 88a9b4140cb..131b79dcaa6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleAArch64.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Xamarin.Android.Tools; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs index a2419e1b6d8..f7ff54f8a48 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleArmV7a.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Xamarin.Android.Tools; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs index 8b36d2a01d9..44eede79e54 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX64.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Xamarin.Android.Tools; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs index 51f86cbf9ff..7e43558cb84 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModuleX86.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Xamarin.Android.Tools; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs index dcf62b40851..9a49e544428 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; namespace Xamarin.Android.Tasks.LLVMIR; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 0f7f28ea74d..7f741490ce0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -13,8 +13,8 @@ enum LlvmIrVariableWriteOptions abstract class LlvmIrVariable : IEquatable { - public abstract bool Global { get; } - public abstract string NamePrefix { get; } + public abstract bool Global { get; } + public abstract string NamePrefix { get; } public string? Name { get; protected set; } public Type Type { get; protected set; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs index ef4d7759d9f..3c0c53ab471 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInstance.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; namespace Xamarin.Android.Tasks.LLVMIR { abstract class StructureInstance { - Dictionary? pointees; StructureInfo info; public object? Obj { get; } @@ -41,24 +39,6 @@ protected StructureInstance (StructureInfo info, object instance) this.info = info; Obj = instance; } - - public void AddPointerData (StructureMemberInfo smi, string? variableName, ulong dataSize) - { - if (pointees == null) { - pointees = new Dictionary (); - } - - 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/StructureStringData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs deleted file mode 100644 index 8e23e81b1e6..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureStringData.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Xamarin.Android.Tasks.LLVMIR -{ - sealed class StructurePointerData - { - public string? VariableName { get; } - public ulong Size { get; } - - public StructurePointerData (string? name, ulong size) - { - VariableName = name; - Size = size; - } - } -} From 580aae942f9661c5c0a69368aaadcfef38e78437 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 22 Jun 2023 13:00:17 +0200 Subject: [PATCH 54/60] Update libunwind to 1.7.0 --- external/libunwind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libunwind b/external/libunwind index 74ab1eb268e..688caaf6ef9 160000 --- a/external/libunwind +++ b/external/libunwind @@ -1 +1 @@ -Subproject commit 74ab1eb268eb6d8cde5db3274c12560a1cd2c73c +Subproject commit 688caaf6ef9853cc26ad8bd1706804d48a0df0bc From 944b73b1ceb6246d36d95285931475144b3eac4c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 22 Jun 2023 22:59:28 +0200 Subject: [PATCH 55/60] Tracing progress --- .../Utilities/EnvironmentHelper.cs | 2 +- .../Utilities/NativeAssemblyParser.cs | 16 +- .../LlvmIrGenerator/LlvmIrFunction.cs | 13 ++ .../LlvmIrFunctionAttributeSet.cs | 3 +- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 27 +++ .../LlvmIrGenerator/LlvmIrGenerator.cs | 21 +- .../LlvmIrGenerator/LlvmIrInstructions.cs | 50 ++++- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 203 +++++++++++++++++- .../MarshalMethodsNativeAssemblyGenerator.cs | 16 +- 9 files changed, 331 insertions(+), 20 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 5b79d0e8309..afd2c24fbab 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -402,7 +402,7 @@ static Dictionary ReadEnvironmentVariables (EnvironmentFile envF static string[] GetField (string llvmAssemblerFile, string nativeAssemblerFile, string line, ulong lineNumber) { string[] ret = line?.Trim ()?.Split ('\t'); - Assert.IsTrue (ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'"); + Assert.IsTrue (ret != null && ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'"); return ret; } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs index bfbf5fb709c..3a2aa982f0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/NativeAssemblyParser.cs @@ -192,7 +192,7 @@ public SymbolMetadata (SymbolMetadataKind kind, string value = null) static readonly char[] splitOnWhitespace = new char[] { ' ', '\t' }; static readonly char[] splitOnComma = new char[] { ',' }; - static readonly Regex assemblerLabelRegex = new Regex ("^[_.a-zA-Z0-9]+:", RegexOptions.Compiled); + static readonly Regex assemblerLabelRegex = new Regex ("^[_.$a-zA-Z0-9]+:", RegexOptions.Compiled); Dictionary symbols = new Dictionary (StringComparer.Ordinal); Dictionary> symbolMetadata = new Dictionary> (StringComparer.Ordinal); @@ -238,8 +238,10 @@ void Load (string sourceFilePath) AssemblerSection currentSection = null; AssemblerSymbol currentSymbol = null; - string symbolName; + string symbolName = null; ulong lineNumber = 0; + bool addedNewSymbol = false; + foreach (string l in File.ReadLines (sourceFilePath, Encoding.UTF8)) { lineNumber++; @@ -253,6 +255,15 @@ void Load (string sourceFilePath) continue; } + if (addedNewSymbol) { + addedNewSymbol = false; + // Some forms of LLVM IR can generate two labels for a single symbol, depending on symbol visibility, attributes and llc parameters. + // The exported symbol name 'symbol:' may be followed by another one '.Lsymbol$local:', we need to detect this and ignore the new symbol. + if (assemblerLabelRegex.IsMatch (line) && String.Compare (line.Trim (), $".L{symbolName}$local:", StringComparison.Ordinal) == 0) { + continue; + } + } + if (StartsNewSection (parts, ref currentSection)) { currentSymbol = null; // Symbols cannot cross sections continue; @@ -265,6 +276,7 @@ void Load (string sourceFilePath) if (assemblerLabelRegex.IsMatch (line)) { symbolName = GetSymbolName (line); currentSymbol = AddNewSymbol (symbolName); + addedNewSymbol = true; continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index d65a8fef5a6..43fec28bf65 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -22,6 +22,7 @@ sealed class SavedParameterState : ILlvmIrSavedFunctionParameterState public bool? SignExt; public bool? ZeroExt; public bool? IsCplusPlusReference; + public bool IsVarArgs; public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? previousState = null) { @@ -39,6 +40,8 @@ public SavedParameterState (LlvmIrFunctionParameter owner, SavedParameterState? ReadNone = previousState.ReadNone; SignExt = previousState.SignExt; ZeroExt = previousState.ZeroExt; + IsCplusPlusReference = previousState.IsCplusPlusReference; + IsVarArgs = previousState.IsVarArgs; } } @@ -137,6 +140,14 @@ public bool? IsCplusPlusReference { set => state.IsCplusPlusReference = value; } + /// + /// Indicates that the argument is a C variable arguments placeholder (`...`) + /// + public bool IsVarArgs { + get => state.IsVarArgs; + set => state.IsVarArgs = value; + } + public LlvmIrFunctionParameter (Type type, string? name = null) : base (type, name) { @@ -193,6 +204,7 @@ public sealed class ReturnTypeAttributes public bool? NoUndef; public bool? SignExt; public bool? ZeroExt; + public bool? NonNull; public ReturnTypeAttributes () {} @@ -201,6 +213,7 @@ public ReturnTypeAttributes (ReturnTypeAttributes other) { InReg = other.InReg; NoUndef = other.NoUndef; + NonNull = other.NonNull; SignExt = other.SignExt; ZeroExt = other.ZeroExt; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs index 8b8dadbf0fd..2af6759d0c2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionAttributeSet.cs @@ -9,7 +9,8 @@ namespace Xamarin.Android.Tasks.LLVMIR; class LlvmIrFunctionAttributeSet : IEnumerable, IEquatable { - public uint Number { get; set; } = 0; + public uint Number { get; set; } = 0; + public bool DoNotAddTargetSpecificAttributes { get; set; } HashSet attributes; Dictionary>? privateTargetSpecificAttributes; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index 3336b5d0b7d..e268e98afb7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -171,6 +171,13 @@ public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState fun previousLabel = implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); } + public LlvmIrInstructions.Alloca Alloca (LlvmIrVariable result) + { + var ret = new LlvmIrInstructions.Alloca (result); + Add (ret); + return ret; + } + public LlvmIrInstructions.Br Br (LlvmIrFunctionLabelItem label) { var ret = new LlvmIrInstructions.Br (label); @@ -208,6 +215,26 @@ public LlvmIrInstructions.Load Load (LlvmIrVariable source, LlvmIrVariable resul return ret; } + public LlvmIrInstructions.Store Store (LlvmIrVariable from, LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Store (from, to) { + TBAA = tbaa, + }; + + Add (ret); + return ret; + } + + public LlvmIrInstructions.Store Store (LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + { + var ret = new LlvmIrInstructions.Store (to) { + TBAA = tbaa, + }; + + Add (ret); + return ret; + } + /// /// Creates the `phi` instruction form we use the most throughout marshal methods generator - one which refers to an if/else block and where /// **both** value:label pairs are **required**. Parameters and are nullable because, in theory, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 202d04a48c3..bb81b03db0e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -1012,6 +1012,10 @@ public static void WriteReturnAttributes (GeneratorWriteContext context, LlvmIrF context.Output.Write ("noundef "); } + if (AttributeIsSet (returnAttrs.NonNull)) { + context.Output.Write ("nonnull "); + } + if (AttributeIsSet (returnAttrs.SignExt)) { context.Output.Write ("signext "); } @@ -1033,15 +1037,28 @@ void WriteFunctionSignature (GeneratorWriteContext context, LlvmIrFunction func, context.Output.Write ('('); bool first = true; + bool varargsFound = false; + foreach (LlvmIrFunctionParameter parameter in func.Signature.Parameters) { + if (varargsFound) { + throw new InvalidOperationException ($"Internal error: function '{func.Signature.Name}' has extra parameters following the C varargs parameter. This is not allowed."); + } + if (!first) { context.Output.Write (", "); } else { first = false; } + if (parameter.IsVarArgs) { + context.Output.Write ("..."); + varargsFound = true; + continue; + } + context.Output.Write (MapToIRType (parameter.Type)); WriteParameterAttributes (context, parameter); + if (writeParameterNames) { if (String.IsNullOrEmpty (parameter.Name)) { throw new InvalidOperationException ($"Internal error: parameter must have a name"); @@ -1126,7 +1143,9 @@ void WriteAttributeSets (GeneratorWriteContext context) foreach (LlvmIrFunctionAttributeSet attrSet in context.Module.AttributeSets) { // Must not modify the original set, it is shared with other targets. var targetSet = new LlvmIrFunctionAttributeSet (attrSet); - target.AddTargetSpecificAttributes (targetSet); + if (!attrSet.DoNotAddTargetSpecificAttributes) { + target.AddTargetSpecificAttributes (targetSet); + } IList? privateTargetSet = attrSet.GetPrivateTargetAttributes (target.TargetArch); if (privateTargetSet != null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 2efa5e45db8..33297922814 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -104,8 +104,45 @@ protected LlvmIrInstructionArgumentValuePlaceholder () public abstract object? GetValue (LlvmIrModuleTarget target); } +class LlvmIrInstructionPointerSizeArgumentPlaceholder : LlvmIrInstructionArgumentValuePlaceholder +{ + public override object? GetValue (LlvmIrModuleTarget target) + { + return target.NativePointerSize; + } +} + sealed class LlvmIrInstructions { + public class Alloca : LlvmIrInstruction + { + LlvmIrVariable result; + + public Alloca (LlvmIrVariable result) + : base ("alloca") + { + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + if (result == null) { + return; + } + + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + string irType = LlvmIrGenerator.MapToIRType (result.Type, out ulong size, out bool isPointer); + + context.Output.Write (irType); + WriteAlignment (context, size, isPointer); + } + } + public class Br : LlvmIrInstruction { const string OpName = "br"; @@ -452,16 +489,27 @@ protected override void WriteBody (GeneratorWriteContext context) public class Store : LlvmIrInstruction { + const string Opcode = "store"; + object? from; LlvmIrVariable to; public Store (LlvmIrVariable from, LlvmIrVariable to) - : base ("store") + : base (Opcode) { this.from = from; this.to = to; } + /// + /// Stores `null` in the indicated variable + /// + public Store (LlvmIrVariable to) + : base (Opcode) + { + this.to = to; + } + protected override void WriteBody (GeneratorWriteContext context) { string irType = LlvmIrGenerator.MapToIRType (to.Type, out ulong size, out bool isPointer); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 2e733561000..c066ba7adf1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -8,18 +8,94 @@ namespace Xamarin.Android.Tasks { partial class MarshalMethodsNativeAssemblyGenerator { + sealed class LlvmLifetimePointerSizeArgumentPlaceholder : LlvmIrInstructionPointerSizeArgumentPlaceholder + { + public override object? GetValue (LlvmIrModuleTarget target) + { + // the llvm.lifetime functions need a 64-bit integer, target.NativePointerSize is 32-bit + return (ulong)(uint)base.GetValue (target); + } + } + readonly MarshalMethodsTracingMode tracingMode; + LlvmIrFunction? asprintf; + LlvmIrFunction? free; + + LlvmIrFunction? llvm_lifetime_end; + LlvmIrFunction? llvm_lifetime_start; + LlvmIrFunction? mm_trace_func_enter; LlvmIrFunction? mm_trace_func_leave; - LlvmIrFunction? llvm_lifetime_start; - LlvmIrFunction? llvm_lifetime_end; + LlvmIrFunction? mm_trace_get_boolean_string; + LlvmIrFunction? mm_trace_get_c_string; + LlvmIrFunction? mm_trace_get_class_name; + LlvmIrFunction? mm_trace_get_object_class_name; + LlvmIrFunction? mm_trace_init; void InitTracing (LlvmIrModule module) { - var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); - var traceFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + var externalFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); + + InitLlvmFunctions (module); + InitLibcFunctions (module, externalFunctionsAttributeSet); + InitTracingFunctions (module, externalFunctionsAttributeSet); + } + + void WriteTracingAtFunctionTop (LlvmIrModule module, LlvmIrFunctionBody body, LlvmIrFunction function) + { + LlvmIrLocalVariable render_buf = function.CreateLocalVariable (typeof(string), "render_buf"); + body.Alloca (render_buf); + + LlvmIrFunctionAttributeSet lifetimeAttrSet = MakeLlvmLifetimeAttributeSet (module); + LlvmIrInstructions.Call call = body.Call (llvm_lifetime_start, arguments: new List { new LlvmLifetimePointerSizeArgumentPlaceholder (), render_buf }); + call.AttributeSet = lifetimeAttrSet; + + body.Store (render_buf, module.TbaaAnyPointer); + } + + void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { + var asprintf_params = new List { + new (typeof(string), "strp") { + NoUndef = true, + }, + new (typeof(string), "fmt") { + NoUndef = true, + }, + new (typeof(void)) { + IsVarArgs = true, + }, + }; + var asprintf_sig = new LlvmIrFunctionSignature ( + name: "asprintf", + returnType: typeof(int), + parameters: asprintf_params + ); + + asprintf = module.DeclareExternalFunction (new LlvmIrFunction (asprintf_sig, attrSet)); + + var free_params = new List { + new (typeof(IntPtr), "ptr") { + AllocPtr = true, + NoCapture = true, + NoUndef = true, + }, + }; + + var free_sig = new LlvmIrFunctionSignature ( + name: "free", + returnType: typeof(void), + parameters: free_params + ); + + free = module.DeclareExternalFunction (new LlvmIrFunction (free_sig, MakeFreeFunctionAttributeSet (module))); + } + + void InitLlvmFunctions (LlvmIrModule module) + { + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); var llvm_lifetime_params = new List { new (typeof(ulong), "size"), new (typeof(IntPtr), "pointer"), @@ -36,13 +112,29 @@ void InitTracing (LlvmIrModule module) AddressSignificance = LlvmIrAddressSignificance.Default } ); - llvm_lifetime_start = module.DeclareExternalFunction ( + + llvm_lifetime_end = module.DeclareExternalFunction ( new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { AddressSignificance = LlvmIrAddressSignificance.Default } ); + } + void InitTracingFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_init_params = new List { + new (typeof(IntPtr), "env"), + }; + + var mm_trace_init_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_init", + returnType: typeof(void), + parameters: mm_trace_init_params + ); + + mm_trace_init = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_init_sig, attrSet)); + var mm_trace_func_enter_or_leave_params = new List { new (typeof(IntPtr), "env"), // JNIEnv *env new (typeof(int), "tracing_mode"), @@ -59,8 +151,74 @@ void InitTracing (LlvmIrModule module) parameters: mm_trace_func_enter_or_leave_params ); - mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, traceFunctionsAttributeSet)); + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, attrSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, attrSet)); + + var mm_trace_get_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jclass), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_class_name", + returnType: typeof(string), + parameters: mm_trace_get_class_name_params + ); + + mm_trace_get_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_class_name_sig, attrSet)); + + var mm_trace_get_object_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jobject), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_object_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_object_class_name", + returnType: typeof(string), + parameters: mm_trace_get_object_class_name_params + ); + + mm_trace_get_object_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_object_class_name_sig, attrSet)); + + var mm_trace_get_c_string_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jstring), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_c_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_c_string", + returnType: typeof(string), + parameters: mm_trace_get_c_string_params + ); + + mm_trace_get_c_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_c_string_sig, attrSet)); + + var mm_trace_get_boolean_string_params = new List { + new (typeof(bool), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_boolean_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_boolean_string", + returnType: typeof(string), + parameters: mm_trace_get_boolean_string_params + ); + mm_trace_get_boolean_string_sig.ReturnAttributes.NonNull = true; + + mm_trace_get_boolean_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_boolean_string_sig, attrSet)); } LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) @@ -91,5 +249,36 @@ LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) return ret; } + + LlvmIrFunctionAttributeSet MakeFreeFunctionAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + new AllockindFunctionAttribute ("free"), + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, + new AllocFamilyFunctionAttribute ("malloc"), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeLlvmLifetimeAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + + ret.DoNotAddTargetSpecificAttributes = true; + return module.AddAttributeSet (ret); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 854e8ee6860..3af26172673 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -592,7 +592,9 @@ protected override void Construct (LlvmIrModule module) MapStructures (module); Init (); - // InitTracing (module); + if (tracingMode != MarshalMethodsTracingMode.None) { + InitTracing (module); + } AddAssemblyImageCache (module, out AssemblyCacheState acs); // class cache @@ -680,6 +682,10 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI void WriteBody (LlvmIrFunctionBody body) { + if (tracingMode != MarshalMethodsTracingMode.None) { + WriteTracingAtFunctionTop (module, body, func); + } + LlvmIrLocalVariable cb1 = func.CreateLocalVariable (typeof(IntPtr), "cb1"); body.Load (backingField, cb1, tbaa: module.TbaaAnyPointer); @@ -813,12 +819,8 @@ LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); - xamarin_app_init.Body.Add ( - new LlvmIrInstructions.Store (init_params[1], getFunctionPtrVariable) { - TBAA = module.TbaaAnyPointer, - } - ); - xamarin_app_init.Body.Add (new LlvmIrInstructions.Ret (typeof(void))); + xamarin_app_init.Body.Store (init_params[1], getFunctionPtrVariable, module.TbaaAnyPointer); + xamarin_app_init.Body.Ret (typeof(void)); module.Add (xamarin_app_init); From 95d423de289fe9835c25465b0fd57efed6061a96 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 23 Jun 2023 22:57:41 +0200 Subject: [PATCH 56/60] asprintf progress, new style --- .../LlvmIrGenerator/LlvmIrFunction.cs | 10 + .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 103 ++- .../LlvmIrGenerator/LlvmIrInstructions.cs | 53 +- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 2 +- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 840 +++++++++++++----- .../MarshalMethodsNativeAssemblyGenerator.cs | 4 +- .../Xamarin.Android.Build.Tasks.targets | 46 +- 7 files changed, 762 insertions(+), 296 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs index 43fec28bf65..aa156ecaef9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -414,6 +414,7 @@ public SavedFunctionState (LlvmIrFunction owner, ILlvmIrSavedFunctionSignatureSt public LlvmIrFunctionBody Body { get; } public string? Comment { get; set; } public bool ReturnsValue => Signature.ReturnType != typeof(void); + public bool UsesVarArgs { get; } public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttributeSet? attributeSet = null) { @@ -422,6 +423,15 @@ public LlvmIrFunction (LlvmIrFunctionSignature signature, LlvmIrFunctionAttribut functionState = new FunctionState (); foreach (LlvmIrFunctionParameter parameter in signature.Parameters) { + if (UsesVarArgs) { + throw new InvalidOperationException ($"Internal error: function '{signature.Name}' uses variable arguments and it has at least one argument following the varargs (...) one. This is not allowed."); + } + + if (parameter.IsVarArgs) { + UsesVarArgs = true; + continue; + } + if (!String.IsNullOrEmpty (parameter.Name)) { continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index e268e98afb7..2ac5d515d29 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -171,6 +171,43 @@ public LlvmIrFunctionBody (LlvmIrFunction func, LlvmIrFunction.FunctionState fun previousLabel = implicitStartBlock = new LlvmIrFunctionImplicitStartLabel (functionState.StartingBlockNumber); } + public void Add (LlvmIrFunctionLabelItem label) + { + label.WillAddToBody (this, functionState); + if (definedLabels.Contains (label.Name)) { + throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{ownerFunction.Signature.Name}' body"); + } + items.Add (label); + definedLabels.Add (label.Name); + + // Rotate preceding blocks + if (precedingBlock2 != null) { + precedingBlock2 = null; + } + + precedingBlock2 = precedingBlock1; + precedingBlock1 = previousLabel; + previousLabel = label; + + var comment = new StringBuilder (" preds = %"); + comment.Append (precedingBlock1.Name); + if (precedingBlock2 != null) { + comment.Append (", %"); + comment.Append (precedingBlock2.Name); + } + label.Comment = comment.ToString (); + } + + public void Add (LlvmIrFunctionBodyItem item) + { + items.Add (item); + } + + public void AddComment (string text) + { + Add (new LlvmIrFunctionBodyComment (text)); + } + public LlvmIrInstructions.Alloca Alloca (LlvmIrVariable result) { var ret = new LlvmIrInstructions.Alloca (result); @@ -199,38 +236,25 @@ public LlvmIrInstructions.Call Call (LlvmIrFunction function, LlvmIrVariable? re return ret; } - public LlvmIrInstructions.Icmp Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) - { - var ret = new LlvmIrInstructions.Icmp (cond, op1, op2, result); - Add (ret); - return ret; - } - - public LlvmIrInstructions.Load Load (LlvmIrVariable source, LlvmIrVariable result, LlvmIrMetadataItem? tbaa = null) + public LlvmIrInstructions.Ext Ext (LlvmIrVariable source, Type targetType, LlvmIrVariable result) { - var ret = new LlvmIrInstructions.Load (source, result) { - TBAA = tbaa, - }; + var ret = new LlvmIrInstructions.Ext (source, targetType, result); Add (ret); return ret; } - public LlvmIrInstructions.Store Store (LlvmIrVariable from, LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + public LlvmIrInstructions.Icmp Icmp (LlvmIrIcmpCond cond, LlvmIrVariable op1, object? op2, LlvmIrVariable result) { - var ret = new LlvmIrInstructions.Store (from, to) { - TBAA = tbaa, - }; - + var ret = new LlvmIrInstructions.Icmp (cond, op1, op2, result); Add (ret); return ret; } - public LlvmIrInstructions.Store Store (LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) + public LlvmIrInstructions.Load Load (LlvmIrVariable source, LlvmIrVariable result, LlvmIrMetadataItem? tbaa = null) { - var ret = new LlvmIrInstructions.Store (to) { + var ret = new LlvmIrInstructions.Load (source, result) { TBAA = tbaa, }; - Add (ret); return ret; } @@ -255,35 +279,26 @@ public LlvmIrInstructions.Ret Ret (Type retvalType, object? retval = null) return ret; } - public void Add (LlvmIrFunctionLabelItem label) + public LlvmIrInstructions.Store Store (LlvmIrVariable from, LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) { - label.WillAddToBody (this, functionState); - if (definedLabels.Contains (label.Name)) { - throw new InvalidOperationException ($"Internal error: label with name '{label.Name}' already added to function '{ownerFunction.Signature.Name}' body"); - } - items.Add (label); - definedLabels.Add (label.Name); - - // Rotate preceding blocks - if (precedingBlock2 != null) { - precedingBlock2 = null; - } - - precedingBlock2 = precedingBlock1; - precedingBlock1 = previousLabel; - previousLabel = label; + var ret = new LlvmIrInstructions.Store (from, to) { + TBAA = tbaa, + }; - var comment = new StringBuilder (" preds = %"); - comment.Append (precedingBlock1.Name); - if (precedingBlock2 != null) { - comment.Append (", %"); - comment.Append (precedingBlock2.Name); - } - label.Comment = comment.ToString (); + Add (ret); + return ret; } - public void Add (LlvmIrFunctionBodyItem item) + /// + /// Stores `null` in the indicated variable + /// + public LlvmIrInstructions.Store Store (LlvmIrVariable to, LlvmIrMetadataItem? tbaa = null) { - items.Add (item); + var ret = new LlvmIrInstructions.Store (to) { + TBAA = tbaa, + }; + + Add (ret); + return ret; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 33297922814..1a670106c1a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -230,7 +230,11 @@ public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments", nameof (arguments)); } - if (arguments.Count != argCount) { + if (function.UsesVarArgs) { + if (arguments.Count < argCount) { + throw new ArgumentException ($"Internal error: varargs function '{function.Signature.Name}' needs at least {argCount} fixed arguments, got {arguments.Count} instead"); + } + } else if (arguments.Count != argCount) { throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments, but {arguments.Count} were provided", nameof (arguments)); } @@ -334,6 +338,53 @@ void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter param } } + public class Ext : LlvmIrInstruction + { + const string FpextOpCode = "fpext"; + const string SextOpCode = "sext"; + const string ZextOpCode = "zext"; + + LlvmIrVariable result; + LlvmIrVariable source; + Type targetType; + + public Ext (LlvmIrVariable source, Type targetType, LlvmIrVariable result) + : base (GetOpCode (targetType)) + { + this.source = source; + this.targetType = targetType; + this.result = result; + } + + protected override void WriteValueAssignment (GeneratorWriteContext context) + { + context.Output.Write (result.Reference); + context.Output.Write (" = "); + } + + protected override void WriteBody (GeneratorWriteContext context) + { + context.Output.Write (LlvmIrGenerator.MapToIRType (source.Type)); + context.Output.Write (' '); + context.Output.Write (source.Reference); + context.Output.Write (" to "); + context.Output.Write ( LlvmIrGenerator.MapToIRType (targetType)); + } + + static string GetOpCode (Type targetType) + { + if (targetType == typeof(double)) { + return FpextOpCode; + } else if (targetType == typeof(int)) { + return SextOpCode; + } else if (targetType == typeof(uint)) { + return ZextOpCode; + } else { + throw new InvalidOperationException ($"Unsupported target type for upcasting: {targetType}"); + } + } + } + public class Icmp : LlvmIrInstruction { LlvmIrIcmpCond cond; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index f7094dbbd42..170f2c6a860 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -279,7 +279,7 @@ void RegisterString (LlvmIrGlobalVariable variable, string? stringGroupName = nu RegisterString ((string)variable.Value, stringGroupName, stringGroupComment, symbolSuffix); } - void RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) + public void RegisterString (string value, string? stringGroupName = null, string? stringGroupComment = null, string? symbolSuffix = null) { if (stringManager == null) { stringManager = new LlvmIrStringManager (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index c066ba7adf1..ff73fdaff90 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -1,284 +1,676 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Text; using Xamarin.Android.Tools; using Xamarin.Android.Tasks.LLVMIR; -namespace Xamarin.Android.Tasks +namespace Xamarin.Android.Tasks; + +using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; +using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; + +partial class MarshalMethodsNativeAssemblyGenerator { - partial class MarshalMethodsNativeAssemblyGenerator + sealed class LlvmLifetimePointerSizeArgumentPlaceholder : LlvmIrInstructionPointerSizeArgumentPlaceholder { - sealed class LlvmLifetimePointerSizeArgumentPlaceholder : LlvmIrInstructionPointerSizeArgumentPlaceholder + public override object? GetValue (LlvmIrModuleTarget target) { - public override object? GetValue (LlvmIrModuleTarget target) - { - // the llvm.lifetime functions need a 64-bit integer, target.NativePointerSize is 32-bit - return (ulong)(uint)base.GetValue (target); - } + // the llvm.lifetime functions need a 64-bit integer, target.NativePointerSize is 32-bit + return (ulong)(uint)base.GetValue (target); } + } - readonly MarshalMethodsTracingMode tracingMode; + enum TracingRenderArgumentFunction + { + None, + GetClassName, + GetObjectClassname, + GetCString, + GetBooleanString, + } - LlvmIrFunction? asprintf; - LlvmIrFunction? free; + sealed class AsprintfParameterOperation + { + public readonly Type? UpcastType; + public readonly TracingRenderArgumentFunction RenderFunction = TracingRenderArgumentFunction.None; + public readonly bool MustBeFreed; - LlvmIrFunction? llvm_lifetime_end; - LlvmIrFunction? llvm_lifetime_start; + public AsprintfParameterOperation (Type? upcastType, TracingRenderArgumentFunction renderFunction) + { + UpcastType = upcastType; + RenderFunction = renderFunction; + } - LlvmIrFunction? mm_trace_func_enter; - LlvmIrFunction? mm_trace_func_leave; - LlvmIrFunction? mm_trace_get_boolean_string; - LlvmIrFunction? mm_trace_get_c_string; - LlvmIrFunction? mm_trace_get_class_name; - LlvmIrFunction? mm_trace_get_object_class_name; - LlvmIrFunction? mm_trace_init; + public AsprintfParameterOperation (Type upcastType) + : this (upcastType, TracingRenderArgumentFunction.None) + {} - void InitTracing (LlvmIrModule module) + public AsprintfParameterOperation (TracingRenderArgumentFunction renderFunction, bool mustBeFreed) + : this (null, renderFunction) { - var externalFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); - - InitLlvmFunctions (module); - InitLibcFunctions (module, externalFunctionsAttributeSet); - InitTracingFunctions (module, externalFunctionsAttributeSet); + MustBeFreed = mustBeFreed; } - void WriteTracingAtFunctionTop (LlvmIrModule module, LlvmIrFunctionBody body, LlvmIrFunction function) + public AsprintfParameterOperation () + : this (null, TracingRenderArgumentFunction.None) + {} + } + + sealed class AsprintfParameterTransform : IEnumerable + { + public List Operations { get; } = new List (); + + public void Add (AsprintfParameterOperation parameterOp) { - LlvmIrLocalVariable render_buf = function.CreateLocalVariable (typeof(string), "render_buf"); - body.Alloca (render_buf); + Operations.Add (parameterOp); + } - LlvmIrFunctionAttributeSet lifetimeAttrSet = MakeLlvmLifetimeAttributeSet (module); - LlvmIrInstructions.Call call = body.Call (llvm_lifetime_start, arguments: new List { new LlvmLifetimePointerSizeArgumentPlaceholder (), render_buf }); - call.AttributeSet = lifetimeAttrSet; + public IEnumerator GetEnumerator () + { + return ((IEnumerable)Operations).GetEnumerator (); + } - body.Store (render_buf, module.TbaaAnyPointer); + IEnumerator IEnumerable.GetEnumerator () + { + return ((IEnumerable)Operations).GetEnumerator (); } + } + + sealed class AsprintfCallState + { + public readonly string Format; + public readonly List ParameterTransforms; + public readonly List VariablesToFree = new List (); + public readonly List VariadicArgsVariables = new List (); - void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + public AsprintfCallState (string format, List parameterTransforms) { - var asprintf_params = new List { - new (typeof(string), "strp") { - NoUndef = true, - }, - new (typeof(string), "fmt") { - NoUndef = true, - }, - new (typeof(void)) { - IsVarArgs = true, - }, - }; + Format = format; + ParameterTransforms = parameterTransforms; + } + } + + sealed class TracingState + { + public List? trace_enter_leave_args = null; + public LlvmIrLocalVariable? tracingParamsStringLifetimeTracker = null; + public List? asprintfVariadicArgs = null; + public LlvmIrLocalVariable? asprintfAllocatedStringVar = null; + } - var asprintf_sig = new LlvmIrFunctionSignature ( - name: "asprintf", - returnType: typeof(int), - parameters: asprintf_params - ); + readonly MarshalMethodsTracingMode tracingMode; - asprintf = module.DeclareExternalFunction (new LlvmIrFunction (asprintf_sig, attrSet)); + LlvmIrFunction? asprintf; + LlvmIrFunction? free; - var free_params = new List { - new (typeof(IntPtr), "ptr") { - AllocPtr = true, - NoCapture = true, - NoUndef = true, - }, - }; + LlvmIrFunction? llvm_lifetime_end; + LlvmIrFunction? llvm_lifetime_start; - var free_sig = new LlvmIrFunctionSignature ( - name: "free", - returnType: typeof(void), - parameters: free_params - ); + LlvmIrFunction? mm_trace_func_enter; + LlvmIrFunction? mm_trace_func_leave; + LlvmIrFunction? mm_trace_get_boolean_string; + LlvmIrFunction? mm_trace_get_c_string; + LlvmIrFunction? mm_trace_get_class_name; + LlvmIrFunction? mm_trace_get_object_class_name; + LlvmIrFunction? mm_trace_init; - free = module.DeclareExternalFunction (new LlvmIrFunction (free_sig, MakeFreeFunctionAttributeSet (module))); - } + void InitTracing (LlvmIrModule module) + { + var externalFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); - void InitLlvmFunctions (LlvmIrModule module) - { - var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); - var llvm_lifetime_params = new List { - new (typeof(ulong), "size"), - new (typeof(IntPtr), "pointer"), - }; + InitLlvmFunctions (module); + InitLibcFunctions (module, externalFunctionsAttributeSet); + InitTracingFunctions (module, externalFunctionsAttributeSet); + } - var lifetime_sig = new LlvmIrFunctionSignature ( - name: "llvm.lifetime.start", - returnType: typeof(void), - parameters: llvm_lifetime_params - ); - - llvm_lifetime_start = module.DeclareExternalFunction ( - new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); - - llvm_lifetime_end = module.DeclareExternalFunction ( - new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { - AddressSignificance = LlvmIrAddressSignificance.Default - } - ); + void WriteTracingAtFunctionTop (LlvmIrModule module, MarshalMethodInfo method, LlvmIrFunctionBody body, LlvmIrFunction function, MarshalMethodsWriteState writeState) + { + if (tracingMode == MarshalMethodsTracingMode.None) { + return; } - void InitTracingFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) - { - // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh - var mm_trace_init_params = new List { - new (typeof(IntPtr), "env"), - }; + module.RegisterString (method.NativeSymbolName); - var mm_trace_init_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_init", - returnType: typeof(void), - parameters: mm_trace_init_params - ); - - mm_trace_init = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_init_sig, attrSet)); - - var mm_trace_func_enter_or_leave_params = new List { - new (typeof(IntPtr), "env"), // JNIEnv *env - new (typeof(int), "tracing_mode"), - new (typeof(uint), "mono_image_index"), - new (typeof(uint), "class_index"), - new (typeof(uint), "method_token"), - new (typeof(string), "native_method_name"), - new (typeof(string), "method_extra_info"), - }; + body.AddComment (" Tracing code start"); - var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_func_enter", - returnType: typeof(void), - parameters: mm_trace_func_enter_or_leave_params - ); - - mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, attrSet)); - mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, attrSet)); - - var mm_trace_get_class_name_params = new List { - new (typeof(IntPtr), "env") { - NoUndef = true, - }, - new (typeof(_jclass), "v") { - NoUndef = true, - }, - }; + var tracingState = new TracingState { + asprintfAllocatedStringVar = function.CreateLocalVariable (typeof(string), "render_buf"), + }; + body.Alloca (tracingState.asprintfAllocatedStringVar); - var mm_trace_get_class_name_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_get_class_name", - returnType: typeof(string), - parameters: mm_trace_get_class_name_params - ); - - mm_trace_get_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_class_name_sig, attrSet)); - - var mm_trace_get_object_class_name_params = new List { - new (typeof(IntPtr), "env") { - NoUndef = true, - }, - new (typeof(_jobject), "v") { - NoUndef = true, - }, - }; + LlvmIrFunctionAttributeSet lifetimeAttrSet = MakeLlvmLifetimeAttributeSet (module); + LlvmIrInstructions.Call call = body.Call (llvm_lifetime_start, arguments: new List { new LlvmLifetimePointerSizeArgumentPlaceholder (), tracingState.asprintfAllocatedStringVar }); + call.AttributeSet = lifetimeAttrSet; - var mm_trace_get_object_class_name_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_get_object_class_name", - returnType: typeof(string), - parameters: mm_trace_get_object_class_name_params - ); - - mm_trace_get_object_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_object_class_name_sig, attrSet)); - - var mm_trace_get_c_string_params = new List { - new (typeof(IntPtr), "env") { - NoUndef = true, - }, - new (typeof(_jstring), "v") { - NoUndef = true, - }, - }; + body.Store (tracingState.asprintfAllocatedStringVar, module.TbaaAnyPointer); - var mm_trace_get_c_string_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_get_c_string", - returnType: typeof(string), - parameters: mm_trace_get_c_string_params - ); + tracingState.asprintfVariadicArgs = new List (function.Signature.Parameters); - mm_trace_get_c_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_c_string_sig, attrSet)); + AsprintfCallState asprintfState = GetPrintfStateForFunctionParams (module, method, function); + AddAsprintfCall (function, asprintfState, tracingState); - var mm_trace_get_boolean_string_params = new List { - new (typeof(bool), "v") { - NoUndef = true, - }, + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; + var assemblyCacheIndexPlaceholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + tracingState.trace_enter_leave_args = new List { + function.Signature.Parameters[0], // JNIEnv *env + (int)tracingMode, + assemblyCacheIndexPlaceholder, + method.ClassCacheIndex, + nativeCallback.MetadataToken.ToUInt32 (), + method.NativeSymbolName, + tracingState.asprintfAllocatedStringVar, + }; + body.Call (mm_trace_func_enter, arguments: tracingState.trace_enter_leave_args); + body.Call (free, arguments: new List { tracingState.asprintfAllocatedStringVar }); + + body.AddComment (" Tracing code end"); + } + + void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms, out bool isNativePointer) + { + string format; + AsprintfParameterTransform? transform = null; + isNativePointer = false; + + if (type == typeof(string)) { + format = "\"%s\""; + isNativePointer = true; + } else if (type == typeof(_jclass)) { + format = "%s @%p"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetClassName, mustBeFreed: true), + new AsprintfParameterOperation (), + }; + isNativePointer = true; + } else if (type == typeof(_jobject)) { + format = "%s @%p"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetObjectClassname, mustBeFreed: true), + new AsprintfParameterOperation (), + }; + isNativePointer = true; + } else if (type == typeof(_jstring)) { + format = "\"%s\""; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetCString, mustBeFreed: true), + new AsprintfParameterOperation (), + }; + isNativePointer = true; + } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { + format = "%p"; + isNativePointer = true; + } else if (type == typeof(bool)) { + format = "%s"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (TracingRenderArgumentFunction.GetBooleanString, mustBeFreed: false), + }; + isNativePointer = true; + } else if (type == typeof(byte) || type == typeof(ushort)) { + format = "%u"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(uint)), + }; + } else if (type == typeof(sbyte) || type == typeof(short)) { + format = "%d"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(int)), + }; + } else if (type == typeof(char)) { + format = "'\\%x'"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(uint)), }; + } else if (type == typeof(int)) { + format = "%d"; + } else if (type == typeof(uint)) { + format = "%u"; + } else if (type == typeof(long)) { + format = "%ld"; + } else if (type == typeof(ulong)) { + format = "%lu"; + } else if (type == typeof(float)) { + format = "%g"; + transform = new AsprintfParameterTransform { + new AsprintfParameterOperation (typeof(double)), + }; + } else if (type == typeof(double)) { + format = "%g"; + } else { + throw new InvalidOperationException ($"Unsupported type '{type}'"); + }; + + parameterTransforms.Add (transform); + sb.Append (format); + } + + (StringBuilder sb, List parameterOps) InitPrintfState (string startChars = "(") + { + return (new StringBuilder (startChars), new List ()); + } - var mm_trace_get_boolean_string_sig = new LlvmIrFunctionSignature ( - name: "_mm_trace_get_boolean_string", - returnType: typeof(string), - parameters: mm_trace_get_boolean_string_params - ); - mm_trace_get_boolean_string_sig.ReturnAttributes.NonNull = true; + AsprintfCallState FinishPrintfState (LlvmIrModule module, StringBuilder sb, List parameterTransforms, string endChars = ")") + { + sb.Append (endChars); + string format = sb.ToString (); + module.RegisterString (format); + return new AsprintfCallState (format, parameterTransforms); + } - mm_trace_get_boolean_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_boolean_string_sig, attrSet)); + AsprintfCallState GetPrintfStateForFunctionParams (LlvmIrModule module, MarshalMethodInfo method, LlvmIrFunction func) + { + (StringBuilder ret, List parameterOps) = InitPrintfState (); + bool first = true; + + List nativeMethodParameters = method.Parameters; + Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters ?? method.Method.ImplementedMethod?.Parameters; + int expectedRegisteredParamCount = nativeMethodParameters.Count - 2; + if (managedMethodParameters != null && managedMethodParameters.Count != expectedRegisteredParamCount) { + throw new InvalidOperationException ($"Internal error: unexpected number of registered method parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); } - LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) - { - return new LlvmIrFunctionAttributeSet { - new ArgmemonlyFunctionAttribute (), - new MustprogressFunctionAttribute (), - new NocallbackFunctionAttribute (), - new NofreeFunctionAttribute (), - new NosyncFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - }; + if (nativeMethodParameters.Count != func.Signature.Parameters.Count) { + throw new InvalidOperationException ($"Internal error: number of native method parameter ({nativeMethodParameters.Count}) doesn't match the number of marshal method parameters ({func.Signature.Parameters.Count})"); } - LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) - { - var ret = new LlvmIrFunctionAttributeSet { - new NounwindFunctionAttribute (), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), - }; + var variadicArgs = new List (); + bool haveManagedParams = managedMethodParameters != null; + for (int i = 0; i < nativeMethodParameters.Count; i++) { + LlvmIrFunctionParameter parameter = nativeMethodParameters[i]; - ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); - ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); - ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); - ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); + if (!first) { + ret.Append (", "); + } else { + first = false; + } - return ret; + // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two + // parameters, so we start looking at the managed parameters only once the first two are out of the way + CecilParameterDefinition? managedParameter = haveManagedParams && i >= 2 ? managedMethodParameters[i - 2] : null; + AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, managedParameter), parameterOps, out bool isNativePointer); + variadicArgs.Add (func.Signature.Parameters[i]); } - LlvmIrFunctionAttributeSet MakeFreeFunctionAttributeSet (LlvmIrModule module) + AsprintfCallState state = FinishPrintfState (module, ret, parameterOps); + state.VariadicArgsVariables.AddRange (variadicArgs); + return state; + + Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) { - var ret = new LlvmIrFunctionAttributeSet { - new MustprogressFunctionAttribute (), - new NounwindFunctionAttribute (), - new WillreturnFunctionAttribute (), - new AllockindFunctionAttribute ("free"), - // TODO: LLVM 16+ feature, enable when we switch to this version - // new MemoryFunctionAttribute { - // Default = MemoryAttributeAccessKind.Write, - // Argmem = MemoryAttributeAccessKind.None, - // InaccessibleMem = MemoryAttributeAccessKind.None, - // }, - new AllocFamilyFunctionAttribute ("malloc"), - new NoTrappingMathFunctionAttribute (true), - new StackProtectorBufferSizeFunctionAttribute (8), - }; + if (managedParameter == null) { + return nativeParameter.Type; + } - return module.AddAttributeSet (ret); + if (nativeParameter.Type == typeof(byte) && String.Compare ("System.Boolean", managedParameter.Name, StringComparison.Ordinal) == 0) { + // `bool`, as a non-blittable type, is mapped to `byte` by the marshal method rewriter + return typeof(bool); + } + + return nativeParameter.Type; } + } + + AsprintfCallState GetPrintfStateForReturnValue (LlvmIrModule module, LlvmIrLocalVariable localVariable) + { + (StringBuilder ret, List parameterTransforms) = InitPrintfState ("=>["); + + AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms, out bool isNativePointer); + + return FinishPrintfState (module, ret, parameterTransforms, "]"); + } + + void DoWriteAsprintfCall (LlvmIrFunction func, List variadicArgs, AsprintfCallState asprintfState, TracingState tracingState) + { + var asprintfArgs = new List { + tracingState.asprintfAllocatedStringVar, + asprintfState.Format, + }; + asprintfArgs.AddRange (variadicArgs); + + LlvmIrLocalVariable asprintf_ret = func.CreateLocalVariable (typeof(int), "asprintf_ret"); + LlvmIrInstructions.Call call = func.Body.Call (asprintf, asprintf_ret, asprintfArgs); + call.Comment = $"Format: {asprintfState.Format}"; + + // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) + LlvmIrLocalVariable asprintf_failed = func.CreateLocalVariable (typeof(bool), "asprintf_failed"); + func.Body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_failed, (int)0, asprintf_failed); + + var asprintfIfThenLabel = new LlvmIrFunctionLabelItem ("if.then"); + var asprintfIfElseLabel = new LlvmIrFunctionLabelItem ("if.else"); + var ifElseDoneLabel = new LlvmIrFunctionLabelItem ("if.done"); + + func.Body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfElseLabel); + + // Condition is true if asprintf **failed** + func.Body.Add (asprintfIfThenLabel); + LlvmIrLocalVariable bufferPointerNull = func.CreateLocalVariable (typeof(IntPtr), "bufferPointerNull"); + func.Body.Store (bufferPointerNull); + func.Body.Br (ifElseDoneLabel); + + func.Body.Add (asprintfIfElseLabel); + LlvmIrLocalVariable bufferPointerAllocated = func.CreateLocalVariable (typeof(IntPtr), "bufferPointerAllocated"); + func.Body.Load (tracingState.asprintfAllocatedStringVar, bufferPointerAllocated); + func.Body.Br (ifElseDoneLabel); + + func.Body.Add (ifElseDoneLabel); + func.Body.Phi (tracingState.asprintfAllocatedStringVar, bufferPointerNull, asprintfIfThenLabel, bufferPointerAllocated, asprintfIfElseLabel); + } + + LlvmIrVariable? WriteTransformFunctionCall (LlvmIrFunction func, AsprintfCallState asprintfState, AsprintfParameterOperation paramOp, LlvmIrVariable paramVar) + { + if (paramOp.RenderFunction == TracingRenderArgumentFunction.None) { + return null; + } + + var transformerArgs = new List (); + LlvmIrFunction transformerFunc; + + switch (paramOp.RenderFunction) { + case TracingRenderArgumentFunction.GetClassName: + transformerFunc = mm_trace_get_class_name; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetObjectClassname: + transformerFunc = mm_trace_get_object_class_name; + AddJNIEnvArgument (); + break; - LlvmIrFunctionAttributeSet MakeLlvmLifetimeAttributeSet (LlvmIrModule module) + case TracingRenderArgumentFunction.GetCString: + transformerFunc = mm_trace_get_c_string; + AddJNIEnvArgument (); + break; + + case TracingRenderArgumentFunction.GetBooleanString: + transformerFunc = mm_trace_get_boolean_string; + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported transformer function {paramOp.RenderFunction}"); + }; + transformerArgs.Add (paramVar); + + if (!transformerFunc.ReturnsValue) { + return null; + } + LlvmIrLocalVariable? result = func.CreateLocalVariable (transformerFunc.Signature.ReturnType); + func.Body.Call (transformerFunc, result, transformerArgs); + + if (paramOp.MustBeFreed) { + asprintfState.VariablesToFree.Add (result); + } + + return result; + + void AddJNIEnvArgument () { - var ret = new LlvmIrFunctionAttributeSet { - new NounwindFunctionAttribute (), - }; + transformerArgs.Add (func.Signature.Parameters[0]); + } + } - ret.DoNotAddTargetSpecificAttributes = true; - return module.AddAttributeSet (ret); + void AddAsprintfArgument (LlvmIrFunction func, AsprintfCallState asprintfState, List asprintfArgs, List? paramOps, LlvmIrVariable paramVar) + { + if (paramOps == null || paramOps.Count == 0) { + asprintfArgs.Add (paramVar); + return; } + + foreach (AsprintfParameterOperation paramOp in paramOps) { + LlvmIrVariable? paramRef = WriteTransformFunctionCall (func, asprintfState, paramOp, paramVar); + + if (paramOp.UpcastType != null) { + LlvmIrLocalVariable upcastVar = func.CreateLocalVariable ((paramRef ?? paramVar).Type); + func.Body.Ext (paramRef ?? paramVar, paramOp.UpcastType, upcastVar); + paramRef = upcastVar; + } + + if (paramRef == null) { + paramRef = paramVar; + } + + asprintfArgs.Add (paramRef); + } + } + + void AddAsprintfCall (LlvmIrFunction function, AsprintfCallState asprintfState, TracingState tracingState) + { + if (asprintfState.VariadicArgsVariables.Count != asprintfState.ParameterTransforms.Count) { + throw new ArgumentException (nameof (asprintfState), $"Number of transforms ({asprintfState.ParameterTransforms.Count}) is not equal to the number of variadic arguments ({asprintfState.VariadicArgsVariables.Count})"); + } + + var asprintfArgs = new List (); + for (int i = 0; i < asprintfState.VariadicArgsVariables.Count; i++) { + AddAsprintfArgument (function, asprintfState, asprintfArgs, asprintfState.ParameterTransforms[i]?.Operations, asprintfState.VariadicArgsVariables[i]); + } + + DoWriteAsprintfCall (function, asprintfArgs, asprintfState, tracingState); + } + + void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { + var asprintf_params = new List { + new (typeof(string), "strp") { + NoUndef = true, + }, + new (typeof(string), "fmt") { + NoUndef = true, + }, + new (typeof(void)) { + IsVarArgs = true, + }, + }; + + var asprintf_sig = new LlvmIrFunctionSignature ( + name: "asprintf", + returnType: typeof(int), + parameters: asprintf_params + ); + + asprintf = module.DeclareExternalFunction (new LlvmIrFunction (asprintf_sig, attrSet)); + + var free_params = new List { + new (typeof(IntPtr), "ptr") { + AllocPtr = true, + NoCapture = true, + NoUndef = true, + }, + }; + + var free_sig = new LlvmIrFunctionSignature ( + name: "free", + returnType: typeof(void), + parameters: free_params + ); + + free = module.DeclareExternalFunction (new LlvmIrFunction (free_sig, MakeFreeFunctionAttributeSet (module))); + } + + void InitLlvmFunctions (LlvmIrModule module) + { + var llvmFunctionsAttributeSet = module.AddAttributeSet (MakeLlvmIntrinsicFunctionsAttributeSet (module)); + var llvm_lifetime_params = new List { + new (typeof(ulong), "size"), + new (typeof(IntPtr), "pointer"), + }; + + var lifetime_sig = new LlvmIrFunctionSignature ( + name: "llvm.lifetime.start", + returnType: typeof(void), + parameters: llvm_lifetime_params + ); + + llvm_lifetime_start = module.DeclareExternalFunction ( + new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + + llvm_lifetime_end = module.DeclareExternalFunction ( + new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { + AddressSignificance = LlvmIrAddressSignificance.Default + } + ); + } + + void InitTracingFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) + { + // Function names and declarations must match those in src/monodroid/jni/marshal-methods-tracing.hh + var mm_trace_init_params = new List { + new (typeof(IntPtr), "env"), + }; + + var mm_trace_init_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_init", + returnType: typeof(void), + parameters: mm_trace_init_params + ); + + mm_trace_init = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_init_sig, attrSet)); + + var mm_trace_func_enter_or_leave_params = new List { + new (typeof(IntPtr), "env"), // JNIEnv *env + new (typeof(int), "tracing_mode"), + new (typeof(uint), "mono_image_index"), + new (typeof(uint), "class_index"), + new (typeof(uint), "method_token"), + new (typeof(string), "native_method_name"), + new (typeof(string), "method_extra_info"), + }; + + var mm_trace_func_enter_leave_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_func_enter", + returnType: typeof(void), + parameters: mm_trace_func_enter_or_leave_params + ); + + mm_trace_func_enter = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_func_enter_leave_sig, attrSet)); + mm_trace_func_leave = module.DeclareExternalFunction (new LlvmIrFunction ("_mm_trace_func_leave", mm_trace_func_enter_leave_sig, attrSet)); + + var mm_trace_get_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jclass), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_class_name", + returnType: typeof(string), + parameters: mm_trace_get_class_name_params + ); + + mm_trace_get_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_class_name_sig, attrSet)); + + var mm_trace_get_object_class_name_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jobject), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_object_class_name_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_object_class_name", + returnType: typeof(string), + parameters: mm_trace_get_object_class_name_params + ); + + mm_trace_get_object_class_name = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_object_class_name_sig, attrSet)); + + var mm_trace_get_c_string_params = new List { + new (typeof(IntPtr), "env") { + NoUndef = true, + }, + new (typeof(_jstring), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_c_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_c_string", + returnType: typeof(string), + parameters: mm_trace_get_c_string_params + ); + + mm_trace_get_c_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_c_string_sig, attrSet)); + + var mm_trace_get_boolean_string_params = new List { + new (typeof(bool), "v") { + NoUndef = true, + }, + }; + + var mm_trace_get_boolean_string_sig = new LlvmIrFunctionSignature ( + name: "_mm_trace_get_boolean_string", + returnType: typeof(string), + parameters: mm_trace_get_boolean_string_params + ); + mm_trace_get_boolean_string_sig.ReturnAttributes.NonNull = true; + + mm_trace_get_boolean_string = module.DeclareExternalFunction (new LlvmIrFunction (mm_trace_get_boolean_string_sig, attrSet)); + } + + LlvmIrFunctionAttributeSet MakeLlvmIntrinsicFunctionsAttributeSet (LlvmIrModule module) + { + return new LlvmIrFunctionAttributeSet { + new ArgmemonlyFunctionAttribute (), + new MustprogressFunctionAttribute (), + new NocallbackFunctionAttribute (), + new NofreeFunctionAttribute (), + new NosyncFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + }; + } + + LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + ret.Add (AndroidTargetArch.Arm64, new FramePointerFunctionAttribute ("non-leaf")); + ret.Add (AndroidTargetArch.Arm, new FramePointerFunctionAttribute ("all")); + ret.Add (AndroidTargetArch.X86, new FramePointerFunctionAttribute ("none")); + ret.Add (AndroidTargetArch.X86_64, new FramePointerFunctionAttribute ("none")); + + return ret; + } + + LlvmIrFunctionAttributeSet MakeFreeFunctionAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new MustprogressFunctionAttribute (), + new NounwindFunctionAttribute (), + new WillreturnFunctionAttribute (), + new AllockindFunctionAttribute ("free"), + // TODO: LLVM 16+ feature, enable when we switch to this version + // new MemoryFunctionAttribute { + // Default = MemoryAttributeAccessKind.Write, + // Argmem = MemoryAttributeAccessKind.None, + // InaccessibleMem = MemoryAttributeAccessKind.None, + // }, + new AllocFamilyFunctionAttribute ("malloc"), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeLlvmLifetimeAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + + ret.DoNotAddTargetSpecificAttributes = true; + return module.AddAttributeSet (ret); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 3af26172673..f4958e5ba1b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -682,9 +682,7 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI void WriteBody (LlvmIrFunctionBody body) { - if (tracingMode != MarshalMethodsTracingMode.None) { - WriteTracingAtFunctionTop (module, body, func); - } + WriteTracingAtFunctionTop (module, method, body, func, writeState); LlvmIrLocalVariable cb1 = func.CreateLocalVariable (typeof(IntPtr), "cb1"); body.Load (backingField, cb1, tbaa: module.TbaaAnyPointer); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index ad49a5e4116..56d82fe2a35 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -271,29 +271,29 @@ BeforeTargets="CopyFilesToOutputDirectory" Inputs="$(MSBuildAllProjects);@(IntermediateAssembly);@(InputAssemblies)" Outputs="$(IntermediateOutputPath)ILRepacker.stamp" > - - <_InputAssembliesThatExist Include="@(InputAssemblies)" Condition="Exists('%(Identity)')" /> - <_NetstandardPath Include="@(ReferencePath->'%(RootDir)%(Directory)')" Condition="'%(FileName)%(Extension)' == 'netstandard.dll'" /> - - - <_NetstandardDir>@(_NetstandardPath) - <_ILRepackArgs>/out:"$(MSBuildThisFileDirectory)$(IntermediateOutputPath)$(AssemblyName).dll" /internalize - <_ILRepackArgs>$(_ILRepackArgs) /keyfile:"$(XamarinAndroidSourcePath)product.snk" - <_ILRepackArgs>$(_ILRepackArgs) "$(MSBuildThisFileDirectory)$(IntermediateOutputPath)$(AssemblyName).dll" - <_ILRepackArgs>$(_ILRepackArgs) @(_InputAssembliesThatExist->'"%(Identity)"', ' ') - <_ILRepackArgs>$(_ILRepackArgs) /lib:"$(_NetstandardDir.TrimEnd('\'))" - - - - - - + + + + + + + + + + + + + + + + + + + + + + + From 1ef3255f17d2b352b7161c8d10499d466fa6e047 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 26 Jun 2023 21:53:35 +0200 Subject: [PATCH 57/60] Add some more tracing capabilities --- .../LlvmIrGenerator/LlvmIrFunctionBody.cs | 8 ++ .../LlvmIrGenerator/LlvmIrInstructions.cs | 7 + .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 121 ++++++++++++++++++ ...lMethodsNativeAssemblyGenerator.Tracing.cs | 6 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 18 +++ 5 files changed, 157 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs index 2ac5d515d29..5c1b03990ae 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs @@ -301,4 +301,12 @@ public LlvmIrInstructions.Store Store (LlvmIrVariable to, LlvmIrMetadataItem? tb Add (ret); return ret; } + + public LlvmIrInstructions.Unreachable Unreachable () + { + var ret = new LlvmIrInstructions.Unreachable (); + + Add (ret); + return ret; + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 1a670106c1a..d6885cf2734 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -575,4 +575,11 @@ protected override void WriteBody (GeneratorWriteContext context) WriteAlignment (context, size, isPointer); } } + + public class Unreachable : LlvmIrInstruction + { + public Unreachable () + : base ("unreachable") + {} + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index 170f2c6a860..3dd0c72290e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -40,6 +40,9 @@ partial class LlvmIrModule List? globalVariables; + LlvmIrFunction? puts; + LlvmIrFunction? abort; + public LlvmIrModule () { metadataManager = new LlvmIrMetadataManager (); @@ -125,6 +128,119 @@ public void Add (LlvmIrFunction func) functions.Add (func, func); } + public LlvmIrInstructions.Call CreatePuts (string text, LlvmIrVariable result) + { + EnsurePuts (); + RegisterString (text); + return new LlvmIrInstructions.Call (puts, result, new List { text }); + } + + /// + /// Generate code to call the `puts(3)` C library function to print a simple string to standard output. + /// + public LlvmIrInstructions.Call AddPuts (LlvmIrFunction function, string text, LlvmIrVariable result) + { + EnsurePuts (); + RegisterString (text); + return function.Body.Call (puts, result, new List { text }); + } + + void EnsurePuts () + { + if (puts != null) { + return; + } + + var puts_params = new List { + new (typeof(string), "s"), + }; + + var puts_sig = new LlvmIrFunctionSignature ( + name: "puts", + returnType: typeof(int), + parameters: puts_params + ); + puts_sig.ReturnAttributes.NoUndef = true; + + puts = DeclareExternalFunction (puts_sig, MakePutsAttributeSet ()); + } + + LlvmIrFunctionAttributeSet MakePutsAttributeSet () + { + var ret = new LlvmIrFunctionAttributeSet { + new NofreeFunctionAttribute (), + new NounwindFunctionAttribute (), + }; + + ret.DoNotAddTargetSpecificAttributes = true; + return AddAttributeSet (ret); + } + + public LlvmIrInstructions.Call CreateAbort () + { + EnsureAbort (); + return new LlvmIrInstructions.Call (abort); + } + + public LlvmIrInstructions.Call AddAbort (LlvmIrFunction function) + { + EnsureAbort (); + LlvmIrInstructions.Call ret = function.Body.Call (abort); + function.Body.Unreachable (); + + return ret; + } + + void EnsureAbort () + { + if (abort != null) { + return; + } + + var abort_sig = new LlvmIrFunctionSignature (name: "abort", returnType: typeof(void)); + abort = DeclareExternalFunction (abort_sig, MakeAbortAttributeSet ()); + } + + LlvmIrFunctionAttributeSet MakeAbortAttributeSet () + { + var ret = new LlvmIrFunctionAttributeSet { + new NoreturnFunctionAttribute (), + new NounwindFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new StackProtectorBufferSizeFunctionAttribute (8), + }; + + return AddAttributeSet (ret); + } + + public void AddIfThenElse (LlvmIrFunction function, LlvmIrVariable result, LlvmIrIcmpCond condition, LlvmIrVariable conditionVariable, object? conditionComparand, ICollection codeIfThen, ICollection? codeIfElse = null) + { + function.Body.Icmp (condition, conditionVariable, conditionComparand, result); + + var labelIfThen = new LlvmIrFunctionLabelItem (); + LlvmIrFunctionLabelItem? labelIfElse = codeIfElse != null ? new LlvmIrFunctionLabelItem () : null; + var labelIfDone = new LlvmIrFunctionLabelItem (); + + function.Body.Br (result, labelIfThen, labelIfElse == null ? labelIfDone : labelIfElse); + function.Body.Add (labelIfThen); + + AddInstructions (codeIfThen); + + if (codeIfElse != null) { + function.Body.Add (labelIfElse); + AddInstructions (codeIfElse); + } + + function.Body.Add (labelIfDone); + + void AddInstructions (ICollection instructions) + { + foreach (LlvmIrInstruction ins in instructions) { + function.Body.Add (ins); + } + } + } + /// /// A shortcut way to add a global variable without first having to create an instance of first. This overload /// requires the parameter to not be null. @@ -509,6 +625,11 @@ public LlvmIrFunction DeclareExternalFunction (LlvmIrFunction func) return func; } + public LlvmIrFunction DeclareExternalFunction (LlvmIrFunctionSignature sig, LlvmIrFunctionAttributeSet? attrSet = null) + { + return DeclareExternalFunction (new LlvmIrFunction (sig, attrSet)); + } + /// /// Since LLVM IR is strongly typed, it requires each structure to be properly declared before it is /// used throughout the code. This method uses reflection to scan the managed type diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index ff73fdaff90..94eedfe7e64 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -336,9 +336,9 @@ void DoWriteAsprintfCall (LlvmIrFunction func, List variadicArgs, Aspri LlvmIrLocalVariable asprintf_failed = func.CreateLocalVariable (typeof(bool), "asprintf_failed"); func.Body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_failed, (int)0, asprintf_failed); - var asprintfIfThenLabel = new LlvmIrFunctionLabelItem ("if.then"); - var asprintfIfElseLabel = new LlvmIrFunctionLabelItem ("if.else"); - var ifElseDoneLabel = new LlvmIrFunctionLabelItem ("if.done"); + var asprintfIfThenLabel = new LlvmIrFunctionLabelItem (); + var asprintfIfElseLabel = new LlvmIrFunctionLabelItem (); + var ifElseDoneLabel = new LlvmIrFunctionLabelItem (); func.Body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfElseLabel); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index f4958e5ba1b..5c1272d7bf5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -817,6 +817,24 @@ LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) LlvmIrFunctionAttributeSet attrSet = MakeXamarinAppInitAttributeSet (module); var xamarin_app_init = new LlvmIrFunction (init_signature, attrSet); + + // If `fn` is nullptr, print a message and abort... + // + // We must allocate result variables for both the null comparison and puts call here and with names, because + // labels and local unnamed variables must be numbered sequentially otherwise and the `AddIfThenElse` call will + // allocate up to 3 labels which would have been **defined** after these labels, but **used** before them - and + // thus the numbering sequence would be out of order and the .ll file wouldn't build. + var fnNullResult = xamarin_app_init.CreateLocalVariable (typeof(bool), "fnIsNull"); + LlvmIrVariable putsResult = xamarin_app_init.CreateLocalVariable (typeof(int), "putsResult"); + var ifThenInstructions = new List { + module.CreatePuts ("get_function_pointer MUST be specified\n", putsResult), + module.CreateAbort (), + new LlvmIrInstructions.Unreachable (), + }; + + module.AddIfThenElse (xamarin_app_init, fnNullResult, LlvmIrIcmpCond.Equal, init_params[1], null, ifThenInstructions); + + // ...otherwise store the pointer and return xamarin_app_init.Body.Store (init_params[1], getFunctionPtrVariable, module.TbaaAnyPointer); xamarin_app_init.Body.Ret (typeof(void)); From 5316425fc2b8a4238f11b21b7d56471351a855dc Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 27 Jun 2023 23:06:16 +0200 Subject: [PATCH 58/60] More tracing improvements --- .../LlvmIrGenerator/LlvmIrInstructions.cs | 32 ++++++++++++++----- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 22 ++++--------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index d6885cf2734..8f3be296bf9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -42,10 +42,6 @@ protected override void DoWrite (GeneratorWriteContext context, LlvmIrGenerator context.Output.Write (" #"); context.Output.Write (AttributeSet.Number.ToString (CultureInfo.InvariantCulture)); } - - if (!String.IsNullOrEmpty (Comment)) { - generator.WriteComment (context, Comment); - } } /// @@ -284,24 +280,44 @@ protected override void WriteBody (GeneratorWriteContext context) } context.Output.Write ('('); + bool isVararg = false; for (int i = 0; i < function.Signature.Parameters.Count; i++) { if (i > 0) { context.Output.Write (", "); } - WriteArgument (context, function.Signature.Parameters[i], i); + LlvmIrFunctionParameter parameter = function.Signature.Parameters[i]; + if (parameter.IsVarArgs) { + isVararg = true; + } + + WriteArgument (context, parameter, i, isVararg); } context.Output.Write (')'); } - void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter parameter, int index) + void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter parameter, int index, bool isVararg) { - context.Output.Write (LlvmIrGenerator.MapToIRType (parameter.Type)); + object? value = arguments[index]; + string irType; + + if (!isVararg) { + irType = LlvmIrGenerator.MapToIRType (parameter.Type); + } else { + if (value == null) { + // We have no way of verifying the vararg parameter type if value is null, so we'll assume it's a pointer. + // If our assumption is wrong, llc will fail and signal the error + irType = "ptr"; + } else { + irType = LlvmIrGenerator.MapToIRType (value.GetType ()); + } + } + + context.Output.Write (irType); LlvmIrGenerator.WriteParameterAttributes (context, parameter); context.Output.Write (' '); - object? value = arguments[index]; if (value is LlvmIrInstructionArgumentValuePlaceholder placeholder) { value = placeholder.GetValue (context.Target); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 94eedfe7e64..2b02f271ebd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -330,31 +330,23 @@ void DoWriteAsprintfCall (LlvmIrFunction func, List variadicArgs, Aspri LlvmIrLocalVariable asprintf_ret = func.CreateLocalVariable (typeof(int), "asprintf_ret"); LlvmIrInstructions.Call call = func.Body.Call (asprintf, asprintf_ret, asprintfArgs); - call.Comment = $"Format: {asprintfState.Format}"; + call.Comment = $" Format: {asprintfState.Format}"; // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) LlvmIrLocalVariable asprintf_failed = func.CreateLocalVariable (typeof(bool), "asprintf_failed"); - func.Body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_failed, (int)0, asprintf_failed); + func.Body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_ret, (int)0, asprintf_failed); var asprintfIfThenLabel = new LlvmIrFunctionLabelItem (); - var asprintfIfElseLabel = new LlvmIrFunctionLabelItem (); - var ifElseDoneLabel = new LlvmIrFunctionLabelItem (); + var asprintfIfDoneLabel = new LlvmIrFunctionLabelItem (); - func.Body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfElseLabel); + func.Body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfDoneLabel); // Condition is true if asprintf **failed** func.Body.Add (asprintfIfThenLabel); - LlvmIrLocalVariable bufferPointerNull = func.CreateLocalVariable (typeof(IntPtr), "bufferPointerNull"); - func.Body.Store (bufferPointerNull); - func.Body.Br (ifElseDoneLabel); + func.Body.Store (tracingState.asprintfAllocatedStringVar); + func.Body.Br (asprintfIfDoneLabel); - func.Body.Add (asprintfIfElseLabel); - LlvmIrLocalVariable bufferPointerAllocated = func.CreateLocalVariable (typeof(IntPtr), "bufferPointerAllocated"); - func.Body.Load (tracingState.asprintfAllocatedStringVar, bufferPointerAllocated); - func.Body.Br (ifElseDoneLabel); - - func.Body.Add (ifElseDoneLabel); - func.Body.Phi (tracingState.asprintfAllocatedStringVar, bufferPointerNull, asprintfIfThenLabel, bufferPointerAllocated, asprintfIfElseLabel); + func.Body.Add (asprintfIfDoneLabel); } LlvmIrVariable? WriteTransformFunctionCall (LlvmIrFunction func, AsprintfCallState asprintfState, AsprintfParameterOperation paramOp, LlvmIrVariable paramVar) From b8642539eb49757ef59b63497aa1102f841483a1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 28 Jun 2023 19:45:35 +0200 Subject: [PATCH 59/60] asprintf simpler, general `call` improvements, assorted fixes --- .../Tasks/LinkApplicationSharedLibraries.cs | 24 +- .../LlvmIrGenerator/LlvmIrInstructions.cs | 48 +- ...lMethodsNativeAssemblyGenerator.Tracing.cs | 425 ++++++++---------- .../MarshalMethodsNativeAssemblyGenerator.cs | 2 + src/monodroid/CMakeLists.txt | 3 +- src/monodroid/libstub/stub.cc | 14 + 6 files changed, 247 insertions(+), 269 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 64b27983784..eec7af7a03f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -206,20 +206,20 @@ IEnumerable GetLinkerConfigs () InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir) { List extraLibraries = null; + string RID = MonoAndroidHelper.AbiToRid (abi); + AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); + string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); + string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID); + + extraLibraries = new List { + $"-L \"{runtimeLibsDir}\"", + $"-L \"{libStubsPath}\"", + "-lc", + }; if (mmTracingEnabled) { - string RID = MonoAndroidHelper.AbiToRid (abi); - AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi); - string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID); - string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID); - - extraLibraries = new List { - Path.Combine (runtimeLibsDir, "libmarshal-methods-tracing.a"), - $"-L \"{runtimeLibsDir}\"", - $"-L \"{libStubsPath}\"", - "-lxamarin-native-tracing", - "-lc", - }; + extraLibraries.Add (Path.Combine (runtimeLibsDir, "libmarshal-methods-tracing.a")); + extraLibraries.Add ("-lxamarin-native-tracing"); } return new InputFiles { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 8f3be296bf9..5ce7e98bd9d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -271,6 +271,21 @@ protected override void WriteBody (GeneratorWriteContext context) } context.Output.Write (LlvmIrGenerator.MapToIRType (function.Signature.ReturnType)); + + if (function.UsesVarArgs) { + context.Output.Write (" ("); + for (int j = 0; j < function.Signature.Parameters.Count; j++) { + if (j > 0) { + context.Output.Write (", "); + } + + LlvmIrFunctionParameter parameter = function.Signature.Parameters[j]; + string irType = parameter.IsVarArgs ? "..." : LlvmIrGenerator.MapToIRType (parameter.Type); + context.Output.Write (irType); + } + context.Output.Write (')'); + } + if (FuncPointer == null) { context.Output.Write (" @"); context.Output.Write (function.Signature.Name); @@ -281,7 +296,8 @@ protected override void WriteBody (GeneratorWriteContext context) context.Output.Write ('('); bool isVararg = false; - for (int i = 0; i < function.Signature.Parameters.Count; i++) { + int i; + for (i = 0; i < function.Signature.Parameters.Count; i++) { if (i > 0) { context.Output.Write (", "); } @@ -294,16 +310,28 @@ protected override void WriteBody (GeneratorWriteContext context) WriteArgument (context, parameter, i, isVararg); } + if (arguments != null) { + for (; i < arguments.Count; i++) { + context.Output.Write (", "); + WriteArgument (context, null, i, isVararg: true); + } + } + context.Output.Write (')'); } - void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter parameter, int index, bool isVararg) + void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter? parameter, int index, bool isVararg) { object? value = arguments[index]; - string irType; + if (value is LlvmIrInstructionArgumentValuePlaceholder placeholder) { + value = placeholder.GetValue (context.Target); + } + string irType; if (!isVararg) { irType = LlvmIrGenerator.MapToIRType (parameter.Type); + } else if (value is LlvmIrVariable v1) { + irType = LlvmIrGenerator.MapToIRType (v1.Type); } else { if (value == null) { // We have no way of verifying the vararg parameter type if value is null, so we'll assume it's a pointer. @@ -315,12 +343,10 @@ void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter param } context.Output.Write (irType); - LlvmIrGenerator.WriteParameterAttributes (context, parameter); - context.Output.Write (' '); - - if (value is LlvmIrInstructionArgumentValuePlaceholder placeholder) { - value = placeholder.GetValue (context.Target); + if (parameter != null) { + LlvmIrGenerator.WriteParameterAttributes (context, parameter); } + context.Output.Write (' '); if (value == null) { if (!parameter.Type.IsNativePointer ()) { @@ -331,12 +357,12 @@ void WriteArgument (GeneratorWriteContext context, LlvmIrFunctionParameter param return; } - if (value is LlvmIrVariable variable) { - context.Output.Write (variable.Reference); + if (value is LlvmIrVariable v2) { + context.Output.Write (v2.Reference); return; } - if (!parameter.Type.IsAssignableFrom (value.GetType ())) { + if (parameter != null && !parameter.Type.IsAssignableFrom (value.GetType ())) { throw new InvalidOperationException ($"Internal error: value type '{value.GetType ()}' for argument {index} to function '{function.Signature.Name}' is invalid. Expected '{parameter.Type}' or compatible"); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs index 2b02f271ebd..7f4afc832c9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.Tracing.cs @@ -31,72 +31,9 @@ enum TracingRenderArgumentFunction GetBooleanString, } - sealed class AsprintfParameterOperation - { - public readonly Type? UpcastType; - public readonly TracingRenderArgumentFunction RenderFunction = TracingRenderArgumentFunction.None; - public readonly bool MustBeFreed; - - public AsprintfParameterOperation (Type? upcastType, TracingRenderArgumentFunction renderFunction) - { - UpcastType = upcastType; - RenderFunction = renderFunction; - } - - public AsprintfParameterOperation (Type upcastType) - : this (upcastType, TracingRenderArgumentFunction.None) - {} - - public AsprintfParameterOperation (TracingRenderArgumentFunction renderFunction, bool mustBeFreed) - : this (null, renderFunction) - { - MustBeFreed = mustBeFreed; - } - - public AsprintfParameterOperation () - : this (null, TracingRenderArgumentFunction.None) - {} - } - - sealed class AsprintfParameterTransform : IEnumerable - { - public List Operations { get; } = new List (); - - public void Add (AsprintfParameterOperation parameterOp) - { - Operations.Add (parameterOp); - } - - public IEnumerator GetEnumerator () - { - return ((IEnumerable)Operations).GetEnumerator (); - } - - IEnumerator IEnumerable.GetEnumerator () - { - return ((IEnumerable)Operations).GetEnumerator (); - } - } - - sealed class AsprintfCallState - { - public readonly string Format; - public readonly List ParameterTransforms; - public readonly List VariablesToFree = new List (); - public readonly List VariadicArgsVariables = new List (); - - public AsprintfCallState (string format, List parameterTransforms) - { - Format = format; - ParameterTransforms = parameterTransforms; - } - } - sealed class TracingState { public List? trace_enter_leave_args = null; - public LlvmIrLocalVariable? tracingParamsStringLifetimeTracker = null; - public List? asprintfVariadicArgs = null; public LlvmIrLocalVariable? asprintfAllocatedStringVar = null; } @@ -116,6 +53,9 @@ sealed class TracingState LlvmIrFunction? mm_trace_get_object_class_name; LlvmIrFunction? mm_trace_init; + LlvmIrFunctionAttributeSet? freeCallAttributes; + LlvmIrFunctionAttributeSet? asprintfCallAttributes; + void InitTracing (LlvmIrModule module) { var externalFunctionsAttributeSet = module.AddAttributeSet (MakeTraceFunctionsAttributeSet (module)); @@ -146,10 +86,7 @@ void WriteTracingAtFunctionTop (LlvmIrModule module, MarshalMethodInfo method, L body.Store (tracingState.asprintfAllocatedStringVar, module.TbaaAnyPointer); - tracingState.asprintfVariadicArgs = new List (function.Signature.Parameters); - - AsprintfCallState asprintfState = GetPrintfStateForFunctionParams (module, method, function); - AddAsprintfCall (function, asprintfState, tracingState); + AddAsprintfCall (module, function, method, tracingState); CecilMethodDefinition nativeCallback = method.Method.NativeCallback; var assemblyCacheIndexPlaceholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); @@ -163,138 +100,71 @@ void WriteTracingAtFunctionTop (LlvmIrModule module, MarshalMethodInfo method, L tracingState.asprintfAllocatedStringVar, }; body.Call (mm_trace_func_enter, arguments: tracingState.trace_enter_leave_args); - body.Call (free, arguments: new List { tracingState.asprintfAllocatedStringVar }); + AddFreeCall (function, tracingState.asprintfAllocatedStringVar); body.AddComment (" Tracing code end"); } - void AddPrintfFormatAndTransforms (StringBuilder sb, Type type, List parameterTransforms, out bool isNativePointer) + void AddAsprintfCall (LlvmIrModule module, LlvmIrFunction function, MarshalMethodInfo method, TracingState tracingState) { - string format; - AsprintfParameterTransform? transform = null; - isNativePointer = false; - - if (type == typeof(string)) { - format = "\"%s\""; - isNativePointer = true; - } else if (type == typeof(_jclass)) { - format = "%s @%p"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetClassName, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(_jobject)) { - format = "%s @%p"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetObjectClassname, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(_jstring)) { - format = "\"%s\""; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetCString, mustBeFreed: true), - new AsprintfParameterOperation (), - }; - isNativePointer = true; - } else if (type == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (type) || type == typeof(_JNIEnv)) { - format = "%p"; - isNativePointer = true; - } else if (type == typeof(bool)) { - format = "%s"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (TracingRenderArgumentFunction.GetBooleanString, mustBeFreed: false), - }; - isNativePointer = true; - } else if (type == typeof(byte) || type == typeof(ushort)) { - format = "%u"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(uint)), - }; - } else if (type == typeof(sbyte) || type == typeof(short)) { - format = "%d"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(int)), - }; - } else if (type == typeof(char)) { - format = "'\\%x'"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(uint)), - }; - } else if (type == typeof(int)) { - format = "%d"; - } else if (type == typeof(uint)) { - format = "%u"; - } else if (type == typeof(long)) { - format = "%ld"; - } else if (type == typeof(ulong)) { - format = "%lu"; - } else if (type == typeof(float)) { - format = "%g"; - transform = new AsprintfParameterTransform { - new AsprintfParameterOperation (typeof(double)), - }; - } else if (type == typeof(double)) { - format = "%g"; - } else { - throw new InvalidOperationException ($"Unsupported type '{type}'"); - }; - - parameterTransforms.Add (transform); - sb.Append (format); - } - - (StringBuilder sb, List parameterOps) InitPrintfState (string startChars = "(") - { - return (new StringBuilder (startChars), new List ()); - } - - AsprintfCallState FinishPrintfState (LlvmIrModule module, StringBuilder sb, List parameterTransforms, string endChars = ")") - { - sb.Append (endChars); - string format = sb.ToString (); - module.RegisterString (format); - return new AsprintfCallState (format, parameterTransforms); - } + Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters ?? method.Method.ImplementedMethod?.Parameters; - AsprintfCallState GetPrintfStateForFunctionParams (LlvmIrModule module, MarshalMethodInfo method, LlvmIrFunction func) - { - (StringBuilder ret, List parameterOps) = InitPrintfState (); - bool first = true; + int expectedRegisteredParamCount; + int managedParametersOffset; + if (managedMethodParameters == null) { + // There was no registered nor implemented methods, so we're looking at a native callback - it should have the same number of parameters as our wrapper + managedMethodParameters = method.Method.NativeCallback.Parameters; + expectedRegisteredParamCount = method.Parameters.Count; + managedParametersOffset = 0; + } else { + // Managed methods get two less parameters (no `env` and `klass`) + expectedRegisteredParamCount = method.Parameters.Count - 2; + managedParametersOffset = 2; + } - List nativeMethodParameters = method.Parameters; - Mono.Collections.Generic.Collection? managedMethodParameters = method.Method.RegisteredMethod?.Parameters ?? method.Method.ImplementedMethod?.Parameters; - int expectedRegisteredParamCount = nativeMethodParameters.Count - 2; - if (managedMethodParameters != null && managedMethodParameters.Count != expectedRegisteredParamCount) { - throw new InvalidOperationException ($"Internal error: unexpected number of registered method parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); + if (managedMethodParameters.Count != expectedRegisteredParamCount) { + throw new InvalidOperationException ($"Internal error: unexpected number of registered method '{method.Method.NativeCallback.FullName}' parameters. Should be {expectedRegisteredParamCount}, but is {managedMethodParameters.Count}"); } - if (nativeMethodParameters.Count != func.Signature.Parameters.Count) { - throw new InvalidOperationException ($"Internal error: number of native method parameter ({nativeMethodParameters.Count}) doesn't match the number of marshal method parameters ({func.Signature.Parameters.Count})"); + if (method.Parameters.Count != function.Signature.Parameters.Count) { + throw new InvalidOperationException ($"Internal error: number of native method parameter ({method.Parameters.Count}) doesn't match the number of marshal method parameters ({function.Signature.Parameters.Count})"); } - var variadicArgs = new List (); - bool haveManagedParams = managedMethodParameters != null; - for (int i = 0; i < nativeMethodParameters.Count; i++) { - LlvmIrFunctionParameter parameter = nativeMethodParameters[i]; + var varargs = new List (); + var variablesToFree = new List (); + var formatSb = new StringBuilder ('('); + bool first = true; + + for (int i = 0; i < method.Parameters.Count; i++) { + LlvmIrFunctionParameter parameter = method.Parameters[i]; if (!first) { - ret.Append (", "); + formatSb.Append (", "); } else { first = false; } // Native method will have two more parameters than its managed counterpart - one for JNIEnv* and another for jclass. They always are the first two // parameters, so we start looking at the managed parameters only once the first two are out of the way - CecilParameterDefinition? managedParameter = haveManagedParams && i >= 2 ? managedMethodParameters[i - 2] : null; - AddPrintfFormatAndTransforms (ret, VerifyAndGetActualParameterType (parameter, managedParameter), parameterOps, out bool isNativePointer); - variadicArgs.Add (func.Signature.Parameters[i]); + CecilParameterDefinition? managedParameter = i >= managedParametersOffset ? managedMethodParameters[i - managedParametersOffset] : null; + Type actualType = VerifyAndGetActualParameterType (parameter, managedParameter); + PrepareAsprintfArgument (formatSb, function, parameter, actualType, varargs, variablesToFree); } - AsprintfCallState state = FinishPrintfState (module, ret, parameterOps); - state.VariadicArgsVariables.AddRange (variadicArgs); - return state; + formatSb.Append (')'); + + string format = formatSb.ToString (); + var asprintfArgs = new List { + tracingState.asprintfAllocatedStringVar, + format, + }; + asprintfArgs.AddRange (varargs); + + DoAddAsprintfCall (asprintfArgs, module, function, format, tracingState); + + foreach (LlvmIrVariable vtf in variablesToFree) { + AddFreeCall (function, vtf); + } Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, CecilParameterDefinition? managedParameter) { @@ -311,54 +181,106 @@ Type VerifyAndGetActualParameterType (LlvmIrFunctionParameter nativeParameter, C } } - AsprintfCallState GetPrintfStateForReturnValue (LlvmIrModule module, LlvmIrLocalVariable localVariable) + void PrepareAsprintfArgument (StringBuilder format, LlvmIrFunction function, LlvmIrVariable parameter, Type actualType, List varargs, List variablesToFree) { - (StringBuilder ret, List parameterTransforms) = InitPrintfState ("=>["); + LlvmIrVariable? result = null; + if (actualType == typeof(_jclass)) { + format.Append ("%s @%p"); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetClassName, parameter); + varargs.Add (result); + variablesToFree.Add (result); + varargs.Add (parameter); + return; + } - AddPrintfFormatAndTransforms (ret, localVariable.Type, parameterTransforms, out bool isNativePointer); + if (actualType == typeof(_jobject)) { + format.Append ("%s @%p"); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetObjectClassname, parameter); + varargs.Add (result); + variablesToFree.Add (result); + varargs.Add (parameter); + return; + } - return FinishPrintfState (module, ret, parameterTransforms, "]"); - } + if (actualType == typeof(_jstring)) { + format.Append ("\"%s\""); + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetCString, parameter); + varargs.Add (result); + variablesToFree.Add (result); + return; + } - void DoWriteAsprintfCall (LlvmIrFunction func, List variadicArgs, AsprintfCallState asprintfState, TracingState tracingState) - { - var asprintfArgs = new List { - tracingState.asprintfAllocatedStringVar, - asprintfState.Format, - }; - asprintfArgs.AddRange (variadicArgs); + if (actualType == typeof(bool)) { + format.Append ("%s"); - LlvmIrLocalVariable asprintf_ret = func.CreateLocalVariable (typeof(int), "asprintf_ret"); - LlvmIrInstructions.Call call = func.Body.Call (asprintf, asprintf_ret, asprintfArgs); - call.Comment = $" Format: {asprintfState.Format}"; + // No need to free(3) it, returns pointer to a constant + result = AddTransformFunctionCall (function, TracingRenderArgumentFunction.GetBooleanString, parameter); + varargs.Add (result); + return; + } - // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) - LlvmIrLocalVariable asprintf_failed = func.CreateLocalVariable (typeof(bool), "asprintf_failed"); - func.Body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_ret, (int)0, asprintf_failed); + if (actualType == typeof(byte) || actualType == typeof(ushort)) { + format.Append ("%u"); + AddUpcast (); + return; + } - var asprintfIfThenLabel = new LlvmIrFunctionLabelItem (); - var asprintfIfDoneLabel = new LlvmIrFunctionLabelItem (); + if (actualType == typeof(sbyte) || actualType == typeof(short)) { + format.Append ("%d"); + AddUpcast (); + return; + } + + if (actualType == typeof(char)) { + format.Append ("'\\%x'"); + AddUpcast (); + return; + } - func.Body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfDoneLabel); + if (actualType == typeof(float)) { + format.Append ("%g"); + AddUpcast (); + return; + } - // Condition is true if asprintf **failed** - func.Body.Add (asprintfIfThenLabel); - func.Body.Store (tracingState.asprintfAllocatedStringVar); - func.Body.Br (asprintfIfDoneLabel); + if (actualType == typeof(string)) { + format.Append ("\"%s\""); + } else if (actualType == typeof(IntPtr) || typeof(_jobject).IsAssignableFrom (actualType) || actualType == typeof(_JNIEnv)) { + format.Append ("%p"); + } else if (actualType == typeof(int)) { + format.Append ("%d"); + } else if (actualType == typeof(uint)) { + format.Append ("%u"); + } else if (actualType == typeof(long)) { + format.Append ("%ld"); + } else if (actualType == typeof(ulong)) { + format.Append ("%lu"); + } else if (actualType == typeof(double)) { + format.Append ("%g"); + } else { + throw new InvalidOperationException ($"Unsupported type '{actualType}'"); + } - func.Body.Add (asprintfIfDoneLabel); + varargs.Add (parameter); + + void AddUpcast () + { + LlvmIrVariable ret = function.CreateLocalVariable (typeof(T)); + function.Body.Ext (parameter, typeof(T), ret); + varargs.Add (ret); + } } - LlvmIrVariable? WriteTransformFunctionCall (LlvmIrFunction func, AsprintfCallState asprintfState, AsprintfParameterOperation paramOp, LlvmIrVariable paramVar) + LlvmIrVariable? AddTransformFunctionCall (LlvmIrFunction function, TracingRenderArgumentFunction renderFunction, LlvmIrVariable paramVar) { - if (paramOp.RenderFunction == TracingRenderArgumentFunction.None) { + if (renderFunction == TracingRenderArgumentFunction.None) { return null; } var transformerArgs = new List (); LlvmIrFunction transformerFunc; - switch (paramOp.RenderFunction) { + switch (renderFunction) { case TracingRenderArgumentFunction.GetClassName: transformerFunc = mm_trace_get_class_name; AddJNIEnvArgument (); @@ -379,64 +301,55 @@ void DoWriteAsprintfCall (LlvmIrFunction func, List variadicArgs, Aspri break; default: - throw new InvalidOperationException ($"Internal error: unsupported transformer function {paramOp.RenderFunction}"); + throw new InvalidOperationException ($"Internal error: unsupported transformer function {renderFunction}"); }; transformerArgs.Add (paramVar); if (!transformerFunc.ReturnsValue) { return null; } - LlvmIrLocalVariable? result = func.CreateLocalVariable (transformerFunc.Signature.ReturnType); - func.Body.Call (transformerFunc, result, transformerArgs); - - if (paramOp.MustBeFreed) { - asprintfState.VariablesToFree.Add (result); - } + LlvmIrLocalVariable? result = function.CreateLocalVariable (transformerFunc.Signature.ReturnType); + function.Body.Call (transformerFunc, result, transformerArgs); return result; void AddJNIEnvArgument () { - transformerArgs.Add (func.Signature.Parameters[0]); + transformerArgs.Add (function.Signature.Parameters[0]); } } - void AddAsprintfArgument (LlvmIrFunction func, AsprintfCallState asprintfState, List asprintfArgs, List? paramOps, LlvmIrVariable paramVar) + void DoAddAsprintfCall (List args, LlvmIrModule module, LlvmIrFunction function, string format, TracingState tracingState) { - if (paramOps == null || paramOps.Count == 0) { - asprintfArgs.Add (paramVar); - return; - } + module.RegisterString (format); - foreach (AsprintfParameterOperation paramOp in paramOps) { - LlvmIrVariable? paramRef = WriteTransformFunctionCall (func, asprintfState, paramOp, paramVar); + LlvmIrFunctionBody body = function.Body; + LlvmIrLocalVariable asprintf_ret = function.CreateLocalVariable (typeof(int), "asprintf_ret"); + LlvmIrInstructions.Call call = body.Call (asprintf, asprintf_ret, args); + call.AttributeSet = asprintfCallAttributes; + call.Comment = $" Format: {format}"; - if (paramOp.UpcastType != null) { - LlvmIrLocalVariable upcastVar = func.CreateLocalVariable ((paramRef ?? paramVar).Type); - func.Body.Ext (paramRef ?? paramVar, paramOp.UpcastType, upcastVar); - paramRef = upcastVar; - } + // Check whether asprintf returned a negative value (it returns -1 at failure, but we widen the check just in case) + LlvmIrLocalVariable asprintf_failed = function.CreateLocalVariable (typeof(bool), "asprintf_failed"); + body.Icmp (LlvmIrIcmpCond.SignedLessThan, asprintf_ret, (int)0, asprintf_failed); - if (paramRef == null) { - paramRef = paramVar; - } + var asprintfIfThenLabel = new LlvmIrFunctionLabelItem (); + var asprintfIfDoneLabel = new LlvmIrFunctionLabelItem (); - asprintfArgs.Add (paramRef); - } - } + body.Br (asprintf_failed, asprintfIfThenLabel, asprintfIfDoneLabel); - void AddAsprintfCall (LlvmIrFunction function, AsprintfCallState asprintfState, TracingState tracingState) - { - if (asprintfState.VariadicArgsVariables.Count != asprintfState.ParameterTransforms.Count) { - throw new ArgumentException (nameof (asprintfState), $"Number of transforms ({asprintfState.ParameterTransforms.Count}) is not equal to the number of variadic arguments ({asprintfState.VariadicArgsVariables.Count})"); - } + // Condition is true if asprintf **failed** + body.Add (asprintfIfThenLabel); + body.Store (tracingState.asprintfAllocatedStringVar); + body.Br (asprintfIfDoneLabel); - var asprintfArgs = new List (); - for (int i = 0; i < asprintfState.VariadicArgsVariables.Count; i++) { - AddAsprintfArgument (function, asprintfState, asprintfArgs, asprintfState.ParameterTransforms[i]?.Operations, asprintfState.VariadicArgsVariables[i]); - } + body.Add (asprintfIfDoneLabel); + } - DoWriteAsprintfCall (function, asprintfArgs, asprintfState, tracingState); + void AddFreeCall (LlvmIrFunction function, object? toFree) + { + LlvmIrInstructions.Call call = function.Body.Call (free, arguments: new List { toFree }); + call.AttributeSet = freeCallAttributes; } void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet) @@ -476,6 +389,8 @@ void InitLibcFunctions (LlvmIrModule module, LlvmIrFunctionAttributeSet? attrSet ); free = module.DeclareExternalFunction (new LlvmIrFunction (free_sig, MakeFreeFunctionAttributeSet (module))); + freeCallAttributes = MakeFreeCallAttributeSet (module); + asprintfCallAttributes = MakeAsprintfCallAttributeSet (module); } void InitLlvmFunctions (LlvmIrModule module) @@ -495,13 +410,13 @@ void InitLlvmFunctions (LlvmIrModule module) llvm_lifetime_start = module.DeclareExternalFunction ( new LlvmIrFunction (lifetime_sig, llvmFunctionsAttributeSet) { AddressSignificance = LlvmIrAddressSignificance.Default - } + } ); llvm_lifetime_end = module.DeclareExternalFunction ( new LlvmIrFunction ("llvm.lifetime.end", lifetime_sig, llvmFunctionsAttributeSet) { AddressSignificance = LlvmIrAddressSignificance.Default - } + } ); } @@ -635,6 +550,26 @@ LlvmIrFunctionAttributeSet MakeTraceFunctionsAttributeSet (LlvmIrModule module) return ret; } + LlvmIrFunctionAttributeSet MakeFreeCallAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + ret.DoNotAddTargetSpecificAttributes = true; + + return module.AddAttributeSet (ret); + } + + LlvmIrFunctionAttributeSet MakeAsprintfCallAttributeSet (LlvmIrModule module) + { + var ret = new LlvmIrFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + ret.DoNotAddTargetSpecificAttributes = true; + + return module.AddAttributeSet (ret); + } + LlvmIrFunctionAttributeSet MakeFreeFunctionAttributeSet (LlvmIrModule module) { var ret = new LlvmIrFunctionAttributeSet { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 5c1272d7bf5..d32795dcc4e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -672,6 +672,8 @@ void AddMarshalMethod (LlvmIrModule module, MarshalMethodInfo method, ulong asmI funcComment.AppendLine (nativeCallback.Module.Assembly.Name.FullName); funcComment.Append (" Registered: "); funcComment.AppendLine (method.Method.RegisteredMethod?.FullName ?? "none"); + funcComment.Append (" Implemented: "); + funcComment.AppendLine (method.Method.ImplementedMethod?.FullName ?? "none"); var func = new LlvmIrFunction (method.NativeSymbolName, method.ReturnType, method.Parameters, writeState.AttributeSet) { Comment = funcComment.ToString (), diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index aedccc1dfb5..3fb0851355c 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -850,9 +850,10 @@ if(ANDROID AND ENABLE_NET AND (NOT ANALYZERS_ENABLED)) SHARED ${XAMARIN_STUB_LIB_SOURCES} ) + string(TOUPPER ${_libname} _libname_uc) target_compile_definitions( ${_libname} - PRIVATE STUB_LIB_NAME=libc + PRIVATE STUB_LIB_NAME=lib${_libname} IN_LIB${_libname_uc} ) target_compile_options( diff --git a/src/monodroid/libstub/stub.cc b/src/monodroid/libstub/stub.cc index be0ec6b7ec7..1b1c91385d6 100644 --- a/src/monodroid/libstub/stub.cc +++ b/src/monodroid/libstub/stub.cc @@ -6,3 +6,17 @@ void STUB_LIB_NAME () { // no-op } + +#if defined(IN_LIBC) +extern "C" { + [[gnu::weak]] int puts ([[maybe_unused]] const char *s) + { + return -1; + } + + [[gnu::weak]] void abort () + { + // no-op + } +} +#endif From 76cd39a0edb6b82731415311bcf6d988bfc0cccc Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 8 Mar 2024 13:23:04 +0100 Subject: [PATCH 60/60] Fixups after merge --- .../Utilities/LlvmIrGenerator/LlvmIrComposer.cs | 2 ++ .../Utilities/LlvmIrGenerator/LlvmIrGenerator.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 4135e873a58..6209dc2d382 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -39,6 +39,7 @@ public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName); generator.Generate (output, module); output.Flush (); + CleanupAfterGeneration (arch); } @@ -51,6 +52,7 @@ protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) if (gv == null) { throw new InvalidOperationException ("Internal error: global variable expected"); } + return gv; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 4ee8d98eeb7..af1184bd77e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -218,6 +218,7 @@ void WriteGlobalVariables (GeneratorWriteContext context) foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) { context.NumberFormat = gv.NumberFormat; + if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) { if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) { context.Output.WriteLine (); @@ -336,7 +337,6 @@ ulong GetAggregateValueElementCount (GeneratorWriteContext context, Type type, o if (globalVariable.ArrayDataProvider != null) { return globalVariable.ArrayDataProvider.GetTotalDataSize (context.Target); } - return globalVariable.ArrayItemCount; } return 0;