From 5d5331c02ca064dd754807b40063a7442e741f22 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 00:02:02 +0200 Subject: [PATCH 01/18] [coreclr] Use CRC32 for LLVM typemap hashes Replace CoreCLR LLVM typemap xxHash lookups with CRC32 hashes from System.IO.Compression.Native. Keep collision-safe lookup by comparing the original managed or Java type name after matching the hash. This is a first step toward removing the CoreCLR xxHash dependency to reduce size while preserving runtime lookup performance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GenerateEmptyTypemapStub.cs | 4 +- .../Utilities/TypeMapHelper.cs | 8 +- ...eMappingDebugNativeAssemblyGeneratorCLR.cs | 35 +++-- ...appingReleaseNativeAssemblyGeneratorCLR.cs | 77 +++++------ src/native/clr/host/typemap.cc | 128 +++++++++++++----- src/native/clr/include/host/typemap.hh | 5 +- src/native/clr/include/xamarin-app.hh | 10 +- .../xamarin-app-stub/application_dso_stub.cc | 2 +- 8 files changed, 170 insertions(+), 99 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs index 47d16267cda..4d6ee43899a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs @@ -68,7 +68,7 @@ string GenerateStubLlvmIr (string abi) return header + """ %struct.TypeMap = type { i32, i32, ptr, ptr } %struct.TypeMapManagedTypeInfo = type { i64, i32, i32 } -%struct.TypeMapAssembly = type { i64 } +%struct.TypeMapAssembly = type { [16 x i8], i64, i64 } @type_map = dso_local constant %struct.TypeMap zeroinitializer, align 8 @typemap_use_hashes = dso_local constant i8 1, align 1 @@ -84,7 +84,7 @@ string GenerateStubLlvmIr (string abi) @managed_to_java_map_module_count = dso_local constant i32 0, align 4 @managed_to_java_map = dso_local constant [0 x i8] zeroinitializer, align 8 @java_to_managed_map = dso_local constant [0 x i8] zeroinitializer, align 8 -@java_to_managed_hashes = dso_local constant [0 x i64] zeroinitializer, align 8 +@java_to_managed_hashes = dso_local constant [0 x i32] zeroinitializer, align 4 @modules_map_data = dso_local constant [0 x i8] zeroinitializer, align 8 @modules_duplicates_data = dso_local constant [0 x i8] zeroinitializer, align 8 @java_type_count = dso_local constant i32 0, align 4 diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs index 6f8b7b541f9..260cfa8c7ce 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs @@ -21,15 +21,15 @@ public static ulong HashJavaName (string name, bool is64Bit) } /// - /// Hash the given Java type name for use in java-to-managed typemap array (CoreCLR version) + /// Hash the given type name for use in CoreCLR native typemap arrays. /// - public static ulong HashJavaNameForCLR (string name, bool is64Bit) + public static uint HashNameForCLR (string name) { if (name.Length == 0) { - return UInt64.MaxValue; + return UInt32.MaxValue; } - return HashString (name, Encoding.UTF8, is64Bit); + return Crc32.HashToUInt32 (Encoding.UTF8.GetBytes (name)); } // Java type names are always ASCII and typically 20-100 characters, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs index 4ef17765d10..f5deaf16fac 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -73,7 +73,7 @@ public override string GetComment (object data, string fieldName) { var entry = EnsureType (data); - if (MonoAndroidHelper.StringEquals ("mvid_hash", fieldName)) { + if (MonoAndroidHelper.StringEquals ("module_uuid", fieldName)) { return $" MVID: {entry.MVID}"; } @@ -118,7 +118,7 @@ sealed class TypeMapEntry public uint from; [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong from_hash; + public uint from_hash; [NativeAssembler (UsesDataProvider = true)] public uint to; @@ -174,8 +174,9 @@ sealed class TypeMapAssembly [NativeAssembler (Ignore = true)] public Guid MVID; - [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong mvid_hash; + [NativeAssembler (UsesDataProvider = true, InlineArray = true, InlineArraySize = 16)] + public byte[] module_uuid = []; + public ulong name_length; [NativeAssembler (UsesDataProvider = true)] @@ -224,12 +225,12 @@ protected override void Construct (LlvmIrModule module) // 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 - // 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 + // CRC32 collisions on managed type names should be rare, but if one happens we fall back to string matching. 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 (); + 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); @@ -238,7 +239,7 @@ protected override void Construct (LlvmIrModule module) To = entry.JavaName, from = (uint)managedTypeNameOffset, - from_hash = typemap_uses_hashes ? MonoAndroidHelper.GetXxHash (entry.ManagedName, is64Bit: true) : 0, + from_hash = typemap_uses_hashes ? TypeMapHelper.HashNameForCLR (entry.ManagedName) : 0, to = (uint)javaTypeNameOffset, }; managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); @@ -250,7 +251,7 @@ protected override void Construct (LlvmIrModule module) 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}'."); + Log.LogMessage ($"Detected CRC32 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; } @@ -282,7 +283,7 @@ protected override void Construct (LlvmIrModule module) Name = asm.Name, MVID = asm.MVID, - mvid_hash = MonoAndroidHelper.GetXxHash (asm.MVIDBytes, is64Bit: true), + module_uuid = asm.MVIDBytes, name_length = (ulong)assemblyNameLength, // without the trailing NUL name_offset = (ulong)assemblyNameOffset, }; @@ -298,7 +299,7 @@ protected override void Construct (LlvmIrModule module) return 1; } - return a.Instance.mvid_hash.CompareTo (b.Instance.mvid_hash); + return CompareBytes (a.Instance.module_uuid, b.Instance.module_uuid); }); var managedTypeInfos = new List> (); @@ -360,4 +361,16 @@ void MapStructures (LlvmIrModule module) typeMapStructureInfo = module.MapStructure (); typeMapManagedTypeInfoStructureInfo = module.MapStructure (); } + + static int CompareBytes (byte[] left, byte[] right) + { + for (int i = 0; i < left.Length; i++) { + int ret = left [i].CompareTo (right [i]); + if (ret != 0) { + return ret; + } + } + + return 0; + } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs index 284868e2bf8..b60b607d897 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs @@ -1,7 +1,6 @@ #nullable disable using System; -using System.Collections; using System.Collections.Generic; using Microsoft.Build.Utilities; @@ -69,8 +68,7 @@ public override string GetComment (object data, string fieldName) { var module_map_entry = EnsureType (data); - if (MonoAndroidHelper.StringEquals ("managed_type_name_hash_32", fieldName) || - MonoAndroidHelper.StringEquals ("managed_type_name_hash_64", fieldName)) { + if (MonoAndroidHelper.StringEquals ("managed_type_name_hash", fieldName)) { return $" managed type name: {module_map_entry.ManagedTypeName}"; } @@ -93,11 +91,13 @@ sealed class TypeMapModuleEntry [NativeAssembler (Ignore = true)] public string ManagedTypeName; - [NativeAssembler (UsesDataProvider = true, ValidTarget = NativeAssemblerValidTarget.ThirtyTwoBit, MemberName = "managed_type_name_hash", NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public uint managed_type_name_hash_32; + [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] + public uint managed_type_name_hash; - [NativeAssembler (UsesDataProvider = true, ValidTarget = NativeAssemblerValidTarget.SixtyFourBit, MemberName = "managed_type_name_hash", NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong managed_type_name_hash_64; + [NativeAssembler (UsesDataProvider = true)] + public uint managed_type_name_index; + + public uint managed_type_name_length; [NativeAssembler (UsesDataProvider = true)] public uint java_map_index; @@ -145,7 +145,7 @@ sealed class TypeMapJava public string ManagedTypeName; [NativeAssembler (Ignore = true)] - public ulong JavaNameHash; + public uint JavaNameHash; public uint module_index; @@ -175,7 +175,12 @@ sealed class JavaNameHashComparer : IComparer> { public int Compare (StructureInstance a, StructureInstance b) { - return a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash); + int hashCompare = a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash); + if (hashCompare != 0) { + return hashCompare; + } + + return StringComparer.Ordinal.Compare (a.Instance.JavaName, b.Instance.JavaName); } } @@ -241,7 +246,7 @@ protected override void Construct (LlvmIrModule module) // Java hashes are output before Java type map **and** managed modules, because they will also sort the Java map for us. // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save // time sorting only once. - var java_to_managed_hashes = new LlvmIrGlobalVariable (typeof(List), "java_to_managed_hashes") { + var java_to_managed_hashes = new LlvmIrGlobalVariable (typeof(List), "java_to_managed_hashes") { Comment = " Java types name hashes", BeforeWriteCallback = GenerateAndSortJavaHashes, BeforeWriteCallbackCallerState = cs, @@ -278,15 +283,18 @@ void SortEntriesAndUpdateJavaIndexes (LlvmIrVariable variable, LlvmIrModuleTarge var array = (LlvmIrSectionedArray>)variable.Value; foreach (LlvmIrArraySection> section in array.Sections) { - if (target.Is64Bit) { - section.Data.Sort ( - (object a, object b) => ((StructureInstance)a).Instance.managed_type_name_hash_64.CompareTo (((StructureInstance)b).Instance.managed_type_name_hash_64) - ); - } else { - section.Data.Sort ( - (object a, object b) => ((StructureInstance)a).Instance.managed_type_name_hash_32.CompareTo (((StructureInstance)b).Instance.managed_type_name_hash_32) - ); - } + section.Data.Sort ( + (object a, object b) => { + var entryA = ((StructureInstance)a).Instance; + var entryB = ((StructureInstance)b).Instance; + int hashCompare = entryA.managed_type_name_hash.CompareTo (entryB.managed_type_name_hash); + if (hashCompare != 0) { + return hashCompare; + } + + return StringComparer.Ordinal.Compare (entryA.ManagedTypeName, entryB.ManagedTypeName); + } + ); foreach (StructureInstance entry in section.Data) { entry.Instance.java_map_index = GetJavaEntryIndex (entry.Instance.JavaTypeMapEntry); @@ -322,30 +330,17 @@ void GenerateAndSortJavaHashes (LlvmIrVariable variable, LlvmIrModuleTarget targ for (int i = 0; i < cs.JavaMap.Count; i++) { TypeMapJava entry = cs.JavaMap[i].Instance; - entry.JavaNameHash = TypeMapHelper.HashJavaNameForCLR (entry.JavaName, target.Is64Bit); + entry.JavaNameHash = TypeMapHelper.HashNameForCLR (entry.JavaName); } - cs.JavaMap.Sort ((StructureInstance a, StructureInstance b) => a.Instance.JavaNameHash.CompareTo (b.Instance.JavaNameHash)); + cs.JavaMap.Sort (javaNameHashComparer); - Type listType; - IList hashes; - if (target.Is64Bit) { - listType = typeof(List); - var list = new List (); - foreach (StructureInstance si in cs.JavaMap) { - list.Add (si.Instance.JavaNameHash); - } - hashes = list; - } else { - listType = typeof(List); - var list = new List (); - foreach (StructureInstance si in cs.JavaMap) { - list.Add ((uint)si.Instance.JavaNameHash); - } - hashes = list; + var hashes = new List (); + foreach (StructureInstance si in cs.JavaMap) { + hashes.Add (si.Instance.JavaNameHash); } - gv.OverrideTypeAndValue (listType, hashes); + gv.OverrideTypeAndValue (typeof(List), hashes); } ConstructionState EnsureConstructionState (object? callerState) @@ -450,12 +445,14 @@ void PrepareMapModuleData (IEnumerable mod throw new InvalidOperationException ($"Internal error: Java type '{entry.JavaName}' not found in cache"); } + (int managedTypeNameIndex, int managedTypeNameLength) = cs.ManagedTypeNamesBlob.Add (entry.ManagedTypeName); var map_entry = new TypeMapModuleEntry { JavaTypeMapEntry = javaType, ManagedTypeName = entry.ManagedTypeName, - managed_type_name_hash_32 = (uint)MonoAndroidHelper.GetXxHash (entry.ManagedTypeName, is64Bit: false), - managed_type_name_hash_64 = MonoAndroidHelper.GetXxHash (entry.ManagedTypeName, is64Bit: true), + managed_type_name_hash = TypeMapHelper.HashNameForCLR (entry.ManagedTypeName), + managed_type_name_index = (uint)managedTypeNameIndex, + managed_type_name_length = (uint)managedTypeNameLength, java_map_index = UInt32.MaxValue, // will be set later, when the target is known }; moduleSection.Add (new StructureInstance (typeMapModuleEntryStructureInfo, map_entry)); diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index 8a52077d360..1ab4adb9eb3 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -1,15 +1,35 @@ #include +#include +#include +#include #include #include #include #include -#include #include using namespace xamarin::android; +extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int32_t len); + namespace { + [[gnu::always_inline]] + auto typemap_hash (const char *value, size_t len) noexcept -> uint32_t + { + if (len == 0) [[unlikely]] { + return std::numeric_limits::max (); + } + + return CompressionNative_Crc32 (0, reinterpret_cast(const_cast(value)), static_cast(len)); + } + + [[gnu::always_inline]] + auto same_string (const char *value, uint32_t value_length, const char *key, size_t key_length) noexcept -> bool + { + return value_length == key_length && strncmp (value, key, key_length) == 0; + } + class MonoGuidString { static inline constexpr size_t MVID_SIZE = 16; @@ -99,23 +119,35 @@ auto TypeMapper::find_index_by_hash (const char *typeName, const TypeMapEntry *m log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses hashes"sv, from_name, to_name); - auto equal = [](TypeMapEntry const& entry, hash_t key) -> bool { - if (entry.from == std::numeric_limits::max ()) [[unlikely]] { - return 1; + size_t type_name_length = strlen (typeName); + uint32_t type_name_hash = typemap_hash (typeName, type_name_length); + + size_t left = 0; + size_t right = type_map.entry_count; + while (left < right) { + size_t middle = (left + right) >> 1u; + TypeMapEntry const& entry = map[middle]; + if (entry.from != std::numeric_limits::max () && entry.from_hash < type_name_hash) { + left = middle + 1; + } else { + right = middle; } + } - return entry.from_hash == key; - }; + while (left < type_map.entry_count) { + TypeMapEntry const& entry = map[left]; + if (entry.from == std::numeric_limits::max () || entry.from_hash != type_name_hash) { + break; + } - auto less_than = [](TypeMapEntry const& entry, hash_t key) -> bool { - if (entry.from == std::numeric_limits::max ()) [[unlikely]] { - return 1; + const char *mapped_type_name = &name_map[entry.from]; + if (same_string (mapped_type_name, static_cast(strlen (mapped_type_name)), typeName, type_name_length)) { + return static_cast(left); } + left++; + } - 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); + return -1z; } [[gnu::always_inline, gnu::flatten]] @@ -146,11 +178,9 @@ auto TypeMapper::managed_to_java_debug (const char *typeName, const uint8_t *mvi dynamic_local_path_string full_type_name; full_type_name.append (typeName); - hash_t mvid_hash = xxhash::hash (reinterpret_cast(mvid), 16z); // we must hope managed land called us with valid data - - auto equal = [](TypeMapAssembly const& entry, hash_t key) -> bool { return entry.mvid_hash == key; }; - auto less_than = [](TypeMapAssembly const& entry, hash_t key) -> bool { return entry.mvid_hash < key; }; - ssize_t idx = Search::binary_search (mvid_hash, type_map_unique_assemblies, type_map.unique_assemblies_count); + auto equal = [](TypeMapAssembly const& entry, const uint8_t *key) -> bool { return memcmp (entry.module_uuid, key, sizeof(entry.module_uuid)) == 0; }; + auto less_than = [](TypeMapAssembly const& entry, const uint8_t *key) -> bool { return memcmp (entry.module_uuid, key, sizeof(entry.module_uuid)) < 0; }; + ssize_t idx = Search::binary_search (mvid, type_map_unique_assemblies, type_map.unique_assemblies_count); if (idx >= 0) [[likely]] { TypeMapAssembly const& assm = type_map_unique_assemblies[idx]; @@ -196,17 +226,30 @@ auto TypeMapper::find_module_entry (const uint8_t *mvid, const TypeMapModule *en } [[gnu::always_inline]] -auto TypeMapper::find_managed_to_java_map_entry (hash_t name_hash, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry* +auto TypeMapper::find_managed_to_java_map_entry (uint32_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry* { if (map == nullptr) { return nullptr; }; - auto equal = [](TypeMapModuleEntry const& entry, hash_t key) -> bool { return entry.managed_type_name_hash == key; }; - auto less_than = [](TypeMapModuleEntry const& entry, hash_t key) -> bool { return entry.managed_type_name_hash < key; }; - ssize_t idx = Search::binary_search (name_hash, map, entry_count); - if (idx >= 0) [[likely]] { - return &map[idx]; + size_t left = 0; + size_t right = entry_count; + while (left < right) { + size_t middle = (left + right) >> 1u; + if (map[middle].managed_type_name_hash < name_hash) { + left = middle + 1; + } else { + right = middle; + } + } + + while (left < entry_count && map[left].managed_type_name_hash == name_hash) { + TypeMapModuleEntry const& entry = map[left]; + const char *managed_type_name = &managed_type_names[entry.managed_type_name_index]; + if (same_string (managed_type_name, entry.managed_type_name_length, type_name, type_name_length)) { + return &entry; + } + left++; } return nullptr; @@ -226,12 +269,13 @@ auto TypeMapper::managed_to_java_release (const char *typeName, const uint8_t *m } log_debug (LOG_ASSEMBLY, "typemap: found module matching MVID [{}]"sv, MonoGuidString (mvid).c_str ()); - hash_t name_hash = xxhash::hash (typeName, strlen (typeName)); + size_t type_name_length = strlen (typeName); + uint32_t name_hash = typemap_hash (typeName, type_name_length); // We implicitly trust the build process that the indexes are correct. This is by design, the libxamarin-app.so built // with the application is immutable and the build process made sure that the data in it matches the application. const TypeMapModuleEntry *const map = &modules_map_data[match->map_index]; - const TypeMapModuleEntry *entry = find_managed_to_java_map_entry (name_hash, map, match->entry_count); + const TypeMapModuleEntry *entry = find_managed_to_java_map_entry (name_hash, typeName, type_name_length, map, match->entry_count); if (entry == nullptr) [[unlikely]] { if (match->duplicate_count > 0 && match->duplicate_map_index < std::numeric_limitsduplicate_map_index)>::max ()) { log_debug ( @@ -243,7 +287,7 @@ auto TypeMapper::managed_to_java_release (const char *typeName, const uint8_t *m ); const TypeMapModuleEntry *const duplicate_map = &modules_duplicates_data[match->duplicate_map_index]; - entry = find_managed_to_java_map_entry (name_hash, duplicate_map, match->duplicate_count); + entry = find_managed_to_java_map_entry (name_hash, typeName, type_name_length, duplicate_map, match->duplicate_count); } if (entry == nullptr) { @@ -375,14 +419,29 @@ auto TypeMapper::java_to_managed_debug (const char *java_type_name, char const** #else // def DEBUG [[gnu::always_inline]] -auto TypeMapper::find_java_to_managed_entry (hash_t name_hash) noexcept -> const TypeMapJava* +auto TypeMapper::find_java_to_managed_entry (uint32_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava* { - ssize_t idx = Search::binary_search (name_hash, java_to_managed_hashes, java_type_count); - if (idx < 0) [[unlikely]] { - return nullptr; + size_t left = 0; + size_t right = java_type_count; + while (left < right) { + size_t middle = (left + right) >> 1u; + if (java_to_managed_hashes[middle] < name_hash) { + left = middle + 1; + } else { + right = middle; + } } - return &java_to_managed_map[idx]; + while (left < java_type_count && java_to_managed_hashes[left] == name_hash) { + TypeMapJava const& entry = java_to_managed_map[left]; + const char *mapped_java_type_name = &java_type_names[entry.java_name_index]; + if (same_string (mapped_java_type_name, entry.java_name_length, java_type_name, java_type_name_length)) { + return &entry; + } + left++; + } + + return nullptr; } [[gnu::flatten]] @@ -419,8 +478,9 @@ auto TypeMapper::java_to_managed_release (const char *java_type_name, char const return false; } - hash_t name_hash = xxhash::hash (java_type_name, strlen (java_type_name)); - TypeMapJava const* java_entry = find_java_to_managed_entry (name_hash); + size_t java_type_name_length = strlen (java_type_name); + uint32_t name_hash = typemap_hash (java_type_name, java_type_name_length); + TypeMapJava const* java_entry = find_java_to_managed_entry (name_hash, java_type_name, java_type_name_length); if (java_entry == nullptr) { log_info ( LOG_ASSEMBLY, diff --git a/src/native/clr/include/host/typemap.hh b/src/native/clr/include/host/typemap.hh index eea9564cc98..2e191513350 100644 --- a/src/native/clr/include/host/typemap.hh +++ b/src/native/clr/include/host/typemap.hh @@ -4,7 +4,6 @@ #include #include "../runtime-base/logger.hh" -#include #include "../xamarin-app.hh" namespace xamarin::android { @@ -21,11 +20,11 @@ namespace xamarin::android { #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 find_managed_to_java_map_entry (uint32_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry*; 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*; + static auto find_java_to_managed_entry (uint32_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava*; #else 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; diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 39fbb97f822..8857a3041c5 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -46,7 +46,7 @@ static constexpr uint8_t MODULE_FORMAT_VERSION = 2; // Keep in sync with struct TypeMapEntry { const uint32_t from; - const xamarin::android::hash_t from_hash; + const uint32_t from_hash; const uint32_t to; }; @@ -69,14 +69,16 @@ struct TypeMap // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs struct TypeMapAssembly { - xamarin::android::hash_t mvid_hash; + uint8_t module_uuid[16]; uint64_t name_length; uint64_t name_offset; // into the assembly names blob }; #else struct TypeMapModuleEntry { - xamarin::android::hash_t managed_type_name_hash; + uint32_t managed_type_name_hash; + uint32_t managed_type_name_index; + uint32_t managed_type_name_length; uint32_t java_map_index; }; @@ -329,7 +331,7 @@ extern "C" { [[gnu::visibility("default")]] extern const TypeMapModuleEntry modules_map_data[]; [[gnu::visibility("default")]] extern const TypeMapModuleEntry modules_duplicates_data[]; [[gnu::visibility("default")]] extern const TypeMapJava java_to_managed_map[]; - [[gnu::visibility("default")]] extern const xamarin::android::hash_t java_to_managed_hashes[]; + [[gnu::visibility("default")]] extern const uint32_t java_to_managed_hashes[]; #endif [[gnu::visibility("default")]] extern uint32_t compressed_assembly_count; 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 14f33be4c29..cb27706e24a 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -37,7 +37,7 @@ const TypeMapModule managed_to_java_map[] = {}; const TypeMapModuleEntry modules_map_data[] = {}; const TypeMapModuleEntry modules_duplicates_data[] = {}; const TypeMapJava java_to_managed_map[] = {}; -const xamarin::android::hash_t java_to_managed_hashes[] = {}; +const uint32_t java_to_managed_hashes[] = {}; #endif uint32_t compressed_assembly_count = 0; From 57ed8a6ae661095c561ebd61ebf779499afa89d3 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 00:29:54 +0200 Subject: [PATCH 02/18] [coreclr] Use CRC32 for assembly store hashes Emit CoreCLR assembly stores with 32-bit CRC32 name hashes and update the CoreCLR native assembly-store lookup to use the shared CRC32 helper. Keep MonoVM assembly stores on the existing xxHash layout, bump the CoreCLR store format to v4, and update the mk2 store reader to infer index entry width from the header index size. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/AssemblyStoreBuilder.cs | 2 +- .../Utilities/AssemblyStoreGenerator.cs | 70 ++++++++++++++----- .../Utilities/TypeMapHelper.cs | 12 ++++ src/native/clr/host/assembly-store.cc | 11 +-- src/native/clr/host/typemap.cc | 20 ++---- src/native/clr/include/host/assembly-store.hh | 2 +- src/native/clr/include/runtime-base/crc32.hh | 26 +++++++ src/native/clr/include/xamarin-app.hh | 6 +- .../AssemblyStore/StoreReader_V2.Classes.cs | 4 ++ .../AssemblyStore/StoreReader_V2.cs | 22 +++++- 10 files changed, 129 insertions(+), 46 deletions(-) create mode 100644 src/native/clr/include/runtime-base/crc32.hh diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs index 2b976876b86..28bbef25a10 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs @@ -20,7 +20,7 @@ public AssemblyStoreBuilder (TaskLoggingHelper log, AndroidRuntime targetRuntime { this.log = log; this.targetRuntime = targetRuntime; - storeGenerator = new (log); + storeGenerator = new (log, targetRuntime); } public void AddAssembly (string assemblySourcePath, ITaskItem assemblyItem, bool includeDebugSymbols) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs index bad4cacb3f2..6bb3b2e6058 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs @@ -30,7 +30,7 @@ namespace Xamarin.Android.Tasks; // [INDEX_SIZE] uint; index size in bytes // // INDEX (variable size, HEADER.ENTRY_COUNT*2 entries, for assembly names with and without the extension) -// [NAME_HASH] uint on 32-bit platforms, ulong on 64-bit platforms; xxhash of the assembly name +// [NAME_HASH] uint CRC32 for CoreCLR; uint/ulong xxhash for MonoVM depending on target bitness // [DESCRIPTOR_INDEX] uint; index into in-store assembly descriptor array // [IGNORE] byte; if set to anything other than 0, the assembly is to be ignored when loading // @@ -49,12 +49,14 @@ namespace Xamarin.Android.Tasks; // partial class AssemblyStoreGenerator { - // The two constants below must match their counterparts in src/monodroid/jni/xamarin-app.hh + // The constants below must match their counterparts in src/native/*/include/xamarin-app.hh const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian, must match the BUNDLED_ASSEMBLIES_BLOB_MAGIC native constant // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones - const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant - const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; + const uint ASSEMBLY_STORE_FORMAT_VERSION_MONOVM_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_MONOVM_32BIT = 0x00000003; + const uint ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_64BIT = 0x80000004; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_32BIT = 0x00000004; const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; const uint ASSEMBLY_STORE_ABI_ARM = 0x00020000; @@ -63,10 +65,12 @@ partial class AssemblyStoreGenerator readonly TaskLoggingHelper log; readonly Dictionary> assemblies; + readonly AndroidRuntime targetRuntime; - public AssemblyStoreGenerator (TaskLoggingHelper log) + public AssemblyStoreGenerator (TaskLoggingHelper log, AndroidRuntime targetRuntime) { this.log = log; + this.targetRuntime = targetRuntime; assemblies = new Dictionary> (); } @@ -110,6 +114,9 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List (); var descriptors = new List (); + bool useCrc32NameHashes = targetRuntime == AndroidRuntime.CoreCLR; + bool use64BitNameHashes = is64Bit && !useCrc32NameHashes; + uint indexEntrySize = use64BitNameHashes ? AssemblyStoreIndexEntry.NativeSize64 : AssemblyStoreIndexEntry.NativeSize32; ulong namesSize = 0; foreach (AssemblyStoreAssemblyInfo info in infos) { @@ -117,7 +124,7 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List is64Bit ? AssemblyStoreIndexEntry.NativeSize64 : AssemblyStoreIndexEntry.NativeSize32; + return is64Bit ? ASSEMBLY_STORE_FORMAT_VERSION_MONOVM_64BIT : ASSEMBLY_STORE_FORMAT_VERSION_MONOVM_32BIT; } void CopyData (FileInfo? src, Stream dest, string storePath) @@ -254,12 +277,12 @@ AssemblyStoreHeader ReadHeader (BinaryReader reader) } #endif - void WriteIndex (BinaryWriter writer, StreamWriter manifestWriter, List index, List descriptors, bool is64Bit) + void WriteIndex (BinaryWriter writer, StreamWriter manifestWriter, List index, List descriptors, bool use64BitNameHashes) { index.Sort ((AssemblyStoreIndexEntry a, AssemblyStoreIndexEntry b) => a.name_hash.CompareTo (b.name_hash)); foreach (AssemblyStoreIndexEntry entry in index) { - if (is64Bit) { + if (use64BitNameHashes) { writer.Write (entry.name_hash); manifestWriter.Write ($"0x{entry.name_hash:x}"); } else { @@ -295,13 +318,15 @@ List ReadIndex (BinaryReader reader, AssemblyStoreHeade var index = new List ((int)header.index_entry_count); reader.BaseStream.Seek (AssemblyStoreHeader.NativeSize, SeekOrigin.Begin); - bool is64Bit = (header.version & ASSEMBLY_STORE_FORMAT_VERSION_64BIT) == ASSEMBLY_STORE_FORMAT_VERSION_64BIT; + uint indexEntrySize = GetIndexEntrySize (header); for (int i = 0; i < (int)header.index_entry_count; i++) { ulong name_hash; - if (is64Bit) { + if (indexEntrySize == AssemblyStoreIndexEntry.NativeSize64) { name_hash = reader.ReadUInt64 (); - } else { + } else if (indexEntrySize == AssemblyStoreIndexEntry.NativeSize32) { name_hash = reader.ReadUInt32 (); + } else { + throw new InvalidOperationException ($"Assembly store index entry size {indexEntrySize} is not supported"); } uint descriptor_index = reader.ReadUInt32 (); @@ -313,6 +338,15 @@ List ReadIndex (BinaryReader reader, AssemblyStoreHeade return index; } + static uint GetIndexEntrySize (AssemblyStoreHeader header) + { + if (header.index_entry_count == 0) { + return 0; + } + + return header.index_size / header.index_entry_count; + } + void WriteDescriptors (BinaryWriter writer, List descriptors) { foreach (AssemblyStoreEntryDescriptor desc in descriptors) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs index 260cfa8c7ce..fdd7995f186 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs @@ -32,6 +32,18 @@ public static uint HashNameForCLR (string name) return Crc32.HashToUInt32 (Encoding.UTF8.GetBytes (name)); } + /// + /// Hash the given bytes for use in CoreCLR native lookup tables. + /// + public static uint HashBytesForCLR (ReadOnlySpan bytes) + { + if (bytes.Length == 0) { + return UInt32.MaxValue; + } + + return Crc32.HashToUInt32 (bytes); + } + // Java type names are always ASCII and typically 20-100 characters, // so the encoded byte count is well within stackalloc limits. // The unsafe Encoding.GetBytes(char*, int, byte*, int) overload is diff --git a/src/native/clr/host/assembly-store.cc b/src/native/clr/host/assembly-store.cc index 2fdc7119d15..62bf0375d8a 100644 --- a/src/native/clr/host/assembly-store.cc +++ b/src/native/clr/host/assembly-store.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -178,11 +179,11 @@ auto AssemblyStore::get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData co } [[gnu::always_inline]] -auto AssemblyStore::find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry* +auto AssemblyStore::find_assembly_store_entry (uint32_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry* { - auto equal = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash == key; }; - auto less_than = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash < key; }; - ssize_t idx = Search::binary_search (hash, entries, entry_count); + auto equal = [](AssemblyStoreIndexEntry const& entry, uint32_t key) -> bool { return entry.name_hash == key; }; + auto less_than = [](AssemblyStoreIndexEntry const& entry, uint32_t key) -> bool { return entry.name_hash < key; }; + ssize_t idx = Search::binary_search (hash, entries, entry_count); if (idx >= 0) { return &entries[idx]; } @@ -191,7 +192,7 @@ auto AssemblyStore::find_assembly_store_entry (hash_t hash, const AssemblyStoreI auto AssemblyStore::open_assembly (std::string_view const& name, int64_t &size) noexcept -> void* { - hash_t name_hash = xxhash::hash (name.data (), name.length ()); + uint32_t name_hash = crc32_hash (name); if constexpr (Constants::is_debug_build) { // In fastdev mode we might not have any assembly store. diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index 1ab4adb9eb3..1c7ca84bba5 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -1,9 +1,9 @@ #include #include #include -#include #include +#include #include #include #include @@ -11,19 +11,7 @@ using namespace xamarin::android; -extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int32_t len); - namespace { - [[gnu::always_inline]] - auto typemap_hash (const char *value, size_t len) noexcept -> uint32_t - { - if (len == 0) [[unlikely]] { - return std::numeric_limits::max (); - } - - return CompressionNative_Crc32 (0, reinterpret_cast(const_cast(value)), static_cast(len)); - } - [[gnu::always_inline]] auto same_string (const char *value, uint32_t value_length, const char *key, size_t key_length) noexcept -> bool { @@ -120,7 +108,7 @@ auto TypeMapper::find_index_by_hash (const char *typeName, const TypeMapEntry *m log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses hashes"sv, from_name, to_name); size_t type_name_length = strlen (typeName); - uint32_t type_name_hash = typemap_hash (typeName, type_name_length); + uint32_t type_name_hash = crc32_hash (typeName, type_name_length); size_t left = 0; size_t right = type_map.entry_count; @@ -270,7 +258,7 @@ auto TypeMapper::managed_to_java_release (const char *typeName, const uint8_t *m log_debug (LOG_ASSEMBLY, "typemap: found module matching MVID [{}]"sv, MonoGuidString (mvid).c_str ()); size_t type_name_length = strlen (typeName); - uint32_t name_hash = typemap_hash (typeName, type_name_length); + uint32_t name_hash = crc32_hash (typeName, type_name_length); // We implicitly trust the build process that the indexes are correct. This is by design, the libxamarin-app.so built // with the application is immutable and the build process made sure that the data in it matches the application. @@ -479,7 +467,7 @@ auto TypeMapper::java_to_managed_release (const char *java_type_name, char const } size_t java_type_name_length = strlen (java_type_name); - uint32_t name_hash = typemap_hash (java_type_name, java_type_name_length); + uint32_t name_hash = crc32_hash (java_type_name, java_type_name_length); TypeMapJava const* java_entry = find_java_to_managed_entry (name_hash, java_type_name, java_type_name_length); if (java_entry == nullptr) { log_info ( diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh index eac5a30f5f1..1018122be80 100644 --- a/src/native/clr/include/host/assembly-store.hh +++ b/src/native/clr/include/host/assembly-store.hh @@ -27,7 +27,7 @@ namespace xamarin::android { // Returns a tuple of static auto get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, std::string_view const& name) noexcept -> std::tuple; - static auto find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry*; + static auto find_assembly_store_entry (uint32_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry*; private: static inline AssemblyStoreIndexEntry *assembly_store_hashes = nullptr; diff --git a/src/native/clr/include/runtime-base/crc32.hh b/src/native/clr/include/runtime-base/crc32.hh new file mode 100644 index 00000000000..b288b81ec4a --- /dev/null +++ b/src/native/clr/include/runtime-base/crc32.hh @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int32_t len); + +namespace xamarin::android { + [[gnu::always_inline]] + inline auto crc32_hash (const char *value, size_t len) noexcept -> uint32_t + { + if (len == 0) [[unlikely]] { + return std::numeric_limits::max (); + } + + return CompressionNative_Crc32 (0, reinterpret_cast(const_cast(value)), static_cast(len)); + } + + [[gnu::always_inline]] + inline auto crc32_hash (std::string_view const& value) noexcept -> uint32_t + { + return crc32_hash (value.data (), value.length ()); + } +} diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 8857a3041c5..f79969e4475 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -31,7 +31,7 @@ static constexpr uint32_t ASSEMBLY_STORE_ABI = 0x00040000; #endif // Increase whenever an incompatible change is made to the assembly store format -static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 3 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; +static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 4 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; static constexpr uint32_t MODULE_MAGIC_NAMES = 0x53544158; // 'XATS', little-endian static constexpr uint32_t MODULE_INDEX_MAGIC = 0x49544158; // 'XATI', little-endian @@ -140,7 +140,7 @@ struct CompressedAssemblyDescriptor // [INDEX_SIZE] uint; index size in bytes // // INDEX (variable size, HEADER.ENTRY_COUNT*2 entries, for assembly names with and without the extension) -// [NAME_HASH] uint on 32-bit platforms, ulong on 64-bit platforms; xxhash of the assembly name +// [NAME_HASH] uint; CRC32 of the assembly name // [DESCRIPTOR_INDEX] uint; index into in-store assembly descriptor array // [IGNORE] byte; if set to anything other than 0, the assembly is to be ignored when loading // @@ -173,7 +173,7 @@ struct [[gnu::packed]] AssemblyStoreHeader final struct [[gnu::packed]] AssemblyStoreIndexEntry final { - xamarin::android::hash_t name_hash; + uint32_t name_hash; uint32_t descriptor_index; uint8_t ignore; // Assembly should be ignored when loading, its data isn't actually there }; diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs index 3b2c8549ff2..480a15d8fca 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs @@ -30,6 +30,10 @@ public Header (uint magic, uint version, uint entry_count, uint index_entry_coun sealed class IndexEntry { + // We treat `bool` as `byte` here, since that's what gets written to the binary. + public const uint NativeSize32 = 2 * sizeof (uint) + sizeof (byte); + public const uint NativeSize64 = sizeof (ulong) + sizeof (uint) + sizeof (byte); + public readonly ulong name_hash; public readonly uint descriptor_index; public readonly bool ignore; diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs index e10d9a99ab4..3b0ac95e72c 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs @@ -13,6 +13,8 @@ partial class StoreReader_V2 : AssemblyStoreReader // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; + const uint ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_64BIT = 0x80000004; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_32BIT = 0x00000004; const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; @@ -79,6 +81,10 @@ public StoreReader_V2 (Stream store, string path) ASSEMBLY_STORE_FORMAT_VERSION_64BIT | ASSEMBLY_STORE_ABI_X64, ASSEMBLY_STORE_FORMAT_VERSION_32BIT | ASSEMBLY_STORE_ABI_ARM, ASSEMBLY_STORE_FORMAT_VERSION_32BIT | ASSEMBLY_STORE_ABI_X86, + ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_64BIT | ASSEMBLY_STORE_ABI_AARCH64, + ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_64BIT | ASSEMBLY_STORE_ABI_X64, + ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_32BIT | ASSEMBLY_STORE_ABI_ARM, + ASSEMBLY_STORE_FORMAT_VERSION_CORECLR_32BIT | ASSEMBLY_STORE_ABI_X86, }; } @@ -150,13 +156,16 @@ protected override void Prepare () StoreStream.Seek ((long)elfOffset + Header.NativeSize, SeekOrigin.Begin); using var reader = CreateReader (); + uint indexEntrySize = GetIndexEntrySize (); var index = new List (); for (uint i = 0; i < header.index_entry_count; i++) { ulong name_hash; - if (Is64Bit) { + if (indexEntrySize == IndexEntry.NativeSize64) { name_hash = reader.ReadUInt64 (); - } else { + } else if (indexEntrySize == IndexEntry.NativeSize32) { name_hash = (ulong)reader.ReadUInt32 (); + } else { + throw new InvalidOperationException ($"Assembly store '{StorePath}' index entry size {indexEntrySize} is not supported."); } uint descriptor_index = reader.ReadUInt32 (); @@ -213,5 +222,14 @@ protected override void Prepare () storeItems.Add (item); } Assemblies = storeItems.AsReadOnly (); + + uint GetIndexEntrySize () + { + if (header.index_entry_count == 0) { + return 0; + } + + return header.index_size / header.index_entry_count; + } } } From 14dad0dc6d219c574fd8c117a4dc6de41da59910 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 00:38:07 +0200 Subject: [PATCH 03/18] [coreclr] Use CRC32 for runtime DSO hashes Replace CoreCLR runtime property, DSO APK, and DSO/AOT cache xxHash lookups with CRC32 hashes from the shared native CRC32 helper. Update generated CoreCLR app config, desktop app stub data, and strict generated-layout test parsing for the smaller 32-bit hash fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/EnvironmentHelper.cs | 14 +++++----- ...icationConfigNativeAssemblyGeneratorCLR.cs | 19 ++++++------- src/native/clr/host/host.cc | 12 ++++----- .../clr/include/runtime-base/monodroid-dl.hh | 24 ++++++++--------- src/native/clr/include/xamarin-app.hh | 10 +++---- .../xamarin-app-stub/application_dso_stub.cc | 27 +++++++++---------- 6 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 927e4ccad70..60eb77c7ee9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -103,7 +103,7 @@ public sealed class DSOCacheEntry64 { // Hardcoded, by design - we want to know if there are any changes in the // native assembly layout. - public const uint NativeSize_CoreCLR = 32; + public const uint NativeSize_CoreCLR = 24; public const uint NativeSize_MonoVM = 40; public ulong hash; @@ -1135,13 +1135,13 @@ static List ReadDsoCache64_CoreCLR (EnvironmentFile envFile, Na string value; int index = i; - // uint64_t hash - (lineNumber, value) = ReadNextArrayIndex (envFile, parser, dsoCache, index++, expectedUInt64Types); - ulong hash = ConvertFieldToUInt64 ("hash", envFile.Path, parser.SourceFilePath, lineNumber, value); + // uint32_t hash + (lineNumber, value) = ReadNextArrayIndex (envFile, parser, dsoCache, index++, expectedUInt32Types); + ulong hash = ConvertFieldToUInt32 ("hash", envFile.Path, parser.SourceFilePath, lineNumber, value); - // uint64_t real_name_hash - (lineNumber, value) = ReadNextArrayIndex (envFile, parser, dsoCache, index++, expectedUInt64Types); - ulong real_name_hash = ConvertFieldToUInt64 ("real_name_hash", envFile.Path, parser.SourceFilePath, lineNumber, value); + // uint32_t real_name_hash + (lineNumber, value) = ReadNextArrayIndex (envFile, parser, dsoCache, index++, expectedUInt32Types); + ulong real_name_hash = ConvertFieldToUInt32 ("real_name_hash", envFile.Path, parser.SourceFilePath, lineNumber, value); // bool ignore (lineNumber, value) = ReadNextArrayIndex (envFile, parser, dsoCache, index++, ".byte"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index f4ef94d75b1..8f8ebae05e3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -55,10 +55,10 @@ sealed class DSOCacheEntry public string? RealName; [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong hash; + public uint hash; [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong real_name_hash; + public uint real_name_hash; public bool ignore; public bool is_jni_library; @@ -90,7 +90,7 @@ sealed class DSOApkEntry public string Name = String.Empty; [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong name_hash; + public uint name_hash; public uint offset; // offset into the APK public int fd; // apk file descriptor }; @@ -183,7 +183,7 @@ sealed class RuntimePropertyIndexEntry public string? HashedKey; [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public ulong key_hash; + public uint key_hash; public uint index; } @@ -474,7 +474,6 @@ void HashAndSortRuntimePropertiesIndex (LlvmIrVariable variable, LlvmIrModuleTar return; } - bool is64Bit = target.Is64Bit; foreach (StructureInstance instance in index) { if (instance.Obj == null) { throw new InvalidOperationException ("Internal error: runtime property index must not contain null entries"); @@ -485,7 +484,7 @@ void HashAndSortRuntimePropertiesIndex (LlvmIrVariable variable, LlvmIrModuleTar throw new InvalidOperationException ($"Internal error: runtime property index entry has unexpected type {instance.Obj.GetType ()}"); } - entry.key_hash = MonoAndroidHelper.GetXxHash (entry.HashedKey ?? "", is64Bit); + entry.key_hash = TypeMapHelper.HashNameForCLR (entry.HashedKey ?? ""); }; index.Sort ((StructureInstance a, StructureInstance b) => { @@ -584,13 +583,12 @@ void PopulateDsoApkEntries (LlvmIrVariable variable, LlvmIrModuleTarget target, throw new InvalidOperationException ($"Internal error: DSO apk entries count ({dso_apk_entries.Count}) is different to the native libraries count ({NativeLibraries.Count})."); } - bool is64Bit = target.Is64Bit; foreach (ITaskItem item in NativeLibraries) { string name = Path.GetFileName (item.ItemSpec); var entry = new DSOApkEntry { Name = name, - name_hash = MonoAndroidHelper.GetXxHash (name, is64Bit), + name_hash = TypeMapHelper.HashNameForCLR (name), offset = 0, fd = -1, }; @@ -640,7 +638,6 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob throw new InvalidOperationException ($"Internal error: DSO cache must not be empty"); } - bool is64Bit = target.Is64Bit; foreach (StructureInstance instance in cache) { if (instance.Obj == null) { throw new InvalidOperationException ("Internal error: DSO cache must not contain null entries"); @@ -651,8 +648,8 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob throw new InvalidOperationException ($"Internal error: DSO cache entry has unexpected type {instance.Obj.GetType ()}"); } - entry.hash = MonoAndroidHelper.GetXxHash (entry.HashedName ?? "", is64Bit); - entry.real_name_hash = MonoAndroidHelper.GetXxHash (entry.RealName ?? "", is64Bit); + entry.hash = TypeMapHelper.HashNameForCLR (entry.HashedName ?? ""); + entry.real_name_hash = TypeMapHelper.HashNameForCLR (entry.RealName ?? ""); } cache.Sort ((StructureInstance a, StructureInstance b) => { diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 3e84ef930d8..7df9ba58473 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -22,13 +22,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include using namespace xamarin::android; @@ -60,11 +60,11 @@ size_t Host::clr_get_runtime_property (const char *key, char *value_buffer, size return 0; } - hash_t key_hash = xxhash::hash (key, strlen (key)); + uint32_t key_hash = crc32_hash (key, strlen (key)); - auto equal = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash == key; }; - auto less_than = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash < key; }; - ssize_t idx = Search::binary_search (key_hash, runtime_property_index, application_config.number_of_runtime_properties); + auto equal = [](RuntimePropertyIndexEntry const& entry, uint32_t key) -> bool { return entry.key_hash == key; }; + auto less_than = [](RuntimePropertyIndexEntry const& entry, uint32_t key) -> bool { return entry.key_hash < key; }; + ssize_t idx = Search::binary_search (key_hash, runtime_property_index, application_config.number_of_runtime_properties); if (idx < 0) { log_debug (LOG_DEFAULT, "Runtime property '{}' not found"sv, key); return 0; @@ -154,7 +154,7 @@ auto Host::zip_scan_callback (std::string_view const& apk_path, int apk_fd, dyna log_debug (LOG_ASSEMBLY, "Found shared library in '{}': {}"sv, apk_path, entry_name.get ()); std::string_view lib_name { entry_name.get () + Zip::lib_prefix.length () }; - hash_t name_hash = xxhash::hash (lib_name.data (), lib_name.length ()); + uint32_t name_hash = crc32_hash (lib_name); log_debug (LOG_ASSEMBLY, "Library name is: {}; hash == 0x{:x}", lib_name, name_hash); DSOApkEntry *apk_entry = MonodroidDl::find_dso_apk_entry (name_hash); diff --git a/src/native/clr/include/runtime-base/monodroid-dl.hh b/src/native/clr/include/runtime-base/monodroid-dl.hh index cc2973b4515..5c75457cba9 100644 --- a/src/native/clr/include/runtime-base/monodroid-dl.hh +++ b/src/native/clr/include/runtime-base/monodroid-dl.hh @@ -8,10 +8,10 @@ #include -#include #include "../xamarin-app.hh" #include "android-system.hh" +#include #include #include #include "startup-aware-lock.hh" @@ -33,7 +33,7 @@ namespace xamarin::android template [[gnu::always_inline, gnu::flatten]] - static auto find_dso_cache_entry_common (hash_t hash) noexcept -> DSOCacheEntry* + static auto find_dso_cache_entry_common (uint32_t hash) noexcept -> DSOCacheEntry* { static_assert (WhichCache == CacheKind::AOT || WhichCache == CacheKind::DSO, "Unknown cache type specified"); @@ -50,9 +50,9 @@ namespace xamarin::android arr_size = application_config.number_of_dso_cache_entries; } - auto equal = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash == key; }; - auto less_than = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash < key; }; - ssize_t idx = Search::binary_search (hash, arr, arr_size); + auto equal = [](DSOCacheEntry const& entry, uint32_t key) -> bool { return entry.hash == key; }; + auto less_than = [](DSOCacheEntry const& entry, uint32_t key) -> bool { return entry.hash < key; }; + ssize_t idx = Search::binary_search (hash, arr, arr_size); if (idx >= 0) { return &arr[idx]; @@ -62,13 +62,13 @@ namespace xamarin::android } [[gnu::always_inline, gnu::flatten]] - static auto find_only_aot_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* + static auto find_only_aot_cache_entry (uint32_t hash) noexcept -> DSOCacheEntry* { return find_dso_cache_entry_common (hash); } [[gnu::always_inline, gnu::flatten]] - static auto find_only_dso_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* + static auto find_only_dso_cache_entry (uint32_t hash) noexcept -> DSOCacheEntry* { return find_dso_cache_entry_common (hash); } @@ -85,11 +85,11 @@ namespace xamarin::android } [[gnu::flatten]] - static auto find_dso_apk_entry (hash_t hash) -> DSOApkEntry* + static auto find_dso_apk_entry (uint32_t hash) -> DSOApkEntry* { - auto equal = [](DSOApkEntry const& entry, hash_t key) -> bool { return entry.name_hash == key; }; - auto less_than = [](DSOApkEntry const& entry, hash_t key) -> bool { return entry.name_hash < key; }; - ssize_t idx = Search::binary_search ( + auto equal = [](DSOApkEntry const& entry, uint32_t key) -> bool { return entry.name_hash == key; }; + auto less_than = [](DSOApkEntry const& entry, uint32_t key) -> bool { return entry.name_hash < key; }; + ssize_t idx = Search::binary_search ( hash, dso_apk_entries, application_config.number_of_shared_libraries ); @@ -152,7 +152,7 @@ namespace xamarin::android return nullptr; } - hash_t name_hash = xxhash::hash (name.data (), name.size ()); + uint32_t name_hash = crc32_hash (name); log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); DSOCacheEntry *dso = nullptr; diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index f79969e4475..53403396664 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -6,8 +6,6 @@ #include -#include - static constexpr uint64_t FORMAT_TAG = 0x00045E6972616D58; // 'Xmari^XY' where XY is the format version static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x535A4158; // 'XAZS', little-endian static constexpr uint32_t ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian @@ -244,21 +242,21 @@ struct RuntimeProperty struct RuntimePropertyIndexEntry { - xamarin::android::hash_t key_hash; + uint32_t key_hash; uint32_t index; }; struct DSOApkEntry { - uint64_t name_hash; + uint32_t name_hash; uint32_t offset; // offset into the APK int32_t fd; // apk file descriptor }; struct DSOCacheEntry { - const uint64_t hash; - const uint64_t real_name_hash; + const uint32_t hash; + const uint32_t real_name_hash; const bool ignore; const bool is_jni_library; const uint32_t name_index; 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 cb27706e24a..3f35d8c0c7b 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -2,7 +2,6 @@ #include #include -#include // This file MUST have "valid" values everywhere - the DSO it is compiled into is loaded by the // designer on desktop. @@ -110,8 +109,8 @@ constexpr char fake_dso_name2[] = "libaot-Another.Assembly.dll.so"; DSOCacheEntry dso_cache[] = { { - .hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), - .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), + .hash = 0x4ce380ac, + .real_name_hash = 0x4ce380ac, .ignore = true, .is_jni_library = false, .name_index = 1, @@ -119,8 +118,8 @@ DSOCacheEntry dso_cache[] = { }, { - .hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), - .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), + .hash = 0x5f995ac5, + .real_name_hash = 0x5f995ac5, .ignore = true, .is_jni_library = false, .name_index = 2, @@ -136,8 +135,8 @@ const uint dso_jni_preloads_idx[1] = { DSOCacheEntry aot_dso_cache[] = { { - .hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), - .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), + .hash = 0x4ce380ac, + .real_name_hash = 0x4ce380ac, .ignore = true, .is_jni_library = true, .name_index = 3, @@ -145,8 +144,8 @@ DSOCacheEntry aot_dso_cache[] = { }, { - .hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), - .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), + .hash = 0x5f995ac5, + .real_name_hash = 0x5f995ac5, .ignore = true, .is_jni_library = false, .name_index = 4, @@ -273,18 +272,18 @@ const char runtime_properties_data[] = {}; const RuntimePropertyIndexEntry runtime_property_index[] = { { - .key_hash = xamarin::android::xxhash::hash (prop_test_string_key, sizeof(prop_test_string_key) - 1), + .key_hash = 0x0967b587, .index = 0, }, { - .key_hash = xamarin::android::xxhash::hash (prop_test_integer_key, sizeof(prop_test_integer_key) - 1), - .index = 1, + .key_hash = 0x56c45ca0, + .index = 2, }, { - .key_hash = xamarin::android::xxhash::hash (prop_test_boolean_key, sizeof(prop_test_boolean_key) - 1), - .index = 2, + .key_hash = 0x864e7fc3, + .index = 1, }, }; From 6bb27ca9323b3f806d18ff7e0d7921d68810db2f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 00:52:17 +0200 Subject: [PATCH 04/18] [coreclr] Use CRC32 for dynamic p/invoke hashes Switch the CoreCLR dynamic p/invoke override and generated find_pinvoke tables to 32-bit CRC32 hashes. Keep the precompiled p/invoke table on the existing xxHash layout while removing xxHash from the shared p/invoke override header and fallback cache path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...PreservePinvokesNativeAssemblyGenerator.cs | 30 ++++++++----------- .../clr/include/host/pinvoke-override-impl.hh | 16 +++++----- .../clr/include/host/pinvoke-override.hh | 25 ++++++++++------ src/native/clr/pinvoke-override/dynamic.cc | 8 ++--- .../clr/pinvoke-override/precompiled.cc | 3 +- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs index 3e6d46b104b..572b8b3e915 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs @@ -18,12 +18,12 @@ sealed class PInvoke { public readonly LlvmIrFunction NativeFunction; public readonly PinvokeScanner.PinvokeEntryInfo Info; - public readonly ulong Hash; + public readonly uint Hash; - public PInvoke (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo, bool is64Bit) + public PInvoke (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo) { Info = pinfo; - Hash = MonoAndroidHelper.GetXxHash (pinfo.EntryName, is64Bit); + Hash = TypeMapHelper.HashNameForCLR (pinfo.EntryName); // All the p/invoke functions use the same dummy signature. The only thing we care about is // a way to reference to the symbol at build time so that we can return pointer to it. For @@ -36,21 +36,19 @@ public PInvoke (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo, bool sealed class Component { public readonly string Name; - public readonly ulong NameHash; + public readonly uint NameHash; public readonly List PInvokes; - public bool Is64Bit; - public Component (string name, bool is64Bit) + public Component (string name) { Name = name; - NameHash = MonoAndroidHelper.GetXxHash (name, is64Bit); + NameHash = TypeMapHelper.HashNameForCLR (name); PInvokes = new (); - Is64Bit = is64Bit; } public void Add (LlvmIrModule module, PinvokeScanner.PinvokeEntryInfo pinfo) { - PInvokes.Add (new PInvoke (module, pinfo, Is64Bit)); + PInvokes.Add (new PInvoke (module, pinfo)); } public void Sort () @@ -132,11 +130,11 @@ protected override void Construct (LlvmIrModule module) return; } - bool is64Bit = state.TargetArch switch { + _ = state.TargetArch switch { AndroidTargetArch.Arm64 => true, AndroidTargetArch.X86_64 => true, - AndroidTargetArch.Arm => false, - AndroidTargetArch.X86 => false, + AndroidTargetArch.Arm => true, + AndroidTargetArch.X86 => true, _ => throw new NotSupportedException ($"Architecture {state.TargetArch} is not supported here") }; @@ -182,7 +180,7 @@ protected override void Construct (LlvmIrModule module) } if (!preservedPerComponent.TryGetValue (pinfo.LibraryName, out Component? component)) { - component = new Component (pinfo.LibraryName, is64Bit); + component = new Component (pinfo.LibraryName); preservedPerComponent.Add (component.Name, component); } component.Add (module, pinfo); @@ -192,11 +190,7 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable ("__explicitly_preserved_symbols", symbolsToExplicitlyPreserve, LlvmIrVariableOptions.GlobalConstant); var components = new List (preservedPerComponent.Values); - if (is64Bit) { - AddFindPinvoke (module, components, is64Bit); - } else { - AddFindPinvoke (module, components, is64Bit); - } + AddFindPinvoke (module, components, is64Bit: false); } void AddFindPinvoke (LlvmIrModule module, List components, bool is64Bit) where T: struct diff --git a/src/native/clr/include/host/pinvoke-override-impl.hh b/src/native/clr/include/host/pinvoke-override-impl.hh index 2f2b9498394..d5288548308 100644 --- a/src/native/clr/include/host/pinvoke-override-impl.hh +++ b/src/native/clr/include/host/pinvoke-override-impl.hh @@ -85,9 +85,9 @@ namespace xamarin::android { } PINVOKE_OVERRIDE_INLINE - auto PinvokeOverride::fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, hash_t entrypoint_name_hash, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void* + auto PinvokeOverride::fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void* { - auto iter = api_map->find (entrypoint_name, entrypoint_name_hash); + auto iter = api_map->find (entrypoint_name); if (iter != api_map->end () && iter->second != nullptr) { return iter->second; } @@ -101,7 +101,7 @@ namespace xamarin::android { } PINVOKE_OVERRIDE_INLINE - auto PinvokeOverride::find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry* + auto PinvokeOverride::find_pinvoke_address (pinvoke_entry_hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry* { while (entry_count > 0uz) { const size_t mid = entry_count / 2uz; @@ -122,19 +122,19 @@ namespace xamarin::android { } PINVOKE_OVERRIDE_INLINE - auto PinvokeOverride::handle_other_pinvoke_request (std::string_view const& library_name, hash_t library_name_hash, std::string_view const& entrypoint_name, hash_t entrypoint_name_hash) noexcept -> void* + auto PinvokeOverride::handle_other_pinvoke_request (std::string_view const& library_name, std::string_view const& entrypoint_name) noexcept -> void* { std::string lib_name {library_name}; std::string entry_name {entrypoint_name}; - auto iter = other_pinvoke_map.find (lib_name, library_name_hash); + auto iter = other_pinvoke_map.find (lib_name); void *handle = nullptr; if (iter == other_pinvoke_map.end ()) { StartupAwareLock lock (pinvoke_map_write_lock); pinvoke_api_map_ptr lib_map; // Make sure some other thread hasn't just added the map - iter = other_pinvoke_map.find (lib_name, library_name_hash); + iter = other_pinvoke_map.find (lib_name); if (iter == other_pinvoke_map.end () || iter->second == nullptr) { lib_map = new pinvoke_api_map (1); other_pinvoke_map[lib_name] = lib_map; @@ -142,14 +142,14 @@ namespace xamarin::android { lib_map = iter->second; } - handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, lib_map, /* need_lock */ false); + handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, lib_map, /* need_lock */ false); } else { if (iter->second == nullptr) [[unlikely]] { log_warn (LOG_ASSEMBLY, "Internal error: null entry in p/invoke map for key '{}'", library_name); return nullptr; // fall back to `monodroid_dlopen` } - handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, entrypoint_name_hash, iter->second, /* need_lock */ true); + handle = fetch_or_create_pinvoke_map_entry (lib_name, entry_name, iter->second, /* need_lock */ true); } return handle; diff --git a/src/native/clr/include/host/pinvoke-override.hh b/src/native/clr/include/host/pinvoke-override.hh index 099c5264669..c6a6ab39189 100644 --- a/src/native/clr/include/host/pinvoke-override.hh +++ b/src/native/clr/include/host/pinvoke-override.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -31,22 +32,28 @@ #endif #include "../runtime-base/monodroid-dl.hh" -#include +#include namespace xamarin::android { +#if INTPTR_MAX == INT64_MAX + using pinvoke_entry_hash_t = uint64_t; +#else + using pinvoke_entry_hash_t = uint32_t; +#endif + struct PinvokeEntry { - hash_t hash; - const char *name; - void *func; + pinvoke_entry_hash_t hash; + const char *name; + void *func; }; struct string_hash { [[gnu::always_inline]] - xamarin::android::hash_t operator() (std::string const& s) const noexcept + size_t operator() (std::string const& s) const noexcept { - return xamarin::android::xxhash::hash (s.c_str (), s.length ()); + return xamarin::android::crc32_hash (s.c_str (), s.length ()); } }; @@ -76,9 +83,9 @@ namespace xamarin::android { public: static auto load_library_symbol (std::string_view const& library_name, std::string_view const& symbol_name, void **dso_handle = nullptr) noexcept -> void*; static auto load_library_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map) noexcept -> void*; - static auto fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, hash_t entrypoint_name_hash, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*; - static auto find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*; - static auto handle_other_pinvoke_request (std::string_view const& library_name, hash_t library_name_hash, std::string_view const& entrypoint_name, hash_t entrypoint_name_hash) noexcept -> void*; + static auto fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*; + static auto find_pinvoke_address (pinvoke_entry_hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*; + static auto handle_other_pinvoke_request (std::string_view const& library_name, std::string_view const& entrypoint_name) noexcept -> void*; static void handle_jni_on_load (JavaVM *vm, void *reserved) noexcept; static auto monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name) noexcept -> void*; diff --git a/src/native/clr/pinvoke-override/dynamic.cc b/src/native/clr/pinvoke-override/dynamic.cc index 5352aacab68..291e70b9905 100644 --- a/src/native/clr/pinvoke-override/dynamic.cc +++ b/src/native/clr/pinvoke-override/dynamic.cc @@ -13,7 +13,7 @@ using JniOnLoadHandler = jint (*) (JavaVM *vm, void *reserved); // These external functions are generated during application build (see obj/${CONFIGURATION}/${RID}/android/pinvoke_preserve.*.ll) // extern "C" { - void* find_pinvoke (hash_t library_name_hash, hash_t entrypoint_hash, bool &known_library); + void* find_pinvoke (uint32_t library_name_hash, uint32_t entrypoint_hash, bool &known_library); extern const uint32_t __jni_on_load_handler_count; extern const JniOnLoadHandler __jni_on_load_handlers[]; @@ -37,8 +37,8 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons ); } - hash_t library_name_hash = xxhash::hash (library_name, strlen (library_name)); - hash_t entrypoint_hash = xxhash::hash (entrypoint_name, strlen (entrypoint_name)); + uint32_t library_name_hash = crc32_hash (library_name, strlen (library_name)); + uint32_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); log_debug (LOG_ASSEMBLY, "library_name_hash == 0x{:x}; entrypoint_hash == 0x{:x}"sv, library_name_hash, entrypoint_hash); bool known_library = true; @@ -69,7 +69,7 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons } log_debug (LOG_ASSEMBLY, "p/invoke not from a known library, slow path taken."sv); - pinvoke_ptr = handle_other_pinvoke_request (library_name, library_name_hash, entrypoint_name, entrypoint_hash); + pinvoke_ptr = handle_other_pinvoke_request (library_name, entrypoint_name); log_debug (LOG_ASSEMBLY, "foreign library pinvoke_ptr == {:p}"sv, pinvoke_ptr); return pinvoke_ptr; } diff --git a/src/native/clr/pinvoke-override/precompiled.cc b/src/native/clr/pinvoke-override/precompiled.cc index a57748bdb4e..cec8ea36e90 100644 --- a/src/native/clr/pinvoke-override/precompiled.cc +++ b/src/native/clr/pinvoke-override/precompiled.cc @@ -1,6 +1,7 @@ #define PINVOKE_OVERRIDE_INLINE [[gnu::always_inline]] #include +#include #include #include @@ -72,7 +73,7 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons // directories. CoreCLR's default resolver does not replicate that behaviour, so this path is kept. // It also carries no static table, so it is not subject to the drift problem that motivated the // BCL change above. - return handle_other_pinvoke_request (library_name, library_name_hash, entrypoint_name, entrypoint_hash); + return handle_other_pinvoke_request (library_name, entrypoint_name); } const void* Host::clr_pinvoke_override (const char *library_name, const char *entry_point_name) noexcept From 64a25a87f067e623d67a62d2dcd1a8c4d5b787f0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 01:44:44 +0200 Subject: [PATCH 05/18] [coreclr] Use CRC32 for precompiled p/invoke hashes Switch the CoreCLR precompiled p/invoke override table and generator to 32-bit CRC32 hashes. This removes the remaining CoreCLR p/invoke override dependency on xxHash while keeping the table collision check in the generator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../clr/include/host/pinvoke-override.hh | 4 - .../generate-pinvoke-tables.cc | 108 ++++++++-------- .../pinvoke-override/pinvoke-tables.include | 117 ++++++------------ .../clr/pinvoke-override/precompiled.cc | 5 +- 4 files changed, 93 insertions(+), 141 deletions(-) diff --git a/src/native/clr/include/host/pinvoke-override.hh b/src/native/clr/include/host/pinvoke-override.hh index c6a6ab39189..b4d39ed482a 100644 --- a/src/native/clr/include/host/pinvoke-override.hh +++ b/src/native/clr/include/host/pinvoke-override.hh @@ -35,11 +35,7 @@ #include namespace xamarin::android { -#if INTPTR_MAX == INT64_MAX - using pinvoke_entry_hash_t = uint64_t; -#else using pinvoke_entry_hash_t = uint32_t; -#endif struct PinvokeEntry { diff --git a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc index 4264ab3e8c4..b7e5256e16b 100644 --- a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc +++ b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc @@ -18,20 +18,44 @@ // #include #include +#include #include #include #include #include #include +#include #include #include +#include #include #include -#include - namespace fs = std::filesystem; -using namespace xamarin::android; + +constexpr uint32_t CRC32_POLYNOMIAL = 0xedb88320; + +constexpr uint32_t crc32_hash (const char *p, size_t len) noexcept +{ + if (len == 0) { + return std::numeric_limits::max (); + } + + uint32_t crc = 0xffffffff; + for (size_t i = 0; i < len; i++) { + crc ^= static_cast(p[i]); + for (size_t bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ ((crc & 1) != 0 ? CRC32_POLYNOMIAL : 0); + } + } + + return ~crc; +} + +constexpr uint32_t crc32_hash (std::string_view const& input) noexcept +{ + return crc32_hash (input.data (), input.length ()); +} const std::vector internal_pinvoke_names = { // "create_public_directory", @@ -89,15 +113,14 @@ const std::vector internal_pinvoke_names = { "__android_log_print", }; -template struct PinvokeEntry { std::string name; - Hash hash; + uint32_t hash; bool write_func_pointer; template friend - Os& operator<< (Os& os, PinvokeEntry const& p) + Os& operator<< (Os& os, PinvokeEntry const& p) { os << std::showbase << std::hex << p.hash << ", \"" << p.name << "\", "; @@ -121,12 +144,11 @@ void print (std::ostream& os, std::string comment, std::string variable_name, au os << "\t}};" << std::endl << std::endl; } -template -bool add_hash (std::string const& pinvoke, Hash hash, std::vector>& vec, std::unordered_set& used_cache, bool write_func_pointer) +bool add_hash (std::string const& pinvoke, uint32_t hash, std::vector& vec, std::unordered_set& used_cache, bool write_func_pointer) { vec.emplace_back (pinvoke, hash, write_func_pointer); if (used_cache.contains (hash)) { - std::cerr << (sizeof(Hash) == 4 ? "32" : "64") << "-bit hash collision for key '" << pinvoke << "': " << std::hex << std::showbase << hash << std::endl; + std::cerr << "CRC32 hash collision for key '" << pinvoke << "': " << std::hex << std::showbase << hash << std::endl; return true; } @@ -134,45 +156,38 @@ bool add_hash (std::string const& pinvoke, Hash hash, std::vector const& names, std::vector>& pinvokes32, std::vector>& pinvokes64, bool write_func_pointer) +bool generate_hashes (std::string table_name, std::vector const& names, std::vector& pinvokes, bool write_func_pointer) { std::unordered_set used_pinvokes32{}; - std::unordered_set used_pinvokes64{}; - uint32_t hash32; - uint64_t hash64; bool have_collisions = false; std::cout << "There are " << names.size () << " " << table_name << " p/invoke functions" << std::endl; for (std::string const& pinvoke : names) { - have_collisions |= add_hash (pinvoke, xxhash32::hash (pinvoke.c_str (), pinvoke.length ()), pinvokes32, used_pinvokes32, write_func_pointer); - have_collisions |= add_hash (pinvoke, xxhash64::hash (pinvoke.c_str (), pinvoke.length ()), pinvokes64, used_pinvokes64, write_func_pointer); + have_collisions |= add_hash (pinvoke, crc32_hash (pinvoke.c_str (), pinvoke.length ()), pinvokes, used_pinvokes32, write_func_pointer); } std::cout << "p/invoke hash collisions for '" << table_name << "' were " << (have_collisions ? "" : "not ") << "found" << std::endl; - std::ranges::sort (pinvokes32, {}, &PinvokeEntry::hash); - std::ranges::sort (pinvokes64, {}, &PinvokeEntry::hash); + std::ranges::sort (pinvokes, {}, &PinvokeEntry::hash); return have_collisions; } -template -void write_library_name_hash (Hash (*hasher)(const char*, size_t), std::ostream& os, std::string library_name, std::string variable_prefix) +void write_library_name_hash (std::ostream& os, std::string library_name, std::string variable_prefix) { - Hash hash = hasher (library_name.c_str (), library_name.length ()); - os << "constexpr hash_t " << variable_prefix << "_library_hash = " << std::hex << hash << ";" << std::endl; + uint32_t hash = crc32_hash (library_name.c_str (), library_name.length ()); + os << "constexpr pinvoke_entry_hash_t " << variable_prefix << "_library_hash = " << std::hex << hash << ";" << std::endl; } -template -void write_library_name_hashes (Hash (*hasher)(const char*, size_t), std::ostream& output) +void write_library_name_hashes (std::ostream& output) { - write_library_name_hash (hasher, output, "java-interop", "java_interop"); - write_library_name_hash (hasher, output, "xa-internal-api", "xa_internal_api"); - write_library_name_hash (hasher, output, "liblog", "android_liblog"); - write_library_name_hash (hasher, output, "libSystem.Native", "system_native"); - write_library_name_hash (hasher, output, "libSystem.IO.Compression.Native", "system_io_compression_native"); - write_library_name_hash (hasher, output, "libSystem.Security.Cryptography.Native.Android", "system_security_cryptography_native_android"); - write_library_name_hash (hasher, output, "libSystem.Globalization.Native", "system_globalization_native"); + write_library_name_hash (output, "java-interop", "java_interop"); + write_library_name_hash (output, "xa-internal-api", "xa_internal_api"); + write_library_name_hash (output, "liblog", "android_liblog"); + write_library_name_hash (output, "libSystem.Native", "system_native"); + write_library_name_hash (output, "libSystem.IO.Compression.Native", "system_io_compression_native"); + write_library_name_hash (output, "libSystem.Security.Cryptography.Native.Android", "system_security_cryptography_native_android"); + write_library_name_hash (output, "libSystem.Globalization.Native", "system_globalization_native"); } int main (int argc, char **argv) @@ -208,9 +223,8 @@ int main (int argc, char **argv) } bool have_collisions = false; - std::vector> internal_pinvokes32{}; - std::vector> internal_pinvokes64{}; - have_collisions |= generate_hashes ("internal", internal_pinvoke_names, internal_pinvokes32, internal_pinvokes64, true); + std::vector internal_pinvokes{}; + have_collisions |= generate_hashes ("internal", internal_pinvoke_names, internal_pinvokes, true); std::cout << "Generating tables in file: " << output_file_path << std::endl; @@ -219,7 +233,7 @@ int main (int argc, char **argv) output << "//" << std::endl; output << "// Autogenarated file. DO NOT EDIT." << std::endl; output << "//" << std::endl; - output << "// To regenerate run ../../../../build-tools/scripts/generate-pinvoke-tables.sh on Linux or macOS" << std::endl; + output << "// To regenerate, compile and run generate-pinvoke-tables.cc with the output path as the only argument." << std::endl; output << "// A compiler with support for C++20 ranges is required" << std::endl; output << "//" << std::endl << std::endl; @@ -227,18 +241,10 @@ int main (int argc, char **argv) output << "#include " << std::endl << std::endl; output << "namespace {" << std::endl; - output << "#if INTPTR_MAX == INT64_MAX" << std::endl; - print (output, "64-bit internal p/invoke table", "internal_pinvokes", internal_pinvokes64); + print (output, " CRC32 internal p/invoke table", "internal_pinvokes", internal_pinvokes); output << std::endl; - write_library_name_hashes (xxhash64::hash, output); - - output << "#else" << std::endl; - - print (output, "32-bit internal p/invoke table", "internal_pinvokes", internal_pinvokes32); + write_library_name_hashes (output); output << std::endl; - write_library_name_hashes (xxhash32::hash, output); - - output << "#endif" << std::endl << std::endl; output << "constexpr size_t internal_pinvokes_count = " << std::dec << std::noshowbase << internal_pinvoke_names.size () << ";" << std::endl; output << "} // end of anonymous namespace" << std::endl; @@ -247,18 +253,12 @@ int main (int argc, char **argv) } // This serves as a quick compile-time test of the algorithm's correctness. -// The tests are copied from https://github.com/ekpyron/xxhashct/test.cpp -template +template struct constexpr_test { static_assert (value == expected, "Compile-time hash mismatch."); }; -constexpr_test ("", 0), 0x2CC5D05U> constexprTest_1; -constexpr_test ("", 0), 0x36B78AE7U> constexprTest_2; -//constexpr_test ("", 0), 0xEF46DB3751D8E999ULL> constexprTest_3; -//constexpr_test ("", 0), 0xAC75FDA2929B17EFULL> constexprTest_4; -constexpr_test ("test", 4), 0x3E2023CFU> constexprTest32_5; -constexpr_test ("test", 4), 0xA9C14438U> constexprTest32_6; -//constexpr_test ("test", 4), 0x4fdcca5ddb678139ULL> constexprTest64_7; -//constexpr_test ("test", 4), 0x5A183B8150E2F651ULL> constexprTest64_8; +constexpr_test::max ()> constexprTest_1; +constexpr_test constexprTest_2; +constexpr_test constexprTest_3; diff --git a/src/native/clr/pinvoke-override/pinvoke-tables.include b/src/native/clr/pinvoke-override/pinvoke-tables.include index 716ef4ced44..40f30b8bbb2 100644 --- a/src/native/clr/pinvoke-override/pinvoke-tables.include +++ b/src/native/clr/pinvoke-override/pinvoke-tables.include @@ -1,7 +1,7 @@ // // Autogenarated file. DO NOT EDIT. // -// To regenerate run ../../../../build-tools/scripts/generate-pinvoke-tables.sh on Linux or macOS +// To regenerate, compile and run generate-pinvoke-tables.cc with the output path as the only argument. // A compiler with support for C++20 ranges is required // @@ -9,89 +9,46 @@ #include namespace { -#if INTPTR_MAX == INT64_MAX - //64-bit internal p/invoke table + // CRC32 internal p/invoke table std::array internal_pinvokes {{ - {0x37307e5fddf709dc, "_monodroid_weak_gref_dec", reinterpret_cast(&_monodroid_weak_gref_dec)}, - {0x3b2467e7eadd4a6a, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, - {0x423c8f539a2c56d2, "_monodroid_lookup_replacement_type", reinterpret_cast(&_monodroid_lookup_replacement_type)}, - {0x4310c1531ddddc14, "__android_log_print", reinterpret_cast(&__android_log_print)}, - {0x4b1956138764939a, "_monodroid_gref_log_new", reinterpret_cast(&_monodroid_gref_log_new)}, - {0x5f0b4e426eff086b, "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, - {0x9099a4b95e3c3a89, "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, - {0x9187e6bc6294cacf, "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, - {0x920bf58357fb56f3, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, - {0x9a946dfe9916a942, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, - {0x9d2b3233c41789df, "_monodroid_weak_gref_inc", reinterpret_cast(&_monodroid_weak_gref_inc)}, - {0xa6ec846592d99536, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, - {0xa7f58f3ee428cc6b, "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, - {0xab094d8d2fc086a1, "_monodroid_gref_dec", reinterpret_cast(&_monodroid_gref_dec)}, - {0xae3df96dda0143bd, "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, - {0xb6222d90af401865, "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, - {0xb8306f71b963cd3d, "monodroid_log", reinterpret_cast(&monodroid_log)}, - {0xb9bae9c43fb05089, "xamarin_app_init", reinterpret_cast(&xamarin_app_init)}, - {0xc2a21d3f6c8ccc24, "_monodroid_lookup_replacement_method_info", reinterpret_cast(&_monodroid_lookup_replacement_method_info)}, - {0xc5b4690e13898fa3, "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, - {0xcaab0a3ab6057bbd, "_monodroid_gref_inc", reinterpret_cast(&_monodroid_gref_inc)}, - {0xd1e121b94ea63f2e, "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, - {0xd5151b00eb33d85e, "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, - {0xe27b9849b7e982cb, "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, - {0xe86307aac9a2631a, "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, - {0xf3048baf83034541, "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, - {0xf41c48df6f9be476, "monodroid_free", reinterpret_cast(&monodroid_free)}, - {0xf5a918ef520db207, "monodroid_timing_stop", reinterpret_cast(&monodroid_timing_stop)}, + {0xc7eb66d, "monodroid_log", reinterpret_cast(&monodroid_log)}, + {0x1208417b, "_monodroid_gref_log_new", reinterpret_cast(&_monodroid_gref_log_new)}, + {0x21f6db74, "_monodroid_lookup_replacement_type", reinterpret_cast(&_monodroid_lookup_replacement_type)}, + {0x28cb701d, "_monodroid_weak_gref_inc", reinterpret_cast(&_monodroid_weak_gref_inc)}, + {0x2e4c74a2, "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, + {0x36396c04, "monodroid_timing_stop", reinterpret_cast(&monodroid_timing_stop)}, + {0x36f14311, "_monodroid_gref_inc", reinterpret_cast(&_monodroid_gref_inc)}, + {0x4272011b, "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, + {0x5c483217, "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, + {0x5e487bd1, "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, + {0x638c99a0, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, + {0x639ae575, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, + {0x6c845282, "__android_log_print", reinterpret_cast(&__android_log_print)}, + {0x741c16ae, "xamarin_app_init", reinterpret_cast(&xamarin_app_init)}, + {0x80bcdd2f, "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, + {0x95a0abcc, "monodroid_free", reinterpret_cast(&monodroid_free)}, + {0x9fe0c9d9, "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, + {0xb29db2e8, "_monodroid_lookup_replacement_method_info", reinterpret_cast(&_monodroid_lookup_replacement_method_info)}, + {0xba99e855, "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, + {0xc3e73a85, "_monodroid_weak_gref_dec", reinterpret_cast(&_monodroid_weak_gref_dec)}, + {0xc58330cb, "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, + {0xcdc08ad5, "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, + {0xce55fbf3, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, + {0xd4aa6b2e, "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, + {0xdddd0989, "_monodroid_gref_dec", reinterpret_cast(&_monodroid_gref_dec)}, + {0xdf04d569, "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, + {0xf403b8b9, "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, + {0xf65149df, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, }}; -constexpr hash_t java_interop_library_hash = 0x54568ec36068e6b6; -constexpr hash_t xa_internal_api_library_hash = 0x43fd1b21148361b2; -constexpr hash_t android_liblog_library_hash = 0x1f2e4bce0544fb0a; -constexpr hash_t system_native_library_hash = 0x4cd7bd0032e920e1; -constexpr hash_t system_io_compression_native_library_hash = 0x9190f4cb761b1d3c; -constexpr hash_t system_security_cryptography_native_android_library_hash = 0x1848c0093f0afd8; -constexpr hash_t system_globalization_native_library_hash = 0x28b5c8fca080abd5; -#else - //32-bit internal p/invoke table - std::array internal_pinvokes {{ - {0xb7a486a, "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, - {0x1bef8dce, "_monodroid_gref_inc", reinterpret_cast(&_monodroid_gref_inc)}, - {0x1f1e0ee9, "_monodroid_gref_dec", reinterpret_cast(&_monodroid_gref_dec)}, - {0x2aea7c33, "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, - {0x3227d81a, "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, - {0x333d4835, "_monodroid_lookup_replacement_method_info", reinterpret_cast(&_monodroid_lookup_replacement_method_info)}, - {0x39e5b5d4, "__android_log_print", reinterpret_cast(&__android_log_print)}, - {0x656e00bd, "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, - {0x9070e02c, "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, - {0x910452d0, "monodroid_timing_stop", reinterpret_cast(&monodroid_timing_stop)}, - {0x9a734f16, "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, - {0x9c5b24a8, "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, - {0xa04e5d1c, "monodroid_free", reinterpret_cast(&monodroid_free)}, - {0xa3c1e548, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, - {0xb02468aa, "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, - {0xb6431f9a, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, - {0xbe8d7701, "_monodroid_gref_log_new", reinterpret_cast(&_monodroid_gref_log_new)}, - {0xc0d097a7, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, - {0xc439b5d7, "_monodroid_lookup_replacement_type", reinterpret_cast(&_monodroid_lookup_replacement_type)}, - {0xc5146c54, "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, - {0xe215a17c, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, - {0xe7e77ca5, "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, - {0xe8d7ba9b, "_monodroid_weak_gref_inc", reinterpret_cast(&_monodroid_weak_gref_inc)}, - {0xea2184e3, "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, - {0xeac7f6e3, "xamarin_app_init", reinterpret_cast(&xamarin_app_init)}, - {0xeea5931c, "_monodroid_weak_gref_dec", reinterpret_cast(&_monodroid_weak_gref_dec)}, - {0xfa1832a6, "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, - {0xfa4e32ca, "monodroid_log", reinterpret_cast(&monodroid_log)}, - }}; - - -constexpr hash_t java_interop_library_hash = 0x6e36e350; -constexpr hash_t xa_internal_api_library_hash = 0x13c9bd62; -constexpr hash_t android_liblog_library_hash = 0xa70c9969; -constexpr hash_t system_native_library_hash = 0x5b9ade60; -constexpr hash_t system_io_compression_native_library_hash = 0xafe3142c; -constexpr hash_t system_security_cryptography_native_android_library_hash = 0x93625cd; -constexpr hash_t system_globalization_native_library_hash = 0xa66f1e5a; -#endif +constexpr pinvoke_entry_hash_t java_interop_library_hash = 0x33b98009; +constexpr pinvoke_entry_hash_t xa_internal_api_library_hash = 0xf03cbae; +constexpr pinvoke_entry_hash_t android_liblog_library_hash = 0xbb87f5e1; +constexpr pinvoke_entry_hash_t system_native_library_hash = 0xa9877ba; +constexpr pinvoke_entry_hash_t system_io_compression_native_library_hash = 0xb234abb7; +constexpr pinvoke_entry_hash_t system_security_cryptography_native_android_library_hash = 0x6bfed756; +constexpr pinvoke_entry_hash_t system_globalization_native_library_hash = 0x7d160aeb; constexpr size_t internal_pinvokes_count = 28; } // end of anonymous namespace diff --git a/src/native/clr/pinvoke-override/precompiled.cc b/src/native/clr/pinvoke-override/precompiled.cc index cec8ea36e90..3dde3788ead 100644 --- a/src/native/clr/pinvoke-override/precompiled.cc +++ b/src/native/clr/pinvoke-override/precompiled.cc @@ -1,7 +1,6 @@ #define PINVOKE_OVERRIDE_INLINE [[gnu::always_inline]] #include -#include #include #include @@ -16,8 +15,8 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons return nullptr; } - hash_t library_name_hash = xxhash::hash (library_name, strlen (library_name)); - hash_t entrypoint_hash = xxhash::hash (entrypoint_name, strlen (entrypoint_name)); + uint32_t library_name_hash = crc32_hash (library_name, strlen (library_name)); + uint32_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); if (library_name_hash == java_interop_library_hash || library_name_hash == xa_internal_api_library_hash || library_name_hash == android_liblog_library_hash) { PinvokeEntry *entry = find_pinvoke_address (entrypoint_hash, internal_pinvokes.data (), internal_pinvokes_count); From 047ca86a1ec409e35bab03673beaa3be92374b12 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 01:44:44 +0200 Subject: [PATCH 06/18] [native] Move xxHash search helpers to MonoVM Keep the common Search helper generic-only and move the xxHash-specific overloads to a MonoVM-only search-xxhash.hh wrapper. This prevents CoreCLR native code from including shared/xxhash.hh through the common search helper while preserving MonoVM's existing hash_t search callsites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../common/include/runtime-base/search.hh | 35 -------------- .../mono/monodroid/embedded-assemblies.cc | 6 +-- .../mono/monodroid/mono-image-loader.hh | 4 +- src/native/mono/runtime-base/monodroid-dl.hh | 4 +- src/native/mono/runtime-base/search-xxhash.hh | 47 +++++++++++++++++++ 5 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 src/native/mono/runtime-base/search-xxhash.hh diff --git a/src/native/common/include/runtime-base/search.hh b/src/native/common/include/runtime-base/search.hh index 855bac976b0..1c0a0fd5584 100644 --- a/src/native/common/include/runtime-base/search.hh +++ b/src/native/common/include/runtime-base/search.hh @@ -3,8 +3,6 @@ #include -#include - namespace xamarin::android { class Search final { @@ -54,38 +52,5 @@ namespace xamarin::android { return equal (arr[right], key) ? right : -1z; } - - template - [[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::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; }; - auto less_than = [](hash_t const& entry, hash_t key) -> bool { return entry < key; }; - - return binary_search (key, arr, n); - } - - [[gnu::always_inline]] - static ptrdiff_t binary_search_branchless (hash_t x, const hash_t *arr, uint32_t len) noexcept - { - const hash_t *base = arr; - while (len > 1) { - uint32_t half = len >> 1; - // __builtin_prefetch(&base[(len - half) / 2]); - // __builtin_prefetch(&base[half + (len - half) / 2]); - base = (base[half] < x ? &base[half] : base); - len -= half; - } - - //return *(base + (*base < x)); - ptrdiff_t ret = (base + (*base < x)) - arr; - return arr[ret] == x ? ret : -1; - } }; } diff --git a/src/native/mono/monodroid/embedded-assemblies.cc b/src/native/mono/monodroid/embedded-assemblies.cc index 08099f65324..b553e3d16b3 100644 --- a/src/native/mono/monodroid/embedded-assemblies.cc +++ b/src/native/mono/monodroid/embedded-assemblies.cc @@ -32,7 +32,7 @@ #include "monodroid-state.hh" #include "startup-aware-lock.hh" #include -#include +#include #include using namespace xamarin::android; @@ -399,7 +399,7 @@ EmbeddedAssemblies::find_assembly_store_entry (hash_t hash, const AssemblyStoreI { auto equal = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash == key; }; auto less_than = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash < key; }; - ssize_t idx = Search::binary_search (hash, entries, entry_count); + ssize_t idx = SearchXxHash::binary_search (hash, entries, entry_count); if (idx >= 0) { return &entries[idx]; } @@ -674,7 +674,7 @@ EmbeddedAssemblies::typemap_java_to_managed (hash_t hash, const MonoString *java // In microbrenchmarks, `binary_search_branchless` is faster than `binary_search` but in "real" application tests, // the simple version appears to yield faster startup... Leaving both for now, for further investigation and // potential optimizations - ssize_t idx = Search::binary_search (hash, map_java_hashes, java_type_count); + ssize_t idx = SearchXxHash::binary_search (hash, map_java_hashes, java_type_count); //ptrdiff_t idx = binary_search_branchless (hash, map_java_hashes, java_type_count); TypeMapJava const* java_entry = idx >= 0 ? &map_java[idx] : nullptr; diff --git a/src/native/mono/monodroid/mono-image-loader.hh b/src/native/mono/monodroid/mono-image-loader.hh index ac33fe53588..011fb4dbe64 100644 --- a/src/native/mono/monodroid/mono-image-loader.hh +++ b/src/native/mono/monodroid/mono-image-loader.hh @@ -12,7 +12,7 @@ #include "platform-compat.hh" #include "xamarin-app.hh" #include -#include +#include #include #include "util.hh" @@ -101,7 +101,7 @@ namespace xamarin::android::internal { force_inline static ssize_t find_index (hash_t hash) noexcept { - ssize_t idx = Search::binary_search (hash, assembly_image_cache_hashes, number_of_cache_index_entries); + ssize_t idx = SearchXxHash::binary_search (hash, assembly_image_cache_hashes, number_of_cache_index_entries); return idx >= 0 ? static_cast(assembly_image_cache_indices[idx]) : -1z; } diff --git a/src/native/mono/runtime-base/monodroid-dl.hh b/src/native/mono/runtime-base/monodroid-dl.hh index 3e2cfe62402..383c1921185 100644 --- a/src/native/mono/runtime-base/monodroid-dl.hh +++ b/src/native/mono/runtime-base/monodroid-dl.hh @@ -11,7 +11,7 @@ #include "android-system.hh" #include "monodroid-state.hh" #include -#include +#include #include "shared-constants.hh" #include "startup-aware-lock.hh" #include "util.hh" @@ -57,7 +57,7 @@ namespace xamarin::android::internal auto equal = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash == key; }; auto less_than = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash < key; }; - ssize_t idx = Search::binary_search (hash, arr, arr_size); + ssize_t idx = SearchXxHash::binary_search (hash, arr, arr_size); if (idx >= 0) { log_debug (LOG_ASSEMBLY, "Found hash 0x{:x} entry at index {} of the cache", hash, idx); diff --git a/src/native/mono/runtime-base/search-xxhash.hh b/src/native/mono/runtime-base/search-xxhash.hh new file mode 100644 index 00000000000..8f4adf03c30 --- /dev/null +++ b/src/native/mono/runtime-base/search-xxhash.hh @@ -0,0 +1,47 @@ +// Dear Emacs, this is a -*- C++ -*- header +#pragma once + +#include +#include + +#include +#include + +namespace xamarin::android { + class SearchXxHash final + { + public: + template + [[gnu::always_inline, gnu::flatten]] + static ssize_t binary_search (hash_t key, const T *arr, size_t n) noexcept + { + return Search::binary_search (key, arr, n); + } + + [[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; }; + auto less_than = [](hash_t const& entry, hash_t key) -> bool { return entry < key; }; + + return Search::binary_search (key, arr, n); + } + + [[gnu::always_inline]] + static ptrdiff_t binary_search_branchless (hash_t x, const hash_t *arr, uint32_t len) noexcept + { + const hash_t *base = arr; + while (len > 1) { + uint32_t half = len >> 1; + // __builtin_prefetch(&base[(len - half) / 2]); + // __builtin_prefetch(&base[half + (len - half) / 2]); + base = (base[half] < x ? &base[half] : base); + len -= half; + } + + //return *(base + (*base < x)); + ptrdiff_t ret = (base + (*base < x)) - arr; + return arr[ret] == x ? ret : -1; + } + }; +} From cc9c04093dbd8987907a3b5260185ae05f367ef2 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 07:42:49 +0200 Subject: [PATCH 07/18] [coreclr] Remove xxHash include paths from native CMake Stop adding external/xxHash and constexpr-xxh3 include paths to CoreCLR native targets now that CoreCLR no longer includes shared/xxhash.hh. Keep the xxHash include support available for MonoVM and NativeAOT paths that still use it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/CMakeLists.txt | 3 --- src/native/clr/host/CMakeLists.txt | 1 - src/native/clr/shared/CMakeLists.txt | 1 - 3 files changed, 5 deletions(-) diff --git a/src/native/CMakeLists.txt b/src/native/CMakeLists.txt index 26139682952..5de34c2f2de 100644 --- a/src/native/CMakeLists.txt +++ b/src/native/CMakeLists.txt @@ -248,7 +248,6 @@ endif() set(JAVA_INTEROP_INCLUDE_DIR ${JAVA_INTEROP_SRC_PATH}) set(CONSTEXPR_XXH3_DIR "${EXTERNAL_DIR}/constexpr-xxh3") -set(XXHASH_DIR "${EXTERNAL_DIR}/xxHash") include_directories(common/include) @@ -260,8 +259,6 @@ if(IS_CLR_RUNTIME) ${TARGET} PRIVATE ${XA_RUNTIME_INCLUDE_DIR} - ${EXTERNAL_DIR} - ${CONSTEXPR_XXH3_DIR} ${RUNTIME_INCLUDE_DIR} ) endmacro() diff --git a/src/native/clr/host/CMakeLists.txt b/src/native/clr/host/CMakeLists.txt index e18d1a2531d..9a1beec7b75 100644 --- a/src/native/clr/host/CMakeLists.txt +++ b/src/native/clr/host/CMakeLists.txt @@ -118,7 +118,6 @@ macro(lib_target_options TARGET_NAME) ${TARGET_NAME} BEFORE PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include - ${EXTERNAL_DIR} ${ROBIN_MAP_DIR}/include ) diff --git a/src/native/clr/shared/CMakeLists.txt b/src/native/clr/shared/CMakeLists.txt index aa6062066a6..2fe4716d5e5 100644 --- a/src/native/clr/shared/CMakeLists.txt +++ b/src/native/clr/shared/CMakeLists.txt @@ -21,7 +21,6 @@ macro(lib_target_options TARGET_NAME) ${TARGET_NAME} PUBLIC "$" - "$" ) target_link_libraries( From 3015d58ac9dc3c5d1c76419d0903a9b8d6db85be Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 09:36:52 +0200 Subject: [PATCH 08/18] [coreclr] Define local hash_t alias for CRC32 Introduce a CoreCLR-local hash_t alias to uint32_t in the CRC32 helper and use it for CoreCLR hash fields, lookup signatures, and generated p/invoke tables. This keeps the code readable without reintroducing the shared xxHash wrapper or changing MonoVM's hash_t layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/clr/host/assembly-store.cc | 10 ++++----- src/native/clr/host/host.cc | 10 ++++----- src/native/clr/host/typemap.cc | 10 ++++----- src/native/clr/include/host/assembly-store.hh | 2 +- .../clr/include/host/pinvoke-override-impl.hh | 2 +- .../clr/include/host/pinvoke-override.hh | 10 ++++----- src/native/clr/include/host/typemap.hh | 4 ++-- src/native/clr/include/runtime-base/crc32.hh | 6 +++-- .../clr/include/runtime-base/monodroid-dl.hh | 22 +++++++++---------- src/native/clr/include/xamarin-app.hh | 17 +++++++------- src/native/clr/pinvoke-override/dynamic.cc | 6 ++--- .../generate-pinvoke-tables.cc | 2 +- .../pinvoke-override/pinvoke-tables.include | 14 ++++++------ .../clr/pinvoke-override/precompiled.cc | 4 ++-- .../xamarin-app-stub/application_dso_stub.cc | 2 +- 15 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/native/clr/host/assembly-store.cc b/src/native/clr/host/assembly-store.cc index 62bf0375d8a..45722d29276 100644 --- a/src/native/clr/host/assembly-store.cc +++ b/src/native/clr/host/assembly-store.cc @@ -179,11 +179,11 @@ auto AssemblyStore::get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData co } [[gnu::always_inline]] -auto AssemblyStore::find_assembly_store_entry (uint32_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry* +auto AssemblyStore::find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry* { - auto equal = [](AssemblyStoreIndexEntry const& entry, uint32_t key) -> bool { return entry.name_hash == key; }; - auto less_than = [](AssemblyStoreIndexEntry const& entry, uint32_t key) -> bool { return entry.name_hash < key; }; - ssize_t idx = Search::binary_search (hash, entries, entry_count); + auto equal = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash == key; }; + auto less_than = [](AssemblyStoreIndexEntry const& entry, hash_t key) -> bool { return entry.name_hash < key; }; + ssize_t idx = Search::binary_search (hash, entries, entry_count); if (idx >= 0) { return &entries[idx]; } @@ -192,7 +192,7 @@ auto AssemblyStore::find_assembly_store_entry (uint32_t hash, const AssemblyStor auto AssemblyStore::open_assembly (std::string_view const& name, int64_t &size) noexcept -> void* { - uint32_t name_hash = crc32_hash (name); + hash_t name_hash = crc32_hash (name); if constexpr (Constants::is_debug_build) { // In fastdev mode we might not have any assembly store. diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 7df9ba58473..efca594e485 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -60,11 +60,11 @@ size_t Host::clr_get_runtime_property (const char *key, char *value_buffer, size return 0; } - uint32_t key_hash = crc32_hash (key, strlen (key)); + hash_t key_hash = crc32_hash (key, strlen (key)); - auto equal = [](RuntimePropertyIndexEntry const& entry, uint32_t key) -> bool { return entry.key_hash == key; }; - auto less_than = [](RuntimePropertyIndexEntry const& entry, uint32_t key) -> bool { return entry.key_hash < key; }; - ssize_t idx = Search::binary_search (key_hash, runtime_property_index, application_config.number_of_runtime_properties); + auto equal = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash == key; }; + auto less_than = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash < key; }; + ssize_t idx = Search::binary_search (key_hash, runtime_property_index, application_config.number_of_runtime_properties); if (idx < 0) { log_debug (LOG_DEFAULT, "Runtime property '{}' not found"sv, key); return 0; @@ -154,7 +154,7 @@ auto Host::zip_scan_callback (std::string_view const& apk_path, int apk_fd, dyna log_debug (LOG_ASSEMBLY, "Found shared library in '{}': {}"sv, apk_path, entry_name.get ()); std::string_view lib_name { entry_name.get () + Zip::lib_prefix.length () }; - uint32_t name_hash = crc32_hash (lib_name); + hash_t name_hash = crc32_hash (lib_name); log_debug (LOG_ASSEMBLY, "Library name is: {}; hash == 0x{:x}", lib_name, name_hash); DSOApkEntry *apk_entry = MonodroidDl::find_dso_apk_entry (name_hash); diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index 1c7ca84bba5..a970e7b2d07 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -108,7 +108,7 @@ auto TypeMapper::find_index_by_hash (const char *typeName, const TypeMapEntry *m log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses hashes"sv, from_name, to_name); size_t type_name_length = strlen (typeName); - uint32_t type_name_hash = crc32_hash (typeName, type_name_length); + hash_t type_name_hash = crc32_hash (typeName, type_name_length); size_t left = 0; size_t right = type_map.entry_count; @@ -214,7 +214,7 @@ auto TypeMapper::find_module_entry (const uint8_t *mvid, const TypeMapModule *en } [[gnu::always_inline]] -auto TypeMapper::find_managed_to_java_map_entry (uint32_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry* +auto TypeMapper::find_managed_to_java_map_entry (hash_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry* { if (map == nullptr) { return nullptr; @@ -258,7 +258,7 @@ auto TypeMapper::managed_to_java_release (const char *typeName, const uint8_t *m log_debug (LOG_ASSEMBLY, "typemap: found module matching MVID [{}]"sv, MonoGuidString (mvid).c_str ()); size_t type_name_length = strlen (typeName); - uint32_t name_hash = crc32_hash (typeName, type_name_length); + hash_t name_hash = crc32_hash (typeName, type_name_length); // We implicitly trust the build process that the indexes are correct. This is by design, the libxamarin-app.so built // with the application is immutable and the build process made sure that the data in it matches the application. @@ -407,7 +407,7 @@ auto TypeMapper::java_to_managed_debug (const char *java_type_name, char const** #else // def DEBUG [[gnu::always_inline]] -auto TypeMapper::find_java_to_managed_entry (uint32_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava* +auto TypeMapper::find_java_to_managed_entry (hash_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava* { size_t left = 0; size_t right = java_type_count; @@ -467,7 +467,7 @@ auto TypeMapper::java_to_managed_release (const char *java_type_name, char const } size_t java_type_name_length = strlen (java_type_name); - uint32_t name_hash = crc32_hash (java_type_name, java_type_name_length); + hash_t name_hash = crc32_hash (java_type_name, java_type_name_length); TypeMapJava const* java_entry = find_java_to_managed_entry (name_hash, java_type_name, java_type_name_length); if (java_entry == nullptr) { log_info ( diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh index 1018122be80..eac5a30f5f1 100644 --- a/src/native/clr/include/host/assembly-store.hh +++ b/src/native/clr/include/host/assembly-store.hh @@ -27,7 +27,7 @@ namespace xamarin::android { // Returns a tuple of static auto get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, std::string_view const& name) noexcept -> std::tuple; - static auto find_assembly_store_entry (uint32_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry*; + static auto find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept -> const AssemblyStoreIndexEntry*; private: static inline AssemblyStoreIndexEntry *assembly_store_hashes = nullptr; diff --git a/src/native/clr/include/host/pinvoke-override-impl.hh b/src/native/clr/include/host/pinvoke-override-impl.hh index d5288548308..93192e44fa5 100644 --- a/src/native/clr/include/host/pinvoke-override-impl.hh +++ b/src/native/clr/include/host/pinvoke-override-impl.hh @@ -101,7 +101,7 @@ namespace xamarin::android { } PINVOKE_OVERRIDE_INLINE - auto PinvokeOverride::find_pinvoke_address (pinvoke_entry_hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry* + auto PinvokeOverride::find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry* { while (entry_count > 0uz) { const size_t mid = entry_count / 2uz; diff --git a/src/native/clr/include/host/pinvoke-override.hh b/src/native/clr/include/host/pinvoke-override.hh index b4d39ed482a..1a80acdd6e1 100644 --- a/src/native/clr/include/host/pinvoke-override.hh +++ b/src/native/clr/include/host/pinvoke-override.hh @@ -35,13 +35,11 @@ #include namespace xamarin::android { - using pinvoke_entry_hash_t = uint32_t; - struct PinvokeEntry { - pinvoke_entry_hash_t hash; - const char *name; - void *func; + hash_t hash; + const char *name; + void *func; }; struct string_hash @@ -80,7 +78,7 @@ namespace xamarin::android { static auto load_library_symbol (std::string_view const& library_name, std::string_view const& symbol_name, void **dso_handle = nullptr) noexcept -> void*; static auto load_library_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map) noexcept -> void*; static auto fetch_or_create_pinvoke_map_entry (std::string const& library_name, std::string const& entrypoint_name, pinvoke_api_map_ptr api_map, bool need_lock) noexcept -> void*; - static auto find_pinvoke_address (pinvoke_entry_hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*; + static auto find_pinvoke_address (hash_t hash, const PinvokeEntry *entries, size_t entry_count) noexcept -> PinvokeEntry*; static auto handle_other_pinvoke_request (std::string_view const& library_name, std::string_view const& entrypoint_name) noexcept -> void*; static void handle_jni_on_load (JavaVM *vm, void *reserved) noexcept; static auto monodroid_pinvoke_override (const char *library_name, const char *entrypoint_name) noexcept -> void*; diff --git a/src/native/clr/include/host/typemap.hh b/src/native/clr/include/host/typemap.hh index 2e191513350..6a3ee0565e5 100644 --- a/src/native/clr/include/host/typemap.hh +++ b/src/native/clr/include/host/typemap.hh @@ -20,11 +20,11 @@ namespace xamarin::android { #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 (uint32_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry*; + static auto find_managed_to_java_map_entry (hash_t name_hash, const char *type_name, size_t type_name_length, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry*; 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 (uint32_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava*; + static auto find_java_to_managed_entry (hash_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava*; #else 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; diff --git a/src/native/clr/include/runtime-base/crc32.hh b/src/native/clr/include/runtime-base/crc32.hh index b288b81ec4a..e774e6fedbc 100644 --- a/src/native/clr/include/runtime-base/crc32.hh +++ b/src/native/clr/include/runtime-base/crc32.hh @@ -8,8 +8,10 @@ extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int32_t len); namespace xamarin::android { + using hash_t = uint32_t; + [[gnu::always_inline]] - inline auto crc32_hash (const char *value, size_t len) noexcept -> uint32_t + inline auto crc32_hash (const char *value, size_t len) noexcept -> hash_t { if (len == 0) [[unlikely]] { return std::numeric_limits::max (); @@ -19,7 +21,7 @@ namespace xamarin::android { } [[gnu::always_inline]] - inline auto crc32_hash (std::string_view const& value) noexcept -> uint32_t + inline auto crc32_hash (std::string_view const& value) noexcept -> hash_t { return crc32_hash (value.data (), value.length ()); } diff --git a/src/native/clr/include/runtime-base/monodroid-dl.hh b/src/native/clr/include/runtime-base/monodroid-dl.hh index 5c75457cba9..5164a6ce823 100644 --- a/src/native/clr/include/runtime-base/monodroid-dl.hh +++ b/src/native/clr/include/runtime-base/monodroid-dl.hh @@ -33,7 +33,7 @@ namespace xamarin::android template [[gnu::always_inline, gnu::flatten]] - static auto find_dso_cache_entry_common (uint32_t hash) noexcept -> DSOCacheEntry* + static auto find_dso_cache_entry_common (hash_t hash) noexcept -> DSOCacheEntry* { static_assert (WhichCache == CacheKind::AOT || WhichCache == CacheKind::DSO, "Unknown cache type specified"); @@ -50,9 +50,9 @@ namespace xamarin::android arr_size = application_config.number_of_dso_cache_entries; } - auto equal = [](DSOCacheEntry const& entry, uint32_t key) -> bool { return entry.hash == key; }; - auto less_than = [](DSOCacheEntry const& entry, uint32_t key) -> bool { return entry.hash < key; }; - ssize_t idx = Search::binary_search (hash, arr, arr_size); + auto equal = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash == key; }; + auto less_than = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash < key; }; + ssize_t idx = Search::binary_search (hash, arr, arr_size); if (idx >= 0) { return &arr[idx]; @@ -62,13 +62,13 @@ namespace xamarin::android } [[gnu::always_inline, gnu::flatten]] - static auto find_only_aot_cache_entry (uint32_t hash) noexcept -> DSOCacheEntry* + static auto find_only_aot_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* { return find_dso_cache_entry_common (hash); } [[gnu::always_inline, gnu::flatten]] - static auto find_only_dso_cache_entry (uint32_t hash) noexcept -> DSOCacheEntry* + static auto find_only_dso_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* { return find_dso_cache_entry_common (hash); } @@ -85,11 +85,11 @@ namespace xamarin::android } [[gnu::flatten]] - static auto find_dso_apk_entry (uint32_t hash) -> DSOApkEntry* + static auto find_dso_apk_entry (hash_t hash) -> DSOApkEntry* { - auto equal = [](DSOApkEntry const& entry, uint32_t key) -> bool { return entry.name_hash == key; }; - auto less_than = [](DSOApkEntry const& entry, uint32_t key) -> bool { return entry.name_hash < key; }; - ssize_t idx = Search::binary_search ( + auto equal = [](DSOApkEntry const& entry, hash_t key) -> bool { return entry.name_hash == key; }; + auto less_than = [](DSOApkEntry const& entry, hash_t key) -> bool { return entry.name_hash < key; }; + ssize_t idx = Search::binary_search ( hash, dso_apk_entries, application_config.number_of_shared_libraries ); @@ -152,7 +152,7 @@ namespace xamarin::android return nullptr; } - uint32_t name_hash = crc32_hash (name); + hash_t name_hash = crc32_hash (name); log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); DSOCacheEntry *dso = nullptr; diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 53403396664..f28e0e6c6f1 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -5,6 +5,7 @@ #include #include +#include static constexpr uint64_t FORMAT_TAG = 0x00045E6972616D58; // 'Xmari^XY' where XY is the format version static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x535A4158; // 'XAZS', little-endian @@ -44,7 +45,7 @@ static constexpr uint8_t MODULE_FORMAT_VERSION = 2; // Keep in sync with struct TypeMapEntry { const uint32_t from; - const uint32_t from_hash; + const xamarin::android::hash_t from_hash; const uint32_t to; }; @@ -74,7 +75,7 @@ struct TypeMapAssembly #else struct TypeMapModuleEntry { - uint32_t managed_type_name_hash; + xamarin::android::hash_t managed_type_name_hash; uint32_t managed_type_name_index; uint32_t managed_type_name_length; uint32_t java_map_index; @@ -171,7 +172,7 @@ struct [[gnu::packed]] AssemblyStoreHeader final struct [[gnu::packed]] AssemblyStoreIndexEntry final { - uint32_t name_hash; + xamarin::android::hash_t name_hash; uint32_t descriptor_index; uint8_t ignore; // Assembly should be ignored when loading, its data isn't actually there }; @@ -242,21 +243,21 @@ struct RuntimeProperty struct RuntimePropertyIndexEntry { - uint32_t key_hash; + xamarin::android::hash_t key_hash; uint32_t index; }; struct DSOApkEntry { - uint32_t name_hash; + xamarin::android::hash_t name_hash; uint32_t offset; // offset into the APK int32_t fd; // apk file descriptor }; struct DSOCacheEntry { - const uint32_t hash; - const uint32_t real_name_hash; + const xamarin::android::hash_t hash; + const xamarin::android::hash_t real_name_hash; const bool ignore; const bool is_jni_library; const uint32_t name_index; @@ -329,7 +330,7 @@ extern "C" { [[gnu::visibility("default")]] extern const TypeMapModuleEntry modules_map_data[]; [[gnu::visibility("default")]] extern const TypeMapModuleEntry modules_duplicates_data[]; [[gnu::visibility("default")]] extern const TypeMapJava java_to_managed_map[]; - [[gnu::visibility("default")]] extern const uint32_t java_to_managed_hashes[]; + [[gnu::visibility("default")]] extern const xamarin::android::hash_t java_to_managed_hashes[]; #endif [[gnu::visibility("default")]] extern uint32_t compressed_assembly_count; diff --git a/src/native/clr/pinvoke-override/dynamic.cc b/src/native/clr/pinvoke-override/dynamic.cc index 291e70b9905..9506874f51e 100644 --- a/src/native/clr/pinvoke-override/dynamic.cc +++ b/src/native/clr/pinvoke-override/dynamic.cc @@ -13,7 +13,7 @@ using JniOnLoadHandler = jint (*) (JavaVM *vm, void *reserved); // These external functions are generated during application build (see obj/${CONFIGURATION}/${RID}/android/pinvoke_preserve.*.ll) // extern "C" { - void* find_pinvoke (uint32_t library_name_hash, uint32_t entrypoint_hash, bool &known_library); + void* find_pinvoke (hash_t library_name_hash, hash_t entrypoint_hash, bool &known_library); extern const uint32_t __jni_on_load_handler_count; extern const JniOnLoadHandler __jni_on_load_handlers[]; @@ -37,8 +37,8 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons ); } - uint32_t library_name_hash = crc32_hash (library_name, strlen (library_name)); - uint32_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); + hash_t library_name_hash = crc32_hash (library_name, strlen (library_name)); + hash_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); log_debug (LOG_ASSEMBLY, "library_name_hash == 0x{:x}; entrypoint_hash == 0x{:x}"sv, library_name_hash, entrypoint_hash); bool known_library = true; diff --git a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc index b7e5256e16b..decf59542a2 100644 --- a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc +++ b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc @@ -176,7 +176,7 @@ bool generate_hashes (std::string table_name, std::vector const& na void write_library_name_hash (std::ostream& os, std::string library_name, std::string variable_prefix) { uint32_t hash = crc32_hash (library_name.c_str (), library_name.length ()); - os << "constexpr pinvoke_entry_hash_t " << variable_prefix << "_library_hash = " << std::hex << hash << ";" << std::endl; + os << "constexpr hash_t " << variable_prefix << "_library_hash = " << std::hex << hash << ";" << std::endl; } void write_library_name_hashes (std::ostream& output) diff --git a/src/native/clr/pinvoke-override/pinvoke-tables.include b/src/native/clr/pinvoke-override/pinvoke-tables.include index 40f30b8bbb2..0bee2a46104 100644 --- a/src/native/clr/pinvoke-override/pinvoke-tables.include +++ b/src/native/clr/pinvoke-override/pinvoke-tables.include @@ -42,13 +42,13 @@ namespace { }}; -constexpr pinvoke_entry_hash_t java_interop_library_hash = 0x33b98009; -constexpr pinvoke_entry_hash_t xa_internal_api_library_hash = 0xf03cbae; -constexpr pinvoke_entry_hash_t android_liblog_library_hash = 0xbb87f5e1; -constexpr pinvoke_entry_hash_t system_native_library_hash = 0xa9877ba; -constexpr pinvoke_entry_hash_t system_io_compression_native_library_hash = 0xb234abb7; -constexpr pinvoke_entry_hash_t system_security_cryptography_native_android_library_hash = 0x6bfed756; -constexpr pinvoke_entry_hash_t system_globalization_native_library_hash = 0x7d160aeb; +constexpr hash_t java_interop_library_hash = 0x33b98009; +constexpr hash_t xa_internal_api_library_hash = 0xf03cbae; +constexpr hash_t android_liblog_library_hash = 0xbb87f5e1; +constexpr hash_t system_native_library_hash = 0xa9877ba; +constexpr hash_t system_io_compression_native_library_hash = 0xb234abb7; +constexpr hash_t system_security_cryptography_native_android_library_hash = 0x6bfed756; +constexpr hash_t system_globalization_native_library_hash = 0x7d160aeb; constexpr size_t internal_pinvokes_count = 28; } // end of anonymous namespace diff --git a/src/native/clr/pinvoke-override/precompiled.cc b/src/native/clr/pinvoke-override/precompiled.cc index 3dde3788ead..d29e612fbbb 100644 --- a/src/native/clr/pinvoke-override/precompiled.cc +++ b/src/native/clr/pinvoke-override/precompiled.cc @@ -15,8 +15,8 @@ auto PinvokeOverride::monodroid_pinvoke_override (const char *library_name, cons return nullptr; } - uint32_t library_name_hash = crc32_hash (library_name, strlen (library_name)); - uint32_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); + hash_t library_name_hash = crc32_hash (library_name, strlen (library_name)); + hash_t entrypoint_hash = crc32_hash (entrypoint_name, strlen (entrypoint_name)); if (library_name_hash == java_interop_library_hash || library_name_hash == xa_internal_api_library_hash || library_name_hash == android_liblog_library_hash) { PinvokeEntry *entry = find_pinvoke_address (entrypoint_hash, internal_pinvokes.data (), internal_pinvokes_count); 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 3f35d8c0c7b..b08ede8db8a 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -36,7 +36,7 @@ const TypeMapModule managed_to_java_map[] = {}; const TypeMapModuleEntry modules_map_data[] = {}; const TypeMapModuleEntry modules_duplicates_data[] = {}; const TypeMapJava java_to_managed_map[] = {}; -const uint32_t java_to_managed_hashes[] = {}; +const xamarin::android::hash_t java_to_managed_hashes[] = {}; #endif uint32_t compressed_assembly_count = 0; From 3b5fdcc32f24bbc19a99e1dc0d3d87ee7e91fd80 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 09:46:03 +0200 Subject: [PATCH 09/18] [coreclr] Use constexpr CRC32 expressions in tables Add a compile-time crc32_hash overload for string literals and use it in CoreCLR generated/default tables instead of hardcoded hex hash values. This keeps the generated p/invoke table and app stub self-documenting while preserving the runtime CRC32 helper for dynamic strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/clr/include/runtime-base/crc32.hh | 19 +++++ .../generate-pinvoke-tables.cc | 5 +- .../pinvoke-override/pinvoke-tables.include | 70 +++++++++---------- .../xamarin-app-stub/application_dso_stub.cc | 22 +++--- 4 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/native/clr/include/runtime-base/crc32.hh b/src/native/clr/include/runtime-base/crc32.hh index e774e6fedbc..cd5bb1ed60e 100644 --- a/src/native/clr/include/runtime-base/crc32.hh +++ b/src/native/clr/include/runtime-base/crc32.hh @@ -9,6 +9,25 @@ extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int3 namespace xamarin::android { using hash_t = uint32_t; + static constexpr hash_t CRC32_POLYNOMIAL = 0xedb88320; + + template + consteval auto crc32_hash (const char (&value)[Size]) noexcept -> hash_t + { + if constexpr (Size <= 1) { + return std::numeric_limits::max (); + } + + hash_t crc = 0xffffffff; + for (size_t i = 0; i < Size - 1; i++) { + crc ^= static_cast(value [i]); + for (size_t bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ ((crc & 1) != 0 ? CRC32_POLYNOMIAL : 0); + } + } + + return ~crc; + } [[gnu::always_inline]] inline auto crc32_hash (const char *value, size_t len) noexcept -> hash_t diff --git a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc index decf59542a2..6c2892a5944 100644 --- a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc +++ b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc @@ -122,7 +122,7 @@ struct PinvokeEntry template friend Os& operator<< (Os& os, PinvokeEntry const& p) { - os << std::showbase << std::hex << p.hash << ", \"" << p.name << "\", "; + os << "crc32_hash (\"" << p.name << "\"), \"" << p.name << "\", "; if (p.write_func_pointer) { return os << "reinterpret_cast(&" << p.name << ")"; @@ -175,8 +175,7 @@ bool generate_hashes (std::string table_name, std::vector const& na void write_library_name_hash (std::ostream& os, std::string library_name, std::string variable_prefix) { - uint32_t hash = crc32_hash (library_name.c_str (), library_name.length ()); - os << "constexpr hash_t " << variable_prefix << "_library_hash = " << std::hex << hash << ";" << std::endl; + os << "constexpr hash_t " << variable_prefix << "_library_hash = crc32_hash (\"" << library_name << "\");" << std::endl; } void write_library_name_hashes (std::ostream& output) diff --git a/src/native/clr/pinvoke-override/pinvoke-tables.include b/src/native/clr/pinvoke-override/pinvoke-tables.include index 0bee2a46104..6c28f505ad6 100644 --- a/src/native/clr/pinvoke-override/pinvoke-tables.include +++ b/src/native/clr/pinvoke-override/pinvoke-tables.include @@ -11,44 +11,44 @@ namespace { // CRC32 internal p/invoke table std::array internal_pinvokes {{ - {0xc7eb66d, "monodroid_log", reinterpret_cast(&monodroid_log)}, - {0x1208417b, "_monodroid_gref_log_new", reinterpret_cast(&_monodroid_gref_log_new)}, - {0x21f6db74, "_monodroid_lookup_replacement_type", reinterpret_cast(&_monodroid_lookup_replacement_type)}, - {0x28cb701d, "_monodroid_weak_gref_inc", reinterpret_cast(&_monodroid_weak_gref_inc)}, - {0x2e4c74a2, "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, - {0x36396c04, "monodroid_timing_stop", reinterpret_cast(&monodroid_timing_stop)}, - {0x36f14311, "_monodroid_gref_inc", reinterpret_cast(&_monodroid_gref_inc)}, - {0x4272011b, "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, - {0x5c483217, "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, - {0x5e487bd1, "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, - {0x638c99a0, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, - {0x639ae575, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, - {0x6c845282, "__android_log_print", reinterpret_cast(&__android_log_print)}, - {0x741c16ae, "xamarin_app_init", reinterpret_cast(&xamarin_app_init)}, - {0x80bcdd2f, "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, - {0x95a0abcc, "monodroid_free", reinterpret_cast(&monodroid_free)}, - {0x9fe0c9d9, "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, - {0xb29db2e8, "_monodroid_lookup_replacement_method_info", reinterpret_cast(&_monodroid_lookup_replacement_method_info)}, - {0xba99e855, "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, - {0xc3e73a85, "_monodroid_weak_gref_dec", reinterpret_cast(&_monodroid_weak_gref_dec)}, - {0xc58330cb, "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, - {0xcdc08ad5, "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, - {0xce55fbf3, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, - {0xd4aa6b2e, "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, - {0xdddd0989, "_monodroid_gref_dec", reinterpret_cast(&_monodroid_gref_dec)}, - {0xdf04d569, "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, - {0xf403b8b9, "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, - {0xf65149df, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, + {crc32_hash ("monodroid_log"), "monodroid_log", reinterpret_cast(&monodroid_log)}, + {crc32_hash ("_monodroid_gref_log_new"), "_monodroid_gref_log_new", reinterpret_cast(&_monodroid_gref_log_new)}, + {crc32_hash ("_monodroid_lookup_replacement_type"), "_monodroid_lookup_replacement_type", reinterpret_cast(&_monodroid_lookup_replacement_type)}, + {crc32_hash ("_monodroid_weak_gref_inc"), "_monodroid_weak_gref_inc", reinterpret_cast(&_monodroid_weak_gref_inc)}, + {crc32_hash ("_monodroid_gref_log"), "_monodroid_gref_log", reinterpret_cast(&_monodroid_gref_log)}, + {crc32_hash ("monodroid_timing_stop"), "monodroid_timing_stop", reinterpret_cast(&monodroid_timing_stop)}, + {crc32_hash ("_monodroid_gref_inc"), "_monodroid_gref_inc", reinterpret_cast(&_monodroid_gref_inc)}, + {crc32_hash ("_monodroid_weak_gref_get"), "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, + {crc32_hash ("_monodroid_gref_get"), "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, + {crc32_hash ("_monodroid_gref_log_delete"), "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, + {crc32_hash ("_monodroid_weak_gref_delete"), "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, + {crc32_hash ("_monodroid_lref_log_new"), "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, + {crc32_hash ("__android_log_print"), "__android_log_print", reinterpret_cast(&__android_log_print)}, + {crc32_hash ("xamarin_app_init"), "xamarin_app_init", reinterpret_cast(&xamarin_app_init)}, + {crc32_hash ("_monodroid_lref_log_delete"), "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, + {crc32_hash ("monodroid_free"), "monodroid_free", reinterpret_cast(&monodroid_free)}, + {crc32_hash ("_monodroid_detect_cpu_and_architecture"), "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, + {crc32_hash ("_monodroid_lookup_replacement_method_info"), "_monodroid_lookup_replacement_method_info", reinterpret_cast(&_monodroid_lookup_replacement_method_info)}, + {crc32_hash ("monodroid_timing_start"), "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, + {crc32_hash ("_monodroid_weak_gref_dec"), "_monodroid_weak_gref_dec", reinterpret_cast(&_monodroid_weak_gref_dec)}, + {crc32_hash ("monodroid_TypeManager_get_java_class_name"), "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, + {crc32_hash ("clr_typemap_managed_to_java"), "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, + {crc32_hash ("clr_initialize_gc_bridge"), "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, + {crc32_hash ("_monodroid_weak_gref_new"), "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, + {crc32_hash ("_monodroid_gref_dec"), "_monodroid_gref_dec", reinterpret_cast(&_monodroid_gref_dec)}, + {crc32_hash ("_monodroid_gc_wait_for_bridge_processing"), "_monodroid_gc_wait_for_bridge_processing", reinterpret_cast(&_monodroid_gc_wait_for_bridge_processing)}, + {crc32_hash ("_monodroid_max_gref_get"), "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, + {crc32_hash ("clr_typemap_java_to_managed"), "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, }}; -constexpr hash_t java_interop_library_hash = 0x33b98009; -constexpr hash_t xa_internal_api_library_hash = 0xf03cbae; -constexpr hash_t android_liblog_library_hash = 0xbb87f5e1; -constexpr hash_t system_native_library_hash = 0xa9877ba; -constexpr hash_t system_io_compression_native_library_hash = 0xb234abb7; -constexpr hash_t system_security_cryptography_native_android_library_hash = 0x6bfed756; -constexpr hash_t system_globalization_native_library_hash = 0x7d160aeb; +constexpr hash_t java_interop_library_hash = crc32_hash ("java-interop"); +constexpr hash_t xa_internal_api_library_hash = crc32_hash ("xa-internal-api"); +constexpr hash_t android_liblog_library_hash = crc32_hash ("liblog"); +constexpr hash_t system_native_library_hash = crc32_hash ("libSystem.Native"); +constexpr hash_t system_io_compression_native_library_hash = crc32_hash ("libSystem.IO.Compression.Native"); +constexpr hash_t system_security_cryptography_native_android_library_hash = crc32_hash ("libSystem.Security.Cryptography.Native.Android"); +constexpr hash_t system_globalization_native_library_hash = crc32_hash ("libSystem.Globalization.Native"); constexpr size_t internal_pinvokes_count = 28; } // end of anonymous namespace 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 b08ede8db8a..3a07fbacae2 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -109,8 +109,8 @@ constexpr char fake_dso_name2[] = "libaot-Another.Assembly.dll.so"; DSOCacheEntry dso_cache[] = { { - .hash = 0x4ce380ac, - .real_name_hash = 0x4ce380ac, + .hash = xamarin::android::crc32_hash (fake_dso_name), + .real_name_hash = xamarin::android::crc32_hash (fake_dso_name), .ignore = true, .is_jni_library = false, .name_index = 1, @@ -118,8 +118,8 @@ DSOCacheEntry dso_cache[] = { }, { - .hash = 0x5f995ac5, - .real_name_hash = 0x5f995ac5, + .hash = xamarin::android::crc32_hash (fake_dso_name2), + .real_name_hash = xamarin::android::crc32_hash (fake_dso_name2), .ignore = true, .is_jni_library = false, .name_index = 2, @@ -135,8 +135,8 @@ const uint dso_jni_preloads_idx[1] = { DSOCacheEntry aot_dso_cache[] = { { - .hash = 0x4ce380ac, - .real_name_hash = 0x4ce380ac, + .hash = xamarin::android::crc32_hash (fake_dso_name), + .real_name_hash = xamarin::android::crc32_hash (fake_dso_name), .ignore = true, .is_jni_library = true, .name_index = 3, @@ -144,8 +144,8 @@ DSOCacheEntry aot_dso_cache[] = { }, { - .hash = 0x5f995ac5, - .real_name_hash = 0x5f995ac5, + .hash = xamarin::android::crc32_hash (fake_dso_name2), + .real_name_hash = xamarin::android::crc32_hash (fake_dso_name2), .ignore = true, .is_jni_library = false, .name_index = 4, @@ -272,17 +272,17 @@ const char runtime_properties_data[] = {}; const RuntimePropertyIndexEntry runtime_property_index[] = { { - .key_hash = 0x0967b587, + .key_hash = xamarin::android::crc32_hash (prop_test_string_key), .index = 0, }, { - .key_hash = 0x56c45ca0, + .key_hash = xamarin::android::crc32_hash (prop_test_boolean_key), .index = 2, }, { - .key_hash = 0x864e7fc3, + .key_hash = xamarin::android::crc32_hash (prop_test_integer_key), .index = 1, }, }; From e4acaedc4caebb28326d6572f52d0515d2c9d4a5 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 10:52:15 +0200 Subject: [PATCH 10/18] [tests] Update CoreCLR CRC32 test expectations Allow generated-environment tests to parse decimal uint32 values above Int32.MaxValue and update CoreCLR APK size baselines after the CRC32 native size changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/EnvironmentHelper.cs | 29 ++++++++++++------- ...ldReleaseArm64SimpleDotNet.CoreCLR.apkdesc | 20 ++++++------- ...ldReleaseArm64XFormsDotNet.CoreCLR.apkdesc | 20 ++++++------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 60eb77c7ee9..4437a2baca1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -1313,20 +1313,24 @@ static byte ConvertFieldToByte (string fieldName, string llvmAssemblerEnvFile, s return fv; } - // Integers are parsed as signed, since llc will always output signed integers. + // llc usually emits signed decimal integers, but large unsigned fields can also be emitted as + // decimal values above Int32.MaxValue. Accept both forms. static bool TryParseInteger (string value, out uint fv) { if (value.StartsWith ("0x", StringComparison.Ordinal)) { return UInt32.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv); } - fv = 0; - if (!Int32.TryParse (value, out int signedFV)) { - return false; + if (UInt32.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv)) { + return true; } - fv = (uint)signedFV; - return true; + if (Int32.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out int signedFV)) { + fv = (uint)signedFV; + return true; + } + + return false; } static bool TryParseInteger (string value, out ulong fv) @@ -1335,13 +1339,16 @@ static bool TryParseInteger (string value, out ulong fv) return UInt64.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv); } - fv = 0; - if (!Int64.TryParse (value, out long signedFV)) { - return false; + if (UInt64.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv)) { + return true; + } + + if (Int64.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out long signedFV)) { + fv = (ulong)signedFV; + return true; } - fv = (ulong)signedFV; - return true; + return false; } static bool TryParseInteger (string value, out byte fv) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc index 065272635fa..c8814e52e16 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc @@ -5,34 +5,34 @@ "Size": 3036 }, "classes.dex": { - "Size": 402352 + "Size": 402852 }, "lib/arm64-v8a/libassembly-store.so": { - "Size": 2717688 + "Size": 2724560 }, "lib/arm64-v8a/libclrjit.so": { - "Size": 2835128 + "Size": 2757816 }, "lib/arm64-v8a/libcoreclr.so": { - "Size": 4891056 + "Size": 4837240 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1324320 + "Size": 1252416 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 72112 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { - "Size": 1262888 + "Size": 1258776 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 101888 + "Size": 99664 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 168080 + "Size": 163936 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20968 + "Size": 21072 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -59,5 +59,5 @@ "Size": 1904 } }, - "PackageSize": 7271867 + "PackageSize": 7235003 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc index eb6cece9dff..b08d187284e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc @@ -8,7 +8,7 @@ "Size": 9413828 }, "classes2.dex": { - "Size": 158204 + "Size": 157836 }, "kotlin/annotation/annotation.kotlin_builtins": { "Size": 928 @@ -32,31 +32,31 @@ "Size": 2396 }, "lib/arm64-v8a/libassembly-store.so": { - "Size": 11658648 + "Size": 11696200 }, "lib/arm64-v8a/libclrjit.so": { - "Size": 2824392 + "Size": 2757816 }, "lib/arm64-v8a/libcoreclr.so": { - "Size": 4890432 + "Size": 4837240 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1265504 + "Size": 1252416 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 72112 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { - "Size": 1262888 + "Size": 1258776 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 101888 + "Size": 99664 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 168080 + "Size": 163936 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 147824 + "Size": 159496 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -2234,5 +2234,5 @@ "Size": 794696 } }, - "PackageSize": 20786765 + "PackageSize": 20782669 } From 960d8141513dc22bef42b74ca57d559b567d9d21 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 12:07:56 +0200 Subject: [PATCH 11/18] [coreclr] Remove dead typemap and AOT DSO paths Remove the CoreCLR debug typemap string fallback now that CRC32 lookups verify matching strings, and remove the unused CoreCLR AOT DSO cache path while failing fast if a CoreCLR config tries to use it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/EnvironmentHelper.cs | 24 +++---- .../Utilities/ApplicationConfigCLR.cs | 1 - ...icationConfigNativeAssemblyGeneratorCLR.cs | 22 ++---- ...eMappingDebugNativeAssemblyGeneratorCLR.cs | 45 +++--------- src/native/clr/host/typemap.cc | 7 -- .../clr/include/host/pinvoke-override-impl.hh | 7 +- .../clr/include/runtime-base/monodroid-dl.hh | 72 ++----------------- src/native/clr/include/xamarin-app.hh | 3 - .../xamarin-app-stub/application_dso_stub.cc | 26 +------ 9 files changed, 32 insertions(+), 175 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 4437a2baca1..4bc1b18ad31 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -53,7 +53,6 @@ public sealed class ApplicationConfig_CoreCLR : IApplicationConfig public uint number_of_assemblies_in_apk; public uint bundled_assembly_name_width; public uint number_of_dso_cache_entries; - public uint number_of_aot_cache_entries; public uint number_of_shared_libraries; public uint android_runtime_jnienv_class_token; public uint jnienv_initialize_method_token; @@ -64,7 +63,7 @@ public sealed class ApplicationConfig_CoreCLR : IApplicationConfig public bool managed_marshal_methods_lookup_enabled; } - const uint ApplicationConfigFieldCount_CoreCLR = 20; + const uint ApplicationConfigFieldCount_CoreCLR = 19; // This must be identical to the ApplicationConfig structure in src/native/mono/xamarin-app-stub/xamarin-app.hh public sealed class ApplicationConfig_MonoVM : IApplicationConfig @@ -363,47 +362,42 @@ static IApplicationConfig ReadApplicationConfig_CoreCLR (EnvironmentFile envFile ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 11: // number_of_aot_cache_entries: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); - ret.number_of_aot_cache_entries = ConvertFieldToUInt32 ("number_of_aot_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); - break; - - case 12: // number_of_shared_libraries: uint32_t / .word | .long + case 11: // number_of_shared_libraries: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_shared_libraries = ConvertFieldToUInt32 ("number_of_shared_libraries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 13: // android_runtime_jnienv_class_token: uint32_t / .word | .long + case 12: // android_runtime_jnienv_class_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.android_runtime_jnienv_class_token = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 14: // jnienv_initialize_method_token: uint32_t / .word | .long + case 13: // jnienv_initialize_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jnienv_initialize_method_token = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 15: // jnienv_registerjninatives_method_token: uint32_t / .word | .long + case 14: // jnienv_registerjninatives_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jnienv_registerjninatives_method_token = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 16: // jni_remapping_replacement_type_count: uint32_t / .word | .long + case 15: // jni_remapping_replacement_type_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 17: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long + case 16: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 18: // android_package_name: string / [pointer type] + case 17: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; - case 19: // managed_marshal_methods_lookup_enabled: bool / .byte + case 18: // managed_marshal_methods_lookup_enabled: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.managed_marshal_methods_lookup_enabled = ConvertFieldToBool ("managed_marshal_methods_lookup_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs index b48178218f1..adb7b2fcd2e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs @@ -36,7 +36,6 @@ sealed class ApplicationConfigCLR public uint number_of_assemblies_in_apk; public uint bundled_assembly_name_width; public uint number_of_dso_cache_entries; - public uint number_of_aot_cache_entries; public uint number_of_shared_libraries; [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 8f8ebae05e3..644d49f08cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -230,7 +230,6 @@ sealed class DsoCacheState public List> DsoCache = []; public List JniPreloadDSOs = []; public List JniPreloadNames = []; - public List> AotDsoCache = []; public LlvmIrStringBlob NamesBlob = null!; public uint NameMutationsCount = 1; } @@ -366,7 +365,6 @@ protected override void Construct (LlvmIrModule module) number_of_shared_libraries = (uint)NativeLibraries.Count, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count, - number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count, android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, @@ -397,11 +395,6 @@ protected override void Construct (LlvmIrModule module) module.AddGlobalVariable ("dso_jni_preloads_idx_count", dso_jni_preloads_idx.ArrayItemCount); module.Add (dso_jni_preloads_idx); - var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) { - Comment = " AOT DSO cache entries", - BeforeWriteCallback = HashAndSortDSOCache, - }; - module.Add (aot_dso_cache); module.AddGlobalVariable ("dso_names_data", dsoState.NamesBlob, LlvmIrVariableOptions.GlobalConstant); var dso_apk_entries = new LlvmIrGlobalVariable (new List> (NativeLibraries.Count), "dso_apk_entries") { @@ -679,7 +672,6 @@ DsoCacheState InitDSOCache () var dsoCache = new List> (); var jniPreloads = new List (); - var aotDsoCache = new List> (); var nameMutations = new List (); var dsoNamesBlob = new LlvmIrStringBlob (); int nameMutationsCount = -1; @@ -687,6 +679,10 @@ DsoCacheState InitDSOCache () for (int i = 0; i < dsos.Count; i++) { string name = dsos[i].name; + if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) { + throw new InvalidOperationException ($"Internal error: CoreCLR native library configuration must not use AOT DSO cache entries ('{name}')."); + } + (int nameOffset, _) = dsoNamesBlob.Add (name); bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec); @@ -712,10 +708,6 @@ DsoCacheState InitDSOCache () }; var item = new StructureInstance (dsoCacheEntryStructureInfo, entry); - if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) { - aotDsoCache.Add (item); - continue; - } // We must add all aliases to the preloads indices array so that all of them have their handle // set when the library is preloaded. @@ -730,7 +722,6 @@ DsoCacheState InitDSOCache () return new DsoCacheState { DsoCache = dsoCache, JniPreloadDSOs = jniPreloads, - AotDsoCache = aotDsoCache, NamesBlob = dsoNamesBlob, NameMutationsCount = (uint)(nameMutationsCount <= 0 ? 1 : nameMutationsCount), }; @@ -751,11 +742,6 @@ void AddNameMutations (string name) nameMutations.Add (Path.GetFileNameWithoutExtension (name)!); } - const string aotPrefix = "libaot-"; - if (name.StartsWith (aotPrefix, StringComparison.OrdinalIgnoreCase)) { - AddNameMutations (name.Substring (aotPrefix.Length)); - } - const string libPrefix = "lib"; if (name.StartsWith (libPrefix, StringComparison.OrdinalIgnoreCase)) { AddNameMutations (name.Substring (libPrefix.Length)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs index f5deaf16fac..5916f0f81c2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -18,7 +18,6 @@ class TypeMappingDebugNativeAssemblyGeneratorCLR : LlvmIrComposer 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 @@ -225,12 +224,6 @@ protected override void Construct (LlvmIrModule module) // 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 - // CRC32 collisions on managed type names should be rare, but if one happens we fall back to string matching. 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); @@ -239,41 +232,24 @@ protected override void Construct (LlvmIrModule module) To = entry.JavaName, from = (uint)managedTypeNameOffset, - from_hash = typemap_uses_hashes ? TypeMapHelper.HashNameForCLR (entry.ManagedName) : 0, + from_hash = TypeMapHelper.HashNameForCLR (entry.ManagedName), to = (uint)javaTypeNameOffset, }; managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); + } - if (!typemap_uses_hashes) { - continue; + // Input is sorted on name, we need to re-sort it on hashes. + managedToJavaMap.Sort ((StructureInstance a, StructureInstance b) => { + if (a.Instance == null) { + return b.Instance == null ? 0 : -1; } - 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 CRC32 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; + if (b.Instance == null) { + return 1; } - } - // 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); - }); - } - if (!typemap_uses_hashes) { - Log.LogMessage ("Managed-to-java typemaps will use string-based matching."); - } + return a.Instance.from_hash.CompareTo (b.Instance.from_hash); + }); var assemblyNamesBlob = new LlvmIrStringBlob (); foreach (TypeMapGenerator.TypeMapDebugAssembly asm in data.UniqueAssemblies) { @@ -347,7 +323,6 @@ protected override void Construct (LlvmIrModule module) 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); diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index a970e7b2d07..92e393b9a2a 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -101,10 +101,6 @@ auto TypeMapper::find_index_by_name (const char *typeName, const TypeMapEntry *m [[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); - } - log_debug (LOG_ASSEMBLY, "typemap: map {} -> {} uses hashes"sv, from_name, to_name); size_t type_name_length = strlen (typeName); @@ -180,11 +176,8 @@ auto TypeMapper::managed_to_java_debug (const char *typeName, const uint8_t *mvi log_warn (LOG_ASSEMBLY, "typemap: unable to look up assembly name for type '{}', trying without it."sv, typeName); } - // 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 diff --git a/src/native/clr/include/host/pinvoke-override-impl.hh b/src/native/clr/include/host/pinvoke-override-impl.hh index 93192e44fa5..c7621108b18 100644 --- a/src/native/clr/include/host/pinvoke-override-impl.hh +++ b/src/native/clr/include/host/pinvoke-override-impl.hh @@ -19,9 +19,6 @@ namespace xamarin::android { void *lib_handle = dso_handle == nullptr ? nullptr : *dso_handle; if (lib_handle == nullptr) { - // We're being called as part of the p/invoke mechanism, we don't need to look in the AOT cache - constexpr bool PREFER_AOT_CACHE = false; - // Handle p/invokes of the form [DllImport ("liblog")] or [DllImport ("log")] // TODO: try modifying the name to contain both the `log` prefix and the `.so` suffix dynamic_local_path_string short_library_name; @@ -35,11 +32,11 @@ namespace xamarin::android { } log_debug (LOG_ASSEMBLY, "Modified p/invoke library name to '{}'", short_library_name.get ()); - lib_handle = MonodroidDl::monodroid_dlopen (short_library_name.get (), microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY); + lib_handle = MonodroidDl::monodroid_dlopen (short_library_name.get (), microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY); } if (lib_handle == nullptr) { - lib_handle = MonodroidDl::monodroid_dlopen (library_name, microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY); + lib_handle = MonodroidDl::monodroid_dlopen (library_name, microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY); } if (lib_handle == nullptr) { diff --git a/src/native/clr/include/runtime-base/monodroid-dl.hh b/src/native/clr/include/runtime-base/monodroid-dl.hh index 5164a6ce823..73d69b95882 100644 --- a/src/native/clr/include/runtime-base/monodroid-dl.hh +++ b/src/native/clr/include/runtime-base/monodroid-dl.hh @@ -20,59 +20,24 @@ namespace xamarin::android { class MonodroidDl { - enum class CacheKind - { - // Access AOT cache - AOT, - - // Access DSO cache - DSO, - }; - static inline std::mutex dso_handle_write_lock; - template [[gnu::always_inline, gnu::flatten]] - static auto find_dso_cache_entry_common (hash_t hash) noexcept -> DSOCacheEntry* + static auto find_dso_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* { - static_assert (WhichCache == CacheKind::AOT || WhichCache == CacheKind::DSO, "Unknown cache type specified"); - - DSOCacheEntry *arr; - size_t arr_size; - - if constexpr (WhichCache == CacheKind::AOT) { - log_debug (LOG_ASSEMBLY, "Looking for hash {:x} in AOT cache", hash); - arr = aot_dso_cache; - arr_size = application_config.number_of_aot_cache_entries; - } else if constexpr (WhichCache == CacheKind::DSO) { - log_debug (LOG_ASSEMBLY, "Looking for hash {:x} in DSO cache", hash); - arr = dso_cache; - arr_size = application_config.number_of_dso_cache_entries; - } + log_debug (LOG_ASSEMBLY, "Looking for hash {:x} in DSO cache", hash); auto equal = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash == key; }; auto less_than = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash < key; }; - ssize_t idx = Search::binary_search (hash, arr, arr_size); + ssize_t idx = Search::binary_search (hash, dso_cache, application_config.number_of_dso_cache_entries); if (idx >= 0) { - return &arr[idx]; + return &dso_cache[idx]; } return nullptr; } - [[gnu::always_inline, gnu::flatten]] - static auto find_only_aot_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* - { - return find_dso_cache_entry_common (hash); - } - - [[gnu::always_inline, gnu::flatten]] - static auto find_only_dso_cache_entry (hash_t hash) noexcept -> DSOCacheEntry* - { - return find_dso_cache_entry_common (hash); - } - public: [[gnu::always_inline]] static auto get_dso_name (const DSOCacheEntry *const dso) -> std::string_view @@ -144,7 +109,6 @@ namespace xamarin::android return dso->handle; } - template [[gnu::flatten]] static auto monodroid_dlopen (std::string_view const& name, int flags) noexcept -> void* { if (name.empty ()) [[unlikely]] { @@ -155,36 +119,10 @@ namespace xamarin::android hash_t name_hash = crc32_hash (name); log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); - DSOCacheEntry *dso = nullptr; - if constexpr (PREFER_AOT_CACHE) { - // This code isn't currently used by CoreCLR, but it's possible that in the future we will have separate - // .so files for AOT-d assemblies, similar to MonoVM, so let's keep it. - // - // If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the - // MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to - // find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this - // scenario, we look at the AOT cache first. - // - // See: https://github.com/dotnet/android/issues/9081 - dso = find_only_aot_cache_entry (name_hash); - } - - if (dso == nullptr) { - dso = find_only_dso_cache_entry (name_hash); - } - + DSOCacheEntry *dso = find_dso_cache_entry (name_hash); return monodroid_dlopen (dso, name, flags); } - [[gnu::flatten]] - static auto monodroid_dlopen (const char *name, int flags) noexcept -> void* - { - // We're called by MonoVM via a callback, we might need to return an AOT DSO. - // See: https://github.com/dotnet/android/issues/9081 - constexpr bool PREFER_AOT_CACHE = true; - return monodroid_dlopen (name, flags); - } - [[gnu::flatten]] static auto monodroid_dlsym (void *handle, std::string_view const& name) -> void* { diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index f28e0e6c6f1..f958675e50c 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -223,7 +223,6 @@ struct ApplicationConfig uint32_t number_of_assemblies_in_apk; uint32_t bundled_assembly_name_width; uint32_t number_of_dso_cache_entries; - uint32_t number_of_aot_cache_entries; uint32_t number_of_shared_libraries; uint32_t android_runtime_jnienv_class_token; uint32_t jnienv_initialize_method_token; @@ -312,7 +311,6 @@ 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[]; @@ -353,7 +351,6 @@ extern "C" { [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx_stride; [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx_count; [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx[]; - [[gnu::visibility("default")]] extern DSOCacheEntry aot_dso_cache[]; [[gnu::visibility("default")]] extern const char dso_names_data[]; [[gnu::visibility("default")]] extern DSOApkEntry dso_apk_entries[]; 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 3a07fbacae2..e2d5d1e10f5 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -19,7 +19,6 @@ const TypeMap type_map = { .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[] = {}; @@ -61,7 +60,6 @@ const ApplicationConfig application_config = { .number_of_assemblies_in_apk = 2, .bundled_assembly_name_width = 0, .number_of_dso_cache_entries = 2, - .number_of_aot_cache_entries = 2, .number_of_shared_libraries = 2, .android_runtime_jnienv_class_token = 1, .jnienv_initialize_method_token = 2, @@ -104,8 +102,8 @@ AssemblyStoreRuntimeData assembly_store = { .assemblies = nullptr, }; -constexpr char fake_dso_name[] = "libaot-Some.Assembly.dll.so"; -constexpr char fake_dso_name2[] = "libaot-Another.Assembly.dll.so"; +constexpr char fake_dso_name[] = "libSome.Library.so"; +constexpr char fake_dso_name2[] = "libAnother.Library.so"; DSOCacheEntry dso_cache[] = { { @@ -133,26 +131,6 @@ const uint dso_jni_preloads_idx[1] = { 0 }; -DSOCacheEntry aot_dso_cache[] = { - { - .hash = xamarin::android::crc32_hash (fake_dso_name), - .real_name_hash = xamarin::android::crc32_hash (fake_dso_name), - .ignore = true, - .is_jni_library = true, - .name_index = 3, - .handle = nullptr, - }, - - { - .hash = xamarin::android::crc32_hash (fake_dso_name2), - .real_name_hash = xamarin::android::crc32_hash (fake_dso_name2), - .ignore = true, - .is_jni_library = false, - .name_index = 4, - .handle = nullptr, - }, -}; - const char dso_names_data[] = {}; DSOApkEntry dso_apk_entries[2] {}; From f32cc9c0b0321e37f719923dacc0e949059f21c1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 12:20:04 +0200 Subject: [PATCH 12/18] [coreclr] Remove runtime property hash index Reuse the runtime property arrays passed to coreclr_initialize for the host runtime contract callback instead of emitting a duplicate CRC32-indexed lookup table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Utilities/EnvironmentHelper.cs | 2 - ...icationConfigNativeAssemblyGeneratorCLR.cs | 139 ------------------ src/native/clr/host/host.cc | 56 +++---- src/native/clr/include/xamarin-app.hh | 17 --- .../xamarin-app-stub/application_dso_stub.cc | 46 +----- 5 files changed, 30 insertions(+), 230 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 4bc1b18ad31..7571d11757d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -185,8 +185,6 @@ public sealed class JniPreloads "java_type_count", "managed_to_java_map", "managed_to_java_map_module_count", - "runtime_properties", - "runtime_properties_data", AppEnvironmentVariableContentsSymbolName, AppEnvironmentVariablesSymbolName, ApplicationConfigSymbolName, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 644d49f08cd..63dda58ec9e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -139,54 +139,6 @@ sealed class AssemblyStoreRuntimeData public AssemblyStoreAssemblyDescriptor? assemblies; } - sealed class RuntimePropertyContextDataProvider : NativeAssemblerStructContextDataProvider - { - public override string GetComment (object data, string fieldName) - { - var runtimeProp = EnsureType (data); - if (MonoAndroidHelper.StringEquals ("key_index", fieldName)) { - return $" '{runtimeProp.Key}'"; - } - - if (MonoAndroidHelper.StringEquals ("value_index", fieldName)) { - return $" '{runtimeProp.Value}'"; - } - - return String.Empty; - } - } - - // Order of fields and their types must correspond *exactly* to that in - // src/native/clr/include/xamarin-app.hh RuntimeProperty structure - [NativeAssemblerStructContextDataProvider (typeof (RuntimePropertyContextDataProvider))] - sealed class RuntimeProperty - { - [NativeAssembler (Ignore = true)] - public string? Key; - - [NativeAssembler (Ignore = true)] - public string? Value; - - [NativeAssembler (UsesDataProvider = true)] - public uint key_index; - - [NativeAssembler (UsesDataProvider = true)] - public uint value_index; - public uint value_size; - } - - // Order of fields and their types must correspond *exactly* to that in - // src/native/clr/include/xamarin-app.hh RuntimePropertyIndexEntry structure - sealed class RuntimePropertyIndexEntry - { - [NativeAssembler (Ignore = true)] - public string? HashedKey; - - [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] - public uint key_hash; - public uint index; - } - sealed class XamarinAndroidBundledAssemblyContextDataProvider : NativeAssemblerStructContextDataProvider { public override ulong GetBufferSize (object data, string fieldName) @@ -250,8 +202,6 @@ sealed class DsoCacheState #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value - assigned conditionally by build process List>? xamarinAndroidBundledAssemblies; #pragma warning restore CS0649 - List>? runtimePropertiesData; - List>? runtimePropertyIndex; StructureInfo? applicationConfigStructureInfo; StructureInfo? dsoCacheEntryStructureInfo; @@ -259,8 +209,6 @@ sealed class DsoCacheState StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; StructureInfo? assemblyStoreRuntimeDataStructureInfo; - StructureInfo? runtimePropertyStructureInfo; - StructureInfo? runtimePropertyIndexEntryStructureInfo; #pragma warning disable CS0169 // Field is never used - might be used in future versions StructureInfo? hostConfigurationPropertyStructureInfo; #pragma warning restore CS0169 @@ -410,23 +358,6 @@ protected override void Construct (LlvmIrModule module) }; module.Add (bundled_assemblies); - (runtimePropertiesData, runtimePropertyIndex, LlvmIrStringBlob runtimePropsBlob) = InitRuntimeProperties (); - var runtime_properties = new LlvmIrGlobalVariable (runtimePropertiesData, "runtime_properties", LlvmIrVariableOptions.GlobalConstant) { - Comment = "Runtime config properties", - }; - module.Add (runtime_properties); - - var runtime_properties_data = new LlvmIrGlobalVariable (runtimePropsBlob, "runtime_properties_data", LlvmIrVariableOptions.GlobalConstant) { - Comment = "Runtime config properties data", - }; - module.Add (runtime_properties_data); - - var runtime_property_index = new LlvmIrGlobalVariable (runtimePropertyIndex, "runtime_property_index", LlvmIrVariableOptions.GlobalConstant) { - Comment = "Runtime config property index, sorted on property key hash", - BeforeWriteCallback = HashAndSortRuntimePropertiesIndex, - }; - module.Add (runtime_property_index); - // HOST_PROPERTY_RUNTIME_CONTRACT will come first, our native runtime requires that since it needs // to set its value in the values array and we don't want to spend time searching for the index, nor // we want to add yet another variable storing the index to the entry. KISS. @@ -460,74 +391,6 @@ protected override void Construct (LlvmIrModule module) AddAssemblyStores (module); } - void HashAndSortRuntimePropertiesIndex (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) - { - var index = variable.Value as List>; - if (index == null) { - return; - } - - foreach (StructureInstance instance in index) { - if (instance.Obj == null) { - throw new InvalidOperationException ("Internal error: runtime property index must not contain null entries"); - } - - var entry = instance.Obj as RuntimePropertyIndexEntry; - if (entry == null) { - throw new InvalidOperationException ($"Internal error: runtime property index entry has unexpected type {instance.Obj.GetType ()}"); - } - - entry.key_hash = TypeMapHelper.HashNameForCLR (entry.HashedKey ?? ""); - }; - - index.Sort ((StructureInstance a, StructureInstance b) => { - if (a.Instance == null || b.Instance == null) return 0; - return a.Instance.key_hash.CompareTo (b.Instance.key_hash); - }); - } - - ( - List> runtimeProps, - List> runtimePropsIndex, - LlvmIrStringBlob - ) InitRuntimeProperties () - { - var runtimeProps = new List> (); - var runtimePropsIndex = new List> (); - var propsBlob = new LlvmIrStringBlob (); - - if (runtimeProperties == null || runtimeProperties.Count == 0) { - return (runtimeProps, runtimePropsIndex, propsBlob); - } - - foreach (var kvp in runtimeProperties) { - string name = kvp.Key; - string value = kvp.Value; - (int name_index, _) = propsBlob.Add (name); - (int value_index, int value_size) = propsBlob.Add (value); - - var prop = new RuntimeProperty { - Key = name, - Value = value, - - key_index = (uint)name_index, - value_index = (uint)value_index, - - // Includes the terminating NUL - value_size = (uint)value_size, - }; - runtimeProps.Add (new StructureInstance (runtimePropertyStructureInfo, prop)); - - var indexEntry = new RuntimePropertyIndexEntry { - HashedKey = prop.Key, - index = (uint)(runtimeProps.Count - 1), - }; - runtimePropsIndex.Add (new StructureInstance (runtimePropertyIndexEntryStructureInfo, indexEntry)); - } - - return (runtimeProps, runtimePropsIndex, propsBlob); - } - void AddAssemblyStores (LlvmIrModule module) { ulong itemCount = (ulong)(NumberOfAssembliesInApk); @@ -758,8 +621,6 @@ void MapStructures (LlvmIrModule module) xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure (); dsoCacheEntryStructureInfo = module.MapStructure (); dsoApkEntryStructureInfo = module.MapStructure (); - runtimePropertyStructureInfo = module.MapStructure (); - runtimePropertyIndexEntryStructureInfo = module.MapStructure (); appEnvironmentVariableStructureInfo = module.MapStructure (); } diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index efca594e485..65329f0a1fe 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -40,16 +40,13 @@ void Host::clr_error_writer (const char *message) noexcept size_t Host::clr_get_runtime_property (const char *key, char *value_buffer, size_t value_buffer_size, [[maybe_unused]] void *contract_context) noexcept { - // NOTE: this code was tested locally, but it's **not** used by CoreCLR yet, so there's been no - // "live" testing. log_debug (LOG_DEFAULT, "clr_get_runtime_property (\"{}\"...)"sv, optional_string (key)); if (application_config.number_of_runtime_properties == 0) [[unlikely]] { log_debug (LOG_DEFAULT, "No runtime properties defined"sv); - return 0; + return static_cast(-1); } - // value_buffer_size must have enough space for at least 1 character + the terminating NUL - if (key == nullptr || value_buffer == nullptr || value_buffer_size <= 1) [[unlikely]] { + if (key == nullptr || value_buffer == nullptr || value_buffer_size == 0) [[unlikely]] { log_warn ( LOG_DEFAULT, "runtime property retrieval API called with invalid arguments. key == {:p}; value_buffer == {:p}; value_buffer_size == {}"sv, @@ -57,35 +54,40 @@ size_t Host::clr_get_runtime_property (const char *key, char *value_buffer, size static_cast(value_buffer), value_buffer_size ); - return 0; + return static_cast(-1); } - hash_t key_hash = crc32_hash (key, strlen (key)); + for (uint32_t i = 0; i < application_config.number_of_runtime_properties; i++) { + const char *property_key = init_runtime_property_names[i]; + if (property_key == nullptr || strcmp (key, property_key) != 0) { + continue; + } - auto equal = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash == key; }; - auto less_than = [](RuntimePropertyIndexEntry const& entry, hash_t key) -> bool { return entry.key_hash < key; }; - ssize_t idx = Search::binary_search (key_hash, runtime_property_index, application_config.number_of_runtime_properties); - if (idx < 0) { - log_debug (LOG_DEFAULT, "Runtime property '{}' not found"sv, key); - return 0; - } + const char *property_value = init_runtime_property_values[i]; + if (property_value == nullptr) { + log_warn (LOG_DEFAULT, "Runtime property '{}' has no value"sv, key); + return static_cast(-1); + } - RuntimePropertyIndexEntry const& idx_entry = runtime_property_index[idx]; - RuntimeProperty const& prop = runtime_properties[idx_entry.index]; + size_t value_size = strlen (property_value) + 1; + if (value_size > value_buffer_size) { + log_warn ( + LOG_DEFAULT, + "Value of property '{}' is longer than available buffer space. Need {}b, available {}b"sv, + key, + value_size, + value_buffer_size + ); + } - // `value_size` includes the terminating NUL - if (prop.value_size > value_buffer_size) { - log_warn ( - LOG_DEFAULT, - "Value of property '{}' is longer than available buffer space. Need {}b, available {}b"sv, - key, - prop.value_size, - value_buffer_size - ); + size_t bytes_to_copy = std::min (value_size, value_buffer_size); + strncpy (value_buffer, property_value, bytes_to_copy); + value_buffer[value_buffer_size - 1] = '\0'; + return value_size; } - strncpy (value_buffer, &runtime_properties_data[prop.value_index], value_buffer_size); - return std::min (static_cast(prop.value_size - 1), value_buffer_size - 1); + log_debug (LOG_DEFAULT, "Runtime property '{}' not found"sv, key); + return static_cast(-1); } bool Host::clr_external_assembly_probe (const char *path, void **data_start, int64_t *size) noexcept diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index f958675e50c..b351856ac41 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -233,19 +233,6 @@ struct ApplicationConfig bool managed_marshal_methods_lookup_enabled; }; -struct RuntimeProperty -{ - const uint32_t key_index; - const uint32_t value_index; - const uint32_t value_size; // including the terminating NUL -}; - -struct RuntimePropertyIndexEntry -{ - xamarin::android::hash_t key_hash; - uint32_t index; -}; - struct DSOApkEntry { xamarin::android::hash_t name_hash; @@ -354,10 +341,6 @@ extern "C" { [[gnu::visibility("default")]] extern const char dso_names_data[]; [[gnu::visibility("default")]] extern DSOApkEntry dso_apk_entries[]; - [[gnu::visibility("default")]] extern const RuntimeProperty runtime_properties[]; - [[gnu::visibility("default")]] extern const char runtime_properties_data[]; - [[gnu::visibility("default")]] extern const RuntimePropertyIndexEntry runtime_property_index[]; - [[gnu::visibility("default")]] extern const char *init_runtime_property_names[]; [[gnu::visibility("default")]] extern char *init_runtime_property_values[]; } 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 e2d5d1e10f5..8dd5dc9c2ea 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -53,7 +53,7 @@ const ApplicationConfig application_config = { .jni_add_native_method_registration_attribute_present = false, .marshal_methods_enabled = false, .ignore_split_configs = false, - .number_of_runtime_properties = 3, + .number_of_runtime_properties = 1, .package_naming_policy = 0, .environment_variable_count = 0, .system_property_count = 0, @@ -221,50 +221,6 @@ const JniRemappingTypeReplacementEntry jni_remapping_type_replacements[] = { }, }; -constexpr char prop_test_string_key[] = "test_string"; -constexpr char prop_test_integer_key[] = "test_integer"; -constexpr char prop_test_boolean_key[] = "test_boolean"; - -const RuntimeProperty runtime_properties[] = { - { - .key_index = 0, - .value_index = 10, - .value_size = 10, - }, - - { - .key_index = 20, - .value_index = 25, - .value_size = 5, - }, - - { - .key_index = 30, - .value_index = 33, - .value_size = 7, - }, -}; - - -const char runtime_properties_data[] = {}; - -const RuntimePropertyIndexEntry runtime_property_index[] = { - { - .key_hash = xamarin::android::crc32_hash (prop_test_string_key), - .index = 0, - }, - - { - .key_hash = xamarin::android::crc32_hash (prop_test_boolean_key), - .index = 2, - }, - - { - .key_hash = xamarin::android::crc32_hash (prop_test_integer_key), - .index = 1, - }, -}; - const char *init_runtime_property_names[] = { "HOST_RUNTIME_CONTRACT", }; From 2aaacd4efbd1e4d0a5e184cd1faa0c9675d9dafb Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 12:54:21 +0200 Subject: [PATCH 13/18] [coreclr] Disable runtime property callback Leave the host runtime contract in place for p/invoke and assembly probing, but set get_runtime_property to null because Android passes runtime configuration through coreclr_initialize and the callback only produced negative host-property probes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/native/clr/host/host.cc | 53 ----------------------------- src/native/clr/include/host/host.hh | 3 +- 2 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 65329f0a1fe..b07c54466e2 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -38,58 +37,6 @@ void Host::clr_error_writer (const char *message) noexcept log_error (LOG_DEFAULT, "CLR error: {}", optional_string (message)); } -size_t Host::clr_get_runtime_property (const char *key, char *value_buffer, size_t value_buffer_size, [[maybe_unused]] void *contract_context) noexcept -{ - log_debug (LOG_DEFAULT, "clr_get_runtime_property (\"{}\"...)"sv, optional_string (key)); - if (application_config.number_of_runtime_properties == 0) [[unlikely]] { - log_debug (LOG_DEFAULT, "No runtime properties defined"sv); - return static_cast(-1); - } - - if (key == nullptr || value_buffer == nullptr || value_buffer_size == 0) [[unlikely]] { - log_warn ( - LOG_DEFAULT, - "runtime property retrieval API called with invalid arguments. key == {:p}; value_buffer == {:p}; value_buffer_size == {}"sv, - static_cast(key), - static_cast(value_buffer), - value_buffer_size - ); - return static_cast(-1); - } - - for (uint32_t i = 0; i < application_config.number_of_runtime_properties; i++) { - const char *property_key = init_runtime_property_names[i]; - if (property_key == nullptr || strcmp (key, property_key) != 0) { - continue; - } - - const char *property_value = init_runtime_property_values[i]; - if (property_value == nullptr) { - log_warn (LOG_DEFAULT, "Runtime property '{}' has no value"sv, key); - return static_cast(-1); - } - - size_t value_size = strlen (property_value) + 1; - if (value_size > value_buffer_size) { - log_warn ( - LOG_DEFAULT, - "Value of property '{}' is longer than available buffer space. Need {}b, available {}b"sv, - key, - value_size, - value_buffer_size - ); - } - - size_t bytes_to_copy = std::min (value_size, value_buffer_size); - strncpy (value_buffer, property_value, bytes_to_copy); - value_buffer[value_buffer_size - 1] = '\0'; - return value_size; - } - - log_debug (LOG_DEFAULT, "Runtime property '{}' not found"sv, key); - return static_cast(-1); -} - bool Host::clr_external_assembly_probe (const char *path, void **data_start, int64_t *size) noexcept { // TODO: `path` might be a full path, make sure it isn't diff --git a/src/native/clr/include/host/host.hh b/src/native/clr/include/host/host.hh index 3537c9310d7..98e7007725a 100644 --- a/src/native/clr/include/host/host.hh +++ b/src/native/clr/include/host/host.hh @@ -38,7 +38,6 @@ namespace xamarin::android { static void gather_assemblies_and_libraries (jstring_array_wrapper& runtimeApks, bool have_split_apks); static void scan_filesystem_for_assemblies_and_libraries () noexcept; - static size_t clr_get_runtime_property (const char *key, char *value_buffer, size_t value_buffer_size, void *contract_context) noexcept; static bool clr_external_assembly_probe (const char *path, void **data_start, int64_t *size) noexcept; static const void* clr_pinvoke_override (const char *library_name, const char *entry_point_name) noexcept; static void clr_error_writer (const char *message) noexcept; @@ -62,7 +61,7 @@ namespace xamarin::android { static inline host_runtime_contract runtime_contract{ .size = sizeof(host_runtime_contract), .context = nullptr, - .get_runtime_property = clr_get_runtime_property, + .get_runtime_property = nullptr, .bundle_probe = nullptr, .pinvoke_override = clr_pinvoke_override, .external_assembly_probe = clr_external_assembly_probe, From e3ba198b5f369b002b406554fe2c19419c27a6aa Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 13:06:57 +0200 Subject: [PATCH 14/18] [coreclr] Remove unused application config fields Remove unused CoreCLR application config structure-info fields that were only kept for potential future host configuration data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ApplicationConfigNativeAssemblyGeneratorCLR.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 63dda58ec9e..f3434e6e5ea 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -209,12 +209,6 @@ sealed class DsoCacheState StructureInfo? xamarinAndroidBundledAssemblyStructureInfo; StructureInfo? assemblyStoreSingleAssemblyRuntimeDataStructureinfo; StructureInfo? assemblyStoreRuntimeDataStructureInfo; -#pragma warning disable CS0169 // Field is never used - might be used in future versions - StructureInfo? hostConfigurationPropertyStructureInfo; -#pragma warning restore CS0169 -#pragma warning disable CS0169 // Field is never used - might be used in future versions - StructureInfo? hostConfigurationPropertiesStructureInfo; -#pragma warning restore CS0169 StructureInfo? appEnvironmentVariableStructureInfo; public bool UsesAssemblyPreload { get; set; } From 643ebf3730c0240072adc57380987b2c6c4270d0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 8 Jul 2026 18:29:27 +0200 Subject: [PATCH 15/18] Simplify CoreCLR CRC32 typemap helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...eMappingDebugNativeAssemblyGeneratorCLR.cs | 14 +--- src/native/clr/host/typemap.cc | 67 +++++++------------ src/native/clr/include/runtime-base/crc32.hh | 22 +++--- .../generate-pinvoke-tables.cc | 28 +------- .../common/include/runtime-base/search.hh | 57 ++++++++++------ 5 files changed, 73 insertions(+), 115 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs index 5916f0f81c2..db3640f204c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -275,7 +275,7 @@ protected override void Construct (LlvmIrModule module) return 1; } - return CompareBytes (a.Instance.module_uuid, b.Instance.module_uuid); + return a.Instance.module_uuid.AsSpan ().SequenceCompareTo (b.Instance.module_uuid); }); var managedTypeInfos = new List> (); @@ -336,16 +336,4 @@ void MapStructures (LlvmIrModule module) typeMapStructureInfo = module.MapStructure (); typeMapManagedTypeInfoStructureInfo = module.MapStructure (); } - - static int CompareBytes (byte[] left, byte[] right) - { - for (int i = 0; i < left.Length; i++) { - int ret = left [i].CompareTo (right [i]); - if (ret != 0) { - return ret; - } - } - - return 0; - } } diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index 92e393b9a2a..020ec84669a 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -13,7 +13,7 @@ using namespace xamarin::android; namespace { [[gnu::always_inline]] - auto same_string (const char *value, uint32_t value_length, const char *key, size_t key_length) noexcept -> bool + auto same_string (const char *value, size_t value_length, const char *key, size_t key_length) noexcept -> bool { return value_length == key_length && strncmp (value, key, key_length) == 0; } @@ -106,29 +106,22 @@ auto TypeMapper::find_index_by_hash (const char *typeName, const TypeMapEntry *m size_t type_name_length = strlen (typeName); hash_t type_name_hash = crc32_hash (typeName, type_name_length); - size_t left = 0; - size_t right = type_map.entry_count; - while (left < right) { - size_t middle = (left + right) >> 1u; - TypeMapEntry const& entry = map[middle]; - if (entry.from != std::numeric_limits::max () && entry.from_hash < type_name_hash) { - left = middle + 1; - } else { - right = middle; - } - } + auto less_than = [](TypeMapEntry const& entry, hash_t key) -> bool { + return entry.from != std::numeric_limits::max () && entry.from_hash < key; + }; - while (left < type_map.entry_count) { - TypeMapEntry const& entry = map[left]; + size_t idx = Search::lower_bound (type_name_hash, map, type_map.entry_count); + while (idx < type_map.entry_count) { + TypeMapEntry const& entry = map[idx]; if (entry.from == std::numeric_limits::max () || entry.from_hash != type_name_hash) { break; } const char *mapped_type_name = &name_map[entry.from]; - if (same_string (mapped_type_name, static_cast(strlen (mapped_type_name)), typeName, type_name_length)) { - return static_cast(left); + if (same_string (mapped_type_name, strlen (mapped_type_name), typeName, type_name_length)) { + return static_cast(idx); } - left++; + idx++; } return -1z; @@ -213,24 +206,18 @@ auto TypeMapper::find_managed_to_java_map_entry (hash_t name_hash, const char *t return nullptr; }; - size_t left = 0; - size_t right = entry_count; - while (left < right) { - size_t middle = (left + right) >> 1u; - if (map[middle].managed_type_name_hash < name_hash) { - left = middle + 1; - } else { - right = middle; - } - } + auto less_than = [](TypeMapModuleEntry const& entry, hash_t key) -> bool { + return entry.managed_type_name_hash < key; + }; - while (left < entry_count && map[left].managed_type_name_hash == name_hash) { - TypeMapModuleEntry const& entry = map[left]; + size_t idx = Search::lower_bound (name_hash, map, entry_count); + while (idx < entry_count && map[idx].managed_type_name_hash == name_hash) { + TypeMapModuleEntry const& entry = map[idx]; const char *managed_type_name = &managed_type_names[entry.managed_type_name_index]; if (same_string (managed_type_name, entry.managed_type_name_length, type_name, type_name_length)) { return &entry; } - left++; + idx++; } return nullptr; @@ -402,24 +389,18 @@ auto TypeMapper::java_to_managed_debug (const char *java_type_name, char const** [[gnu::always_inline]] auto TypeMapper::find_java_to_managed_entry (hash_t name_hash, const char *java_type_name, size_t java_type_name_length) noexcept -> const TypeMapJava* { - size_t left = 0; - size_t right = java_type_count; - while (left < right) { - size_t middle = (left + right) >> 1u; - if (java_to_managed_hashes[middle] < name_hash) { - left = middle + 1; - } else { - right = middle; - } - } + auto less_than = [](hash_t const& entry, hash_t key) -> bool { + return entry < key; + }; - while (left < java_type_count && java_to_managed_hashes[left] == name_hash) { - TypeMapJava const& entry = java_to_managed_map[left]; + size_t idx = Search::lower_bound (name_hash, java_to_managed_hashes, java_type_count); + while (idx < java_type_count && java_to_managed_hashes[idx] == name_hash) { + TypeMapJava const& entry = java_to_managed_map[idx]; const char *mapped_java_type_name = &java_type_names[entry.java_name_index]; if (same_string (mapped_java_type_name, entry.java_name_length, java_type_name, java_type_name_length)) { return &entry; } - left++; + idx++; } return nullptr; diff --git a/src/native/clr/include/runtime-base/crc32.hh b/src/native/clr/include/runtime-base/crc32.hh index cd5bb1ed60e..11f3dcf3157 100644 --- a/src/native/clr/include/runtime-base/crc32.hh +++ b/src/native/clr/include/runtime-base/crc32.hh @@ -5,21 +5,19 @@ #include #include -extern "C" uint32_t CompressionNative_Crc32 (uint32_t crc, uint8_t *buffer, int32_t len); - namespace xamarin::android { using hash_t = uint32_t; static constexpr hash_t CRC32_POLYNOMIAL = 0xedb88320; - template - consteval auto crc32_hash (const char (&value)[Size]) noexcept -> hash_t + [[gnu::always_inline]] + constexpr auto crc32_hash (const char *value, size_t len) noexcept -> hash_t { - if constexpr (Size <= 1) { + if (len == 0) [[unlikely]] { return std::numeric_limits::max (); } hash_t crc = 0xffffffff; - for (size_t i = 0; i < Size - 1; i++) { + for (size_t i = 0; i < len; i++) { crc ^= static_cast(value [i]); for (size_t bit = 0; bit < 8; bit++) { crc = (crc >> 1) ^ ((crc & 1) != 0 ? CRC32_POLYNOMIAL : 0); @@ -29,18 +27,14 @@ namespace xamarin::android { return ~crc; } - [[gnu::always_inline]] - inline auto crc32_hash (const char *value, size_t len) noexcept -> hash_t + template + consteval auto crc32_hash (const char (&value)[Size]) noexcept -> hash_t { - if (len == 0) [[unlikely]] { - return std::numeric_limits::max (); - } - - return CompressionNative_Crc32 (0, reinterpret_cast(const_cast(value)), static_cast(len)); + return crc32_hash (value, Size - 1); } [[gnu::always_inline]] - inline auto crc32_hash (std::string_view const& value) noexcept -> hash_t + constexpr auto crc32_hash (std::string_view const& value) noexcept -> hash_t { return crc32_hash (value.data (), value.length ()); } diff --git a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc index 6c2892a5944..44a7efad40e 100644 --- a/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc +++ b/src/native/clr/pinvoke-override/generate-pinvoke-tables.cc @@ -27,35 +27,13 @@ #include #include #include -#include #include #include -namespace fs = std::filesystem; - -constexpr uint32_t CRC32_POLYNOMIAL = 0xedb88320; +#include -constexpr uint32_t crc32_hash (const char *p, size_t len) noexcept -{ - if (len == 0) { - return std::numeric_limits::max (); - } - - uint32_t crc = 0xffffffff; - for (size_t i = 0; i < len; i++) { - crc ^= static_cast(p[i]); - for (size_t bit = 0; bit < 8; bit++) { - crc = (crc >> 1) ^ ((crc & 1) != 0 ? CRC32_POLYNOMIAL : 0); - } - } - - return ~crc; -} - -constexpr uint32_t crc32_hash (std::string_view const& input) noexcept -{ - return crc32_hash (input.data (), input.length ()); -} +namespace fs = std::filesystem; +using namespace xamarin::android; const std::vector internal_pinvoke_names = { // "create_public_directory", diff --git a/src/native/common/include/runtime-base/search.hh b/src/native/common/include/runtime-base/search.hh index 1c0a0fd5584..0bb132eda49 100644 --- a/src/native/common/include/runtime-base/search.hh +++ b/src/native/common/include/runtime-base/search.hh @@ -1,56 +1,73 @@ // Dear Emacs, this is a -*- C++ -*- header #pragma once +#include #include 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 + template [[gnu::always_inline, gnu::flatten]] - static ssize_t binary_search (const TState& state, TKey key, const T *arr, size_t n) noexcept + static size_t lower_bound (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); + size_t left = 0; + size_t right = n; - while (right - left > 1) { - ssize_t middle = (left + right) >> 1u; + while (left < right) { + size_t middle = (left + right) >> 1u; if (less_than (arr[middle], key, state)) { - left = middle; + left = middle + 1; } else { right = middle; } } - return equal (arr[right], key, state) ? right : -1z; + return left; } - template + template [[gnu::always_inline, gnu::flatten]] - static ssize_t binary_search (TKey key, const T *arr, size_t n) noexcept + static size_t lower_bound (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); + size_t left = 0; + size_t right = n; - while (right - left > 1) { - ssize_t middle = (left + right) >> 1u; + while (left < right) { + size_t middle = (left + right) >> 1u; if (less_than (arr[middle], key)) { - left = middle; + left = middle + 1; } else { right = middle; } } - return equal (arr[right], key) ? right : -1z; + return left; + } + + 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"); + + size_t idx = lower_bound (state, key, arr, n); + return idx < n && equal (arr[idx], key, state) ? static_cast(idx) : -1z; + } + + template + [[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"); + + size_t idx = lower_bound (key, arr, n); + return idx < n && equal (arr[idx], key) ? static_cast(idx) : -1z; } }; } From a629d3e27dd8377ad104ba14a0a4a7180ceea7e0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 9 Jul 2026 00:22:22 +0200 Subject: [PATCH 16/18] Update CoreCLR arm64 APK size reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc index c8814e52e16..dbef4c838e5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc @@ -17,7 +17,7 @@ "Size": 4837240 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1252416 + "Size": 1252840 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 72112 @@ -32,7 +32,7 @@ "Size": 163936 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 21072 + "Size": 17896 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 From 5f0a6c2c4b9fa5da592741929e3bf96624fc79d0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 9 Jul 2026 23:24:45 +0200 Subject: [PATCH 17/18] Address code review feedback - Avoid per-call byte[] allocation in HashNameForCLR via stackalloc - Replace unused switch expression with explicit arch guard - Validate index_size divisibility in GetIndexEntrySize (both readers) - Remove stale typemap_use_hashes symbol from debug typemap stub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 13027389-e819-4a27-b98f-dbcd7586a1ab --- .../Tasks/GenerateEmptyTypemapStub.cs | 1 - .../Utilities/AssemblyStoreGenerator.cs | 4 ++++ .../PreservePinvokesNativeAssemblyGenerator.cs | 16 +++++++++------- .../Utilities/TypeMapHelper.cs | 12 ++++++++++-- .../AssemblyStore/StoreReader_V2.cs | 4 ++++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs index 4d6ee43899a..7a89cc4e569 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs @@ -71,7 +71,6 @@ string GenerateStubLlvmIr (string abi) %struct.TypeMapAssembly = type { [16 x i8], i64, i64 } @type_map = dso_local constant %struct.TypeMap zeroinitializer, align 8 -@typemap_use_hashes = dso_local constant i8 1, align 1 @type_map_managed_type_info = dso_local constant [0 x %struct.TypeMapManagedTypeInfo] zeroinitializer, align 8 @type_map_unique_assemblies = dso_local constant [0 x %struct.TypeMapAssembly] zeroinitializer, align 8 @type_map_assembly_names = dso_local constant [1 x i8] zeroinitializer, align 1 diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs index 6bb3b2e6058..42dace7c726 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs @@ -344,6 +344,10 @@ static uint GetIndexEntrySize (AssemblyStoreHeader header) return 0; } + if (header.index_size % header.index_entry_count != 0) { + throw new InvalidOperationException ($"Assembly store index is corrupted: index size {header.index_size} is not evenly divisible by entry count {header.index_entry_count}."); + } + return header.index_size / header.index_entry_count; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs index 572b8b3e915..08308157d70 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/PreservePinvokesNativeAssemblyGenerator.cs @@ -130,13 +130,15 @@ protected override void Construct (LlvmIrModule module) return; } - _ = state.TargetArch switch { - AndroidTargetArch.Arm64 => true, - AndroidTargetArch.X86_64 => true, - AndroidTargetArch.Arm => true, - AndroidTargetArch.X86 => true, - _ => throw new NotSupportedException ($"Architecture {state.TargetArch} is not supported here") - }; + switch (state.TargetArch) { + case AndroidTargetArch.Arm64: + case AndroidTargetArch.X86_64: + case AndroidTargetArch.Arm: + case AndroidTargetArch.X86: + break; + default: + throw new NotSupportedException ($"Architecture {state.TargetArch} is not supported here"); + } Log.LogDebugMessage (" Checking discovered p/invokes against the list of components"); var preservedPerComponent = new Dictionary (StringComparer.OrdinalIgnoreCase); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs index fdd7995f186..3f5bc917b6f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs @@ -23,13 +23,21 @@ public static ulong HashJavaName (string name, bool is64Bit) /// /// Hash the given type name for use in CoreCLR native typemap arrays. /// - public static uint HashNameForCLR (string name) + public static unsafe uint HashNameForCLR (string name) { if (name.Length == 0) { return UInt32.MaxValue; } - return Crc32.HashToUInt32 (Encoding.UTF8.GetBytes (name)); + int byteCount = Encoding.UTF8.GetByteCount (name); + Span buffer = byteCount <= 256 + ? stackalloc byte [byteCount] + : new byte [byteCount]; + fixed (char* pChars = name) + fixed (byte* pBuffer = buffer) { + Encoding.UTF8.GetBytes (pChars, name.Length, pBuffer, byteCount); + } + return Crc32.HashToUInt32 (buffer); } /// diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs index 3b0ac95e72c..f31e44c7a70 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs @@ -229,6 +229,10 @@ uint GetIndexEntrySize () return 0; } + if (header.index_size % header.index_entry_count != 0) { + throw new InvalidOperationException ($"Assembly store '{StorePath}' index is corrupted: index size {header.index_size} is not evenly divisible by entry count {header.index_entry_count}."); + } + return header.index_size / header.index_entry_count; } } From 5fd899cc36f95d65e0a651309e07e10c60a9d1cd Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 10 Jul 2026 07:50:04 +0200 Subject: [PATCH 18/18] [tests] Parse generated unsigned integers directly Generated uint32_t and uint64_t values are emitted using unsigned decimal representations, so remove the unnecessary signed parsing fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 11570f57-4494-4f92-977b-47f37db2e81b --- .../Utilities/EnvironmentHelper.cs | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 7571d11757d..fdfbb7613d9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -1305,24 +1305,16 @@ static byte ConvertFieldToByte (string fieldName, string llvmAssemblerEnvFile, s return fv; } - // llc usually emits signed decimal integers, but large unsigned fields can also be emitted as - // decimal values above Int32.MaxValue. Accept both forms. + // These fields are always uint32_t/uint64_t and the generator (LlvmIrGenerator/MonoAndroidHelper.CultureInvariantToString) + // writes them via uint/ulong.ToString(), which is always an unsigned decimal representation - there's no signed form to + // account for here, so parse directly as unsigned. static bool TryParseInteger (string value, out uint fv) { if (value.StartsWith ("0x", StringComparison.Ordinal)) { return UInt32.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv); } - if (UInt32.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv)) { - return true; - } - - if (Int32.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out int signedFV)) { - fv = (uint)signedFV; - return true; - } - - return false; + return UInt32.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv); } static bool TryParseInteger (string value, out ulong fv) @@ -1331,16 +1323,7 @@ static bool TryParseInteger (string value, out ulong fv) return UInt64.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv); } - if (UInt64.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv)) { - return true; - } - - if (Int64.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out long signedFV)) { - fv = (ulong)signedFV; - return true; - } - - return false; + return UInt64.TryParse (value, NumberStyles.None, CultureInfo.InvariantCulture, out fv); } static bool TryParseInteger (string value, out byte fv)