From 3070fbf012afcfa36c5f45bab502a17f0888bd6d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 15 Feb 2024 21:21:54 +0100 Subject: [PATCH 1/2] Bring in changes from PR #8478 Context: https://github.com/xamarin/xamarin-android/pull/8478 This PR serves to make #8478 smaller and easier to review --- .../Utilities/ApplicationConfig.cs | 8 + ...pplicationConfigNativeAssemblyGenerator.cs | 10 +- .../Utilities/AssemblyCompression.cs | 2 +- .../LlvmIrGenerator/LlvmIrComposer.cs | 15 +- .../LlvmIrGenerator/LlvmIrGenerator.cs | 403 ++++++++++++++---- .../Utilities/LlvmIrGenerator/LlvmIrModule.cs | 8 +- .../LlvmIrGenerator/LlvmIrVariable.cs | 94 +++- .../LlvmIrGenerator/MemberInfoUtilities.cs | 10 + .../NativeAssemblerAttribute.cs | 2 + .../MarshalMethodsNativeAssemblyGenerator.cs | 17 +- .../Utilities/MonoAndroidHelper.Basic.cs | 200 +++++++++ .../Utilities/MonoAndroidHelper.cs | 89 +--- ...peMappingReleaseNativeAssemblyGenerator.cs | 7 +- 13 files changed, 679 insertions(+), 186 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 25550eb3473..02b6c6ba776 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -42,11 +42,19 @@ sealed class ApplicationConfig public uint bundled_assembly_name_width; public uint number_of_assembly_store_files; public uint number_of_dso_cache_entries; + + [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint android_runtime_jnienv_class_token; + + [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint jnienv_initialize_method_token; + + [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint jnienv_registerjninatives_method_token; public uint jni_remapping_replacement_type_count; public uint jni_remapping_replacement_method_index_entry_count; + + [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint mono_components_mask; public string android_package_name = String.Empty; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index a442da7e221..cee11386b4a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -29,7 +29,7 @@ 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 $" from name: {dso_entry.HashedName}"; } if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { @@ -49,7 +49,7 @@ sealed class DSOCacheEntry [NativeAssembler (Ignore = true)] public string HashedName; - [NativeAssembler (UsesDataProvider = true)] + [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public ulong hash; public bool ignore; @@ -93,11 +93,11 @@ sealed class AssemblyStoreSingleAssemblyRuntimeData // src/monodroid/jni/xamarin-app.hh AssemblyStoreRuntimeData structure sealed class AssemblyStoreRuntimeData { - [NativePointer] + [NativePointer (IsNull = true)] public byte data_start; public uint assembly_count; - [NativePointer] + [NativePointer (IsNull = true)] public AssemblyStoreAssemblyDescriptor assemblies; } @@ -299,7 +299,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); } - entry.hash = GetXxHash (entry.HashedName, is64Bit); + entry.hash = MonoAndroidHelper.GetXxHash (entry.HashedName, is64Bit); } cache.Sort ((StructureInstance a, StructureInstance b) => a.Instance.hash.CompareTo (b.Instance.hash)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs index 1972dba0d85..39a899bd31a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs @@ -78,7 +78,7 @@ public static CompressionResult Compress (AssemblyData data, string outputDirect } destBytes = bytePool.Rent (LZ4Codec.MaximumOutputSize (sourceBytes.Length)); - int encodedLength = LZ4Codec.Encode (sourceBytes, 0, checked((int)fi.Length), destBytes, 0, destBytes.Length, LZ4Level.L09_HC); + int encodedLength = LZ4Codec.Encode (sourceBytes, 0, checked((int)fi.Length), destBytes, 0, destBytes.Length, LZ4Level.L12_MAX); if (encodedLength < 0) return CompressionResult.EncodingFailed; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index b250bf24085..6209dc2d382 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.IO.Hashing; -using System.Text; using Microsoft.Build.Utilities; @@ -41,18 +39,13 @@ public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter 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); + CleanupAfterGeneration (arch); } + protected virtual void CleanupAfterGeneration (AndroidTargetArch arch) + {} + protected LlvmIrGlobalVariable EnsureGlobalVariable (LlvmIrVariable variable) { var gv = variable as LlvmIrGlobalVariable; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index bb81b03db0e..af1184bd77e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -29,6 +29,7 @@ sealed class GeneratorWriteContext public readonly LlvmIrMetadataManager MetadataManager; public string CurrentIndent { get; private set; } = String.Empty; public bool InVariableGroup { get; set; } + public LlvmIrVariableNumberFormat NumberFormat { get; set; } = LlvmIrVariableNumberFormat.Default; public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager) { @@ -80,31 +81,56 @@ sealed class BasicType public readonly string Name; public readonly ulong Size; public readonly bool IsNumeric; + public readonly bool IsUnsigned; + public readonly bool PreferHex; + public readonly string HexFormat; - public BasicType (string name, ulong size, bool isNumeric = true) + public BasicType (string name, ulong size, bool isNumeric = true, bool isUnsigned = false, bool? preferHex = null) { Name = name; Size = size; IsNumeric = isNumeric; + IsUnsigned = isUnsigned; + + // If hex preference isn't specified, we determine whether the type wants to be represented in + // the hexadecimal notation based on signedness. Unsigned types will be represented in hexadecimal, + // but signed types will remain decimal, as it's easier for humans to see the actual value of the + // variable, given this note from LLVM IR manual: + // + // Note that hexadecimal integers are sign extended from the number of active bits, i.e. the bit width minus the number of leading zeros. So ‘s0x0001’ of type ‘i16’ will be -1, not 1. + // + // See: https://llvm.org/docs/LangRef.html#simple-constants + // + if (preferHex.HasValue) { + PreferHex = preferHex.Value; + } else { + PreferHex = isUnsigned; + } + if (!PreferHex) { + HexFormat = String.Empty; + return; + } + + HexFormat = $"x{size * 2}"; } } 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 (bool), new ("i1", 1, isNumeric: false, isUnsigned: true, preferHex: false) }, + { typeof (byte), new ("i8", 1, isUnsigned: true) }, + { typeof (char), new ("i16", 2, isUnsigned: true, preferHex: false) }, { typeof (sbyte), new ("i8", 1) }, { typeof (short), new ("i16", 2) }, - { typeof (ushort), new ("i16", 2) }, + { typeof (ushort), new ("i16", 2, isUnsigned: true) }, { typeof (int), new ("i32", 4) }, - { typeof (uint), new ("i32", 4) }, + { typeof (uint), new ("i32", 4, isUnsigned: true) }, { typeof (long), new ("i64", 8) }, - { typeof (ulong), new ("i64", 8) }, + { typeof (ulong), new ("i64", 8, isUnsigned: true) }, { typeof (float), new ("float", 4) }, { typeof (double), new ("double", 8) }, - { typeof (void), new ("void", 0, isNumeric: false) }, + { typeof (void), new ("void", 0, isNumeric: false, preferHex: false) }, }; public string FilePath { get; } @@ -191,6 +217,8 @@ 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 (); @@ -205,6 +233,10 @@ void WriteGlobalVariables (GeneratorWriteContext context) continue; } + if (gv.TargetArch.HasValue && gv.TargetArch.Value != target.TargetArch) { + continue; + } + if (gv.BeforeWriteCallback != null) { gv.BeforeWriteCallback (gv, target, gv.BeforeWriteCallbackCallerState); } @@ -240,8 +272,10 @@ void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable va context.Output.Write (", align "); ulong alignment; - if (typeInfo.IsAggregate) { - ulong count = GetAggregateValueElementCount (variable); + if (variable.Alignment.HasValue) { + alignment = variable.Alignment.Value; + } else if (typeInfo.IsAggregate) { + ulong count = GetAggregateValueElementCount (context, variable); alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size); } else if (typeInfo.IsStructure) { alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size); @@ -280,7 +314,7 @@ void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, return; } - throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value"); + throw new InvalidOperationException ($"Internal error: variable '{variable.Name}'' of type {variable.Type} must not have a null value"); } if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) { @@ -290,9 +324,9 @@ void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable, WriteValue (context, valueType, variable); } - ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable); + ulong GetAggregateValueElementCount (GeneratorWriteContext context, LlvmIrVariable variable) => GetAggregateValueElementCount (context, variable.Type, variable.Value, variable as LlvmIrGlobalVariable); - ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) + ulong GetAggregateValueElementCount (GeneratorWriteContext context, 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"); @@ -300,6 +334,9 @@ ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVaria if (value == null) { if (globalVariable != null) { + if (globalVariable.ArrayDataProvider != null) { + return globalVariable.ArrayDataProvider.GetTotalDataSize (context.Target); + } return globalVariable.ArrayItemCount; } return 0; @@ -373,7 +410,7 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv { if (IsStructureInstance (type)) { if (value == null) { - throw new ArgumentException ("must not be null for structure instances", nameof (value)); + throw new ArgumentException ($"must not be null for structure instances ({type})", nameof (value)); } WriteStructureType (context, (StructureInstance)value, out typeInfo); @@ -386,9 +423,9 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv if (type.IsArray ()) { Type elementType = type.GetArrayElementType (); - ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable); + ulong elementCount = GetAggregateValueElementCount (context, type, value, globalVariable); - WriteArrayType (context, elementType, elementCount, out typeInfo); + WriteArrayType (context, elementType, elementCount, globalVariable, out typeInfo); return; } @@ -404,6 +441,11 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv } void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) + { + WriteArrayType (context, elementType, elementCount, variable: null, out typeInfo); + } + + void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, LlvmIrGlobalVariable? variable, out LlvmTypeInfo typeInfo) { string irType; ulong size; @@ -420,6 +462,35 @@ void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elem } else { irType = GetIRType (elementType, out size, out isPointer); maxFieldAlignment = size; + + if (elementType.IsArray) { + if (variable == null) { + throw new InvalidOperationException ($"Internal error: array of arrays ({elementType}) requires variable to be defined"); + } + + // For the sake of simpler code, we currently assume that all the element arrays are of the same size, because that's the only scenario + // that we use at this time. + var value = variable.Value as ICollection; + if (value == null) { + throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value of type which implements the ICollection interface"); + } + + if (value.Count == 0) { + throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value which is a non-empty ICollection"); + } + + Array? firstItem = null; + foreach (object v in value) { + firstItem = (Array)v; + break; + } + + if (firstItem == null) { + throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value which is a non-empty ICollection with non-null elements"); + } + + irType = $"[{MonoAndroidHelper.CultureInvariantToString (firstItem.Length)} x {irType}]"; + } } typeInfo = new LlvmTypeInfo ( isPointer: isPointer, @@ -449,12 +520,17 @@ ulong GetStructureMaxFieldAlignment (StructureInfo si) void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable) { + if (variable is LlvmIrGlobalVariable globalVariable && globalVariable.ArrayDataProvider != null) { + WriteStreamedArrayValue (context, globalVariable, globalVariable.ArrayDataProvider); + return; + } + if (variable.Type.IsArray ()) { bool zeroInitialize = false; if (variable is LlvmIrGlobalVariable gv) { zeroInitialize = gv.ZeroInitializeArray || variable.Value == null; } else { - zeroInitialize = GetAggregateValueElementCount (variable) == 0; + zeroInitialize = GetAggregateValueElementCount (context, variable) == 0; } if (zeroInitialize) { @@ -478,6 +554,29 @@ 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 WriteInlineArray (GeneratorWriteContext context, byte[] bytes, bool encodeAsASCII) + { + if (encodeAsASCII) { + context.Output.Write ('c'); + context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false)); + return; + } + + string irType = MapToIRType (typeof(byte)); + bool first = true; + context.Output.Write ("[ "); + foreach (byte b in bytes) { + if (!first) { + context.Output.Write (", "); + } else { + first = false; + } + + context.Output.Write ($"{irType} u0x{b:x02}"); + } + context.Output.Write (" ]"); + } + void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value) { if (smi.IsNativePointer) { @@ -495,8 +594,7 @@ void WriteValue (GeneratorWriteContext context, StructureInstance structInstance // 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)); + WriteInlineArray (context, bytes, encodeAsASCII: false); return; } @@ -549,6 +647,27 @@ bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance s return false; } + string ToHex (BasicType basicTypeDesc, Type type, object? value) + { + const char prefixSigned = 's'; + const char prefixUnsigned = 'u'; + + string hex; + if (type == typeof(byte)) { + hex = ((byte)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture); + } else if (type == typeof(ushort)) { + hex = ((ushort)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture); + } else if (type == typeof(uint)) { + hex = ((uint)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture); + } else if (type == typeof(ulong)) { + hex = ((ulong)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture); + } else { + throw new NotImplementedException ($"Conversion to hexadecimal from type {type} is not implemented"); + }; + + return $"{(basicTypeDesc.IsUnsigned ? prefixUnsigned : prefixSigned)}0x{hex}"; + } + void WriteValue (GeneratorWriteContext context, Type type, object? value) { if (value is LlvmIrVariable variableRef) { @@ -556,14 +675,26 @@ void WriteValue (GeneratorWriteContext context, Type type, object? value) return; } - if (IsNumeric (type)) { - context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value)); - return; - } + bool isBasic = basicTypeMap.TryGetValue (type, out BasicType basicTypeDesc); + if (isBasic) { + if (basicTypeDesc.IsNumeric) { + bool hex = context.NumberFormat switch { + LlvmIrVariableNumberFormat.Default => basicTypeDesc.PreferHex, + LlvmIrVariableNumberFormat.Decimal => false, + LlvmIrVariableNumberFormat.Hexadecimal => true, + _ => throw new InvalidOperationException ($"Internal error: number format {context.NumberFormat} is unsupported") + }; - if (type == typeof(bool)) { - context.Output.Write ((bool)value ? '1' : '0'); - return; + context.Output.Write ( + hex ? ToHex (basicTypeDesc, type, value) : MonoAndroidHelper.CultureInvariantToString (value) + ); + return; + } + + if (type == typeof(bool)) { + context.Output.Write ((bool)value ? "true" : "false"); + return; + } } if (IsStructureInstance (type)) { @@ -588,8 +719,13 @@ void WriteValue (GeneratorWriteContext context, Type type, object? value) return; } - if (type.IsInlineArray ()) { + if (type.IsArray) { + if (type == typeof(byte[])) { + WriteInlineArray (context, (byte[])value, encodeAsASCII: true); + return; + } + throw new NotSupportedException ($"Internal error: array of type {type} is unsupported"); } throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); @@ -616,8 +752,20 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst context.Output.Write (' '); object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType); + LlvmIrVariableNumberFormat numberFormat = smi.Info.GetNumberFormat (); + LlvmIrVariableNumberFormat? savedNumberFormat = null; + + if (numberFormat != LlvmIrVariableNumberFormat.Default && numberFormat != context.NumberFormat) { + savedNumberFormat = context.NumberFormat; + context.NumberFormat = numberFormat; + } + WriteValue (context, instance, smi, value); + if (savedNumberFormat.HasValue) { + context.NumberFormat = savedNumberFormat.Value; + } + if (i < lastMember) { context.Output.Write (", "); } @@ -628,9 +776,6 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst 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); @@ -641,75 +786,80 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst context.Output.Write ('}'); } - void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) + void WriteArrayValueStart (GeneratorWriteContext context) { - 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 (entries.Count == 0) { - context.Output.Write ("zeroinitializer"); - return; - } - context.Output.WriteLine ('['); context.IncreaseIndent (); + } - Type elementType = variable.Type.GetArrayElementType (); - bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; - ulong counter = 0; - string? prevItemComment = null; - uint stride; + void WriteArrayValueEnd (GeneratorWriteContext context) + { + context.DecreaseIndent (); + context.Output.Write (']'); + } + uint GetArrayStride (LlvmIrVariable variable) + { if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) { - stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1; - } else { - stride = 1; + return variable.ArrayStride > 0 ? variable.ArrayStride : 1; } + return 1; + } + + void WriteArrayEntries (GeneratorWriteContext context, LlvmIrVariable variable, ICollection? entries, Type elementType, uint stride, bool writeIndices, bool terminateWithComma = false) + { bool first = true; + bool ignoreComments = stride > 1; + string? prevItemComment = null; + ulong counter = 0; - // TODO: implement output in rows - foreach (object entry in entries) { - if (!first) { - context.Output.Write (','); - WritePrevItemCommentOrNewline (); - } else { - first = false; - } + if (entries != null) { + foreach (object entry in entries) { + if (!first) { + context.Output.Write (','); + if (stride == 1 || counter % stride == 0) { + WritePrevItemCommentOrNewline (); + context.Output.Write (context.CurrentIndent); + } else { + context.Output.Write (' '); + } + } else { + context.Output.Write (context.CurrentIndent); + first = false; + } - prevItemComment = null; - if (variable.GetArrayItemCommentCallback != null) { - prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); - } + if (!ignoreComments) { + prevItemComment = null; + if (variable.GetArrayItemCommentCallback != null) { + prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState); + } - if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { - prevItemComment = $" {counter}"; - } + if (writeIndices && String.IsNullOrEmpty (prevItemComment)) { + prevItemComment = $" {counter}"; + } + } - counter++; - context.Output.Write (context.CurrentIndent); - WriteType (context, elementType, entry, out _); + counter++; + WriteType (context, elementType, entry, out _); - context.Output.Write (' '); - WriteValue (context, elementType, entry); + context.Output.Write (' '); + WriteValue (context, elementType, entry); + } } - WritePrevItemCommentOrNewline (); - context.DecreaseIndent (); - context.Output.Write (']'); + if (terminateWithComma) { + if (!ignoreComments) { + context.Output.WriteLine (); // must put comma outside the comment + context.Output.Write (context.CurrentIndent); + } + context.Output.Write (','); + } + WritePrevItemCommentOrNewline (); void WritePrevItemCommentOrNewline () { - if (!String.IsNullOrEmpty (prevItemComment)) { + if (!ignoreComments && !String.IsNullOrEmpty (prevItemComment)) { context.Output.Write (' '); WriteCommentLine (context, prevItemComment); } else { @@ -718,6 +868,97 @@ void WritePrevItemCommentOrNewline () } } + bool ArrayWantsToWriteIndices (LlvmIrVariable variable) => (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments; + + void WriteStreamedArrayValue (GeneratorWriteContext context, LlvmIrGlobalVariable variable, LlvmIrStreamedArrayDataProvider dataProvider) + { + ulong dataSizeSoFar = 0; + ulong totalDataSize = dataProvider.GetTotalDataSize (context.Target); + bool first = true; + + WriteArrayValueStart (context); + while (true) { + (LlvmIrStreamedArrayDataProviderState state, ICollection? data) = dataProvider.GetData (context.Target); + if (state == LlvmIrStreamedArrayDataProviderState.NextSectionNoData) { + continue; + } + + bool mustHaveData = state != LlvmIrStreamedArrayDataProviderState.LastSectionNoData; + if (mustHaveData) { + if (data.Count == 0) { + throw new InvalidOperationException ("Data must be provided for streamed arrays"); + } + + dataSizeSoFar += (ulong)data.Count; + if (dataSizeSoFar > totalDataSize) { + throw new InvalidOperationException ($"Data provider {dataProvider} is trying to write more data than declared"); + } + + if (first) { + first = false; + } else { + context.Output.WriteLine (); + } + string comment = dataProvider.GetSectionStartComment (context.Target); + + if (comment.Length > 0) { + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, comment); + } + } + + bool lastSection = state == LlvmIrStreamedArrayDataProviderState.LastSection || state == LlvmIrStreamedArrayDataProviderState.LastSectionNoData; + WriteArrayEntries ( + context, + variable, + data, + dataProvider.ArrayElementType, + GetArrayStride (variable), + writeIndices: false, + terminateWithComma: !lastSection + ); + + if (lastSection) { + break; + } + + } + WriteArrayValueEnd (context); + } + + void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable) + { + ICollection entries; + if (variable.Type.ImplementsInterface (typeof(IDictionary))) { + var list = new List (); + foreach (var kvp in (IDictionary)variable.Value) { + list.Add (kvp.Key); + list.Add (kvp.Value); + } + entries = list; + } else { + entries = (ICollection)variable.Value; + } + + if (entries.Count == 0) { + context.Output.Write ("zeroinitializer"); + return; + } + + WriteArrayValueStart (context); + + WriteArrayEntries ( + context, + variable, + entries, + variable.Type.GetArrayElementType (), + GetArrayStride (variable), + writeIndices: ArrayWantsToWriteIndices (variable) + ); + + WriteArrayValueEnd (context); + } + void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage) { if (linkage == LlvmIrLinkage.Default) { @@ -1232,8 +1473,6 @@ static string MapManagedTypeToNative (StructureMemberInfo smi) 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); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs index be1570dafe8..9a50f4ee5cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs @@ -464,7 +464,7 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string Register (kvp.Value); } } else if (typeof(ICollection).IsAssignableFrom (variable.Type)) { - foreach (string s in (ICollection)variable.Value) { + foreach (string s in (ICollection)variable.Value) { Register (s); } } else { @@ -473,8 +473,12 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string AddStandardGlobalVariable (variable); - void Register (string value) + void Register (string? value) { + if (value == null) { + return; + } + RegisterString (value, stringGroupName, stringGroupComment, symbolSuffix); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs index 7f741490ce0..d66bda3d7c9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -1,6 +1,9 @@ using System; +using System.Collections; using System.Globalization; +using Xamarin.Android.Tools; + namespace Xamarin.Android.Tasks.LLVMIR; [Flags] @@ -11,6 +14,13 @@ enum LlvmIrVariableWriteOptions ArrayFormatInRows = 0x0002, } +enum LlvmIrVariableNumberFormat +{ + Default, + Hexadecimal, + Decimal, +} + abstract class LlvmIrVariable : IEquatable { public abstract bool Global { get; } @@ -29,6 +39,8 @@ abstract class LlvmIrVariable : IEquatable public object? Value { get; set; } public string? Comment { get; set; } + public LlvmIrVariableNumberFormat NumberFormat { get; set; } = LlvmIrVariableNumberFormat.Decimal; + /// /// 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, @@ -146,6 +158,50 @@ public void AssignNumber (ulong n) } } +enum LlvmIrStreamedArrayDataProviderState +{ + NextSection, + LastSection, + NextSectionNoData, + LastSectionNoData, +} + +abstract class LlvmIrStreamedArrayDataProvider +{ + /// + /// Type of every member of the array returned by . Generator will check + /// every member type against this property, allowing also derived types. + /// + public Type ArrayElementType { get; } + + protected LlvmIrStreamedArrayDataProvider (Type arrayElementType) + { + ArrayElementType = arrayElementType; + } + + /// + /// Whenever returns the generator will call this method to obtain the new section + /// comment, if any, to be output before the actual data. Returning `String.Empty` prevents the comment + /// from being added. + /// + public virtual string GetSectionStartComment (LlvmIrModuleTarget target) => String.Empty; + + /// + /// Provide the next chunk of data for the specified target (ABI). Implementations need to return at least one + /// non-empty collection of data. The returned collection **must** be exactly the size of contained data (e.g. it cannot be + /// a byte array rented from a byte pool, because these can be bigger than requested. When returning the last (or the only) section, + /// must have a value of . + /// Each section may be preceded by a comment, . + /// + public abstract (LlvmIrStreamedArrayDataProviderState status, ICollection data) GetData (LlvmIrModuleTarget target); + + /// + /// Provide the total data size for the specified target (ABI). This needs to be used instead of + /// because a variable instance is created once and shared by all targets, while per-target data sets might have different sizes. + /// + public abstract ulong GetTotalDataSize (LlvmIrModuleTarget target); +} + class LlvmIrGlobalVariable : LlvmIrVariable { /// @@ -162,9 +218,40 @@ class LlvmIrGlobalVariable : LlvmIrVariable /// public virtual LlvmIrVariableOptions? Options { get; set; } + /// + /// There are situations when a variable differs enough between architectures, that the difference cannot be + /// handled with . In such situations one can create a separate variable + /// for each architecture and set this property. + /// + public AndroidTargetArch? TargetArch { get; set; } + + /// + /// If set to `true`, initialize the array with a shortcut zero-initializer statement. Useful when pre-allocating + /// space for runtime use that won't be filled in with any data at the build time. + /// public bool ZeroInitializeArray { get; set; } + + /// + /// Specify number of items in an array. Used in cases when we want to pre-allocate an array without giving it any + /// value, thus making it impossible for the generator to discover the number of items automatically. This is useful + /// when using . This property is used **only** if the variable + /// is `null`. + /// public ulong ArrayItemCount { get; set; } + /// + /// If set, it will override any automatically calculated alignment for this variable + /// + public ulong? Alignment { get; set; } + + /// + /// If set, the provider will be called to obtain all the data to be placed in an array variable. The total amount + /// of data that will be returned by the provider **must** be specified in the property, + /// in order for the generator to properly declare the variable. The generator will verify that the amount of data + /// is exactly that much and throw an exception otherwise. + /// + public LlvmIrStreamedArrayDataProvider? ArrayDataProvider { 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 @@ -196,11 +283,16 @@ public LlvmIrGlobalVariable (object value, string name, LlvmIrVariableOptions? o /// 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) + public void OverrideTypeAndValue (Type newType, object? newValue) { Type = newType; Value = newValue; } + + public void OverrideName (string newName) + { + Name = newName; + } } class LlvmIrStringVariable : LlvmIrGlobalVariable diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs index 431d92b6229..713b0c5a1a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs @@ -71,5 +71,15 @@ public static bool InlineArrayNeedsPadding (this MemberInfo mi) return attr.NeedsPadding; } + + public static LlvmIrVariableNumberFormat GetNumberFormat (this MemberInfo mi) + { + var attr = mi.GetCustomAttribute (); + if (attr == null) { + return LlvmIrVariableNumberFormat.Default; + } + + return attr.NumberFormat; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs index 39fc5e094bb..c239255e1ac 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs @@ -33,6 +33,8 @@ class NativeAssemblerAttribute : Attribute /// size to which the member must be padded is specified by /// public bool NeedsPadding { get; set; } + + public LLVMIR.LlvmIrVariableNumberFormat NumberFormat { get; set; } = LLVMIR.LlvmIrVariableNumberFormat.Default; } [AttributeUsage (AttributeTargets.Class, Inherited = true)] diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index c30df45bb8e..ee6fc0e7f28 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -108,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 $" class name: {klass.ClassName}"; } return String.Empty; @@ -118,7 +118,7 @@ public override string GetComment (object data, string fieldName) [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodsManagedClassDataProvider))] sealed class MarshalMethodsManagedClass { - [NativeAssembler (UsesDataProvider = true)] + [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public uint token; [NativePointer (IsNull = true)] @@ -982,10 +982,10 @@ void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) 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); + ulong hashFull32 = MonoAndroidHelper.GetXxHash (name, is64Bit: false); + ulong hashClipped32 = MonoAndroidHelper.GetXxHash (clippedName, is64Bit: false); + ulong hashFull64 = MonoAndroidHelper.GetXxHash (name, is64Bit: true); + ulong hashClipped64 = MonoAndroidHelper.GetXxHash (clippedName, is64Bit: true); // // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the @@ -1021,6 +1021,7 @@ void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs) BeforeWriteCallbackCallerState = acs, GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment, GetArrayItemCommentCallbackCallerState = acs, + NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal, }; module.Add (assembly_image_cache_hashes); @@ -1047,7 +1048,7 @@ void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget } LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - gv.OverrideValueAndType (type, value); + gv.OverrideTypeAndValue (type, value); } string? GetAssemblyImageCacheItemComment (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) @@ -1081,7 +1082,7 @@ void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarge } LlvmIrGlobalVariable gv = EnsureGlobalVariable (variable); - gv.OverrideValueAndType (variable.Type, value); + gv.OverrideTypeAndValue (variable.Type, value); } AssemblyCacheState EnsureAssemblyCacheState (object? callerState) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs new file mode 100644 index 00000000000..09d3efd1291 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO.Hashing; +using System.Text; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +partial class MonoAndroidHelper +{ + public static class AndroidAbi + { + public const string Arm32 = "armeabi-v7a"; + public const string Arm64 = "arm64-v8a"; + public const string X86 = "x86"; + public const string X64 = "x86_64"; + } + + public static class RuntimeIdentifier + { + public const string Arm32 = "android-arm"; + public const string Arm64 = "android-arm64"; + public const string X86 = "android-x86"; + public const string X64 = "android-x64"; + } + + public static readonly HashSet SupportedTargetArchitectures = new HashSet { + AndroidTargetArch.Arm, + AndroidTargetArch.Arm64, + AndroidTargetArch.X86, + AndroidTargetArch.X86_64, + }; + + static readonly char[] ZipPathTrimmedChars = {'/', '\\'}; + + static readonly Dictionary ClangAbiMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { + {"arm64-v8a", "aarch64"}, + {"armeabi-v7a", "arm"}, + {"x86", "i686"}, + {"x86_64", "x86_64"} + }; + + static readonly Dictionary AbiToArchMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { + { AndroidAbi.Arm32, AndroidTargetArch.Arm }, + { AndroidAbi.Arm64, AndroidTargetArch.Arm64 }, + { AndroidAbi.X86, AndroidTargetArch.X86 }, + { AndroidAbi.X64, AndroidTargetArch.X86_64 }, + }; + + static readonly Dictionary AbiToRidMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { + { AndroidAbi.Arm32, RuntimeIdentifier.Arm32 }, + { AndroidAbi.Arm64, RuntimeIdentifier.Arm64 }, + { AndroidAbi.X86, RuntimeIdentifier.X86 }, + { AndroidAbi.X64, RuntimeIdentifier.X64 }, + }; + + static readonly Dictionary RidToAbiMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { + { RuntimeIdentifier.Arm32, AndroidAbi.Arm32 }, + { RuntimeIdentifier.Arm64, AndroidAbi.Arm64 }, + { RuntimeIdentifier.X86, AndroidAbi.X86 }, + { RuntimeIdentifier.X64, AndroidAbi.X64 }, + }; + + static readonly Dictionary RidToArchMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { + { RuntimeIdentifier.Arm32, AndroidTargetArch.Arm }, + { RuntimeIdentifier.Arm64, AndroidTargetArch.Arm64 }, + { RuntimeIdentifier.X86, AndroidTargetArch.X86 }, + { RuntimeIdentifier.X64, AndroidTargetArch.X86_64 }, + }; + + static readonly Dictionary ArchToRidMap = new Dictionary { + { AndroidTargetArch.Arm, RuntimeIdentifier.Arm32 }, + { AndroidTargetArch.Arm64, RuntimeIdentifier.Arm64 }, + { AndroidTargetArch.X86, RuntimeIdentifier.X86 }, + { AndroidTargetArch.X86_64, RuntimeIdentifier.X64 }, + }; + + static readonly Dictionary ArchToAbiMap = new Dictionary { + { AndroidTargetArch.Arm, AndroidAbi.Arm32 }, + { AndroidTargetArch.Arm64, AndroidAbi.Arm64 }, + { AndroidTargetArch.X86, AndroidAbi.X86 }, + { AndroidTargetArch.X86_64, AndroidAbi.X64 }, + }; + + public static AndroidTargetArch AbiToTargetArch (string abi) + { + if (!AbiToArchMap.TryGetValue (abi, out AndroidTargetArch arch)) { + throw new NotSupportedException ($"Internal error: unsupported ABI '{abi}'"); + }; + + return arch; + } + + public static string AbiToRid (string abi) + { + if (!AbiToRidMap.TryGetValue (abi, out string rid)) { + throw new NotSupportedException ($"Internal error: unsupported ABI '{abi}'"); + }; + + return rid; + } + + public static string RidToAbi (string rid) + { + if (!RidToAbiMap.TryGetValue (rid, out string abi)) { + throw new NotSupportedException ($"Internal error: unsupported Runtime Identifier '{rid}'"); + }; + + return abi; + } + + public static AndroidTargetArch RidToArch (string rid) + { + if (!RidToArchMap.TryGetValue (rid, out AndroidTargetArch arch)) { + throw new NotSupportedException ($"Internal error: unsupported Runtime Identifier '{rid}'"); + }; + + return arch; + } + + public static string ArchToRid (AndroidTargetArch arch) + { + if (!ArchToRidMap.TryGetValue (arch, out string rid)) { + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + }; + + return rid; + } + + public static string ArchToAbi (AndroidTargetArch arch) + { + if (!ArchToAbiMap.TryGetValue (arch, out string abi)) { + throw new InvalidOperationException ($"Internal error: unsupported architecture '{arch}'"); + }; + + return abi; + } + + public static string? CultureInvariantToString (object? obj) + { + if (obj == null) { + return null; + } + + return Convert.ToString (obj, CultureInfo.InvariantCulture); + } + + public static string MapAndroidAbiToClang (string androidAbi) + { + if (ClangAbiMap.TryGetValue (androidAbi, out string clangAbi)) { + return clangAbi; + } + return null; + } + + public static string MakeZipArchivePath (string part1, params string[]? pathParts) + { + return MakeZipArchivePath (part1, (ICollection?)pathParts); + } + + public static string MakeZipArchivePath (string part1, ICollection? pathParts) + { + var parts = new List (); + if (!String.IsNullOrEmpty (part1)) { + parts.Add (part1.TrimEnd (ZipPathTrimmedChars)); + }; + + if (pathParts != null && pathParts.Count > 0) { + foreach (string p in pathParts) { + if (String.IsNullOrEmpty (p)) { + continue; + } + parts.Add (p.TrimEnd (ZipPathTrimmedChars)); + } + } + + if (parts.Count == 0) { + return String.Empty; + } + + return String.Join ("/", parts); + } + + public static bool IsValidAbi (string abi) => AbiToRidMap.ContainsKey (abi); + + public static byte[] Utf8StringToBytes (string str) => Encoding.UTF8.GetBytes (str); + + public static ulong GetXxHash (string str, bool is64Bit) => GetXxHash (Utf8StringToBytes (str), is64Bit); + + public static ulong GetXxHash (byte[] stringBytes, bool is64Bit) + { + if (is64Bit) { + return XxHash64.HashToUInt64 (stringBytes); + } + + return (ulong)XxHash32.HashToUInt32 (stringBytes); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 4efceda5141..4a37d6eee80 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.IO; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using System.Security.Cryptography; using System.Text; using Xamarin.Android.Tools; using Xamarin.Tools.Zip; -using Microsoft.Android.Build.Tasks; #if MSBUILD +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; #endif @@ -277,22 +275,6 @@ public static void LogWarning (object log, string msg, params object [] args) } #if MSBUILD - static readonly Dictionary ClangAbiMap = new Dictionary (StringComparer.OrdinalIgnoreCase) { - {"arm64-v8a", "aarch64"}, - {"armeabi-v7a", "arm"}, - {"x86", "i686"}, - {"x86_64", "x86_64"} - }; - - public static string MapAndroidAbiToClang (string androidAbi) - { - if (ClangAbiMap.TryGetValue (androidAbi, out string clangAbi)) { - return clangAbi; - } - return null; - } -#endif - public static bool IsMonoAndroidAssembly (ITaskItem assembly) { var tfi = assembly.GetMetadata ("TargetFrameworkIdentifier"); @@ -313,6 +295,7 @@ public static bool HasMonoAndroidReference (ITaskItem assembly) var reader = pe.GetMetadataReader (); return HasMonoAndroidReference (reader); } +#endif public static bool HasMonoAndroidReference (MetadataReader reader) { @@ -387,7 +370,6 @@ internal static IEnumerable GetFrameworkAssembliesToTreatAsUserAssemb return ret; } -#endif public static bool SaveMapFile (IBuildEngine4 engine, string mapFile, Dictionary map) { @@ -448,6 +430,7 @@ public static bool SaveCustomViewMapFile (IBuildEngine4 engine, string mapFile, return Files.CopyIfStreamChanged (writer.BaseStream, mapFile); } } +#endif // MSBUILD public static string [] GetProguardEnvironmentVaribles (string proguardHome) { @@ -482,6 +465,8 @@ public static IEnumerable Executables (string executable) yield return executable; } + +#if MSBUILD public static string TryGetAndroidJarPath (TaskLoggingHelper log, string platform, bool designTimeBuild = false, bool buildingInsideVisualStudio = false, string targetFramework = "", string androidSdkDirectory = "") { var platformPath = MonoAndroidHelper.AndroidSdk.TryGetPlatformDirectoryFromApiLevel (platform, MonoAndroidHelper.SupportedVersions); @@ -503,7 +488,7 @@ public static void SaveResourceCaseMap (IBuildEngine4 engine, Dictionary LoadResourceCaseMap (IBuildEngine4 engine, Func keyCallback) => engine.GetRegisteredTaskObjectAssemblyLocal> (keyCallback (ResourceCaseMapKey), RegisteredTaskObjectLifetime.Build) ?? new Dictionary (0); - +#endif // MSBUILD public static string FixUpAndroidResourcePath (string file, string resourceDirectory, string resourceDirectoryFullPath, Dictionary resource_name_case_map) { string newfile = null; @@ -523,7 +508,7 @@ public static string FixUpAndroidResourcePath (string file, string resourceDirec } static readonly char [] DirectorySeparators = new [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - +#if MSBUILD /// /// Returns the relative path that should be used for an @(AndroidAsset) item /// @@ -535,57 +520,9 @@ 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; } +#endif // MSBUILD - public static AndroidTargetArch AbiToTargetArch (string abi) - { - return abi switch { - "armeabi-v7a" => AndroidTargetArch.Arm, - "arm64-v8a" => AndroidTargetArch.Arm64, - "x86_64" => AndroidTargetArch.X86_64, - "x86" => AndroidTargetArch.X86, - _ => throw new NotSupportedException ($"Internal error: unsupported ABI '{abi}'") - }; - } - public static string AbiToRid (string abi) - { - switch (abi) { - case "arm64-v8a": - return "android-arm64"; - - case "armeabi-v7a": - return "android-arm"; - - case "x86": - return "android-x86"; - - case "x86_64": - return "android-x64"; - - default: - throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); - } - } - - public static string ArchToRid (AndroidTargetArch arch) - { - return arch switch { - AndroidTargetArch.Arm64 => "android-arm64", - AndroidTargetArch.Arm => "android-arm", - AndroidTargetArch.X86 => "android-x86", - AndroidTargetArch.X86_64 => "android-x64", - _ => throw new InvalidOperationException ($"Internal error: unsupported ABI '{arch}'") - }; - } - - public static string? CultureInvariantToString (object? obj) - { - if (obj == null) { - return null; - } - - return Convert.ToString (obj, CultureInfo.InvariantCulture); - } /// /// Converts $(SupportedOSPlatformVersion) to an API level, as it can be a version (21.0), or an int (21). @@ -604,16 +541,20 @@ public static int ConvertSupportedOSPlatformVersionToApiLevel (string version) return apiLevel; } - public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) +#if MSBUILD + public static string GetAssemblyAbi (ITaskItem asmItem) { string? abi = asmItem.GetMetadata ("Abi"); if (String.IsNullOrEmpty (abi)) { - return AndroidTargetArch.None; + throw new InvalidOperationException ($"Internal error: assembly '{asmItem}' lacks ABI metadata"); } - return AbiToTargetArch (abi); + return abi; } + public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetAssemblyAbi (asmItem)); +#endif // MSBUILD + static string GetToolsRootDirectoryRelativePath (string androidBinUtilsDirectory) { // We need to link against libc and libm, but since NDK is not in use, the linker won't be able to find the actual Android libraries. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index 01f979632d8..61b3f5c314e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -71,6 +71,7 @@ sealed class TypeMapModuleEntry [NativeAssembler (Ignore = true)] public TypeMapJava JavaTypeMapEntry; + [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public uint type_token_id; public uint java_map_index; } @@ -128,6 +129,8 @@ sealed class TypeMapJava public ulong JavaNameHash64; public uint module_index; + + [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public uint type_token_id; public uint java_name_index; } @@ -261,7 +264,7 @@ uint GetJavaEntryIndex (TypeMapJava javaEntry) throw new InvalidOperationException ("Internal error: construction state expected but not found"); } - return $" {index}: 0x{value:x} => {cs.JavaMap[(int)index].Instance.JavaName}"; + return $" {index}: {cs.JavaMap[(int)index].Instance.JavaName}"; } void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) @@ -290,7 +293,7 @@ void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget targ hashes = list; } - gv.OverrideValueAndType (listType, hashes); + gv.OverrideTypeAndValue (listType, hashes); } ConstructionState EnsureConstructionState (object? callerState) From b985997e8ae03f9fbcddbaca0f504d77c421850b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 16 Feb 2024 11:16:24 +0100 Subject: [PATCH 2/2] Remove code that belongs only in #8478 --- .../Utilities/MonoAndroidHelper.Basic.cs | 2 +- .../Utilities/MonoAndroidHelper.cs | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs index 09d3efd1291..3e8fe9a2d8c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Basic.cs @@ -87,7 +87,7 @@ public static class RuntimeIdentifier public static AndroidTargetArch AbiToTargetArch (string abi) { if (!AbiToArchMap.TryGetValue (abi, out AndroidTargetArch arch)) { - throw new NotSupportedException ($"Internal error: unsupported ABI '{abi}'"); + return AndroidTargetArch.None; }; return arch; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 4a37d6eee80..9c2794a367a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -542,14 +542,9 @@ public static int ConvertSupportedOSPlatformVersionToApiLevel (string version) } #if MSBUILD - public static string GetAssemblyAbi (ITaskItem asmItem) + public static string? GetAssemblyAbi (ITaskItem asmItem) { - string? abi = asmItem.GetMetadata ("Abi"); - if (String.IsNullOrEmpty (abi)) { - throw new InvalidOperationException ($"Internal error: assembly '{asmItem}' lacks ABI metadata"); - } - - return abi; + return asmItem.GetMetadata ("Abi"); } public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetAssemblyAbi (asmItem));