From f48b97cb084aa8331592290cb0b581af0f794bcc Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 13 Apr 2022 23:04:38 +0000 Subject: [PATCH] [monodroid] Speed up java-to-managed typemap lookups (#6905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://en.algorithmica.org/hpc/ Up until now, Xamarin.Android used string comparison when finding a Managed type corresponding to a given Java type. Even though the strings were pre-sorted at build time, multiple string comparisons cost more time than necessary. To improve comparison speed, implement lookups based on hash values using the `xxHash` algorithm (c9270261), calculated for all bound Java names at build time. This allows us to process each Java type once at run time, to generate its hash. After that, the hash is used to binary search an array of hashes and the result (if found) is an index into array with the appropriate Java-to-Managed mapping. This change also allows us to move Java type names from the mapping structure (`TypeMapJava`) and array (`map_java`) to a separate `java_type_names` array. We used to keep Java type name in the structure to make matching slightly faster, but it required unnecessarily complicated structure size calculation at runtime, so that binary search can properly work on an array of `TypeMapJava` structures whose size would differ from application to application (and sometimes even between builds). The change also saves space, because when the Java type name was stored in the structure, all the structures had to have the same size, and thus all type names shorter than the longest one had to be padded with NUL characters. A handful of other optimizations are implemented as well. Namely: * the `JNIEnv.RegisterJniNatives()` method is now called directly (thanks to the `[UnmanagedCallersOnly]` attribute) when running under .NET6+; see also 16680700. * A conceptually simpler binary search function was implemented, which doesn't use C++ templates and also appears to generate faster code. There are two versions of the function, one "simple" using the standard branching binary search algorithm, and the other "branchless". The latter is currently not used, needing a better timing infrastructure to verify it's actually faster on Android devices. (Microbenchmarks suggest it's faster, application measurements when the branchless version is used suggest it's slower than the simple one) * the `typemap_managed_to_java()` and `typemap_java_to_managed()` internal calls are now registered directly from the `EmbeddedAssemblies` class instead of from the `MonodroidRuntime` class * a number of native functions are now forcibly inlined * a number of native functions are now `static` instead of instance. ~~ File Formats ~~ The `TypeMapJava::java_name` string field (ce2bc689) is now an `TypeMapJava::java_name_index` int32 field, which is an index into the `java_type_names` global array: extern "C" const char* const java_type_names[]; A new `map_java_hashes` global array is also introduced, which contains the xxHash value of each entry within `java_type_names`. `map_java_hashes` is sorted for binary search purposes: extern "C" const xamarin::android::hash_t map_java_hashes[]; ~~ Performance ~~ Startup performance was measured on a .NET6 MAUI application created with the `dotnet new maui` template. Gains vary depending on where we look. The `Displayed` time sees changes that are negligible, however the most affected area of the startup sequence (`JNIEnv.Initialize()`) which registers types and involves the biggest number of lookups sees improvements of up to 12%. The measurements have a degree of uncertainty and instability to them because of our use of Android `logcat` to report timings as they are taken. (`adb logcat` calls need to send messages to a system daemon which involves a lot of steps and allows for a large variation in time spent processing each call.) The `Displayed` time is also not a very stable reporting system (it depends on CPU and GPU load among other factors). The changes will also positively affect application performance after startup. All times are from devices running Android 12. | JNIEnv.Initialize() time; Scenario | Before ms | After ms | Δ | | ------------------------------------- | --------: | --------: | --------: | | Pixel 3 XL, 32-bit, Preload enabled | 14.967 | 13.586 | -9.23% ✓ | | Pixel 3 XL, 64-bit, Preload disabled | 13.601 | 12.838 | -5.61% ✓ | | Pixel 6 XL, 32-bit, Preload enabled | 8.972 | 7.826 | -12.78% ✓ | | Pixel 6 XL, 64-bit, Preload disabled | 6.426 | 6.052 | -5.83% ✓ | --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 3 + .../Utilities/EnvironmentHelper.cs | 2 +- .../BuildReleaseArm64SimpleDotNet.apkdesc | 14 +- .../BuildReleaseArm64SimpleLegacy.apkdesc | 14 +- .../BuildReleaseArm64XFormsDotNet.apkdesc | 110 ++++----- .../BuildReleaseArm64XFormsLegacy.apkdesc | 26 +- .../LlvmIrGenerator/LlvmIrGenerator.cs | 129 +++++++--- .../Utilities/NativeTypeMappingData.cs | 23 +- .../Utilities/TypeMapGenerator.cs | 21 +- ...peMappingReleaseNativeAssemblyGenerator.cs | 202 ++++++++++----- src/monodroid/jni/application_dso_stub.cc | 9 +- src/monodroid/jni/embedded-assemblies.cc | 232 ++++++++++-------- src/monodroid/jni/embedded-assemblies.hh | 50 ++-- src/monodroid/jni/monodroid-glue-internal.hh | 10 +- src/monodroid/jni/monodroid-glue.cc | 56 +++-- src/monodroid/jni/platform-compat.hh | 5 + src/monodroid/jni/util.hh | 2 +- src/monodroid/jni/xamarin-app.hh | 8 +- 18 files changed, 548 insertions(+), 368 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 904562f25b1..aa27ecba7bc 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -119,6 +119,9 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem [DllImport ("libc")] static extern int gettid (); +#if NETCOREAPP + [UnmanagedCallersOnly] +#endif static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) { string typeName = new string ((char*) typeName_ptr, 0, typeName_len); 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 14d34d759a4..1fafe5c14e8 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 @@ -89,7 +89,7 @@ public sealed class ApplicationConfig "map_modules", "map_module_count", "java_type_count", - "java_name_width", + "map_java_hashes", "map_java", "mono_aot_mode_name", }; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index cfd7832cd49..3cbcb3bb767 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,10 +5,10 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 55111 + "Size": 55106 }, "assemblies/Mono.Android.dll": { - "Size": 88334 + "Size": 88461 }, "assemblies/rc.bin": { "Size": 1083 @@ -26,13 +26,13 @@ "Size": 2374 }, "assemblies/UnnamedProject.dll": { - "Size": 3551 + "Size": 3546 }, "classes.dex": { "Size": 345328 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 380832 + "Size": 382304 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3192432 @@ -47,7 +47,7 @@ "Size": 150032 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 8688 + "Size": 9424 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 @@ -59,7 +59,7 @@ "Size": 2467 }, "res/drawable-hdpi-v4/icon.png": { - "Size": 4762 + "Size": 4791 }, "res/drawable-mdpi-v4/icon.png": { "Size": 2200 @@ -83,5 +83,5 @@ "Size": 1904 } }, - "PackageSize": 2701303 + "PackageSize": 2705399 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc index 18bbf23b1d2..33a48ec8ce0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc @@ -5,22 +5,22 @@ "Size": 2604 }, "assemblies/Java.Interop.dll": { - "Size": 67953 + "Size": 67956 }, "assemblies/Mono.Android.dll": { - "Size": 256591 + "Size": 256630 }, "assemblies/mscorlib.dll": { - "Size": 769016 + "Size": 769015 }, "assemblies/System.Core.dll": { - "Size": 28198 + "Size": 28199 }, "assemblies/System.dll": { "Size": 9180 }, "assemblies/UnnamedProject.dll": { - "Size": 2880 + "Size": 2881 }, "classes.dex": { "Size": 347796 @@ -32,7 +32,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 297544 + "Size": 296192 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4030448 @@ -41,7 +41,7 @@ "Size": 65512 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 18272 + "Size": 19960 }, "META-INF/ANDROIDD.RSA": { "Size": 1213 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 2694418af3e..f3acfe04246 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -8,124 +8,124 @@ "Size": 7247 }, "assemblies/Java.Interop.dll": { - "Size": 62006 + "Size": 62017 }, "assemblies/Mono.Android.dll": { - "Size": 441706 + "Size": 442008 }, "assemblies/mscorlib.dll": { - "Size": 3798 + "Size": 3803 }, "assemblies/netstandard.dll": { - "Size": 5499 + "Size": 5503 }, "assemblies/rc.bin": { "Size": 1083 }, "assemblies/System.Collections.Concurrent.dll": { - "Size": 11227 + "Size": 11230 }, "assemblies/System.Collections.dll": { - "Size": 16736 + "Size": 16741 }, "assemblies/System.Collections.NonGeneric.dll": { - "Size": 8439 + "Size": 8443 }, "assemblies/System.ComponentModel.dll": { - "Size": 1961 + "Size": 1965 }, "assemblies/System.ComponentModel.Primitives.dll": { - "Size": 2566 + "Size": 2569 }, "assemblies/System.ComponentModel.TypeConverter.dll": { - "Size": 5968 + "Size": 5970 }, "assemblies/System.Console.dll": { - "Size": 6530 + "Size": 6532 }, "assemblies/System.Core.dll": { - "Size": 1928 + "Size": 1932 }, "assemblies/System.Diagnostics.TraceSource.dll": { - "Size": 6756 + "Size": 6761 }, "assemblies/System.dll": { - "Size": 2275 + "Size": 2279 }, "assemblies/System.Drawing.dll": { - "Size": 1957 + "Size": 1961 }, "assemblies/System.Drawing.Primitives.dll": { - "Size": 12199 + "Size": 12206 }, "assemblies/System.IO.Compression.dll": { - "Size": 17218 + "Size": 17221 }, "assemblies/System.IO.IsolatedStorage.dll": { - "Size": 10566 + "Size": 10569 }, "assemblies/System.Linq.dll": { - "Size": 19474 + "Size": 19480 }, "assemblies/System.Linq.Expressions.dll": { - "Size": 182081 + "Size": 182084 }, "assemblies/System.Net.Http.dll": { - "Size": 65831 + "Size": 65842 }, "assemblies/System.Net.Primitives.dll": { - "Size": 22364 + "Size": 22368 }, "assemblies/System.Net.Requests.dll": { - "Size": 3731 + "Size": 3734 }, "assemblies/System.ObjectModel.dll": { - "Size": 11970 + "Size": 11974 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 757425 + "Size": 757472 }, "assemblies/System.Private.DataContractSerialization.dll": { - "Size": 191072 + "Size": 191079 }, "assemblies/System.Private.Uri.dll": { - "Size": 43677 + "Size": 43502 }, "assemblies/System.Private.Xml.dll": { - "Size": 220171 + "Size": 220183 }, "assemblies/System.Private.Xml.Linq.dll": { - "Size": 17101 + "Size": 17099 }, "assemblies/System.Runtime.CompilerServices.Unsafe.dll": { - "Size": 1214 + "Size": 1216 }, "assemblies/System.Runtime.dll": { - "Size": 2557 + "Size": 2561 }, "assemblies/System.Runtime.Serialization.dll": { - "Size": 1889 + "Size": 1893 }, "assemblies/System.Runtime.Serialization.Formatters.dll": { - "Size": 2634 + "Size": 2637 }, "assemblies/System.Runtime.Serialization.Primitives.dll": { - "Size": 3940 + "Size": 3943 }, "assemblies/System.Security.Cryptography.Algorithms.dll": { - "Size": 6809 + "Size": 6815 }, "assemblies/System.Security.Cryptography.Primitives.dll": { - "Size": 2968 + "Size": 2973 }, "assemblies/System.Text.RegularExpressions.dll": { - "Size": 76698 + "Size": 76702 }, "assemblies/System.Xml.dll": { - "Size": 1779 + "Size": 1782 }, "assemblies/UnnamedProject.dll": { - "Size": 117239 + "Size": 117237 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 6069 @@ -134,40 +134,40 @@ "Size": 6095 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { - "Size": 112590 + "Size": 112591 }, "assemblies/Xamarin.AndroidX.CardView.dll": { - "Size": 6809 + "Size": 6810 }, "assemblies/Xamarin.AndroidX.CoordinatorLayout.dll": { "Size": 16603 }, "assemblies/Xamarin.AndroidX.Core.dll": { - "Size": 96723 + "Size": 96722 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 14273 + "Size": 14271 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { - "Size": 39924 + "Size": 39926 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { - "Size": 6132 + "Size": 6133 }, "assemblies/Xamarin.AndroidX.Lifecycle.Common.dll": { "Size": 6592 }, "assemblies/Xamarin.AndroidX.Lifecycle.LiveData.Core.dll": { - "Size": 6671 + "Size": 6672 }, "assemblies/Xamarin.AndroidX.Lifecycle.ViewModel.dll": { - "Size": 3273 + "Size": 3272 }, "assemblies/Xamarin.AndroidX.Loader.dll": { - "Size": 12671 + "Size": 12670 }, "assemblies/Xamarin.AndroidX.RecyclerView.dll": { - "Size": 84688 + "Size": 84687 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { "Size": 5077 @@ -176,13 +176,13 @@ "Size": 10382 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 17986 + "Size": 17985 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 528450 }, "assemblies/Xamarin.Forms.Platform.Android.dll": { - "Size": 384996 + "Size": 384997 }, "assemblies/Xamarin.Forms.Platform.dll": { "Size": 56878 @@ -191,13 +191,13 @@ "Size": 60774 }, "assemblies/Xamarin.Google.Android.Material.dll": { - "Size": 40134 + "Size": 40135 }, "classes.dex": { "Size": 3458288 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 382776 + "Size": 382304 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3192432 @@ -212,7 +212,7 @@ "Size": 150032 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 133192 + "Size": 98624 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1967,5 +1967,5 @@ "Size": 341228 } }, - "PackageSize": 7930399 + "PackageSize": 7942687 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc index aa11b5cb651..b4adbbf5847 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc @@ -8,19 +8,19 @@ "Size": 7215 }, "assemblies/Java.Interop.dll": { - "Size": 68919 + "Size": 68921 }, "assemblies/Mono.Android.dll": { - "Size": 567108 + "Size": 567161 }, "assemblies/Mono.Security.dll": { - "Size": 68431 + "Size": 68433 }, "assemblies/mscorlib.dll": { "Size": 915405 }, "assemblies/System.Core.dll": { - "Size": 164046 + "Size": 164045 }, "assemblies/System.dll": { "Size": 388864 @@ -32,7 +32,7 @@ "Size": 110642 }, "assemblies/System.Numerics.dll": { - "Size": 15682 + "Size": 15683 }, "assemblies/System.Runtime.Serialization.dll": { "Size": 186660 @@ -44,13 +44,13 @@ "Size": 395657 }, "assemblies/UnnamedProject.dll": { - "Size": 116892 + "Size": 116898 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 7701 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { - "Size": 6650 + "Size": 6651 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { "Size": 125337 @@ -65,7 +65,7 @@ "Size": 131939 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 15429 + "Size": 15430 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { "Size": 43150 @@ -80,7 +80,7 @@ "Size": 7195 }, "assemblies/Xamarin.AndroidX.Lifecycle.ViewModel.dll": { - "Size": 4874 + "Size": 4875 }, "assemblies/Xamarin.AndroidX.Loader.dll": { "Size": 13589 @@ -92,7 +92,7 @@ "Size": 6283 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { - "Size": 11271 + "Size": 11270 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { "Size": 19429 @@ -122,7 +122,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 297544 + "Size": 296192 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4030448 @@ -131,7 +131,7 @@ "Size": 65512 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 139912 + "Size": 105016 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1883,5 +1883,5 @@ "Size": 341040 } }, - "PackageSize": 9521310 + "PackageSize": 9533598 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index d216ba52726..ec30d96f62a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -51,7 +51,7 @@ public PackedStructureMember (StructureMemberInfo memberInfo, object? value, } } - sealed class StringSymbolInfo + public sealed class StringSymbolInfo { public readonly string SymbolName; public readonly ulong Size; @@ -377,8 +377,8 @@ bool MaybeWriteStructureString (StructureInfo info, StructureMemberInfo return false; } - string symbolName = WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter, out ulong size); - instance.AddPointerData (smi, symbolName, size); + StringSymbolInfo stringSymbol = WriteUniqueString ($"__{info.Name}_{smi.Info.Name}", str, ref structStringCounter); + instance.AddPointerData (smi, stringSymbol.SymbolName, stringSymbol.Size); return true; } @@ -477,10 +477,10 @@ public void WriteStructureArray (StructureInfo info, IList instance = instances[i]; + arrayOutput.WriteLine ($"{Indent}; {i}"); WriteStructureBody (info, instance, bodyWriterOptions); if (i < count - 1) { arrayOutput.Write (", "); @@ -508,6 +508,76 @@ public void WriteStructureArray (StructureInfo info, IList (info, instances, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment); } + public void WriteArray (IList values, string symbolName) + { + WriteEOL (); + WriteEOL (symbolName); + + ulong arrayStringCounter = 0; + var strings = new List (); + + foreach (string s in values) { + StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}", s, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); + strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); + } + + if (strings.Count > 0) { + Output.WriteLine (); + } + + WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); + } + + public void WriteArray (IList values, LlvmIrVariableOptions options, string symbolName, Func? commentProvider = null) where T: struct + { + bool optimizeOutput = commentProvider == null; + + WriteGlobalSymbolStart (symbolName, options); + string elementType = MapManagedTypeToIR (typeof (T), out ulong size); + Output.WriteLine ($"[{values.Count} x {elementType}] ["); + Output.Write (Indent); + for (int i = 0; i < values.Count; i++) { + if (i != 0) { + if (optimizeOutput) { + Output.Write (','); + if (i % 8 == 0) { + Output.WriteLine ($" ; {i - 8}..{i - 1}"); + Output.Write (Indent); + } else { + Output.Write (' '); + } + } else { + Output.Write (Indent); + } + } + + Output.Write ($"{elementType} {values[i]}"); + + if (!optimizeOutput) { + bool last = i == values.Count - 1; + if (!last) { + Output.Write (','); + } + + string? comment = commentProvider (i, values[i]); + if (!String.IsNullOrEmpty (comment)) { + Output.Write ($" ; {comment}"); + } + + if (!last) { + Output.WriteLine (); + } + } + } + if (optimizeOutput && values.Count / 8 != 0) { + int idx = values.Count - (values.Count % 8); + Output.Write ($" ; {idx}..{values.Count - 1}"); + } + + Output.WriteLine (); + Output.WriteLine ($"], align {GetAggregateAlignment ((int)size, size * (ulong)values.Count)}"); + } + void AssertArraySize (StructureInfo info, StructureMemberInfo smi, ulong length, ulong expectedLength) { if (length == expectedLength) { @@ -911,7 +981,7 @@ public void WriteNameValueArray (string symbolName, IDictionary WriteEOL (); WriteEOL (symbolName); - var strings = new List<(ulong stringSize, string varName)> (); + var strings = new List (); long i = 0; ulong arrayStringCounter = 0; @@ -923,19 +993,31 @@ public void WriteNameValueArray (string symbolName, IDictionary WriteArrayString (value, $"v_{i}"); i++; } + if (strings.Count > 0) { Output.WriteLine (); } - WriteGlobalSymbolStart (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer); + WriteStringArray (symbolName, LlvmIrVariableOptions.GlobalConstantStringPointer, strings); + + void WriteArrayString (string str, string symbolSuffix) + { + StringSymbolInfo symbol = WriteUniqueString ($"__{symbolName}_{symbolSuffix}", str, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString); + strings.Add (new StringSymbolInfo (symbol.SymbolName, symbol.Size)); + } + } + + void WriteStringArray (string symbolName, LlvmIrVariableOptions options, List strings) + { + WriteGlobalSymbolStart (symbolName, options); Output.Write ($"[{strings.Count} x i8*]"); if (strings.Count > 0) { Output.WriteLine (" ["); for (int j = 0; j < strings.Count; j++) { - ulong size = strings[j].stringSize; - string varName = strings[j].varName; + ulong size = strings[j].Size; + string varName = strings[j].SymbolName; // // Syntax: https://llvm.org/docs/LangRef.html#getelementptr-instruction @@ -961,12 +1043,6 @@ public void WriteNameValueArray (string symbolName, IDictionary Output.Write ("]"); } Output.WriteLine ($", align {GetAggregateAlignment (PointerSize, arraySize)}"); - - void WriteArrayString (string str, string symbolSuffix) - { - string name = WriteUniqueString ($"__{symbolName}_{symbolSuffix}", str, ref arrayStringCounter, LlvmIrVariableOptions.LocalConstexprString, out ulong size); - strings.Add (new (size, name)); - } } /// @@ -1073,20 +1149,9 @@ public string WriteString (string symbolName, string value, LlvmIrVariableOption /// string value. If a new symbol is written, its name is constructed by combining prefix () with value /// of a string counter referenced by the parameter. Symbol is created as a local, C++ constexpr style string. /// - public string WriteUniqueString (string potentialSymbolName, string value, ref ulong counter) - { - return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString, out _); - } - - /// - /// Writes a string, creating a new symbol if the is unique or returns name of a previously created symbol with the same - /// string value. If a new symbol is written, its name is constructed by combining prefix () with value - /// of a string counter referenced by the parameter. Symbol is created as a local, C++ constexpr style string. - // String size (in bytes) is returned in . - /// - public string WriteUniqueString (string potentialSymbolName, string value, ref ulong counter, out ulong stringSize) + public StringSymbolInfo WriteUniqueString (string potentialSymbolName, string value, ref ulong counter) { - return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString, out stringSize); + return WriteUniqueString (potentialSymbolName, value, ref counter, LlvmIrVariableOptions.LocalConstexprString); } /// @@ -1095,25 +1160,23 @@ public string WriteUniqueString (string potentialSymbolName, string value, ref u /// of a string counter referenced by the parameter. Symbol options (writeability, visibility etc) are specified in the parameter. String size (in bytes) is returned in . /// - public string WriteUniqueString (string potentialSymbolNamePrefix, string value, ref ulong counter, LlvmIrVariableOptions options, out ulong stringSize) + public StringSymbolInfo WriteUniqueString (string potentialSymbolNamePrefix, string value, ref ulong counter, LlvmIrVariableOptions options) { if (value == null) { - stringSize = 0; return null; } StringSymbolInfo info; if (stringSymbolCache.TryGetValue (value, out info)) { - stringSize = info.Size; - return info.SymbolName; + return info; } string newSymbolName = $"{potentialSymbolNamePrefix}.{counter++}"; - WriteString (newSymbolName, value, options, out stringSize); + WriteString (newSymbolName, value, options, out ulong stringSize); info = new StringSymbolInfo (newSymbolName, stringSize); stringSymbolCache.Add (value, info); - return info.SymbolName; + return info; } public virtual void WriteFileTop () diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs index b5b57c23ec1..f1a5205f746 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs @@ -7,31 +7,21 @@ namespace Xamarin.Android.Tasks class NativeTypeMappingData { public TypeMapGenerator.ModuleReleaseData[] Modules { get; } - public IDictionary AssemblyNames { get; } - public string[] JavaTypeNames { get; } public TypeMapGenerator.TypeMapReleaseEntry[] JavaTypes { get; } public uint MapModuleCount { get; } public uint JavaTypeCount { get; } - public uint JavaNameWidth { get; } - public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleReleaseData[] modules, int javaNameWidth) + public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleReleaseData[] modules) { Modules = modules ?? throw new ArgumentNullException (nameof (modules)); MapModuleCount = (uint)modules.Length; - JavaNameWidth = (uint)javaNameWidth; - - AssemblyNames = new Dictionary (StringComparer.Ordinal); var tempJavaTypes = new Dictionary (StringComparer.Ordinal); - int managedStringCounter = 0; var moduleComparer = new TypeMapGenerator.ModuleUUIDArrayComparer (); foreach (TypeMapGenerator.ModuleReleaseData data in modules) { - data.AssemblyNameLabel = $"map_aname.{managedStringCounter++}"; - AssemblyNames.Add (data.AssemblyNameLabel, data.AssemblyName); - int moduleIndex = Array.BinarySearch (modules, data, moduleComparer); if (moduleIndex < 0) throw new InvalidOperationException ($"Unable to map module with MVID {data.Mvid} to array index"); @@ -44,16 +34,7 @@ public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleRele } } - var javaNames = tempJavaTypes.Keys.ToArray (); - Array.Sort (javaNames, StringComparer.Ordinal); - - var javaTypes = new TypeMapGenerator.TypeMapReleaseEntry[javaNames.Length]; - for (int i = 0; i < javaNames.Length; i++) { - javaTypes[i] = tempJavaTypes[javaNames[i]]; - } - - JavaTypes = javaTypes; - JavaTypeNames = javaNames; + JavaTypes = tempJavaTypes.Values.ToArray (); JavaTypeCount = (uint)JavaTypes.Length; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 15989928466..d5f2c5e17da 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -40,18 +40,9 @@ public int Compare (ModuleReleaseData left, ModuleReleaseData right) } } - internal sealed class TypeMapEntryArrayComparer : IComparer - { - public int Compare (TypeMapReleaseEntry left, TypeMapReleaseEntry right) - { - return String.CompareOrdinal (left.JavaName, right.JavaName); - } - } - internal sealed class TypeMapReleaseEntry { public string JavaName; - public int JavaNameLength; public string ManagedTypeName; public uint Token; public int AssemblyNameIndex = -1; @@ -67,7 +58,6 @@ internal sealed class ModuleReleaseData public TypeMapReleaseEntry[] Types; public Dictionary DuplicateTypes; public string AssemblyName; - public string AssemblyNameLabel; public string OutputFilePath; public Dictionary TypesScratch; @@ -343,7 +333,6 @@ string GetManagedTypeName (TypeDefinition td) bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) { int assemblyId = 0; - int maxJavaNameLength = 0; var knownAssemblies = new Dictionary (StringComparer.Ordinal); var tempModules = new Dictionary (); Dictionary moduleCounter = null; @@ -392,16 +381,12 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List // a Java type name to a managed type. This fixes https://github.com/xamarin/xamarin-android/issues/4660 var entry = new TypeMapReleaseEntry { JavaName = javaName, - JavaNameLength = outputEncoding.GetByteCount (javaName), ManagedTypeName = td.FullName, Token = td.MetadataToken.ToUInt32 (), AssemblyNameIndex = knownAssemblies [assemblyName], SkipInJavaToManaged = ShouldSkipInJavaToManaged (td), }; - if (entry.JavaNameLength > maxJavaNameLength) - maxJavaNameLength = entry.JavaNameLength; - if (moduleData.TypesScratch.ContainsKey (entry.JavaName)) { // This is disabled because it costs a lot of time (around 150ms per standard XF Integration app // build) and has no value for the end user. The message is left here because it may be useful to us @@ -415,19 +400,19 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List var modules = tempModules.Values.ToArray (); Array.Sort (modules, new ModuleUUIDArrayComparer ()); - var typeMapEntryComparer = new TypeMapEntryArrayComparer (); foreach (ModuleReleaseData module in modules) { if (module.TypesScratch.Count == 0) { module.Types = Array.Empty (); continue; } + // No need to sort here, the LLVM IR generator will compute hashes and sort + // the array on write. module.Types = module.TypesScratch.Values.ToArray (); - Array.Sort (module.Types, typeMapEntryComparer); } NativeTypeMappingData data; - data = new NativeTypeMappingData (logger, modules, maxJavaNameLength + 1); + data = new NativeTypeMappingData (logger, modules); var generator = new TypeMappingReleaseNativeAssemblyGenerator (data); generator.Init (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index ecc42af1eb5..7804dfba5a7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; +using K4os.Hash.xxHash; using Xamarin.Android.Tasks.LLVMIR; namespace Xamarin.Android.Tasks @@ -57,18 +58,11 @@ public override ulong GetBufferSize (object data, string fieldName) } } - sealed class TypeMapJavaContextDataProvider : NativeAssemblerStructContextDataProvider + sealed class JavaNameHashComparer : IComparer> { - public override uint GetMaxInlineWidth (object data, string fieldName) + public int Compare (StructureInstance a, StructureInstance b) { - if (String.Compare ("java_name", fieldName, StringComparison.Ordinal) == 0) { - // Using a static field for this is **very** clunky, but it works in our case since we will - // set that field only once per build session and it allows us to query the array size while - // generating the structure declarations (as required by LLVM IR) - return TypeMapJava.MaxJavaNameLength; - } - - return 0; + return a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash); } } @@ -98,6 +92,9 @@ sealed class TypeMapModule [NativeAssembler (Ignore = true)] public string? DuplicateMapSymbolName; + [NativeAssembler (Ignore = true)] + public TypeMapGenerator.ModuleReleaseData Data; + [NativeAssembler (UsesDataProvider = true, InlineArray = true, InlineArraySize = 16)] public byte[] module_uuid; public uint entry_count; @@ -122,17 +119,17 @@ sealed class TypeMapModule // Order of fields and their type must correspond *exactly* to that in // src/monodroid/jni/xamarin-app.hh TypeMapJava structure - [NativeAssemblerStructContextDataProvider (typeof (TypeMapJavaContextDataProvider))] sealed class TypeMapJava { [NativeAssembler (Ignore = true)] - public static uint MaxJavaNameLength; + public string JavaName; + + [NativeAssembler (Ignore = true)] + public ulong JavaNameHash; public uint module_index; public uint type_token_id; - - [NativeAssembler (UsesDataProvider = true, InlineArray = true, NeedsPadding = true)] - public byte[] java_name; + public uint java_name_index; } sealed class ModuleMapData @@ -140,10 +137,10 @@ sealed class ModuleMapData public string SymbolLabel { get; } public List> Entries { get; } - public ModuleMapData (string symbolLabel) + public ModuleMapData (string symbolLabel, List> entries) { SymbolLabel = symbolLabel; - Entries = new List> (); + Entries = entries; } } @@ -151,37 +148,45 @@ public ModuleMapData (string symbolLabel) StructureInfo typeMapJavaStructureInfo; StructureInfo typeMapModuleStructureInfo; StructureInfo typeMapModuleEntryStructureInfo; - List mapModulesData; List> mapModules; List> javaMap; + Dictionary javaTypesByName; + List javaNames; + JavaNameHashComparer javaNameHashComparer; ulong moduleCounter = 0; public TypeMappingReleaseNativeAssemblyGenerator (NativeTypeMappingData mappingData) { this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - mapModulesData = new List (); mapModules = new List> (); javaMap = new List> (); + javaTypesByName = new Dictionary (StringComparer.Ordinal); + javaNameHashComparer = new JavaNameHashComparer (); + javaNames = new List (); } public override void Init () { - TypeMapJava.MaxJavaNameLength = mappingData.JavaNameWidth; InitMapModules (); InitJavaMap (); } void InitJavaMap () { + TypeMapJava map_entry; foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { - var map_entry = new TypeMapJava { - module_index = (uint)entry.ModuleIndex, + javaNames.Add (entry.JavaName); + + map_entry = new TypeMapJava { + module_index = (uint)entry.ModuleIndex, // UInt32.MaxValue, type_token_id = entry.SkipInJavaToManaged ? 0 : entry.Token, - java_name = Encoding.UTF8.GetBytes (entry.JavaName), + java_name_index = (uint)(javaNames.Count - 1), + JavaName = entry.JavaName, }; javaMap.Add (new StructureInstance (map_entry)); + javaTypesByName.Add (map_entry.JavaName, map_entry); } } @@ -200,6 +205,7 @@ void InitMapModules () MVID = data.Mvid, MapSymbolName = mapName, DuplicateMapSymbolName = duplicateMapName.Length == 0 ? null : duplicateMapName, + Data = data, module_uuid = data.MvidBytes, entry_count = (uint)data.Types.Length, @@ -208,73 +214,153 @@ void InitMapModules () java_name_width = 0, }; - InitMapModuleData (mapName, data.Types, mapModulesData); - if (data.DuplicateTypes.Count > 0) { - InitMapModuleData (duplicateMapName, data.DuplicateTypes.Values, mapModulesData); - } - mapModules.Add (new StructureInstance (map_module)); } } - void InitMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData) + protected override void MapStructures (LlvmIrGenerator generator) { - var tokens = new Dictionary (); - foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { - int idx = Array.BinarySearch (mappingData.JavaTypeNames, entry.JavaName, StringComparer.Ordinal); - if (idx < 0) - throw new InvalidOperationException ($"Could not map entry '{entry.JavaName}' to array index"); + generator.MapStructure (); + typeMapJavaStructureInfo = generator.MapStructure (); + typeMapModuleStructureInfo = generator.MapStructure (); + typeMapModuleEntryStructureInfo = generator.MapStructure (); + } - tokens[entry.Token] = (uint)idx; + // Prepare module map entries by sorting them on the managed token, and then mapping each entry to its corresponding Java type map index. + // Requires that `javaMap` is sorted on the type name hash. + void PrepareMapModuleData (string moduleDataSymbolLabel, IEnumerable moduleEntries, List allModulesData) + { + var mapModuleEntries = new List> (); + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in moduleEntries) { + var map_entry = new TypeMapModuleEntry { + type_token_id = entry.Token, + java_map_index = GetJavaEntryIndex (entry.JavaName), + }; + mapModuleEntries.Add (new StructureInstance (map_entry)); } - var sortedTokens = tokens.Keys.ToArray (); - Array.Sort (sortedTokens); + mapModuleEntries.Sort ((StructureInstance a, StructureInstance b) => a.Obj.type_token_id.CompareTo (b.Obj.type_token_id)); + allModulesData.Add (new ModuleMapData (moduleDataSymbolLabel, mapModuleEntries)); - var moduleData = new ModuleMapData (moduleDataSymbolLabel); - foreach (uint token in sortedTokens) { - var map_entry = new TypeMapModuleEntry { - type_token_id = token, - java_map_index = tokens[token], - }; + uint GetJavaEntryIndex (string javaTypeName) + { + if (!javaTypesByName.TryGetValue (javaTypeName, out TypeMapJava javaType)) { + throw new InvalidOperationException ($"INTERNAL ERROR: Java type '{javaTypeName}' not found in cache"); + } - moduleData.Entries.Add (new StructureInstance (map_entry)); - } + var key = new StructureInstance (javaType); + int idx = javaMap.BinarySearch (key, javaNameHashComparer); + if (idx < 0) { + throw new InvalidOperationException ($"Could not map entry '{javaTypeName}' to array index"); + } - allModulesData.Add (moduleData); + return (uint)idx; + } } - protected override void MapStructures (LlvmIrGenerator generator) + // Generate hashes for all Java type names, then sort javaMap on the name hash. This has to be done in the writing phase because hashes + // will depend on architecture (or, actually, on its bitness) and may differ between architectures (they will be the same for all architectures + // with the same bitness) + (List allMapModulesData, List javaMapHashes) PrepareMapsForWriting (LlvmIrGenerator generator) { - generator.MapStructure (); - typeMapJavaStructureInfo = generator.MapStructure (); - typeMapModuleStructureInfo = generator.MapStructure (); - typeMapModuleEntryStructureInfo = generator.MapStructure (); + bool is64Bit = generator.Is64Bit; + + // Generate Java type name hashes... + for (int i = 0; i < javaMap.Count; i++) { + TypeMapJava entry = javaMap[i].Obj; + entry.JavaNameHash = HashName (entry.JavaName); + } + + // ...sort them... + javaMap.Sort ((StructureInstance a, StructureInstance b) => a.Obj.JavaNameHash.CompareTo (b.Obj.JavaNameHash)); + + var allMapModulesData = new List (); + + // ...and match managed types to Java... + foreach (StructureInstance moduleInstance in mapModules) { + TypeMapModule module = moduleInstance.Obj; + PrepareMapModuleData (module.MapSymbolName, module.Data.Types, allMapModulesData); + if (module.Data.DuplicateTypes.Count > 0) { + PrepareMapModuleData (module.DuplicateMapSymbolName, module.Data.DuplicateTypes.Values, allMapModulesData); + } + } + + var javaMapHashes = new HashSet (); + foreach (StructureInstance entry in javaMap) { + javaMapHashes.Add (entry.Obj.JavaNameHash); + } + + return (allMapModulesData, javaMapHashes.ToList ()); + + ulong HashName (string name) + { + if (name.Length == 0) { + return UInt64.MaxValue; + } + + // Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do + // the same + return HashBytes (Encoding.Unicode.GetBytes (name)); + } + + ulong HashBytes (byte[] bytes) + { + if (is64Bit) { + return XXH64.DigestOf (bytes, 0, bytes.Length); + } + + return (ulong)XXH32.DigestOf (bytes, 0, bytes.Length); + } } protected override void Write (LlvmIrGenerator generator) { generator.WriteVariable ("map_module_count", mappingData.MapModuleCount); - generator.WriteVariable ("java_type_count", mappingData.JavaTypeCount); - generator.WriteVariable ("java_name_width", mappingData.JavaNameWidth); + generator.WriteVariable ("java_type_count", javaMap.Count); // must include the padding item, if any - WriteMapModules (generator); - WriteJavaMap (generator); + (List allMapModulesData, List javaMapHashes) = PrepareMapsForWriting (generator); + WriteMapModules (generator, allMapModulesData); + WriteJavaMap (generator, javaMapHashes); } - void WriteJavaMap (LlvmIrGenerator generator) + void WriteJavaMap (LlvmIrGenerator generator, List javaMapHashes) { generator.WriteEOL (); generator.WriteEOL ("Java to managed map"); - generator.WritePackedStructureArray ( + + generator.WriteStructureArray ( typeMapJavaStructureInfo, javaMap, LlvmIrVariableOptions.GlobalConstant, "map_java" ); + + if (generator.Is64Bit) { + WriteHashes (javaMapHashes); + } else { + // A bit ugly, but simple. We know that hashes are really 32-bit, so we can cast without + // worrying. + var hashes = new List (javaMapHashes.Count); + foreach (ulong hash in javaMapHashes) { + hashes.Add ((uint)hash); + } + WriteHashes (hashes); + } + + generator.WriteArray (javaNames, "java_type_names"); + + void WriteHashes (List hashes) where T: struct + { + generator.WriteArray ( + hashes, + LlvmIrVariableOptions.GlobalConstant, + "map_java_hashes", + (int idx, T value) => $"{idx}: 0x{value:x} => {javaMap[idx].Obj.JavaName}" + ); + } } - void WriteMapModules (LlvmIrGenerator generator) + void WriteMapModules (LlvmIrGenerator generator, List mapModulesData) { if (mapModules.Count == 0) { return; diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 64dff21b874..0fc78f49b46 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -24,15 +24,16 @@ const TypeMap type_map = { #else const uint32_t map_module_count = 0; const uint32_t java_type_count = 0; -const uint32_t java_name_width = 0; +const char* const java_type_names[] = {}; -const TypeMapModule map_modules[] = {}; +TypeMapModule map_modules[] = {}; const TypeMapJava map_java[] = {}; +const xamarin::android::hash_t map_java_hashes[] = {}; #endif CompressedAssemblies compressed_assemblies = { - /*.count = */ 0, - /*.descriptors = */ nullptr, + .count = 0, + .descriptors = nullptr, }; // diff --git a/src/monodroid/jni/embedded-assemblies.cc b/src/monodroid/jni/embedded-assemblies.cc index ee39aee5a7b..cd842cd54ac 100644 --- a/src/monodroid/jni/embedded-assemblies.cc +++ b/src/monodroid/jni/embedded-assemblies.cc @@ -548,8 +548,8 @@ EmbeddedAssemblies::install_preload_hooks_for_alc () #endif // def NET6 template -const Entry* -EmbeddedAssemblies::binary_search (const Key *key, const Entry *base, size_t nmemb, [[maybe_unused]] size_t precalculated_size) +force_inline const Entry* +EmbeddedAssemblies::binary_search (const Key *key, const Entry *base, size_t nmemb, [[maybe_unused]] size_t precalculated_size) noexcept { static_assert (compare != nullptr, "compare is a required template parameter"); @@ -599,9 +599,65 @@ EmbeddedAssemblies::binary_search (const Key *key, const Entry *base, size_t nme return nullptr; } +force_inline ssize_t +EmbeddedAssemblies::binary_search (hash_t key, const hash_t *arr, size_t n) noexcept +{ + ssize_t left = -1; + ssize_t right = static_cast(n); + + while (right - left > 1) { + ssize_t middle = (left + right) >> 1; + if (arr[middle] < key) { + left = middle; + } else { + right = middle; + } + } + + return arr[right] == key ? right : -1; +} + +force_inline ptrdiff_t +EmbeddedAssemblies::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; +} + +#if defined (RELEASE) && defined (ANDROID) +force_inline const TypeMapModuleEntry* +EmbeddedAssemblies::binary_search (uint32_t key, const TypeMapModuleEntry *arr, uint32_t n) noexcept +{ + ssize_t left = -1; + ssize_t right = static_cast(n); + ssize_t middle; + + while (right - left > 1) { + middle = (left + right) >> 1; + if (arr[middle].type_token_id < key) { + left = middle; + } else { + right = middle; + } + } + + return arr[right].type_token_id == key ? &arr[right] : nullptr; +} +#endif // def RELEASE && def ANDROID + #if defined (DEBUG) || !defined (ANDROID) -int -EmbeddedAssemblies::compare_type_name (const char *type_name, const TypeMapEntry *entry) +force_inline int +EmbeddedAssemblies::compare_type_name (const char *type_name, const TypeMapEntry *entry) noexcept { if (entry == nullptr) return 1; @@ -609,38 +665,39 @@ EmbeddedAssemblies::compare_type_name (const char *type_name, const TypeMapEntry return strcmp (type_name, entry->from); } -MonoReflectionType* -EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) +force_inline MonoReflectionType* +EmbeddedAssemblies::typemap_java_to_managed ([[maybe_unused]] hash_t hash, const MonoString *java_type) noexcept { + c_unique_ptr java_type_name {mono_string_to_utf8 (const_cast(java_type))}; const TypeMapEntry *entry = nullptr; if (application_config.instant_run_enabled) { TypeMap *module; for (size_t i = 0; i < type_map_count; i++) { module = &type_maps[i]; - entry = binary_search (java_type_name, module->java_to_managed, module->entry_count); + entry = binary_search (java_type_name.get (), module->java_to_managed, module->entry_count); if (entry != nullptr) break; } } else { - entry = binary_search (java_type_name, type_map.java_to_managed, type_map.entry_count); + entry = binary_search (java_type_name.get (), type_map.java_to_managed, type_map.entry_count); } if (XA_UNLIKELY (entry == nullptr)) { - log_info (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s'", java_type_name); + log_info (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s'", java_type_name.get ()); return nullptr; } const char *managed_type_name = entry->to; if (managed_type_name == nullptr) { - log_debug (LOG_ASSEMBLY, "typemap: Java type '%s' maps either to an open generic type or an interface type.", java_type_name); + log_debug (LOG_ASSEMBLY, "typemap: Java type '%s' maps either to an open generic type or an interface type.", java_type_name.get ()); return nullptr; } - log_debug (LOG_DEFAULT, "typemap: Java type '%s' corresponds to managed type '%s'", java_type_name, managed_type_name); + log_debug (LOG_DEFAULT, "typemap: Java type '%s' corresponds to managed type '%s'", java_type_name.get (), managed_type_name); MonoType *type = mono_reflection_type_from_name (const_cast(managed_type_name), nullptr); if (XA_UNLIKELY (type == nullptr)) { - log_info (LOG_ASSEMBLY, "typemap: managed type '%s' (mapped from Java type '%s') could not be loaded", managed_type_name, java_type_name); + log_info (LOG_ASSEMBLY, "typemap: managed type '%s' (mapped from Java type '%s') could not be loaded", managed_type_name, java_type_name.get ()); return nullptr; } @@ -653,46 +710,44 @@ EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) return ret; } #else -MonoReflectionType* -EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) +force_inline MonoReflectionType* +EmbeddedAssemblies::typemap_java_to_managed (hash_t hash, const MonoString *java_type_name) noexcept { - TypeMapModule *module; - const TypeMapJava *java_entry = binary_search (java_type_name, map_java, java_type_count, type_map_java_struct_size); - if (java_entry == nullptr) { - log_info (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s'", java_type_name); - return nullptr; - } - - if (java_entry->module_index >= map_module_count) { - log_warn (LOG_ASSEMBLY, "typemap: mapping from Java type '%s' to managed type has invalid module index", java_type_name); + // 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 = 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; + TypeMapModule *module = java_entry != nullptr && java_entry->module_index < map_module_count ? &map_modules[java_entry->module_index] : nullptr; + if (module == nullptr) { + if (java_entry == nullptr) { + log_info (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s' (hash 0x%zx)", to_utf8 (java_type_name).get (), hash); + } else { + log_warn (LOG_ASSEMBLY, "typemap: mapping from Java type '%s' to managed type has invalid module index %u", to_utf8 (java_type_name).get (), java_entry->module_index); + } return nullptr; } - module = const_cast(&map_modules[java_entry->module_index]); - const TypeMapModuleEntry *entry = binary_search (&java_entry->type_token_id, module->map, module->entry_count); + const TypeMapModuleEntry *entry = binary_search (java_entry->type_token_id, module->map, module->entry_count); if (entry == nullptr) { - log_info (LOG_ASSEMBLY, "typemap: unable to find mapping from Java type '%s' to managed type with token ID %u in module [%s]", java_type_name, java_entry->type_token_id, MonoGuidString (module->module_uuid).get ()); + log_info (LOG_ASSEMBLY, "typemap: unable to find mapping from Java type '%s' to managed type with token ID %u in module [%s]", to_utf8 (java_type_name).get (), java_entry->type_token_id, MonoGuidString (module->module_uuid).get ()); return nullptr; } - uint32_t type_token_id = java_entry->type_token_id; if (module->image == nullptr) { module->image = mono_image_loaded (module->assembly_name); if (module->image == nullptr) { - // TODO: load - log_error (LOG_ASSEMBLY, "typemap: assembly '%s' not loaded yet!", module->assembly_name); - } - - if (module->image == nullptr) { - log_error (LOG_ASSEMBLY, "typemap: unable to load assembly '%s' when looking up managed type corresponding to Java type '%s'", module->assembly_name, java_type_name); + log_error (LOG_ASSEMBLY, "typemap: unable to load assembly '%s' when looking up managed type corresponding to Java type '%s'", module->assembly_name, to_utf8 (java_type_name).get ()); return nullptr; } } - log_debug (LOG_ASSEMBLY, "typemap: java type '%s' corresponds to managed token id %u (0x%x)", java_type_name, type_token_id, type_token_id); - MonoClass *klass = mono_class_get (module->image, static_cast(type_token_id)); - if (klass == nullptr) { - log_error (LOG_ASSEMBLY, "typemap: unable to find managed type with token ID %u in assembly '%s', corresponding to Java type '%s'", type_token_id, module->assembly_name, java_type_name); + log_debug (LOG_ASSEMBLY, "typemap: java type '%s' corresponds to managed token id %u (0x%x)", to_utf8 (java_type_name).get (), java_entry->type_token_id, java_entry->type_token_id); + MonoClass *klass = mono_class_get (module->image, java_entry->type_token_id); + if (klass == nullptr) [[unlikely]] { + log_error (LOG_ASSEMBLY, "typemap: unable to find managed type with token ID %u in assembly '%s', corresponding to Java type '%s'", java_entry->type_token_id, module->assembly_name, to_utf8 (java_type_name).get ()); return nullptr; } @@ -706,26 +761,16 @@ EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) #endif MonoReflectionType *ret = mono_type_get_object (domain, mono_class_get_type (klass)); if (ret == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: unable to instantiate managed type with token ID %u in assembly '%s', corresponding to Java type '%s'", type_token_id, module->assembly_name, java_type_name); + log_warn (LOG_ASSEMBLY, "typemap: unable to instantiate managed type with token ID %u in assembly '%s', corresponding to Java type '%s'", java_entry->type_token_id, module->assembly_name, to_utf8 (java_type_name).get ()); return nullptr; } return ret; } - -int -EmbeddedAssemblies::compare_java_name (const char *java_name, const TypeMapJava *entry) -{ - if (entry == nullptr || entry->java_name[0] == '\0') { - return -1; - } - - return strcmp (java_name, reinterpret_cast(entry->java_name)); -} #endif MonoReflectionType* -EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) +EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) noexcept { timing_period total_time; if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { @@ -738,13 +783,17 @@ EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) return nullptr; } - c_unique_ptr java_type_name {mono_string_to_utf8 (java_type)}; - if (XA_UNLIKELY (!java_type_name || *java_type_name == '\0')) { - log_warn (LOG_ASSEMBLY, "typemap: empty Java type name passed to 'typemap_java_to_managed'"); + // We need to generate hash for all the bytes, and since MonoString is Unicode, we double the length to get the + // number of bytes. + int name_len = mono_string_length (java_type) << 1; + if (XA_UNLIKELY (name_len <= 0)) { + log_warn (LOG_ASSEMBLY, "typemap: empty 'java_type' passed to 'typemap_java_to_managed'"); return nullptr; } - MonoReflectionType *ret = typemap_java_to_managed (java_type_name.get ()); + const mono_unichar2 *type_chars = mono_string_chars (java_type); + hash_t hash = xxhash::hash (reinterpret_cast(type_chars), static_cast(name_len)); + MonoReflectionType *ret = typemap_java_to_managed (hash, java_type); if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { total_time.mark_end (); @@ -756,8 +805,8 @@ EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) } #if defined (DEBUG) || !defined (ANDROID) -inline const TypeMapEntry* -EmbeddedAssemblies::typemap_managed_to_java (const char *managed_type_name) +force_inline const TypeMapEntry* +EmbeddedAssemblies::typemap_managed_to_java (const char *managed_type_name) noexcept { const TypeMapEntry *entry = nullptr; @@ -776,8 +825,8 @@ EmbeddedAssemblies::typemap_managed_to_java (const char *managed_type_name) return entry; } -inline const char* -EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, MonoClass *klass, [[maybe_unused]] const uint8_t *mvid) +force_inline const char* +EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, MonoClass *klass, [[maybe_unused]] const uint8_t *mvid) noexcept { c_unique_ptr type_name {mono_type_get_name_full (type, MONO_TYPE_NAME_FORMAT_FULL_NAME)}; MonoImage *image = mono_class_get_image (klass); @@ -800,59 +849,38 @@ EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, Mo return entry->to; } #else -inline int -EmbeddedAssemblies::compare_type_token (const uint32_t *token, const TypeMapModuleEntry *entry) -{ - if (entry == nullptr) { - log_fatal (LOG_ASSEMBLY, "typemap: compare_type_token: entry is nullptr"); - exit (FATAL_EXIT_MISSING_ASSEMBLY); - } - - if (*token < entry->type_token_id) - return -1; - if (*token > entry->type_token_id) - return 1; - return 0; -} - -inline int -EmbeddedAssemblies::compare_mvid (const uint8_t *mvid, const TypeMapModule *module) +force_inline int +EmbeddedAssemblies::compare_mvid (const uint8_t *mvid, const TypeMapModule *module) noexcept { return memcmp (mvid, module->module_uuid, sizeof(module->module_uuid)); } -inline const char* -EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, MonoClass *klass, const uint8_t *mvid) +force_inline const char* +EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, MonoClass *klass, const uint8_t *mvid) noexcept { - if (mvid == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: no mvid specified in call to typemap_managed_to_java"); - return nullptr; - } - - const TypeMapModule *map; - size_t map_entry_count; - map = map_modules; - map_entry_count = map_module_count; - - const TypeMapModule *match = binary_search (mvid, map, map_entry_count); + const TypeMapModule *match = mvid != nullptr ? binary_search (mvid, map_modules, map_module_count) : nullptr; if (match == nullptr) { - log_info (LOG_ASSEMBLY, "typemap: module matching MVID [%s] not found.", MonoGuidString (mvid).get ()); - return nullptr; - } - - if (match->map == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: module with MVID [%s] has no associated type map.", MonoGuidString (mvid).get ()); + if (mvid == nullptr) { + log_warn (LOG_ASSEMBLY, "typemap: no mvid specified in call to typemap_managed_to_java"); + } else { + log_info (LOG_ASSEMBLY, "typemap: module matching MVID [%s] not found.", MonoGuidString (mvid).get ()); + } return nullptr; } uint32_t token = mono_class_get_type_token (klass); log_debug (LOG_ASSEMBLY, "typemap: MVID [%s] maps to assembly %s, looking for token %d (0x%x), table index %d", MonoGuidString (mvid).get (), match->assembly_name, token, token, token & 0x00FFFFFF); // Each map entry is a pair of 32-bit integers: [TypeTokenID][JavaMapArrayIndex] - const TypeMapModuleEntry *entry = binary_search (&token, match->map, match->entry_count); + const TypeMapModuleEntry *entry = match->map != nullptr ? binary_search (token, match->map, match->entry_count) : nullptr; if (entry == nullptr) { + if (match->map == nullptr) { + log_warn (LOG_ASSEMBLY, "typemap: module with mvid [%s] has no associated type map.", MonoGuidString (mvid).get ()); + return nullptr; + } + if (match->duplicate_count > 0 && match->duplicate_map != nullptr) { log_debug (LOG_ASSEMBLY, "typemap: searching module [%s] duplicate map for token %u (0x%x)", MonoGuidString (mvid).get (), token, token); - entry = binary_search (&token, match->duplicate_map, match->duplicate_count); + entry = binary_search (token, match->duplicate_map, match->duplicate_count); } if (entry == nullptr) { @@ -861,15 +889,17 @@ EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, Mo } } - uint32_t java_entry_count; - java_entry_count = java_type_count; - if (entry->java_map_index >= java_entry_count) { + if (XA_UNLIKELY (entry->java_map_index >= java_type_count)) { log_warn (LOG_ASSEMBLY, "typemap: type with token %d (0x%x) in module {%s} (%s) has invalid Java type index %u", token, token, MonoGuidString (mvid).get (), match->assembly_name, entry->java_map_index); return nullptr; } - const TypeMapJava *java_entry = reinterpret_cast (reinterpret_cast(map_java) + (type_map_java_struct_size * entry->java_map_index)); - const char *ret = reinterpret_cast(java_entry->java_name); + TypeMapJava const& java_entry = map_java[entry->java_map_index]; + if (XA_UNLIKELY (java_entry.java_name_index >= java_type_count)) { + log_warn (LOG_ASSEMBLY, "typemap: type with token %d (0x%x) in module {%s} (%s) points to invalid Java type at index %u (invalid type name index %u)", token, token, MonoGuidString (mvid).get (), match->assembly_name, entry->java_map_index, java_entry.java_name_index); + return nullptr; + } + const char *ret = java_type_names[java_entry.java_name_index]; if (XA_UNLIKELY (ret == nullptr)) { log_warn (LOG_ASSEMBLY, "typemap: empty Java type name returned for entry at index %u", entry->java_map_index); @@ -890,7 +920,7 @@ EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, Mo #endif const char* -EmbeddedAssemblies::typemap_managed_to_java (MonoReflectionType *reflection_type, const uint8_t *mvid) +EmbeddedAssemblies::typemap_managed_to_java (MonoReflectionType *reflection_type, const uint8_t *mvid) noexcept { timing_period total_time; if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { diff --git a/src/monodroid/jni/embedded-assemblies.hh b/src/monodroid/jni/embedded-assemblies.hh index 0e46156edba..d27779930bb 100644 --- a/src/monodroid/jni/embedded-assemblies.hh +++ b/src/monodroid/jni/embedded-assemblies.hh @@ -39,6 +39,12 @@ namespace xamarin::android::internal { struct TypeMappingInfo; #endif +#if defined (RELEASE) && defined (ANDROID) +#define STATIC_IN_ANDROID_RELEASE static +#else +#define STATIC_IN_ANDROID_RELEASE +#endif // def RELEASE && def ANDROID + #if defined (HAVE_CONCEPTS) template concept ByteArrayContainer = requires (T a) { @@ -97,20 +103,19 @@ namespace xamarin::android::internal { public: #if defined (RELEASE) && defined (ANDROID) EmbeddedAssemblies () noexcept - : type_map_java_struct_size (calc_type_map_java_struct_size ()) {} #endif // def RELEASE && def ANDROID #if defined (DEBUG) || !defined (ANDROID) void try_load_typemaps_from_directory (const char *path); #endif - const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid); + STATIC_IN_ANDROID_RELEASE const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid) noexcept; void install_preload_hooks_for_appdomains (); #if defined (NET6) void install_preload_hooks_for_alc (); #endif // def NET6 - MonoReflectionType* typemap_java_to_managed (MonoString *java_type); + STATIC_IN_ANDROID_RELEASE MonoReflectionType* typemap_java_to_managed (MonoString *java_type) noexcept; /* returns current number of *all* assemblies found from all invocations */ template @@ -162,21 +167,8 @@ namespace xamarin::android::internal { } private: -#if defined (RELEASE) && defined (ANDROID) - static size_t calc_type_map_java_struct_size () - { - size_t struct_size = sizeof(TypeMapJava) + java_name_width; - size_t padding = struct_size % alignof(TypeMapJava); - if (padding > 0) { - struct_size += alignof(TypeMapJava) - padding; - } - - return struct_size; - } -#endif // def RELEASE && def ANDROID - - const char* typemap_managed_to_java (MonoType *type, MonoClass *klass, const uint8_t *mvid); - MonoReflectionType* typemap_java_to_managed (const char *java_type_name); + STATIC_IN_ANDROID_RELEASE const char* typemap_managed_to_java (MonoType *type, MonoClass *klass, const uint8_t *mvid) noexcept; + STATIC_IN_ANDROID_RELEASE MonoReflectionType* typemap_java_to_managed (hash_t hash, const MonoString *java_type_name) noexcept; size_t register_from (const char *apk_file, monodroid_should_register should_register); void gather_bundled_assemblies_from_apk (const char* apk, monodroid_should_register should_register); #if defined (NET6) @@ -206,7 +198,7 @@ namespace xamarin::android::internal { bool typemap_load_file (int dir_fd, const char *dir_path, const char *file_path, TypeMap &module); bool typemap_load_file (BinaryTypeMapHeader &header, const char *dir_path, const char *file_path, int file_fd, TypeMap &module); static ssize_t do_read (int fd, void *buf, size_t count); - const TypeMapEntry *typemap_managed_to_java (const char *managed_type_name); + const TypeMapEntry *typemap_managed_to_java (const char *managed_type_name) noexcept; #endif // DEBUG || !ANDROID static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename); @@ -278,17 +270,23 @@ namespace xamarin::android::internal { ; } + static force_inline c_unique_ptr to_utf8 (const MonoString *s) noexcept + { + return c_unique_ptr (mono_string_to_utf8 (const_cast(s))); + } + bool is_debug_file (dynamic_local_string const& name) noexcept; template - const Entry* binary_search (const Key *key, const Entry *base, size_t nmemb, size_t extra_size = 0); + static const Entry* binary_search (const Key *key, const Entry *base, size_t nmemb, size_t extra_size = 0) noexcept; + static ssize_t binary_search (hash_t key, const hash_t *arr, size_t n) noexcept; + static ptrdiff_t binary_search_branchless (hash_t x, const hash_t *arr, uint32_t len) noexcept; #if defined (DEBUG) || !defined (ANDROID) - static int compare_type_name (const char *type_name, const TypeMapEntry *entry); + static int compare_type_name (const char *type_name, const TypeMapEntry *entry) noexcept; #else - static int compare_mvid (const uint8_t *mvid, const TypeMapModule *module); - static int compare_type_token (const uint32_t *token, const TypeMapModuleEntry *entry); - static int compare_java_name (const char *java_name, const TypeMapJava *entry); + static int compare_mvid (const uint8_t *mvid, const TypeMapModule *module) noexcept; + static const TypeMapModuleEntry* binary_search (uint32_t key, const TypeMapModuleEntry *arr, uint32_t n) noexcept; #endif template void set_entry_data (XamarinAndroidBundledAssembly &entry, int apk_fd, uint32_t data_offset, uint32_t data_size, uint32_t prefix_len, uint32_t max_name_size, dynamic_local_string const& entry_name) noexcept; @@ -306,10 +304,6 @@ namespace xamarin::android::internal { size_t bundled_assembly_index = 0; size_t number_of_found_assemblies = 0; -#if defined (RELEASE) && defined (ANDROID) - const size_t type_map_java_struct_size; -#endif // def RELEASE && def ANDROID - #if defined (DEBUG) || !defined (ANDROID) TypeMappingInfo *java_to_managed_maps; TypeMappingInfo *managed_to_java_maps; diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 2a6c6775163..8fbb100138f 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -128,6 +128,7 @@ namespace xamarin::android::internal #if defined (NET6) using jnienv_initialize_fn = void (*) (JnienvInitializeArgs*); + using jnienv_register_jni_natives_fn = void (*)(const jchar *typeName_ptr, int32_t typeName_len, jclass jniClass, const jchar *methods_ptr, int32_t methods_len); #endif private: @@ -332,9 +333,11 @@ namespace xamarin::android::internal static void jit_done (MonoProfiler *prof, MonoMethod *method, MonoJitInfo* jinfo); static void thread_start (MonoProfiler *prof, uintptr_t tid); static void thread_end (MonoProfiler *prof, uintptr_t tid); - static MonoReflectionType* typemap_java_to_managed (MonoString *java_type_name); +#if !defined (RELEASE) || !defined (ANDROID) + static MonoReflectionType* typemap_java_to_managed (MonoString *java_type_name) noexcept; + static const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid) noexcept; +#endif // !def RELEASE || !def ANDROID - static const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid); #if defined (NET6) static void monodroid_debugger_unhandled_exception (MonoException *ex); #endif @@ -382,6 +385,9 @@ namespace xamarin::android::internal static bool startup_in_progress; #if defined (NET6) +#if defined (ANDROID) + jnienv_register_jni_natives_fn jnienv_register_jni_natives = nullptr; +#endif MonoAssemblyLoadContextGCHandle default_alc = nullptr; static std::mutex pinvoke_map_write_lock; diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 79a8e24c624..f6f12407667 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1043,8 +1043,22 @@ MonodroidRuntime::init_android_runtime ( #endif // ndef NET6 JNIEnv *env, jclass runtimeClass, jobject loader) { - mono_add_internal_call ("Java.Interop.TypeManager::monodroid_typemap_java_to_managed", reinterpret_cast(typemap_java_to_managed)); - mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_typemap_managed_to_java", reinterpret_cast(typemap_managed_to_java)); + constexpr char icall_typemap_java_to_managed[] = "Java.Interop.TypeManager::monodroid_typemap_java_to_managed"; + constexpr char icall_typemap_managed_to_java[] = "Android.Runtime.JNIEnv::monodroid_typemap_managed_to_java"; + +#if defined (RELEASE) && defined (ANDROID) + // The reason for these using is that otherwise the compiler will complain about not being + // able to cast overloaded methods to const void* pointers. + using j2mFn = MonoReflectionType* (*)(MonoString *java_type); + using m2jFn = const char* (*)(MonoReflectionType *type, const uint8_t *mvid); + + mono_add_internal_call (icall_typemap_java_to_managed, reinterpret_cast(static_cast(EmbeddedAssemblies::typemap_java_to_managed))); + mono_add_internal_call (icall_typemap_managed_to_java, reinterpret_cast(static_cast(EmbeddedAssemblies::typemap_managed_to_java))); +#else + mono_add_internal_call (icall_typemap_java_to_managed, reinterpret_cast(typemap_java_to_managed)); + mono_add_internal_call (icall_typemap_managed_to_java, reinterpret_cast(typemap_managed_to_java)); +#endif // def RELEASE && def ANDROID + #if defined (NET6) mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_debugger_unhandled_exception", reinterpret_cast (monodroid_debugger_unhandled_exception)); mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_unhandled_exception", reinterpret_cast(monodroid_unhandled_exception)); @@ -1138,6 +1152,10 @@ MonodroidRuntime::init_android_runtime ( registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime); +#if defined (NET6) && defined (ANDROID) + MonoError error; + jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); +#endif // def NET6 && def ANDROID } } abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives!"); @@ -1960,17 +1978,19 @@ MonodroidRuntime::monodroid_unhandled_exception (MonoObject *java_exception) } #endif // def NET6 +#if !defined (RELEASE) || !defined (ANDROID) MonoReflectionType* -MonodroidRuntime::typemap_java_to_managed (MonoString *java_type_name) +MonodroidRuntime::typemap_java_to_managed (MonoString *java_type_name) noexcept { return embeddedAssemblies.typemap_java_to_managed (java_type_name); } const char* -MonodroidRuntime::typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid) +MonodroidRuntime::typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid) noexcept { return embeddedAssemblies.typemap_managed_to_java (type, mvid); } +#endif // !def RELEASE || !def ANDROID #if defined (WINDOWS) const char* @@ -2429,7 +2449,7 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, ); } -inline void +force_inline void MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, jclass nativeClass, jstring methods) { timing_period total_time; @@ -2439,20 +2459,13 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) total_time.mark_start (); - int managedType_len = env->GetStringLength (managedType); + jsize managedType_len = env->GetStringLength (managedType); const jchar *managedType_ptr = env->GetStringChars (managedType, nullptr); - if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { - const char *mt_ptr = env->GetStringUTFChars (managedType, nullptr); - type.assign (mt_ptr, strlen (mt_ptr)); - env->ReleaseStringUTFChars (managedType, mt_ptr); - - log_info_nocheck (LOG_TIMING, "Runtime.register: registering type `%s`", type.get ()); - } - - int methods_len = env->GetStringLength (methods); + jsize methods_len = env->GetStringLength (methods); const jchar *methods_ptr = env->GetStringChars (methods, nullptr); +#if !defined (NET6) || !defined (ANDROID) void *args[] = { &managedType_ptr, &managedType_len, @@ -2460,8 +2473,9 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag &methods_ptr, &methods_len, }; - MonoMethod *register_jni_natives = registerType; +#endif // ndef NET6 || ndef ANDROID + #if !defined (NET6) MonoDomain *domain = utils.get_current_domain (/* attach_thread_if_needed */ false); mono_jit_thread_attach (domain); @@ -2475,7 +2489,11 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag utils.monodroid_runtime_invoke (domain, register_jni_natives, nullptr, args, nullptr); #else // ndef NET6 +#if !defined (ANDROID) mono_runtime_invoke (register_jni_natives, nullptr, args, nullptr); +#else + jnienv_register_jni_natives (managedType_ptr, managedType_len, nativeClass, methods_ptr, methods_len); +#endif // ndef ANDROID #endif // def NET6 env->ReleaseStringChars (methods, methods_ptr); @@ -2484,6 +2502,12 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { total_time.mark_end (); + const char *mt_ptr = env->GetStringUTFChars (managedType, nullptr); + type.assign (mt_ptr, strlen (mt_ptr)); + env->ReleaseStringUTFChars (managedType, mt_ptr); + + log_info_nocheck (LOG_TIMING, "Runtime.register: registering type `%s`", type.get ()); + Timing::info (total_time, "Runtime.register: end time"); #if !defined (NET6) dump_counters ("## Runtime.register: type=%s\n", type.get ()); diff --git a/src/monodroid/jni/platform-compat.hh b/src/monodroid/jni/platform-compat.hh index b47f84e555d..62502bbcafa 100644 --- a/src/monodroid/jni/platform-compat.hh +++ b/src/monodroid/jni/platform-compat.hh @@ -37,12 +37,17 @@ typedef struct dirent monodroid_dirent_t; #endif #define force_inline inline __attribute__((always_inline)) +#define never_inline __attribute__((noinline)) #endif // _MSV_VER #ifndef force_inline #define force_inline inline #endif +#ifndef never_inline +#define never_inline +#endif + #ifndef inline #define inline inline #endif diff --git a/src/monodroid/jni/util.hh b/src/monodroid/jni/util.hh index f5055862aa9..82ab57a150e 100644 --- a/src/monodroid/jni/util.hh +++ b/src/monodroid/jni/util.hh @@ -106,7 +106,7 @@ namespace xamarin::android ssize_t recv_uninterrupted (int fd, void *buf, size_t len); jclass get_class_from_runtime_field (JNIEnv *env, jclass runtime, const char *name, bool make_gref = false); - bool should_log (LogCategories category) const + static bool should_log (LogCategories category) noexcept { return (log_categories & category) != 0; } diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 734464ed502..818f3e83c6a 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -7,6 +7,7 @@ #include #include "monodroid.h" +#include "xxhash.hh" static constexpr uint64_t FORMAT_TAG = 0x015E6972616D58; static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x5A4C4158; // 'XALZ', little-endian @@ -75,7 +76,7 @@ struct TypeMapJava { uint32_t module_index; uint32_t type_token_id; - uint8_t java_name[]; + uint32_t java_name_index; }; #endif @@ -238,9 +239,10 @@ MONO_API MONO_API_EXPORT const TypeMap type_map; // MUST match src/Xamarin.Andro #else MONO_API MONO_API_EXPORT const uint32_t map_module_count; MONO_API MONO_API_EXPORT const uint32_t java_type_count; -MONO_API MONO_API_EXPORT const uint32_t java_name_width; -MONO_API MONO_API_EXPORT const TypeMapModule map_modules[]; +MONO_API MONO_API_EXPORT const char* const java_type_names[]; +MONO_API MONO_API_EXPORT TypeMapModule map_modules[]; MONO_API MONO_API_EXPORT const TypeMapJava map_java[]; +MONO_API MONO_API_EXPORT const xamarin::android::hash_t map_java_hashes[]; #endif MONO_API MONO_API_EXPORT CompressedAssemblies compressed_assemblies;