diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs index a6dec7fe137..d5744ce96d4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using NUnit.Framework.Interfaces; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Diagnostics; @@ -207,10 +208,10 @@ protected static string AdbStartActivity (string activity) return RunAdbCommand ($"shell am start -S -n \"{activity}\""); } - protected static void RunProjectAndAssert (XamarinAndroidApplicationProject proj, ProjectBuilder builder, string logName = "run.log", bool doNotCleanupOnUpdate = false, string [] parameters = null) + protected static void RunProjectAndAssert (XamarinAndroidApplicationProject proj, ProjectBuilder builder, string logName = "run.log", Dictionary environmentVariables = null, bool doNotCleanupOnUpdate = false, string [] parameters = null) { builder.BuildLogFile = logName; - Assert.True (builder.RunTarget (proj, "Run", doNotCleanupOnUpdate: doNotCleanupOnUpdate, parameters: parameters), "Project should have run."); + Assert.True (builder.RunTarget (proj, "Run", doNotCleanupOnUpdate: doNotCleanupOnUpdate, parameters: parameters, environmentVariables: environmentVariables), "Project should have run."); } protected static void StartActivityAndAssert (XamarinAndroidApplicationProject proj) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index ccc4fb0b506..28f8a57809e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -374,6 +374,10 @@ bool IsValueAssignableFrom (Type valueType, LlvmIrVariable variable) ulong GetAggregateValueElementCount (GeneratorWriteContext context, Type type, object? value, LlvmIrGlobalVariable? globalVariable = null) { + if (type == typeof(LlvmIrStringBlob)) { + return 1; // String blobs are a collection of bytes + } + if (!type.IsArray ()) { throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count"); } @@ -479,6 +483,11 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv return; } + if (type == typeof(LlvmIrStringBlob)) { + WriteStringBlobType (context, (LlvmIrStringBlob?)value, out typeInfo); + return; + } + irType = GetIRType (context, type, out size, out isPointer); typeInfo = new LlvmTypeInfo ( isPointer: isPointer, @@ -490,6 +499,21 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv context.Output.Write (irType); } + void WriteStringBlobType (GeneratorWriteContext context, LlvmIrStringBlob? blob, out LlvmTypeInfo typeInfo) + { + long size = blob?.Size ?? 0; + // Blobs are always arrays of bytes + context.Output.Write ($"[{size} x i8]"); + + typeInfo = new LlvmTypeInfo ( + isPointer: false, + isAggregate: true, + isStructure: false, + size: (ulong)size, + maxFieldAlignment: 1 + ); + } + void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo) { WriteArrayType (context, elementType, elementCount, variable: null, out typeInfo); @@ -769,6 +793,11 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value, return; } + if (type == typeof(LlvmIrStringBlob)) { + WriteStringBlobArray (context, (LlvmIrStringBlob)value); + return; + } + if (type.IsArray) { if (type == typeof(byte[])) { WriteInlineArray (context, (byte[])value, encodeAsASCII: true); @@ -786,6 +815,69 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value, throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported"); } + void WriteStringBlobArray (GeneratorWriteContext context, LlvmIrStringBlob blob) + { + const uint stride = 16; + Type elementType = typeof(byte); + + LlvmIrVariableNumberFormat oldNumberFormat = context.NumberFormat; + context.NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal; + WriteArrayValueStart (context); + foreach (LlvmIrStringBlob.StringInfo si in blob.GetSegments ()) { + if (si.Offset > 0) { + context.Output.Write (','); + context.Output.WriteLine (); + context.Output.WriteLine (); + } + + context.Output.Write (context.CurrentIndent); + WriteCommentLine (context, $" '{si.Value}' @ {si.Offset}"); + WriteBytes (si.Bytes); + } + context.Output.WriteLine (); + WriteArrayValueEnd (context); + context.NumberFormat = oldNumberFormat; + + void WriteBytes (byte[] bytes) + { + ulong counter = 0; + bool first = true; + foreach (byte b in bytes) { + if (!first) { + WriteCommaWithStride (counter); + } else { + context.Output.Write (context.CurrentIndent); + first = false; + } + + counter++; + WriteByteTypeAndValue (b); + } + + WriteCommaWithStride (counter); + WriteByteTypeAndValue (0); // Terminating NUL is counted for each string, but not included in its bytes + } + + void WriteCommaWithStride (ulong counter) + { + context.Output.Write (','); + if (stride == 1 || counter % stride == 0) { + context.Output.WriteLine (); + context.Output.Write (context.CurrentIndent); + } else { + context.Output.Write (' '); + } + } + + void WriteByteTypeAndValue (byte v) + { + WriteType (context, elementType, v, out _); + + context.Output.Write (' '); + WriteValue (context, elementType, v); + } + } + void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance) { if (instance == null || instance.IsZeroInitialized) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringBlob.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringBlob.cs new file mode 100644 index 00000000000..410efcdcb4d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrStringBlob.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tasks.LLVMIR; + +/// +/// This class is an optimization which allows us to store strings +/// as a single "blob" of data where each string follows another, all +/// of them separated with a NUL character. This allows us to use a single +/// pointer at run time instead of several (one per string). The result is +/// less relocations in the final .so, which is good for performance +/// +/// Each string is converted to UTF8 before storing as a byte array. To optimize +/// for size, duplicate strings are not stored, instead the earlier offset+length +/// are returned when calling the method. +/// +/// +class LlvmIrStringBlob +{ + // Length is one more than byte size, to account for the terminating nul + public record struct StringInfo (int Offset, int Length, byte[] Bytes, string Value); + + Dictionary cache = new (StringComparer.Ordinal); + List segments = new (); + long size = 0; + + public long Size => size; + + public (int offset, int length) Add (string s) + { + if (cache.TryGetValue (s, out StringInfo info)) { + return (info.Offset, info.Length); + } + + byte[] bytes = MonoAndroidHelper.Utf8StringToBytes (s); + int offset; + if (segments.Count > 0) { + StringInfo lastSegment = segments[segments.Count - 1]; + offset = lastSegment.Offset + lastSegment.Length + 1; // Include trailing NUL here + } else { + offset = 0; + } + + info = new StringInfo ( + Offset: offset, + Length: bytes.Length, + Bytes: bytes, + Value: s + ); + segments.Add (info); + cache.Add (s, info); + size += info.Length + 1; // Account for the trailing NUL + + return (info.Offset, info.Length); + } + + public int GetIndexOf (string s) + { + if (String.IsNullOrEmpty (s)) { + return -1; + } + + if (!cache.TryGetValue (s, out StringInfo info)) { + return -1; + } + + return info.Offset; + } + + public IEnumerable GetSegments () + { + foreach (StringInfo si in segments) { + yield return si; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs index fc6b4930af4..e48923c4771 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs @@ -145,8 +145,10 @@ static TypeMapDebugEntry GetDebugEntry (TypeDefinition td, TypeDefinitionCache c return new TypeMapDebugEntry { JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, cache), ManagedName = GetManagedTypeName (td), + ManagedTypeTokenId = td.MetadataToken.ToUInt32 (), TypeDefinition = td, SkipInJavaToManaged = ShouldSkipInJavaToManaged (td), + AssemblyName = td.Module.Assembly.Name.Name, }; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 56b33164d33..7a22e76f2a0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -62,8 +62,10 @@ internal sealed class TypeMapDebugEntry { public string JavaName; public string ManagedName; + public uint ManagedTypeTokenId; public bool SkipInJavaToManaged; public TypeMapDebugEntry DuplicateForJavaToManaged; + public string AssemblyName; // This field is only used by the Cecil adapter for temp storage while reading. // It is not used to create the typemap. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 9eb190dabc0..5be21da534a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Xml; @@ -101,6 +102,7 @@ void WriteTypeMapDebugEntry (XmlWriter xml, TypeMapDebugEntry entry) xml.WriteAttributeStringIfNotDefault ("managed-name", entry.ManagedName); xml.WriteAttributeStringIfNotDefault ("skip-in-java-to-managed", entry.SkipInJavaToManaged); xml.WriteAttributeStringIfNotDefault ("is-invoker", entry.IsInvoker); + xml.WriteAttributeString ("managed-type-token-id", entry.ManagedTypeTokenId.ToString (CultureInfo.InvariantCulture)); xml.WriteEndElement (); } @@ -198,19 +200,20 @@ public static TypeMapObjectsXmlFile Import (string filename) static void ImportDebugData (XElement root, TypeMapObjectsXmlFile file) { - var isMonoAndroid = root.GetAttributeOrDefault ("assembly-name", string.Empty) == "Mono.Android"; + var assemblyName = root.GetAttributeOrDefault ("assembly-name", string.Empty); + var isMonoAndroid = assemblyName == "Mono.Android"; var javaToManaged = root.Element ("java-to-managed"); if (javaToManaged is not null) { foreach (var entry in javaToManaged.Elements ("entry")) - file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid)); + file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); } var managedToJava = root.Element ("managed-to-java"); if (managedToJava is not null) { foreach (var entry in managedToJava.Elements ("entry")) - file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid)); + file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid)); } } @@ -251,19 +254,22 @@ public static void WriteEmptyFile (string destination, TaskLoggingHelper log) File.Create (destination).Dispose (); } - static TypeMapDebugEntry FromDebugEntryXml (XElement entry, bool isMonoAndroid) + static TypeMapDebugEntry FromDebugEntryXml (XElement entry, string assemblyName, bool isMonoAndroid) { var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty); var managedName = entry.GetAttributeOrDefault ("managed-name", string.Empty); var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false); var isInvoker = entry.GetAttributeOrDefault ("is-invoker", false); + var managedTokenId = entry.GetAttributeOrDefault ("managed-type-token-id", (uint)0); return new TypeMapDebugEntry { JavaName = javaName, ManagedName = managedName, + ManagedTypeTokenId = managedTokenId, SkipInJavaToManaged = skipInJavaToManaged, IsInvoker = isInvoker, IsMonoAndroid = isMonoAndroid, + AssemblyName = assemblyName, }; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs index 60e52b0e06d..5613bb6263f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -66,11 +66,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) { - return $"from: {entry.from}"; + return $" from: {entry.from}"; } if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) { - return $"to: {entry.to}"; + return $" to: {entry.to}"; } return String.Empty; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs index 2257754a2d5..7990cd12e51 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -15,7 +15,11 @@ class TypeMappingDebugNativeAssemblyGeneratorCLR : LlvmIrComposer // These names MUST match src/native/clr/include/xamarin-app.hh const string TypeMapSymbol = "type_map"; const string UniqueAssembliesSymbol = "type_map_unique_assemblies"; - const string AssemblyNamesBlobSymbol = "type_map_assembly_names_blob"; + const string AssemblyNamesBlobSymbol = "type_map_assembly_names"; + const string ManagedTypeNamesBlobSymbol = "type_map_managed_type_names"; + const string JavaTypeNamesBlobSymbol = "type_map_java_type_names"; + const string TypeMapUsesHashesSymbol = "typemap_use_hashes"; + const string TypeMapManagedTypeInfoSymbol = "type_map_managed_type_info"; sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider { @@ -52,11 +56,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) { - return $"from: {entry.from}"; + return $" from: '{entry.From}'"; } if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) { - return $"to: {entry.to}"; + return $" to: '{entry.To}'"; } return String.Empty; @@ -81,16 +85,61 @@ public override string GetComment (object data, string fieldName) } } + sealed class TypeMapManagedTypeInfoContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("assembly_name_index", fieldName, StringComparison.Ordinal) == 0) { + return $" '{entry.AssemblyName}'"; + } + + if (String.Compare ("managed_type_token_id", fieldName, StringComparison.Ordinal) == 0) { + return $" '{entry.ManagedTypeName}'"; + } + + return String.Empty; + } + } + // Order of fields and their type must correspond *exactly* to that in // src/native/clr/include/xamarin-app.hh TypeMapEntry structure [NativeAssemblerStructContextDataProvider (typeof (TypeMapEntryContextDataProvider))] sealed class TypeMapEntry { + [NativeAssembler (Ignore = true)] + public string From = String.Empty; + + [NativeAssembler (Ignore = true)] + public string To = String.Empty; + + [NativeAssembler (UsesDataProvider = true)] + public uint from; + + [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] + public ulong from_hash; + [NativeAssembler (UsesDataProvider = true)] - public string from = String.Empty; + public uint to; + }; + + // Order of fields and their type must correspond *exactly* to that in + // src/native/clr/include/xamarin-app.hh TypeMapManagedTypeInfo structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapManagedTypeInfoContextDataProvider))] + sealed class TypeMapManagedTypeInfo + { + [NativeAssembler (Ignore = true)] + public string AssemblyName = String.Empty; + + [NativeAssembler (Ignore = true)] + public string ManagedTypeName = String.Empty; [NativeAssembler (UsesDataProvider = true)] - public string? to; + public uint assembly_name_index; + + [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] + public uint managed_type_token_id; }; // Order of fields and their type must correspond *exactly* to that in @@ -106,7 +155,6 @@ sealed class TypeMap public uint entry_count; public ulong unique_assemblies_count; - public ulong assembly_names_blob_size; [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] public TypeMapEntry? java_to_managed = null; @@ -138,9 +186,11 @@ sealed class TypeMapAssembly StructureInfo? typeMapEntryStructureInfo; StructureInfo? typeMapStructureInfo; StructureInfo? typeMapAssemblyStructureInfo; + StructureInfo? typeMapManagedTypeInfoStructureInfo; List> javaToManagedMap; List> managedToJavaMap; List> uniqueAssemblies; + List> managedTypeInfos; StructureInstance? type_map; public TypeMappingDebugNativeAssemblyGeneratorCLR (TaskLoggingHelper log, TypeMapGenerator.ModuleDebugData data) @@ -155,6 +205,7 @@ public TypeMappingDebugNativeAssemblyGeneratorCLR (TaskLoggingHelper log, TypeMa javaToManagedMap = new (); managedToJavaMap = new (); uniqueAssemblies = new (); + managedTypeInfos = new (); } protected override void Construct (LlvmIrModule module) @@ -167,45 +218,77 @@ 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)); - } - } + var managedTypeNames = new LlvmIrStringBlob (); + var javaTypeNames = new LlvmIrStringBlob (); + + // CoreCLR supports only 64-bit targets, so we can make things simpler by hashing all the things here instead of + // in a callback during code generation - if (data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0) { - foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { - TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; + // Probability of xxHash clashes on managed type names is very low, it might be hard to find such type names that + // would create collision, so in order to be able to test the string-based managed-to-java typemaps, we check whether + // the `CI_TYPEMAP_DEBUG_USE_STRINGS` environment variable is present and not empty. If it's not in the environment + // or its value is an empty string, we default to using hashes for the managed-to-java type maps. + bool typemap_uses_hashes = String.IsNullOrEmpty (Environment.GetEnvironmentVariable ("CI_TYPEMAP_DEBUG_USE_STRINGS")); + var usedHashes = new Dictionary (); + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { + (int managedTypeNameOffset, int _) = managedTypeNames.Add (entry.ManagedName); + (int javaTypeNameOffset, int _) = javaTypeNames.Add (entry.JavaName); + var m2j = new TypeMapEntry { + From = entry.ManagedName, + To = entry.JavaName, + + from = (uint)managedTypeNameOffset, + from_hash = typemap_uses_hashes ? MonoAndroidHelper.GetXxHash (entry.ManagedName, is64Bit: true) : 0, + to = (uint)javaTypeNameOffset, + }; + managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); - var j2m = new TypeMapEntry { - from = entry.JavaName, - to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, - }; - javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); + if (!typemap_uses_hashes) { + continue; } + + if (usedHashes.ContainsKey (m2j.from_hash)) { + typemap_uses_hashes = false; + // It could be a warning, but it's not really actionable - users might not be able to rename the clashing types + Log.LogMessage ($"Detected xxHash conflict between managed type names '{entry.ManagedName}' and '{usedHashes[m2j.from_hash]}' when mapping to Java type '{entry.JavaName}'."); + } else { + usedHashes[m2j.from_hash] = entry.ManagedName; + } + } + // Input is sorted on name, we need to re-sort it on hashes, if used + if (typemap_uses_hashes) { + managedToJavaMap.Sort ((StructureInstance a, StructureInstance b) => { + if (a.Instance == null) { + return b.Instance == null ? 0 : -1; + } + + if (b.Instance == null) { + return 1; + } + + return a.Instance.from_hash.CompareTo (b.Instance.from_hash); + }); } - // CoreCLR supports only 64-bit targets, so we can make things simpler by hashing the MVIDs here instead of - // in a callback during code generation - var assemblyNamesBlob = new List (); + if (!typemap_uses_hashes) { + Log.LogMessage ("Managed-to-java typemaps will use string-based matching."); + } + + var assemblyNamesBlob = new LlvmIrStringBlob (); foreach (TypeMapGenerator.TypeMapDebugAssembly asm in data.UniqueAssemblies) { - byte[] nameBytes = MonoAndroidHelper.Utf8StringToBytes (asm.Name); + (int assemblyNameOffset, int assemblyNameLength) = assemblyNamesBlob.Add (asm.Name); + var entry = new TypeMapAssembly { Name = asm.Name, MVID = asm.MVID, mvid_hash = MonoAndroidHelper.GetXxHash (asm.MVIDBytes, is64Bit: true), - name_length = (ulong)nameBytes.Length, // without the trailing NUL - name_offset = (ulong)assemblyNamesBlob.Count, + name_length = (ulong)assemblyNameLength, // without the trailing NUL + name_offset = (ulong)assemblyNameOffset, }; uniqueAssemblies.Add (new StructureInstance (typeMapAssemblyStructureInfo, entry)); - assemblyNamesBlob.AddRange (nameBytes); - assemblyNamesBlob.Add (0); } + uniqueAssemblies.Sort ((StructureInstance a, StructureInstance b) => { if (a.Instance == null) { return b.Instance == null ? 0 : -1; @@ -218,27 +301,56 @@ protected override void Construct (LlvmIrModule module) return a.Instance.mvid_hash.CompareTo (b.Instance.mvid_hash); }); + var managedTypeInfos = new List> (); + // Java-to-managed maps don't use hashes since many mappings have multiple instances + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { + TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; + (int managedTypeNameOffset, int _) = managedTypeNames.Add (entry.ManagedName); + (int javaTypeNameOffset, int _) = javaTypeNames.Add (entry.JavaName); + + var j2m = new TypeMapEntry { + From = entry.JavaName, + To = managedEntry.SkipInJavaToManaged ? String.Empty : entry.ManagedName, + + from = (uint)javaTypeNameOffset, + from_hash = 0, + to = managedEntry.SkipInJavaToManaged ? uint.MaxValue : (uint)managedTypeNameOffset, + }; + javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); + + int assemblyNameOffset = assemblyNamesBlob.GetIndexOf (entry.AssemblyName); + if (assemblyNameOffset < 0) { + throw new InvalidOperationException ($"Internal error: assembly name '{entry.AssemblyName}' not found in the assembly names blob."); + } + + var typeInfo = new TypeMapManagedTypeInfo { + AssemblyName = entry.AssemblyName, + ManagedTypeName = entry.ManagedName, + + assembly_name_index = (uint)assemblyNameOffset, + managed_type_token_id = entry.ManagedTypeTokenId, + }; + managedTypeInfos.Add (new StructureInstance (typeMapManagedTypeInfoStructureInfo, typeInfo)); + } + var map = new TypeMap { JavaToManagedCount = data.JavaToManagedMap == null ? 0 : data.JavaToManagedMap.Count, ManagedToJavaCount = data.ManagedToJavaMap == null ? 0 : data.ManagedToJavaMap.Count, entry_count = data.EntryCount, unique_assemblies_count = (ulong)data.UniqueAssemblies.Count, - assembly_names_blob_size = (ulong)assemblyNamesBlob.Count, }; type_map = new StructureInstance (typeMapStructureInfo, map); - module.AddGlobalVariable (TypeMapSymbol, type_map, LlvmIrVariableOptions.GlobalConstant); - - if (managedToJavaMap.Count > 0) { - module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LlvmIrVariableOptions.LocalConstant); - } - - if (javaToManagedMap.Count > 0) { - module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LlvmIrVariableOptions.LocalConstant); - } + module.AddGlobalVariable (TypeMapSymbol, type_map, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LlvmIrVariableOptions.LocalConstant); + module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LlvmIrVariableOptions.LocalConstant); + module.AddGlobalVariable (TypeMapManagedTypeInfoSymbol, managedTypeInfos, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable (TypeMapUsesHashesSymbol, typemap_uses_hashes, LlvmIrVariableOptions.GlobalConstant); module.AddGlobalVariable (UniqueAssembliesSymbol, uniqueAssemblies, LlvmIrVariableOptions.GlobalConstant); module.AddGlobalVariable (AssemblyNamesBlobSymbol, assemblyNamesBlob, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable (ManagedTypeNamesBlobSymbol, managedTypeNames, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable (JavaTypeNamesBlobSymbol, javaTypeNames, LlvmIrVariableOptions.GlobalConstant); } void MapStructures (LlvmIrModule module) @@ -246,5 +358,6 @@ void MapStructures (LlvmIrModule module) typeMapAssemblyStructureInfo = module.MapStructure (); typeMapEntryStructureInfo = module.MapStructure (); typeMapStructureInfo = module.MapStructure (); + typeMapManagedTypeInfoStructureInfo = module.MapStructure (); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs index a27b5fa5908..03662f4d459 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs @@ -145,6 +145,7 @@ sealed class TypeMapModule [NativeAssembler (UsesDataProvider = true)] public uint assembly_name_index; + public uint assembly_name_length; [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] public TypeMapModuleEntry map; @@ -174,12 +175,14 @@ sealed class TypeMapJava [NativeAssembler (UsesDataProvider = true)] public uint managed_type_name_index; + public uint managed_type_name_length; [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public uint managed_type_token_id; [NativeAssembler (UsesDataProvider = true)] public uint java_name_index; + public uint java_name_length; } sealed class ModuleMapData @@ -214,11 +217,11 @@ sealed class ConstructionState { public List> MapModules; public Dictionary JavaTypesByName; - public List JavaNames; - public List ManagedTypeNames; public List> JavaMap; public List AllModulesData; - public List AssemblyNames; + public LlvmIrStringBlob AssemblyNamesBlob; + public LlvmIrStringBlob JavaTypeNamesBlob; + public LlvmIrStringBlob ManagedTypeNamesBlob; } readonly NativeTypeMappingData mappingData; @@ -259,7 +262,6 @@ protected override void Construct (LlvmIrModule module) var cs = new ConstructionState (); cs.JavaTypesByName = new Dictionary (StringComparer.Ordinal); - cs.JavaNames = new List (); InitJavaMap (cs); InitMapModules (cs); HashJavaNames (cs); @@ -296,9 +298,10 @@ protected override void Construct (LlvmIrModule module) } module.AddGlobalVariable ("java_to_managed_map", cs.JavaMap, LlvmIrVariableOptions.GlobalConstant, " Java to managed map"); - module.AddGlobalVariable ("java_type_names", cs.JavaNames, LlvmIrVariableOptions.GlobalConstant, " Java type names"); - module.AddGlobalVariable ("managed_type_names", cs.ManagedTypeNames, LlvmIrVariableOptions.GlobalConstant, " Managed type names"); - module.AddGlobalVariable ("managed_assembly_names", cs.AssemblyNames, LlvmIrVariableOptions.GlobalConstant, " Managed assembly names"); + module.AddGlobalVariable ("java_type_names", cs.JavaTypeNamesBlob, LlvmIrVariableOptions.GlobalConstant, " Java type names"); + module.AddGlobalVariable ("java_type_names_size", (ulong)cs.JavaTypeNamesBlob.Size, LlvmIrVariableOptions.GlobalConstant, " Java type names blob size"); + module.AddGlobalVariable ("managed_type_names", cs.ManagedTypeNamesBlob, LlvmIrVariableOptions.GlobalConstant, " Managed type names"); + module.AddGlobalVariable ("managed_assembly_names", cs.AssemblyNamesBlob, LlvmIrVariableOptions.GlobalConstant, " Managed assembly names"); } void SortEntriesAndUpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState) @@ -398,21 +401,24 @@ void InitJavaMap (ConstructionState cs) { var seenManagedTypeNames = new Dictionary (StringComparer.Ordinal); cs.JavaMap = new List> (); - cs.ManagedTypeNames = new List (); + cs.ManagedTypeNamesBlob = new (); + cs.JavaTypeNamesBlob = new (); TypeMapJava map_entry; foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { string assemblyName = mappingData.Modules[entry.ModuleIndex].AssemblyName; - uint managedTypeNameIndex = GetEntryIndex (entry.ManagedTypeName, seenManagedTypeNames, cs.ManagedTypeNames); - cs.JavaNames.Add (entry.JavaName); + (int managedTypeNameIndex, int managedTypeNameLength) = cs.ManagedTypeNamesBlob.Add (entry.ManagedTypeName); + (int javaTypeNameIndex, int javaTypeNameLength) = cs.JavaTypeNamesBlob.Add (entry.JavaName); map_entry = new TypeMapJava { - ManagedTypeName = cs.ManagedTypeNames[(int)managedTypeNameIndex], + ManagedTypeName = entry.ManagedTypeName, module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, - managed_type_name_index = managedTypeNameIndex, + managed_type_name_index = (uint)managedTypeNameIndex, + managed_type_name_length = (uint)managedTypeNameLength, managed_type_token_id = entry.Token, - java_name_index = (uint)(cs.JavaNames.Count - 1), + java_name_index = (uint)javaTypeNameIndex, + java_name_length = (uint)javaTypeNameLength, JavaName = entry.JavaName, }; @@ -426,9 +432,8 @@ void InitMapModules (ConstructionState cs) var seenAssemblyNames = new Dictionary (StringComparer.OrdinalIgnoreCase); cs.MapModules = new List> (); - cs.AssemblyNames = new List (); + cs.AssemblyNamesBlob = new (); foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { - uint assemblyNameIndex = GetEntryIndex (data.AssemblyName, seenAssemblyNames, cs.AssemblyNames); string mapName = $"module{moduleCounter++}_managed_to_java"; string duplicateMapName; @@ -438,17 +443,19 @@ void InitMapModules (ConstructionState cs) duplicateMapName = $"{mapName}_duplicates"; } + (int assemblyNameIndex, int assemblyNameLength) = cs.AssemblyNamesBlob.Add (data.AssemblyName); var map_module = new TypeMapModule { MVID = data.Mvid, MapSymbolName = mapName, DuplicateMapSymbolName = duplicateMapName.Length == 0 ? null : duplicateMapName, Data = data, - AssemblyName = cs.AssemblyNames[(int)assemblyNameIndex], + AssemblyName = data.AssemblyName, module_uuid = data.MvidBytes, entry_count = (uint)data.Types.Length, duplicate_count = (uint)data.DuplicateTypes.Count, - assembly_name_index = assemblyNameIndex, + assembly_name_index = (uint)assemblyNameIndex, + assembly_name_length = (uint)assemblyNameLength, }; cs.MapModules.Add (new StructureInstance (typeMapModuleStructureInfo, map_module)); diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 5ca6b4f7877..8103c70030b 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -433,6 +433,12 @@ void Host::Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, int methods_len = env->GetStringLength (methods); const jchar *methods_ptr = env->GetStringChars (methods, nullptr); + dynamic_local_string managed_type_name; + const char *mt_ptr = env->GetStringUTFChars (managedType, nullptr); + managed_type_name.assign (mt_ptr, strlen (mt_ptr)); + log_debug (LOG_ASSEMBLY, "Registering type: '{}'", managed_type_name.get ()); + env->ReleaseStringUTFChars (managedType, mt_ptr); + // TODO: must attach thread to the runtime here jnienv_register_jni_natives (managedType_ptr, managedType_len, nativeClass, methods_ptr, methods_len); diff --git a/src/native/clr/host/internal-pinvokes.cc b/src/native/clr/host/internal-pinvokes.cc index 4d801afe032..2bcca553fd6 100644 --- a/src/native/clr/host/internal-pinvokes.cc +++ b/src/native/clr/host/internal-pinvokes.cc @@ -28,12 +28,12 @@ void _monodroid_gref_log_delete (jobject handle, char type, const char *threadNa const char* clr_typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept { - return TypeMapper::typemap_managed_to_java (typeName, mvid); + return TypeMapper::managed_to_java (typeName, mvid); } bool clr_typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept { - return TypeMapper::typemap_java_to_managed (java_type_name, assembly_name, managed_type_token_id); + return TypeMapper::java_to_managed (java_type_name, assembly_name, managed_type_token_id); } void monodroid_log (LogLevel level, LogCategories category, const char *message) noexcept diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index 43194203261..012feaa59b2 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -64,44 +64,84 @@ namespace { } #if defined(DEBUG) -[[gnu::always_inline]] -auto TypeMapper::typemap_type_to_type_debug (const char *typeName, const TypeMapEntry *map, std::string_view const& from_name, std::string_view const& to_name) noexcept -> const char* +[[gnu::always_inline, gnu::flatten]] +auto TypeMapper::find_index_by_name (const char *typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) noexcept -> ssize_t { - log_debug (LOG_ASSEMBLY, "Looking up {} type '{}'", from_name, optional_string (typeName)); - auto equal = [](TypeMapEntry const& entry, const char *key) -> bool { - if (entry.from == nullptr) { + log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses strings", from_name, to_name); + + auto equal = [](TypeMapEntry const& entry, const char *key, const char (&name_map)[]) -> bool { + if (entry.from == std::numeric_limits::max ()) [[unlikely]] { return 1; } - return strcmp (entry.from, key) == 0; + const char *type_name = &name_map[entry.from]; + return strcmp (type_name, key) == 0; }; - auto less_than = [](TypeMapEntry const& entry, const char *key) -> bool { - if (entry.from == nullptr) { + auto less_than = [](TypeMapEntry const& entry, const char *key, const char (&name_map)[]) -> bool { + if (entry.from == std::numeric_limits::max ()) [[unlikely]] { return 1; } - return strcmp (entry.from, key) < 0; + const char *type_name = &name_map[entry.from]; + return strcmp (type_name, key) < 0; }; - ssize_t idx = Search::binary_search (typeName, map, type_map.entry_count); - if (idx >= 0) [[likely]] { - log_debug ( - LOG_ASSEMBLY, - "{} type '{}' maps to {} type '{}'", - from_name, - optional_string (typeName), - to_name, - optional_string (type_map.managed_to_java[idx].to) - ); - return type_map.managed_to_java[idx].to; + return Search::binary_search (name_map, typeName, map, type_map.entry_count); +} + +[[gnu::always_inline, gnu::flatten]] +auto TypeMapper::find_index_by_hash (const char *typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) noexcept -> ssize_t +{ + if (!typemap_use_hashes) [[unlikely]] { + return find_index_by_name (typeName, map, name_map, from_name, to_name); } - return nullptr; + log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses hashes", from_name, to_name); + + auto equal = [](TypeMapEntry const& entry, hash_t key) -> bool { + if (entry.from == std::numeric_limits::max ()) [[unlikely]] { + return 1; + } + + return entry.from_hash == key; + }; + + auto less_than = [](TypeMapEntry const& entry, hash_t key) -> bool { + if (entry.from == std::numeric_limits::max ()) [[unlikely]] { + return 1; + } + + return entry.from_hash < key; + }; + hash_t type_name_hash = xxhash::hash (typeName, strlen (typeName)); + return Search::binary_search (type_name_hash, map, type_map.entry_count); } -[[gnu::always_inline]] -auto TypeMapper::typemap_managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char* +[[gnu::always_inline, gnu::flatten]] +auto TypeMapper::index_to_name (ssize_t idx, const char* typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) -> const char* +{ + if (idx < 0) [[unlikely]] { + log_debug (LOG_ASSEMBLY, "typemap: unable to map from {} type '{}' to {} type", from_name, typeName, to_name); + return nullptr; + } + + TypeMapEntry const& entry = map[idx]; + const char *mapped_name = &name_map[entry.to]; + + log_debug ( + LOG_ASSEMBLY, + "typemap: {} type '{}' maps to {} type '{}'", + from_name, + optional_string (typeName), + to_name, + optional_string (mapped_name) + ); + return mapped_name; +} + +[[gnu::always_inline, gnu::flatten]] +auto TypeMapper::managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char* { dynamic_local_path_string full_type_name; full_type_name.append (typeName); @@ -115,13 +155,19 @@ auto TypeMapper::typemap_managed_to_java_debug (const char *typeName, const uint if (idx >= 0) [[likely]] { TypeMapAssembly const& assm = type_map_unique_assemblies[idx]; full_type_name.append (", "sv); - full_type_name.append (&type_map_assembly_names_blob[assm.name_offset], assm.name_length); - log_debug (LOG_ASSEMBLY, "Fixed-up type name: '{}'", full_type_name.get ()); + + // We explicitly trust the build process here, with regards to validity of offsets + full_type_name.append (&type_map_assembly_names[assm.name_offset], assm.name_length); } else { - log_warn (LOG_ASSEMBLY, "Unable to look up assembly name for type '{}', trying without it.", typeName); + log_warn (LOG_ASSEMBLY, "typemap: unable to look up assembly name for type '{}', trying without it.", typeName); } - return typemap_type_to_type_debug (full_type_name.get (), type_map.managed_to_java, MANAGED, JAVA); + // If hashes are used for matching, the type names array is not used. If, however, string-based matching is in + // effect, the managed type name is looked up and then... + idx = find_index_by_hash (full_type_name.get (), type_map.managed_to_java, type_map_managed_type_names, MANAGED, JAVA); + + // ...either method gives us index into the Java type names array + return index_to_name (idx, full_type_name.get (), type_map.managed_to_java, type_map_java_type_names, MANAGED, JAVA); } #endif // def DEBUG @@ -167,7 +213,7 @@ auto TypeMapper::find_managed_to_java_map_entry (hash_t name_hash, const TypeMap } [[gnu::always_inline]] -auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const uint8_t *mvid) noexcept -> const char* +auto TypeMapper::managed_to_java_release (const char *typeName, const uint8_t *mvid) noexcept -> const char* { const TypeMapModule *match = find_module_entry (mvid, managed_to_java_map, managed_to_java_map_module_count); if (match == nullptr) { @@ -207,7 +253,7 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui optional_string (typeName), name_hash, MonoGuidString (mvid).c_str (), - optional_string (managed_assembly_names[match->assembly_name_index]) + std::string_view (&managed_assembly_names[match->assembly_name_index], match->assembly_name_length) ); return nullptr; } @@ -220,21 +266,21 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui optional_string (typeName), name_hash, MonoGuidString (mvid).c_str (), - optional_string (managed_assembly_names[match->assembly_name_index]), + std::string_view (&managed_assembly_names[match->assembly_name_index], match->assembly_name_length), entry->java_map_index ); return nullptr; } TypeMapJava const& java_entry = java_to_managed_map[entry->java_map_index]; - if (java_entry.java_name_index >= java_type_count) [[unlikely]] { + if (java_entry.java_name_index >= java_type_names_size) [[unlikely]] { log_warn ( LOG_ASSEMBLY, "typemap: managed type '{}' (hash {:x}) in module [{}] ({}) points to invalid Java type at index {} (invalid type name index {})", optional_string (typeName), name_hash, MonoGuidString (mvid).c_str (), - optional_string (managed_assembly_names[match->assembly_name_index]), + std::string_view (&managed_assembly_names[match->assembly_name_index], match->assembly_name_length), entry->java_map_index, java_entry.java_name_index ); @@ -242,7 +288,7 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui return nullptr; } - const char *ret = java_type_names[java_entry.java_name_index]; + const char *ret = &java_type_names[java_entry.java_name_index]; if (ret == nullptr) [[unlikely]] { log_warn (LOG_ASSEMBLY, "typemap: empty Java type name returned for entry at index {}", entry->java_map_index); } @@ -253,7 +299,7 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui optional_string (typeName), name_hash, MonoGuidString (mvid).c_str (), - optional_string (managed_assembly_names[match->assembly_name_index]), + std::string_view (&managed_assembly_names[match->assembly_name_index], match->assembly_name_length), ret ); @@ -262,9 +308,9 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui #endif // def RELEASE [[gnu::flatten]] -auto TypeMapper::typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char* +auto TypeMapper::managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char* { - log_debug (LOG_ASSEMBLY, "typemap_managed_to_java: looking up type '{}'", optional_string (typeName)); + log_debug (LOG_ASSEMBLY, "managed_to_java: looking up type '{}'", optional_string (typeName)); if (FastTiming::enabled ()) [[unlikely]] { internal_timing.start_event (TimingEventKind::ManagedToJava); } @@ -274,12 +320,14 @@ auto TypeMapper::typemap_managed_to_java (const char *typeName, const uint8_t *m return nullptr; } - const char *ret = nullptr; + auto do_map = [&typeName, &mvid]() -> const char* { #if defined(RELEASE) - ret = typemap_managed_to_java_release (typeName, mvid); + return managed_to_java_release (typeName, mvid); #else - ret = typemap_managed_to_java_debug (typeName, mvid); + return managed_to_java_debug (typeName, mvid); #endif + }; + const char *ret = do_map (); if (FastTiming::enabled ()) [[unlikely]] { internal_timing.end_event (); @@ -290,12 +338,39 @@ auto TypeMapper::typemap_managed_to_java (const char *typeName, const uint8_t *m #if defined(DEBUG) [[gnu::flatten]] -auto TypeMapper::typemap_java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +auto TypeMapper::java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool { - // FIXME: this is currently VERY broken - *assembly_name = nullptr; - *managed_type_token_id = 0; - return typemap_type_to_type_debug (java_type_name, type_map.java_to_managed, JAVA, MANAGED); + if (assembly_name == nullptr || managed_type_token_id == nullptr) [[unlikely]] { + log_warn (LOG_ASSEMBLY, "Managed land called java-to-managed mapping function with invalid pointers"); + return false; + } + + // We need to find entry matching the Java type name, which will then... + ssize_t idx = find_index_by_name (java_type_name, type_map.java_to_managed, type_map_java_type_names, JAVA, MANAGED); + + // ..provide us with the managed type name index + const char *name = index_to_name (idx, java_type_name, type_map.java_to_managed, type_map_managed_type_names, JAVA, MANAGED); + if (name == nullptr) { + *assembly_name = nullptr; + *managed_type_token_id = 0; + return false; + } + + // We explicitly trust the build process here, with regards to the size of the arrays + TypeMapManagedTypeInfo const& type_info = type_map_managed_type_info[idx]; + *assembly_name = &type_map_assembly_names[type_info.assembly_name_index]; + *managed_type_token_id = type_info.managed_type_token_id; + + log_debug ( + LOG_ASSEMBLY, + "Mapped Java type '{}' to managed type '{}' in assembly '{}' and with token '{:x}'", + optional_string (java_type_name), + name, + *assembly_name, + *managed_type_token_id + ); + + return true; } #else // def DEBUG @@ -311,7 +386,7 @@ auto TypeMapper::find_java_to_managed_entry (hash_t name_hash) noexcept -> const } [[gnu::flatten]] -auto TypeMapper::typemap_java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +auto TypeMapper::java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool { if (java_type_name == nullptr || assembly_name == nullptr || managed_type_token_id == nullptr) [[unlikely]] { if (java_type_name == nullptr) { @@ -358,16 +433,16 @@ auto TypeMapper::typemap_java_to_managed_release (const char *java_type_name, ch } TypeMapModule const &module = managed_to_java_map[java_entry->module_index]; - *assembly_name = managed_assembly_names[module.assembly_name_index]; + *assembly_name = &managed_assembly_names[module.assembly_name_index]; *managed_type_token_id = java_entry->managed_type_token_id; log_debug ( LOG_ASSEMBLY, "Java type '{}' corresponds to managed type '{}' (token 0x{:x} in assembly '{}')", optional_string (java_type_name), - optional_string (managed_type_names[java_entry->managed_type_name_index]), + std::string_view (&managed_type_names[java_entry->managed_type_name_index], java_entry->managed_type_name_length), *managed_type_token_id, - optional_string (*assembly_name) + std::string_view (&managed_assembly_names[module.assembly_name_index], module.assembly_name_length) ); return true; @@ -375,9 +450,9 @@ auto TypeMapper::typemap_java_to_managed_release (const char *java_type_name, ch #endif // ndef DEBUG [[gnu::flatten]] -auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +auto TypeMapper::java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool { - log_debug (LOG_ASSEMBLY, "typemap_java_to_managed: looking up type '{}'", optional_string (java_type_name)); + log_debug (LOG_ASSEMBLY, "java_to_managed: looking up type '{}'", optional_string (java_type_name)); if (FastTiming::enabled ()) [[unlikely]] { internal_timing.start_event (TimingEventKind::JavaToManaged); } @@ -389,9 +464,9 @@ auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const bool ret; #if defined(RELEASE) - ret = typemap_java_to_managed_release (java_type_name, assembly_name, managed_type_token_id); + ret = java_to_managed_release (java_type_name, assembly_name, managed_type_token_id); #else - ret = typemap_java_to_managed_debug (java_type_name, assembly_name, managed_type_token_id); + ret = java_to_managed_debug (java_type_name, assembly_name, managed_type_token_id); #endif if (FastTiming::enabled ()) [[unlikely]] { diff --git a/src/native/clr/include/host/typemap.hh b/src/native/clr/include/host/typemap.hh index fa15db569cf..eea9564cc98 100644 --- a/src/native/clr/include/host/typemap.hh +++ b/src/native/clr/include/host/typemap.hh @@ -14,22 +14,24 @@ namespace xamarin::android { static constexpr std::string_view JAVA { "Java" }; public: - static auto typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char*; - static auto typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; + static auto managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char*; + static auto java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; private: #if defined(RELEASE) static auto compare_mvid (const uint8_t *mvid, TypeMapModule const& module) noexcept -> int; static auto find_module_entry (const uint8_t *mvid, const TypeMapModule *entries, size_t entry_count) noexcept -> const TypeMapModule*; static auto find_managed_to_java_map_entry (hash_t name_hash, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry*; - static auto typemap_managed_to_java_release (const char *typeName, const uint8_t *mvid) noexcept -> const char*; - static auto typemap_java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; + static auto managed_to_java_release (const char *typeName, const uint8_t *mvid) noexcept -> const char*; + static auto java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; static auto find_java_to_managed_entry (hash_t name_hash) noexcept -> const TypeMapJava*; #else - static auto typemap_type_to_type_debug (const char *typeName, const TypeMapEntry *map, std::string_view const& from_name, std::string_view const& to_name) noexcept -> const char*; - static auto typemap_managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char*; - static auto typemap_java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; + static auto index_to_name (ssize_t index, const char *typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) -> const char*; + static auto find_index_by_hash (const char *typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) noexcept -> ssize_t; + static auto find_index_by_name (const char *typeName, const TypeMapEntry *map, const char (&name_map)[], std::string_view const& from_name, std::string_view const& to_name) noexcept -> ssize_t; + static auto managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char*; + static auto java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; #endif }; } diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index b8717e5e7eb..8caa09323d4 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -56,10 +56,23 @@ struct TypeMapIndexHeader uint32_t module_file_name_width; }; +// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +// +// If any of the members is set to maximum uint32_t value it means the entry is ignored (treated +// as equivalent to `nullptr` if the member was a pointer). The reasoning is that no string could +// begin at this offset (well, an empty string could, but we don't have those here) struct TypeMapEntry { - const char *from; - const char *to; + const uint32_t from; + const xamarin::android::hash_t from_hash; + const uint32_t to; +}; + +// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +struct TypeMapManagedTypeInfo +{ + const uint32_t assembly_name_index; + const uint32_t managed_type_token_id; }; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -67,11 +80,11 @@ struct TypeMap { uint32_t entry_count; uint64_t unique_assemblies_count; - uint64_t assembly_names_blob_size; const TypeMapEntry *java_to_managed; const TypeMapEntry *managed_to_java; }; +// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs struct TypeMapAssembly { xamarin::android::hash_t mvid_hash; @@ -91,6 +104,7 @@ struct TypeMapModule uint32_t entry_count; uint32_t duplicate_count; uint32_t assembly_name_index; + uint32_t assembly_name_length; TypeMapModuleEntry const *map; TypeMapModuleEntry const *duplicate_map; }; @@ -99,8 +113,10 @@ struct TypeMapJava { uint32_t module_index; uint32_t managed_type_name_index; + uint32_t managed_type_name_length; uint32_t managed_type_token_id; uint32_t java_name_index; + uint32_t java_name_length; }; #endif @@ -319,15 +335,20 @@ extern "C" { [[gnu::visibility("default")]] extern const uint64_t format_tag; #if defined (DEBUG) + [[gnu::visibility("default")]] extern const bool typemap_use_hashes; [[gnu::visibility("default")]] extern const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs + [[gnu::visibility("default")]] extern const TypeMapManagedTypeInfo type_map_managed_type_info[]; [[gnu::visibility("default")]] extern const TypeMapAssembly type_map_unique_assemblies[]; - [[gnu::visibility("default")]] extern const char type_map_assembly_names_blob[]; + [[gnu::visibility("default")]] extern const char type_map_assembly_names[]; + [[gnu::visibility("default")]] extern const char type_map_managed_type_names[]; + [[gnu::visibility("default")]] extern const char type_map_java_type_names[]; #else [[gnu::visibility("default")]] extern const uint32_t managed_to_java_map_module_count; [[gnu::visibility("default")]] extern const uint32_t java_type_count; - [[gnu::visibility("default")]] extern const char* const java_type_names[]; - [[gnu::visibility("default")]] extern const char* const managed_type_names[]; - [[gnu::visibility("default")]] extern const char* const managed_assembly_names[]; + [[gnu::visibility("default")]] extern const char java_type_names[]; + [[gnu::visibility("default")]] extern const uint64_t java_type_names_size; + [[gnu::visibility("default")]] extern const char managed_type_names[]; + [[gnu::visibility("default")]] extern const char managed_assembly_names[]; [[gnu::visibility("default")]] extern TypeMapModule managed_to_java_map[]; [[gnu::visibility("default")]] extern const TypeMapJava java_to_managed_map[]; [[gnu::visibility("default")]] extern const xamarin::android::hash_t java_to_managed_hashes[]; diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc index 146414e0dc4..7919083ef1f 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -10,26 +10,29 @@ const uint64_t format_tag = FORMAT_TAG; #if defined (DEBUG) static TypeMapEntry java_to_managed[] = {}; - static TypeMapEntry managed_to_java[] = {}; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs const TypeMap type_map = { .entry_count = 0, .unique_assemblies_count = 0, - .assembly_names_blob_size = 0, .java_to_managed = java_to_managed, .managed_to_java = managed_to_java, }; +const bool typemap_use_hashes = true; +const TypeMapManagedTypeInfo type_map_managed_type_info[] = {}; const TypeMapAssembly type_map_unique_assemblies[] = {}; -const char type_map_assembly_names_blob[] = {}; +const char type_map_assembly_names[] = {}; +const char type_map_managed_type_names[] = {}; +const char type_map_java_type_names[] = {}; #else const uint32_t managed_to_java_map_module_count = 0; const uint32_t java_type_count = 0; -const char* const java_type_names[] = {}; -const char* const managed_type_names[] = {}; -const char* const managed_assembly_names[] = {}; +const char java_type_names[] = {}; +const uint64_t java_type_names_size = 0; +const char managed_type_names[] = {}; +const char managed_assembly_names[] = {}; TypeMapModule managed_to_java_map[] = {}; const TypeMapJava java_to_managed_map[] = {}; const xamarin::android::hash_t java_to_managed_hashes[] = {}; diff --git a/src/native/common/include/runtime-base/search.hh b/src/native/common/include/runtime-base/search.hh index 4c789b1bffc..855bac976b0 100644 --- a/src/native/common/include/runtime-base/search.hh +++ b/src/native/common/include/runtime-base/search.hh @@ -9,8 +9,32 @@ namespace xamarin::android { class Search final { public: + // Code duplication in the two functions below is lamentable, but avoiding it would require + // making the code much uglier (and harder to read) with meta programming tricks, just not worth it. + template + [[gnu::always_inline, gnu::flatten]] + static ssize_t binary_search (const TState& state, TKey key, const T *arr, size_t n) noexcept + { + static_assert (equal != nullptr, "equal is a required template parameter"); + static_assert (less_than != nullptr, "less_than is a required template parameter"); + + ssize_t left = -1z; + ssize_t right = static_cast(n); + + while (right - left > 1) { + ssize_t middle = (left + right) >> 1u; + if (less_than (arr[middle], key, state)) { + left = middle; + } else { + right = middle; + } + } + + return equal (arr[right], key, state) ? right : -1z; + } + template - [[gnu::always_inline]] + [[gnu::always_inline, gnu::flatten]] static ssize_t binary_search (TKey key, const T *arr, size_t n) noexcept { static_assert (equal != nullptr, "equal is a required template parameter"); @@ -32,13 +56,13 @@ namespace xamarin::android { } template - [[gnu::always_inline]] + [[gnu::always_inline, gnu::flatten]] static ssize_t binary_search (hash_t key, const T *arr, size_t n) noexcept { return binary_search (key, arr, n); } - [[gnu::always_inline]] + [[gnu::always_inline, gnu::flatten]] static ssize_t binary_search (hash_t key, const hash_t *arr, size_t n) noexcept { auto equal = [](hash_t const& entry, hash_t key) -> bool { return entry == key; };