From 5a53189a1eb7b81679b6cf54766fa453146c5652 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 31 Mar 2020 13:08:40 +0200 Subject: [PATCH] Return of the Strings Fixes: https://github.com/xamarin/xamarin-android/issues/4415 Context: https://github.com/xamarin/xamarin-android/commit/ce2bc689a19cb45f7d7bfdd371c16c54b018a020 This commit partially reverts ce2bc689a19cb45f7d7bfdd371c16c54b018a020 in the sense that it restores the use of type names for Java-to-Managed and Managed-to-Java type lookups for **Debug** builds only. The reason for the change is that in order for the MVID and type token based lookups to work we need two things: - for the MVID to remain unchanged in each mapped assembly - for the type token to remain unchanged in each mapped assembly However, in incremental builds neither of the above requirements is met **unless** the application is fully rebuilt and, thus, the typemaps are regenerated from scratch, which obviously doesn't sit well with the incremental nature of incremental builds :) With MVID/token id maps, every change to any source code that's built into a mapped assembly, may cause the assembly to change its MVID and renaming of any type, removing or adding a type, will rearrange the type definition table in the resulting assembly, thus changing the type token ids (which are basically offsets into the type definition table in the PE executable). This is what may cause an app to crash on the runtime with an exception similar to: android.runtime.JavaProxyThrowable: System.NotSupportedException: Cannot create instance of type 'com.glmsoftware.OBDNowProto.SettingsFragmentCompat': no Java peer type found. at Java.Interop.JniPeerMembers+JniInstanceMethods..ctor (System.Type declaringType) [0x0004b] in :0 at Java.Interop.JniPeerMembers+JniInstanceMethods.GetConstructorsForType (System.Type declaringType) [0x00031] in :0 at Java.Interop.JniPeerMembers+JniInstanceMethods.StartCreateInstance (System.String constructorSignature, System.Type declaringType, Java.Interop.JniArgumentValue* parameters) [0x00038] in :0 at AndroidX.Preference.PreferenceFragmentCompat..ctor () [0x00034] in <005e3ae6340747e1aea6d08b095cf286>:0 at com.glmsoftware.OBDNowProto.SettingsFragmentCompat..ctor () [0x00026] in :0 at com.glmsoftware.OBDNowProto.SettingsActivity.OnCreate (Android.OS.Bundle bundle) [0x00083] in :0 at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState) [0x00011] in :0 at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.3(intptr,intptr,intptr) at crc64596a13587a898911.SettingsActivity.n_onCreate(Native Method) at crc64596a13587a898911.SettingsActivity.onCreate(SettingsActivity.java:40) at android.app.Activity.performCreate(Activity.java:7825) at android.app.Activity.performCreate(Activity.java:7814) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1306) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Thus, we need to revert the Debug builds to the variation of the old string-based type name mapping. This commit implements it in a slightly leaner form than it was implemented before ce2bc689a19cb45f7d7bfdd371c16c54b018a020 landed in that only one copy of each set of type names (Java and managed) is maintained in both memory and on disk, at a tiny (sub millisecond) expense at the run time to fix up pointers between the two tables. --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 20 +- .../Test/Java.Interop/JnienvTest.cs | 11 +- .../Tasks/GenerateJavaStubs.cs | 2 +- .../IncrementalBuildTest.cs | 1 - .../ARMNativeAssemblerTargetProvider.cs | 1 + .../NativeAssemblerTargetProvider.cs | 1 + .../Utilities/NativeTypeMappingData.cs | 14 +- .../Utilities/TypeMapGenerator.cs | 389 +++++++++------ ...TypeMappingDebugNativeAssemblyGenerator.cs | 140 ++++++ ...eMappingReleaseNativeAssemblyGenerator.cs} | 20 +- .../X86NativeAssemblerTargetProvider.cs | 1 + .../Xamarin.Android.Common.targets | 4 + src/monodroid/CMakeLists.txt | 15 +- src/monodroid/jni/application_dso_stub.cc | 27 ++ src/monodroid/jni/embedded-assemblies.cc | 457 +++++++++++------- src/monodroid/jni/embedded-assemblies.hh | 23 +- src/monodroid/jni/external-api.cc | 6 - src/monodroid/jni/monodroid-glue-internal.hh | 3 + src/monodroid/jni/monodroid-glue.cc | 8 + src/monodroid/jni/xamarin-app.hh | 34 +- 20 files changed, 793 insertions(+), 384 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs rename src/Xamarin.Android.Build.Tasks/Utilities/{TypeMappingNativeAssemblyGenerator.cs => TypeMappingReleaseNativeAssemblyGenerator.cs} (90%) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 5bf56887c42..e7fc693a90b 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -670,8 +670,9 @@ public static string GetClassNameFromInstance (IntPtr jobject) } } - [DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr monodroid_typemap_managed_to_java (byte[] mvid, int token); + // TODO: `type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed + [MethodImplAttribute(MethodImplOptions.InternalCall)] + static extern unsafe IntPtr monodroid_typemap_managed_to_java (Type type, byte* mvid, string type_name); internal static void LogTypemapTrace (StackTrace st) { @@ -685,19 +686,24 @@ internal static void LogTypemapTrace (StackTrace st) } } - internal static string TypemapManagedToJava (Type type) + internal static unsafe string TypemapManagedToJava (Type type) { if (mvid_bytes == null) mvid_bytes = new byte[16]; - Span mvid = new Span(mvid_bytes); - byte[] mvid_slow = null; + var mvid = new Span(mvid_bytes); + byte[] mvid_data = null; if (!type.Module.ModuleVersionId.TryWriteBytes (mvid)) { monodroid_log (LogLevel.Warn, LogCategories.Default, $"Failed to obtain module MVID using the fast method, falling back to the slow one"); - mvid_slow = type.Module.ModuleVersionId.ToByteArray (); + mvid_data = type.Module.ModuleVersionId.ToByteArray (); + } else { + mvid_data = mvid_bytes; } - IntPtr ret = monodroid_typemap_managed_to_java (mvid_slow == null ? mvid_bytes : mvid_slow, type.MetadataToken); + IntPtr ret; + fixed (byte* mvidptr = mvid_data) { + ret = monodroid_typemap_managed_to_java (type, mvidptr, type.FullName); + } if (ret == IntPtr.Zero) { if (LogTypemapMissStackTrace) { diff --git a/src/Mono.Android/Test/Java.Interop/JnienvTest.cs b/src/Mono.Android/Test/Java.Interop/JnienvTest.cs index d6b54c45c97..75bdedac0e5 100644 --- a/src/Mono.Android/Test/Java.Interop/JnienvTest.cs +++ b/src/Mono.Android/Test/Java.Interop/JnienvTest.cs @@ -383,19 +383,16 @@ public void JavaToManagedTypeMapping () Assert.AreEqual (null, m); } - [DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr monodroid_typemap_managed_to_java (byte[] mvid, int token); - [Test] public void ManagedToJavaTypeMapping () { Type type = typeof(Activity); - var m = monodroid_typemap_managed_to_java (type.Module.ModuleVersionId.ToByteArray (), type.MetadataToken); - Assert.AreNotEqual (IntPtr.Zero, m, "`Activity` subclasses Java.Lang.Object, it should be in the typemap!"); + string m = JNIEnv.TypemapManagedToJava (type); + Assert.AreNotEqual (null, m, "`Activity` subclasses Java.Lang.Object, it should be in the typemap!"); type = typeof (JnienvTest); - m = monodroid_typemap_managed_to_java (type.Module.ModuleVersionId.ToByteArray (), type.MetadataToken); - Assert.AreEqual (IntPtr.Zero, m, "`JnienvTest` does *not* subclass Java.Lang.Object, it should *not* be in the typemap!"); + m = JNIEnv.TypemapManagedToJava (type); + Assert.AreEqual (null, m, "`JnienvTest` does *not* subclass Java.Lang.Object, it should *not* be in the typemap!"); } [Test] diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 2570de86a1d..a738f99f28e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -402,7 +402,7 @@ void SaveResource (string resource, string filename, string destDir, Func types) { var tmg = new TypeMapGenerator ((string message) => Log.LogDebugMessage (message), SupportedAbis); - if (!tmg.Generate (SkipJniAddNativeMethodRegistrationAttributeScan, types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) + if (!tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) throw new XamarinAndroidException (4308, Properties.Resources.XA4308); GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray (); BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: false); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index 855fa7018cc..c1d6353aac5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -832,7 +832,6 @@ public void GenerateJavaStubsAndAssembly ([Values (true, false)] bool isRelease) readonly string [] ExpectedAssemblyFiles = new [] { Path.Combine ("android", "environment.armeabi-v7a.o"), Path.Combine ("android", "environment.armeabi-v7a.s"), - Path.Combine ("android", "typemaps.armeabi-v7a-managed.inc"), Path.Combine ("android", "typemaps.armeabi-v7a-shared.inc"), Path.Combine ("android", "typemaps.armeabi-v7a.o"), Path.Combine ("android", "typemaps.armeabi-v7a.s"), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ARMNativeAssemblerTargetProvider.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ARMNativeAssemblerTargetProvider.cs index e563f64ffe6..755e63fcd88 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ARMNativeAssemblerTargetProvider.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ARMNativeAssemblerTargetProvider.cs @@ -14,6 +14,7 @@ class ARMNativeAssemblerTargetProvider : NativeAssemblerTargetProvider public override string AbiName => Is64Bit ? ARMV8a : ARMV7a; public override uint MapModulesAlignBits => Is64Bit ? 3u : 2u; public override uint MapJavaAlignBits { get; } = 2; + public override uint DebugTypeMapAlignBits => Is64Bit ? 3u : 2u; public ARMNativeAssemblerTargetProvider (bool is64Bit) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerTargetProvider.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerTargetProvider.cs index 6b379232078..8d5795e3b60 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerTargetProvider.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerTargetProvider.cs @@ -11,6 +11,7 @@ abstract class NativeAssemblerTargetProvider public abstract string AbiName { get; } public abstract uint MapModulesAlignBits { get; } public abstract uint MapJavaAlignBits { get; } + public abstract uint DebugTypeMapAlignBits { get; } public virtual string MapType () { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs index c915d6d46a8..b5b57c23ec1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeTypeMappingData.cs @@ -6,16 +6,16 @@ namespace Xamarin.Android.Tasks { class NativeTypeMappingData { - public TypeMapGenerator.ModuleData[] Modules { get; } + public TypeMapGenerator.ModuleReleaseData[] Modules { get; } public IDictionary AssemblyNames { get; } public string[] JavaTypeNames { get; } - public TypeMapGenerator.TypeMapEntry[] JavaTypes { get; } + public TypeMapGenerator.TypeMapReleaseEntry[] JavaTypes { get; } public uint MapModuleCount { get; } public uint JavaTypeCount { get; } public uint JavaNameWidth { get; } - public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleData[] modules, int javaNameWidth) + public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleReleaseData[] modules, int javaNameWidth) { Modules = modules ?? throw new ArgumentNullException (nameof (modules)); @@ -24,11 +24,11 @@ public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleData AssemblyNames = new Dictionary (StringComparer.Ordinal); - var tempJavaTypes = new Dictionary (StringComparer.Ordinal); + var tempJavaTypes = new Dictionary (StringComparer.Ordinal); int managedStringCounter = 0; var moduleComparer = new TypeMapGenerator.ModuleUUIDArrayComparer (); - foreach (TypeMapGenerator.ModuleData data in modules) { + foreach (TypeMapGenerator.ModuleReleaseData data in modules) { data.AssemblyNameLabel = $"map_aname.{managedStringCounter++}"; AssemblyNames.Add (data.AssemblyNameLabel, data.AssemblyName); @@ -36,7 +36,7 @@ public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleData if (moduleIndex < 0) throw new InvalidOperationException ($"Unable to map module with MVID {data.Mvid} to array index"); - foreach (TypeMapGenerator.TypeMapEntry entry in data.Types) { + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in data.Types) { entry.ModuleIndex = moduleIndex; if (tempJavaTypes.ContainsKey (entry.JavaName)) continue; @@ -47,7 +47,7 @@ public NativeTypeMappingData (Action logger, TypeMapGenerator.ModuleData var javaNames = tempJavaTypes.Keys.ToArray (); Array.Sort (javaNames, StringComparer.Ordinal); - var javaTypes = new TypeMapGenerator.TypeMapEntry[javaNames.Length]; + var javaTypes = new TypeMapGenerator.TypeMapReleaseEntry[javaNames.Length]; for (int i = 0; i < javaNames.Length; i++) { javaTypes[i] = tempJavaTypes[javaNames[i]]; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 8e5c35f8d75..1c4c168187e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -10,11 +10,12 @@ namespace Xamarin.Android.Tasks { class TypeMapGenerator { - const string TypeMapMagicString = "XATM"; // Xamarin Android TypeMap + const string TypeMapMagicString = "XATS"; // Xamarin Android TypeMap const string TypeMapIndexMagicString = "XATI"; // Xamarin Android Typemap Index - const uint TypeMapFormatVersion = 1; // Keep in sync with the value in src/monodroid/jni/xamarin-app.hh + const uint TypeMapFormatVersion = 2; // Keep in sync with the value in src/monodroid/jni/xamarin-app.hh + const string TypemapExtension = ".typemap"; - internal sealed class ModuleUUIDArrayComparer : IComparer + internal sealed class ModuleUUIDArrayComparer : IComparer { int Compare (byte[] left, byte[] right) { @@ -29,21 +30,21 @@ int Compare (byte[] left, byte[] right) return 0; } - public int Compare (ModuleData left, ModuleData right) + public int Compare (ModuleReleaseData left, ModuleReleaseData right) { return Compare (left.MvidBytes, right.MvidBytes); } } - internal sealed class TypeMapEntryArrayComparer : IComparer + internal sealed class TypeMapEntryArrayComparer : IComparer { - public int Compare (TypeMapEntry left, TypeMapEntry right) + public int Compare (TypeMapReleaseEntry left, TypeMapReleaseEntry right) { return String.CompareOrdinal (left.JavaName, right.JavaName); } } - internal sealed class TypeMapEntry + internal sealed class TypeMapReleaseEntry { public string JavaName; public int JavaNameLength; @@ -53,18 +54,39 @@ internal sealed class TypeMapEntry public int ModuleIndex = -1; } - internal sealed class ModuleData + internal sealed class ModuleReleaseData { public Guid Mvid; public byte[] MvidBytes; public AssemblyDefinition Assembly; - public TypeMapEntry[] Types; - public Dictionary DuplicateTypes; + public TypeMapReleaseEntry[] Types; + public Dictionary DuplicateTypes; public string AssemblyName; public string AssemblyNameLabel; public string OutputFilePath; - public Dictionary TypesScratch; + public Dictionary TypesScratch; + } + + internal sealed class TypeMapDebugEntry + { + public string JavaName; + public string JavaLabel; + public string ManagedName; + public string ManagedLabel; + public int JavaIndex; + public int ManagedIndex; + } + + // Widths include the terminating nul character nor the padding! + internal sealed class ModuleDebugData + { + public uint EntryCount; + public uint JavaNameWidth; + public uint ManagedNameWidth; + public List JavaToManagedMap; + public List ManagedToJavaMap; + public string OutputFilePath; } Action logger; @@ -102,7 +124,7 @@ void UpdateApplicationConfig (TypeDefinition javaType, ApplicationConfigTaskStat } } - public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState) + public bool Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState) { if (String.IsNullOrEmpty (outputDirectory)) throw new ArgumentException ("must not be null or empty", nameof (outputDirectory)); @@ -110,16 +132,164 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List if (!Directory.Exists (outputDirectory)) Directory.CreateDirectory (outputDirectory); + appConfState = new ApplicationConfigTaskState { + JniAddNativeMethodRegistrationAttributePresent = skipJniAddNativeMethodRegistrationAttributeScan + }; + + string typemapsOutputDirectory = Path.Combine (outputDirectory, "typemaps"); + + if (debugBuild) { + return GenerateDebug (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, typemapsOutputDirectory, generateNativeAssembly, appConfState); + } + + return GenerateRelease (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, typemapsOutputDirectory, appConfState); + } + + bool GenerateDebug (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, bool generateNativeAssembly, ApplicationConfigTaskState appConfState) + { + if (generateNativeAssembly) + return GenerateDebugNativeAssembly (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, outputDirectory, appConfState); + return GenerateDebugFiles (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, outputDirectory, appConfState); + } + + bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) + { + var modules = new Dictionary (StringComparer.Ordinal); + int maxModuleFileNameWidth = 0; + int maxModuleNameWidth = 0; + + foreach (TypeDefinition td in javaTypes) { + UpdateApplicationConfig (td, appConfState); + string moduleName = td.Module.Assembly.Name.Name; + ModuleDebugData module; + + if (!modules.TryGetValue (moduleName, out module)) { + string outputFileName = $"{moduleName}{TypemapExtension}"; + module = new ModuleDebugData { + EntryCount = 0, + JavaNameWidth = 0, + ManagedNameWidth = 0, + JavaToManagedMap = new List (), + ManagedToJavaMap = new List (), + OutputFilePath = Path.Combine (outputDirectory, outputFileName), + }; + + if (moduleName.Length > maxModuleNameWidth) + maxModuleNameWidth = moduleName.Length; + + if (outputFileName.Length > maxModuleFileNameWidth) + maxModuleFileNameWidth = outputFileName.Length; + + modules.Add (moduleName, module); + } + + TypeMapDebugEntry entry = GetDebugEntry (td); + if (entry.JavaName.Length > module.JavaNameWidth) + module.JavaNameWidth = (uint)entry.JavaName.Length + 1; + + if (entry.ManagedName.Length > module.ManagedNameWidth) + module.ManagedNameWidth = (uint)entry.ManagedName.Length + 1; + + module.JavaToManagedMap.Add (entry); + module.ManagedToJavaMap.Add (entry); + } + + foreach (ModuleDebugData module in modules.Values) { + PrepareDebugMaps (module); + } + + string typeMapIndexPath = Path.Combine (outputDirectory, "typemap.index"); + using (var indexWriter = MemoryStreamPool.Shared.CreateBinaryWriter ()) { + OutputModules (modules, indexWriter, maxModuleFileNameWidth + 1); + indexWriter.Flush (); + MonoAndroidHelper.CopyIfStreamChanged (indexWriter.BaseStream, typeMapIndexPath); + } + GeneratedBinaryTypeMaps.Add (typeMapIndexPath); + + GenerateNativeAssembly ( + (NativeAssemblerTargetProvider asmTargetProvider, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { + return new TypeMappingDebugNativeAssemblyGenerator (asmTargetProvider, new ModuleDebugData (), outputDirectory, sharedBitsWritten); + } + ); + + return true; + } + + bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) + { + var javaToManaged = new List (); + var managedToJava = new List (); + + foreach (TypeDefinition td in javaTypes) { + UpdateApplicationConfig (td, appConfState); + + TypeMapDebugEntry entry = GetDebugEntry (td); + javaToManaged.Add (entry); + managedToJava.Add (entry); + } + + var data = new ModuleDebugData { + EntryCount = (uint)javaToManaged.Count, + JavaToManagedMap = javaToManaged, + ManagedToJavaMap = managedToJava, + }; + + PrepareDebugMaps (data); + GenerateNativeAssembly ( + (NativeAssemblerTargetProvider asmTargetProvider, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { + return new TypeMappingDebugNativeAssemblyGenerator (asmTargetProvider, data, outputDirectory, sharedBitsWritten, sharedIncludeUsesAbiPrefix); + } + ); + + return true; + } + + void PrepareDebugMaps (ModuleDebugData module) + { + module.JavaToManagedMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.JavaName, b.JavaName, StringComparison.Ordinal)); + module.ManagedToJavaMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.ManagedName, b.ManagedName, StringComparison.Ordinal)); + + for (int i = 0; i < module.JavaToManagedMap.Count; i++) { + module.JavaToManagedMap[i].JavaIndex = i; + } + + for (int i = 0; i < module.ManagedToJavaMap.Count; i++) { + module.ManagedToJavaMap[i].ManagedIndex = i; + } + } + + TypeMapDebugEntry GetDebugEntry (TypeDefinition td) + { + // This is necessary because Mono runtime will return to us type name with a `.` for nested types (not a + // `/` or a `+`. So, for instance, a type named `DefaultRenderer` found in the + // `Xamarin.Forms.Platform.Android.Platform` class in the `Xamarin.Forms.Platform.Android` assembly will + // be seen here as + // + // Xamarin.Forms.Platform.Android.Platform/DefaultRenderer + // + // The managed land name for the type will be rendered as + // + // Xamarin.Forms.Platform.Android.Platform+DefaultRenderer + // + // And this is the form that we need in the map file + // + string managedTypeName = td.FullName.Replace ('/', '+'); + + return new TypeMapDebugEntry { + JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td), + ManagedName = $"{managedTypeName}, {td.Module.Assembly.Name.Name}", + }; + } + + bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) + { int assemblyId = 0; int maxJavaNameLength = 0; int maxModuleFileNameLength = 0; var knownAssemblies = new Dictionary (StringComparer.Ordinal); - var tempModules = new Dictionary (); + var tempModules = new Dictionary (); Dictionary moduleCounter = null; var mvidCache = new Dictionary (); - appConfState = new ApplicationConfigTaskState { - JniAddNativeMethodRegistrationAttributePresent = skipJniAddNativeMethodRegistrationAttributeScan - }; foreach (TypeDefinition td in javaTypes) { UpdateApplicationConfig (td, appConfState); @@ -140,40 +310,24 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List mvidCache.Add (td.Module.Mvid, moduleUUID); } - ModuleData moduleData; + ModuleReleaseData moduleData; if (!tempModules.TryGetValue (moduleUUID, out moduleData)) { if (moduleCounter == null) moduleCounter = new Dictionary (); - moduleData = new ModuleData { + moduleData = new ModuleReleaseData { Mvid = td.Module.Mvid, MvidBytes = moduleUUID, Assembly = td.Module.Assembly, AssemblyName = td.Module.Assembly.Name.Name, - TypesScratch = new Dictionary (StringComparer.Ordinal), - DuplicateTypes = new Dictionary (), + TypesScratch = new Dictionary (StringComparer.Ordinal), + DuplicateTypes = new Dictionary (), }; tempModules.Add (moduleUUID, moduleData); - - if (!generateNativeAssembly) { - int moduleNum; - if (!moduleCounter.TryGetValue (moduleData.Assembly, out moduleNum)) { - moduleNum = 0; - moduleCounter [moduleData.Assembly] = 0; - } else { - moduleNum++; - moduleCounter [moduleData.Assembly] = moduleNum; - } - - string fileName = $"{moduleData.Assembly.Name.Name}.{moduleNum}.typemap"; - moduleData.OutputFilePath = Path.Combine (outputDirectory, fileName); - if (maxModuleFileNameLength < fileName.Length) - maxModuleFileNameLength = fileName.Length; - } } string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td); - var entry = new TypeMapEntry { + var entry = new TypeMapReleaseEntry { JavaName = javaName, JavaNameLength = outputEncoding.GetByteCount (javaName), ManagedTypeName = td.FullName, @@ -181,10 +335,8 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List AssemblyNameIndex = knownAssemblies [assemblyName] }; - if (generateNativeAssembly) { - if (entry.JavaNameLength > maxJavaNameLength) - maxJavaNameLength = entry.JavaNameLength; - } + 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 @@ -200,9 +352,9 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List Array.Sort (modules, new ModuleUUIDArrayComparer ()); var typeMapEntryComparer = new TypeMapEntryArrayComparer (); - foreach (ModuleData module in modules) { + foreach (ModuleReleaseData module in modules) { if (module.TypesScratch.Count == 0) { - module.Types = new TypeMapEntry[0]; + module.Types = new TypeMapReleaseEntry[0]; continue; } @@ -211,20 +363,19 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List } NativeTypeMappingData data; - if (!generateNativeAssembly) { - string typeMapIndexPath = Path.Combine (outputDirectory, "typemap.index"); - using (var indexWriter = MemoryStreamPool.Shared.CreateBinaryWriter ()) { - OutputModules (modules, indexWriter, maxModuleFileNameLength + 1); - indexWriter.Flush (); - MonoAndroidHelper.CopyIfStreamChanged (indexWriter.BaseStream, typeMapIndexPath); + data = new NativeTypeMappingData (logger, modules, maxJavaNameLength + 1); + + GenerateNativeAssembly ( + (NativeAssemblerTargetProvider asmTargetProvider, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { + return new TypeMappingReleaseNativeAssemblyGenerator (asmTargetProvider, data, outputDirectory, sharedBitsWritten, sharedIncludeUsesAbiPrefix); } - GeneratedBinaryTypeMaps.Add (typeMapIndexPath); + ); - data = new NativeTypeMappingData (logger, new ModuleData[0], 0); - } else { - data = new NativeTypeMappingData (logger, modules, maxJavaNameLength + 1); - } + return true; + } + void GenerateNativeAssembly (Func getGenerator) + { NativeAssemblerTargetProvider asmTargetProvider; bool sharedBitsWritten = false; bool sharedIncludeUsesAbiPrefix; @@ -253,7 +404,7 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List throw new InvalidOperationException ($"Unknown ABI {abi}"); } - var generator = new TypeMappingNativeAssemblyGenerator (asmTargetProvider, data, Path.Combine (outputDirectory, "typemaps"), sharedBitsWritten, sharedIncludeUsesAbiPrefix); + NativeAssemblyGenerator generator = getGenerator (asmTargetProvider, sharedBitsWritten, sharedIncludeUsesAbiPrefix); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter (outputEncoding)) { generator.Write (sw); @@ -263,7 +414,6 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List sharedBitsWritten = true; } } - return true; } // Binary index file format, all data is little-endian: @@ -276,37 +426,38 @@ public bool Generate (bool skipJniAddNativeMethodRegistrationAttributeScan, List // // Index entry format: // - // [Module UUID][File name] + // [File name] // // Where: // - // [Module UUID] is 16 bytes long // [File name] is right-padded with characters to the [Module file name width] boundary. // - void OutputModules (ModuleData[] modules, BinaryWriter indexWriter, int moduleFileNameWidth) + void OutputModules (Dictionary modules, BinaryWriter indexWriter, int moduleFileNameWidth) { indexWriter.Write (typemapIndexMagicString); indexWriter.Write (TypeMapFormatVersion); - indexWriter.Write (modules.Length); + indexWriter.Write (modules.Count); indexWriter.Write (moduleFileNameWidth); - foreach (ModuleData data in modules) { - OutputModule (data.MvidBytes, data); - indexWriter.Write (data.MvidBytes); + foreach (var kvp in modules) { + string moduleName = kvp.Key; + ModuleDebugData module = kvp.Value; - string outputFilePath = Path.GetFileName (data.OutputFilePath); + OutputModule (moduleName, module); + + string outputFilePath = Path.GetFileName (module.OutputFilePath); indexWriter.Write (outputEncoding.GetBytes (outputFilePath)); PadField (indexWriter, outputFilePath.Length, moduleFileNameWidth); } } - void OutputModule (byte[] moduleUUID, ModuleData moduleData) + void OutputModule (string moduleName, ModuleDebugData moduleData) { - if (moduleData.Types.Length == 0) + if (moduleData.JavaToManagedMap.Count == 0) return; using (var bw = MemoryStreamPool.Shared.CreateBinaryWriter ()) { - OutputModule (bw, moduleUUID, moduleData); + OutputModule (bw, moduleName, moduleData); bw.Flush (); MonoAndroidHelper.CopyIfStreamChanged (bw.BaseStream, moduleData.OutputFilePath); } @@ -315,104 +466,50 @@ void OutputModule (byte[] moduleUUID, ModuleData moduleData) // Binary file format, all data is little-endian: // - // [Magic string] # XATM - // [Format version] # 32-bit integer, 4 bytes - // [Module UUID] # 16 bytes - // [Entry count] # unsigned 32-bit integer, 4 bytes - // [Duplicate count] # unsigned 32-bit integer, 4 bytes (might be 0) - // [Java type name width] # unsigned 32-bit integer, 4 bytes - // [Assembly name size] # unsigned 32-bit integer, 4 bytes + // [Magic string] # XATS + // [Format version] # 32-bit unsigned integer, 4 bytes + // [Entry count] # 32-bit unsigned integer, 4 bytes + // [Java type name width] # 32-bit unsigned integer, 4 bytes + // [Managed type name width] # 32-bit unsigned integer, 4 bytes + // [Assembly name size] # 32-bit unsigned integer, 4 bytes // [Assembly name] # Non-null terminated assembly name // [Java-to-managed map] # Format described below, [Entry count] entries // [Managed-to-java map] # Format described below, [Entry count] entries - // [Managed-to-java duplicates map] # Map of unique managed IDs which point to the same Java type name (might be empty) // // Java-to-managed map format: // - // [Java type name][Managed type token ID] + // [Java type name][Managed type table index] // // Each name is padded with to the width specified in the [Java type name width] field above. // Names are written without the size prefix, instead they are always terminated with a nul character // to make it easier and faster to handle by the native runtime. // - // Each token ID is an unsigned 32-bit integer, 4 bytes - // - // - // Managed-to-java map format: - // - // [Managed type token ID][Java type name table index] - // - // Both fields are unsigned 32-bit integers, to a total of 8 bytes per entry. Index points into the - // [Java-to-managed map] table above. + // Each [Managed type table index] is an unsigned 32-bit integer, 4 bytes // - // Managed-to-java duplicates map format: // - // Format is identical to [Managed-to-java] above. + // Managed-to-java map is identical to the [Java-to-managed] table above, with the exception of the index + // pointing to the Java name table. // - void OutputModule (BinaryWriter bw, byte[] moduleUUID, ModuleData moduleData) + void OutputModule (BinaryWriter bw, string moduleName, ModuleDebugData moduleData) { bw.Write (moduleMagicString); bw.Write (TypeMapFormatVersion); - bw.Write (moduleUUID); - - var javaNames = new Dictionary (StringComparer.Ordinal); - var managedTypes = new Dictionary (); - int maxJavaNameLength = 0; - - foreach (TypeMapEntry entry in moduleData.Types) { - javaNames.Add (entry.JavaName, entry.Token); - if (entry.JavaNameLength > maxJavaNameLength) - maxJavaNameLength = entry.JavaNameLength; - - managedTypes.Add (entry.Token, 0); + bw.Write (moduleData.JavaToManagedMap.Count); + bw.Write (moduleData.JavaNameWidth); + bw.Write (moduleData.ManagedNameWidth); + bw.Write (moduleName.Length); + bw.Write (outputEncoding.GetBytes (moduleName)); + + foreach (TypeMapDebugEntry entry in moduleData.JavaToManagedMap) { + bw.Write (outputEncoding.GetBytes (entry.JavaName)); + PadField (bw, entry.JavaName.Length, (int)moduleData.JavaNameWidth); + bw.Write (entry.ManagedIndex); } - var javaNameList = javaNames.Keys.ToList (); - foreach (TypeMapEntry entry in moduleData.Types) { - var javaIndex = (uint)javaNameList.IndexOf (entry.JavaName); - managedTypes[entry.Token] = javaIndex; - } - - bw.Write (javaNames.Count); - bw.Write (moduleData.DuplicateTypes.Count); - bw.Write (maxJavaNameLength + 1); - - string assemblyName = moduleData.Assembly.Name.Name; - bw.Write (assemblyName.Length); - bw.Write (outputEncoding.GetBytes (assemblyName)); - - var sortedJavaNames = javaNames.Keys.ToArray (); - Array.Sort (sortedJavaNames, StringComparer.Ordinal); - foreach (string typeName in sortedJavaNames) { - byte[] bytes = outputEncoding.GetBytes (typeName); - bw.Write (bytes); - PadField (bw, bytes.Length, maxJavaNameLength + 1); - bw.Write (javaNames[typeName]); - } - - WriteManagedTypes (managedTypes); - if (moduleData.DuplicateTypes.Count == 0) - return; - - var managedDuplicates = new Dictionary (); - foreach (var kvp in moduleData.DuplicateTypes) { - uint javaIndex = kvp.Key; - uint typeId = kvp.Value.Token; - - managedDuplicates.Add (javaIndex, typeId); - } - - WriteManagedTypes (managedDuplicates); - - void WriteManagedTypes (IDictionary types) - { - var sortedTokens = types.Keys.ToArray (); - Array.Sort (sortedTokens); - - foreach (uint token in sortedTokens) { - bw.Write (token); - bw.Write (types[token]); - } + foreach (TypeMapDebugEntry entry in moduleData.ManagedToJavaMap) { + bw.Write (outputEncoding.GetBytes (entry.ManagedName)); + PadField (bw, entry.ManagedName.Length, (int)moduleData.ManagedNameWidth); + bw.Write (entry.JavaIndex); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs new file mode 100644 index 00000000000..fb9d51d2218 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -0,0 +1,140 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Xamarin.Android.Tasks +{ + class TypeMappingDebugNativeAssemblyGenerator : NativeAssemblyGenerator + { + const string JavaToManagedSymbol = "map_java_to_managed"; + const string ManagedToJavaSymbol = "map_managed_to_java"; + const string TypeMapSymbol = "type_map"; // MUST match src/monodroid/xamarin-app.hh + + readonly string baseFileName; + readonly bool sharedBitsWritten; + readonly TypeMapGenerator.ModuleDebugData data; + + public TypeMappingDebugNativeAssemblyGenerator (NativeAssemblerTargetProvider targetProvider, TypeMapGenerator.ModuleDebugData data, string baseFileName, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix = false) + : base (targetProvider, baseFileName, sharedIncludeUsesAbiPrefix) + { + if (String.IsNullOrEmpty (baseFileName)) + throw new ArgumentException("must not be null or empty", nameof (baseFileName)); + this.data = data ?? throw new ArgumentNullException (nameof (data)); + + this.baseFileName = baseFileName; + this.sharedBitsWritten = sharedBitsWritten; + } + + protected override void WriteSymbols (StreamWriter output) + { + bool haveJavaToManaged = data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0; + bool haveManagedToJava = data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0; + + using (var sharedOutput = MemoryStreamPool.Shared.CreateStreamWriter (output.Encoding)) { + WriteSharedBits (sharedOutput, haveJavaToManaged, haveManagedToJava); + sharedOutput.Flush (); + MonoAndroidHelper.CopyIfStreamChanged (sharedOutput.BaseStream, SharedIncludeFile); + } + + if (haveJavaToManaged || haveManagedToJava) { + output.Write (Indent); + output.Write (".include"); + output.Write (Indent); + output.Write ('"'); + output.Write (Path.GetFileName (SharedIncludeFile)); + output.WriteLine ('"'); + + output.WriteLine (); + } + + uint size = 0; + WriteCommentLine (output, "Managed to java map: START", indent: false); + WriteSection (output, $".data.rel.{ManagedToJavaSymbol}", hasStrings: false, writable: true); + WriteStructureSymbol (output, ManagedToJavaSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: false); + if (haveManagedToJava) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { + size += WritePointer (output, entry.ManagedLabel); + size += WritePointer (output, entry.JavaLabel); + } + } + WriteStructureSize (output, ManagedToJavaSymbol, size, alwaysWriteSize: true); + WriteCommentLine (output, "Managed to java map: END", indent: false); + output.WriteLine (); + + size = 0; + WriteCommentLine (output, "Java to managed map: START", indent: false); + WriteSection (output, $".data.rel.{JavaToManagedSymbol}", hasStrings: false, writable: true); + WriteStructureSymbol (output, JavaToManagedSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: false); + if (haveJavaToManaged) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { + size += WritePointer (output, entry.JavaLabel); + size += WritePointer (output, entry.ManagedLabel); + } + } + WriteStructureSize (output, JavaToManagedSymbol, size, alwaysWriteSize: true); + WriteCommentLine (output, "Java to managed map: END", indent: false); + output.WriteLine (); + + // MUST match src/monodroid/xamarin-app.hh + WriteCommentLine (output, "TypeMap structure"); + WriteSection (output, $".data.rel.ro.{TypeMapSymbol}", hasStrings: false, writable: true); + WriteStructureSymbol (output, TypeMapSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: true); + + size = WriteStructure (output, packed: false, structureWriter: () => WriteTypeMapStruct (output)); + + WriteStructureSize (output, TypeMapSymbol, size); + } + + // MUST match the TypeMap struct from src/monodroid/xamarin-app.hh + uint WriteTypeMapStruct (StreamWriter output) + { + uint size = 0; + + WriteCommentLine (output, "entry_count"); + size += WriteData (output, data.EntryCount); + + WriteCommentLine (output, "assembly_name (unused in this mode)"); + size += WritePointer (output); + + WriteCommentLine (output, "data (unused in this mode)"); + size += WritePointer (output); + + WriteCommentLine (output, "java_to_managed"); + size += WritePointer (output, JavaToManagedSymbol); + + WriteCommentLine (output, "managed_to_java"); + size += WritePointer (output, ManagedToJavaSymbol); + + return size; + } + + void WriteSharedBits (StreamWriter output, bool haveJavaToManaged, bool haveManagedToJava) + { + string label; + + if (haveJavaToManaged) { + WriteCommentLine (output, "Java type names: START"); + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { + label = $"java_type_name.{entry.JavaIndex}"; + WriteData (output, entry.JavaName, label, isGlobal: false); + entry.JavaLabel = MakeLocalLabel (label); + output.WriteLine (); + } + WriteCommentLine (output, "Java type names: END"); + output.WriteLine (); + } + + if (haveManagedToJava) { + WriteCommentLine (output, "Managed type names: START"); + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { + label = $"managed_type_name.{entry.ManagedIndex}"; + WriteData (output, entry.ManagedName, label, isGlobal: false); + entry.ManagedLabel = MakeLocalLabel (label); + } + WriteCommentLine (output, "Managed type names: END"); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs similarity index 90% rename from src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingNativeAssemblyGenerator.cs rename to src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs index db47f557ffc..efb6253177c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs @@ -5,20 +5,16 @@ namespace Xamarin.Android.Tasks { - class TypeMappingNativeAssemblyGenerator : NativeAssemblyGenerator + class TypeMappingReleaseNativeAssemblyGenerator : NativeAssemblyGenerator { readonly string baseFileName; readonly NativeTypeMappingData mappingData; readonly bool sharedBitsWritten; - public TypeMappingNativeAssemblyGenerator (NativeAssemblerTargetProvider targetProvider, NativeTypeMappingData mappingData, string baseFileName, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix = false) + public TypeMappingReleaseNativeAssemblyGenerator (NativeAssemblerTargetProvider targetProvider, NativeTypeMappingData mappingData, string baseFileName, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix = false) : base (targetProvider, baseFileName, sharedIncludeUsesAbiPrefix) { this.mappingData = mappingData ?? throw new ArgumentNullException (nameof (mappingData)); - - if (String.IsNullOrEmpty (baseFileName)) - throw new ArgumentException("must not be null or empty", nameof (baseFileName)); - this.baseFileName = baseFileName; this.sharedBitsWritten = sharedIncludeUsesAbiPrefix ? false : sharedBitsWritten; } @@ -93,13 +89,13 @@ void WriteAssemblyNames (StreamWriter output) } } - void WriteManagedMaps (StreamWriter output, string moduleSymbolName, IEnumerable entries) + void WriteManagedMaps (StreamWriter output, string moduleSymbolName, IEnumerable entries) { if (entries == null) return; var tokens = new Dictionary (); - foreach (TypeMapGenerator.TypeMapEntry entry in entries) { + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in entries) { 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"); @@ -138,7 +134,7 @@ void WriteMapModules (StreamWriter output, StreamWriter mapOutput, string symbol uint size = 0; int moduleCounter = 0; - foreach (TypeMapGenerator.ModuleData data in mappingData.Modules) { + foreach (TypeMapGenerator.ModuleReleaseData data in mappingData.Modules) { string mapName = $"module{moduleCounter++}_managed_to_java"; string duplicateMapName; @@ -160,7 +156,7 @@ void WriteMapModules (StreamWriter output, StreamWriter mapOutput, string symbol output.WriteLine (); } - uint WriteMapModule (StreamWriter output, string mapName, string duplicateMapName, TypeMapGenerator.ModuleData data) + uint WriteMapModule (StreamWriter output, string mapName, string duplicateMapName, TypeMapGenerator.ModuleReleaseData data) { uint size = 0; WriteCommentLine (output, $"module_uuid: {data.Mvid}"); @@ -205,7 +201,7 @@ void WriteJavaMap (StreamWriter output, string symbolName) uint size = 0; int entryCount = 0; - foreach (TypeMapGenerator.TypeMapEntry entry in mappingData.JavaTypes) { + foreach (TypeMapGenerator.TypeMapReleaseEntry entry in mappingData.JavaTypes) { size += WriteJavaMapEntry (output, entry, entryCount++); } @@ -214,7 +210,7 @@ void WriteJavaMap (StreamWriter output, string symbolName) output.WriteLine (); } - uint WriteJavaMapEntry (StreamWriter output, TypeMapGenerator.TypeMapEntry entry, int entryIndex) + uint WriteJavaMapEntry (StreamWriter output, TypeMapGenerator.TypeMapReleaseEntry entry, int entryIndex) { uint size = 0; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/X86NativeAssemblerTargetProvider.cs b/src/Xamarin.Android.Build.Tasks/Utilities/X86NativeAssemblerTargetProvider.cs index bb36f000526..898dd3116d4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/X86NativeAssemblerTargetProvider.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/X86NativeAssemblerTargetProvider.cs @@ -13,6 +13,7 @@ class X86NativeAssemblerTargetProvider : NativeAssemblerTargetProvider public override string AbiName => Is64Bit ? X86_64 : X86; public override uint MapModulesAlignBits => Is64Bit ? 4u : 2u; public override uint MapJavaAlignBits => Is64Bit ? 4u : 2u; + public override uint DebugTypeMapAlignBits => Is64Bit ? 4u : 2u; public X86NativeAssemblerTargetProvider (bool is64Bit) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index d13b53cc90f..e25de8f0b69 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1053,6 +1053,9 @@ because xbuild doesn't support framework reference assemblies. <_NuGetAssetsFile Condition=" Exists('$(ProjectLockFile)') ">$(ProjectLockFile) <_NuGetAssetsFile Condition=" '$(_NuGetAssetsFile)' == '' and Exists('packages.config') ">packages.config <_NuGetAssetsTimestamp Condition=" '$(_NuGetAssetsFile)' != '' ">$([System.IO.File]::GetLastWriteTime('$(_NuGetAssetsFile)').Ticks) + <_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">mvid + <_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' And '$(_InstantRunEnabled)' == 'True' ">strings-files + <_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' And '$(_InstantRunEnabled)' != 'True' ">strings-asm @@ -1083,6 +1086,7 @@ because xbuild doesn't support framework reference assemblies. <_PropertyCacheItems Include="AndroidIncludeDebugSymbols=$(AndroidIncludeDebugSymbols)" /> <_PropertyCacheItems Include="AndroidPackageNamingPolicy=$(AndroidPackageNamingPolicy)" /> <_PropertyCacheItems Include="_NuGetAssetsTimestamp=$(_NuGetAssetsTimestamp)" /> + <_PropertyCacheItems Include="TypeMapKind=$(_TypeMapKind)" /> from); +} + MonoReflectionType* -EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) +EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) { - timing_period total_time; - if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { - timing = new Timing (); - total_time.mark_start (); + 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); + if (entry != nullptr) + break; + } + } else { + entry = binary_search (java_type_name, type_map.java_to_managed, type_map.entry_count); } - if (XA_UNLIKELY (java_type == nullptr)) { - log_warn (LOG_ASSEMBLY, "typemap: null 'java_type' passed to 'typemap_java_to_managed'"); + if (XA_UNLIKELY (entry == nullptr)) { + log_warn (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s'", java_type_name); return nullptr; } - simple_pointer_guard 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'"); + const char *managed_type_name = entry->to; + log_debug (LOG_DEFAULT, "typemap: Java type '%s' corresponds to managed type '%s'", java_type_name, managed_type_name); + + MonoType *type = mono_reflection_type_from_name (const_cast(managed_type_name), nullptr); + if (XA_UNLIKELY (type == nullptr)) { + log_warn (LOG_ASSEMBLY, "typemap: managed type '%s' (mapped from Java type '%s') could not be loaded", managed_type_name, java_type_name); + return nullptr; + } + + MonoReflectionType *ret = mono_type_get_object (mono_domain_get (), type); + if (XA_UNLIKELY (ret == nullptr)) { + log_warn (LOG_ASSEMBLY, "typemap: unable to instantiate managed type '%s'", managed_type_name); return nullptr; } + return ret; +} +#else +MonoReflectionType* +EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name) +{ int32_t type_token_id = -1; TypeMapModule *module; -#if defined (DEBUG) || !defined (ANDROID) - if (application_config.instant_run_enabled) { - size_t idx = 0; - for (; idx < module_count; idx++) { - const uint8_t *java_entry = binary_search (java_type_name.get (), modules[idx].java_map, modules[idx].entry_count, modules[idx].java_name_width + 3); - if (java_entry == nullptr) - continue; - type_token_id = *reinterpret_cast(java_entry + modules[idx].java_name_width); - break; - } - - if (idx >= module_count) { - log_error (LOG_ASSEMBLY, "typemap: unable to find module with Java type '%s' mapping", java_type_name.get ()); - return nullptr; - } - - module = &modules[idx]; - } else { -#endif - const TypeMapJava *java_entry = binary_search (java_type_name.get (), map_java, java_type_count, java_name_width); - if (java_entry == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: unable to find mapping to a managed type from Java type '%s'", java_type_name.get ()); - return nullptr; - } + const TypeMapJava *java_entry = binary_search (java_type_name, map_java, java_type_count, java_name_width); + if (java_entry == nullptr) { + log_warn (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.get ()); - 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); + 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); - if (entry == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: unable to find mapping from Java type '%s' to managed type with token ID %u in module [%s]", java_type_name.get (), java_entry->type_token_id, MonoGuidString (module->module_uuid).get ()); - return nullptr; - } - type_token_id = java_entry->type_token_id; -#if defined (DEBUG) || !defined (ANDROID) + module = const_cast(&map_modules[java_entry->module_index]); + const TypeMapModuleEntry *entry = binary_search (&java_entry->type_token_id, module->map, module->entry_count); + if (entry == nullptr) { + log_warn (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 ()); + return nullptr; } -#endif + type_token_id = java_entry->type_token_id; if (module->image == nullptr) { module->image = mono_image_loaded (module->assembly_name); @@ -246,23 +257,22 @@ EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) } 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.get ()); + 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); return nullptr; } } - log_debug (LOG_ASSEMBLY, "typemap: java type '%s' corresponds to managed token id %u (0x%x)", java_type_name.get (), type_token_id, type_token_id); + 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.get ()); + 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); return nullptr; } MonoReflectionType *ret = mono_type_get_object (mono_domain_get (), mono_class_get_type (klass)); - if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { - total_time.mark_end (); - - Timing::info (total_time, "Typemap.java_to_managed: end, total time"); + 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); + return nullptr; } return ret; @@ -277,20 +287,10 @@ EmbeddedAssemblies::compare_java_name (const char *java_name, const TypeMapJava return strcmp (java_name, reinterpret_cast(entry->java_name)); } +#endif -#if defined (DEBUG) || !defined (ANDROID) -int -EmbeddedAssemblies::compare_java_name (const char *java_name, const uint8_t *entry) -{ - if (entry == nullptr) - return 1; - - return strcmp (java_name, reinterpret_cast(entry)); -} -#endif // DEBUG || !ANDROID - -const char* -EmbeddedAssemblies::typemap_managed_to_java (const uint8_t *mvid, const int32_t token) +MonoReflectionType* +EmbeddedAssemblies::typemap_java_to_managed (MonoString *java_type) { timing_period total_time; if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { @@ -298,24 +298,135 @@ EmbeddedAssemblies::typemap_managed_to_java (const uint8_t *mvid, const int32_t total_time.mark_start (); } - if (mvid == nullptr) { - log_warn (LOG_ASSEMBLY, "typemap: no mvid specified in call to typemap_managed_to_java"); + if (XA_UNLIKELY (java_type == nullptr)) { + log_warn (LOG_ASSEMBLY, "typemap: null 'java_type' passed to 'typemap_java_to_managed'"); return nullptr; } - const TypeMapModule *map; - size_t map_entry_count; + simple_pointer_guard 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'"); + return nullptr; + } + + MonoReflectionType *ret = typemap_java_to_managed (java_type_name.get ()); + + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { + total_time.mark_end (); + + Timing::info (total_time, "Typemap.java_to_managed: end, total time"); + } + + return ret; +} + #if defined (DEBUG) || !defined (ANDROID) +inline const TypeMapEntry* +EmbeddedAssemblies::typemap_managed_to_java (const char *managed_type_name) +{ + const TypeMapEntry *entry = nullptr; + + log_warn (LOG_DEFAULT, "%s (\"%s\")", __PRETTY_FUNCTION__, managed_type_name == nullptr ? "" : managed_type_name); if (application_config.instant_run_enabled) { - map = modules; - map_entry_count = module_count; + TypeMap *module; + for (size_t i = 0; i < type_map_count; i++) { + module = &type_maps[i]; + entry = binary_search (managed_type_name, module->managed_to_java, module->entry_count); + if (entry != nullptr) + break; + } } else { -#endif - map = map_modules; - map_entry_count = map_module_count; -#if defined (DEBUG) || !defined (ANDROID) + log_warn (LOG_DEFAULT, " type_map == %p", type_map); + log_warn (LOG_DEFAULT, " type_map.managed_to_java == %p", type_map.managed_to_java); + log_warn (LOG_DEFAULT, " type_map.entry_count == %u", type_map.entry_count); + entry = binary_search (managed_type_name, type_map.managed_to_java, type_map.entry_count); } + + return entry; +} + +// TODO: `managed_type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed +inline const char* +EmbeddedAssemblies::typemap_managed_to_java (MonoType *type, MonoClass *klass, [[maybe_unused]] const uint8_t *mvid, MonoString *managed_type_name) +{ + constexpr char error_message[] = "typemap: unable to find mapping to a Java type from managed type '%s'"; + +#if 0 + simple_pointer_guard type_name (mono_type_get_name (type)); +#else + simple_pointer_guard type_name (mono_string_to_utf8 (managed_type_name)); #endif + MonoImage *image = mono_class_get_image (klass); + const char *image_name = mono_image_get_name (image); + size_t type_name_len = strlen (type_name.get ()); + size_t image_name_len = strlen (image_name); + size_t full_name_size = type_name_len + image_name_len + 3; + const TypeMapEntry *entry = nullptr; + + if (full_name_size > 512) { // Arbitrary, we should be below this limit in most cases + char full_name[full_name_size]; + + char *p = full_name; + memmove (p, type_name.get (), type_name_len); + p += type_name_len; + *p++ = ','; + *p++ = ' '; + memmove (p, image_name, image_name_len); + p += image_name_len; + *p = '\0'; + + entry = typemap_managed_to_java (full_name); + + if (XA_UNLIKELY (entry == nullptr)) { + log_warn (LOG_ASSEMBLY, error_message, full_name); + } + } else { + simple_pointer_guard full_name = utils.string_concat (type_name.get (), ", ", image_name); + entry = typemap_managed_to_java (full_name.get ()); + if (XA_UNLIKELY (entry == nullptr)) { + log_warn (LOG_ASSEMBLY, error_message, full_name.get ()); + } + } + + if (XA_UNLIKELY (entry == nullptr)) { + return nullptr; + } + + 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); + } + + return *token - entry->type_token_id; +} + +inline int +EmbeddedAssemblies::compare_mvid (const uint8_t *mvid, const TypeMapModule *module) +{ + return memcmp (mvid, module->module_uuid, sizeof(module->module_uuid)); +} + +// TODO: `type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed +inline const char* +EmbeddedAssemblies::typemap_managed_to_java ([[maybe_unused]] MonoType *type, MonoClass *klass, const uint8_t *mvid, [[maybe_unused]] MonoString *type_name) +{ + if (mvid == nullptr) { + log_warn (LOG_ASSEMBLY, "typemap: no mvid specified in call to typemap_managed_to_java"); + return nullptr; + } + + uint32_t token = mono_class_get_type_token (klass); + 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); if (match == nullptr) { log_warn (LOG_ASSEMBLY, "typemap: module matching MVID [%s] not found.", MonoGuidString (mvid).get ()); @@ -329,11 +440,11 @@ EmbeddedAssemblies::typemap_managed_to_java (const uint8_t *mvid, const int32_t 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 = binary_search (&token, match->map, match->entry_count); if (entry == 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) { @@ -343,42 +454,20 @@ EmbeddedAssemblies::typemap_managed_to_java (const uint8_t *mvid, const int32_t } uint32_t java_entry_count; -#if defined (DEBUG) || !defined (ANDROID) - if (application_config.instant_run_enabled) { - java_entry_count = match->entry_count; - } else { -#endif - java_entry_count = java_type_count; -#if defined (DEBUG) || !defined (ANDROID) - } -#endif + java_entry_count = java_type_count; if (entry->java_map_index >= java_entry_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 char *ret; -#if defined (DEBUG) || !defined (ANDROID) - if (application_config.instant_run_enabled) { - ret = reinterpret_cast(match->java_map + ((match->java_name_width + 4) * entry->java_map_index)); - } else { -#endif - const TypeMapJava *java_entry = reinterpret_cast (reinterpret_cast(map_java) + ((sizeof(TypeMapJava) + java_name_width) * entry->java_map_index)); - ret = reinterpret_cast(reinterpret_cast(java_entry) + 8); -#if defined (DEBUG) || !defined (ANDROID) - } -#endif + const TypeMapJava *java_entry = reinterpret_cast (reinterpret_cast(map_java) + ((sizeof(TypeMapJava) + java_name_width) * entry->java_map_index)); + ret = reinterpret_cast(reinterpret_cast(java_entry) + 8); if (XA_UNLIKELY (ret == nullptr)) { log_warn (LOG_ASSEMBLY, "typemap: empty Java type name returned for entry at index %u", entry->java_map_index); } - if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { - total_time.mark_end (); - - Timing::info (total_time, "Typemap.managed_to_java: end, total time"); - } - log_debug ( LOG_ASSEMBLY, "typemap: type with token %d (0x%x) in module {%s} (%s) corresponds to Java type '%s'", @@ -391,22 +480,33 @@ EmbeddedAssemblies::typemap_managed_to_java (const uint8_t *mvid, const int32_t return ret; } +#endif -int -EmbeddedAssemblies::compare_type_token (const int32_t *token, const TypeMapModuleEntry *entry) +// TODO: `type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed +const char* +EmbeddedAssemblies::typemap_managed_to_java (MonoReflectionType *reflection_type, const uint8_t *mvid, MonoString *type_name) { - if (entry == nullptr) { - log_fatal (LOG_ASSEMBLY, "typemap: compare_type_token: entry is nullptr"); - exit (FATAL_EXIT_MISSING_ASSEMBLY); + timing_period total_time; + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { + timing = new Timing (); + total_time.mark_start (); } - return *token - entry->type_token_id; -} + MonoType *type = mono_reflection_type_get_type (reflection_type); + if (type == nullptr) { + log_warn (LOG_DEFAULT, "Failed to map reflection type to MonoType"); + return nullptr; + } -int -EmbeddedAssemblies::compare_mvid (const uint8_t *mvid, const TypeMapModule *module) -{ - return memcmp (mvid, module->module_uuid, sizeof(module->module_uuid)); + const char *ret = typemap_managed_to_java (type, mono_type_get_class (type), mvid, type_name); + + if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) { + total_time.mark_end (); + + Timing::info (total_time, "Typemap.managed_to_java: end, total time"); + } + + return ret; } EmbeddedAssemblies::md_mmap_info @@ -545,10 +645,8 @@ EmbeddedAssemblies::typemap_read_header ([[maybe_unused]] int dir_fd, const char uint8_t* EmbeddedAssemblies::typemap_load_index (TypeMapIndexHeader &header, size_t file_size, int index_fd) { - constexpr size_t UUID_SIZE = 16; - - size_t entry_size = header.module_file_name_width + UUID_SIZE; - size_t data_size = entry_size * module_count; + size_t entry_size = header.module_file_name_width; + size_t data_size = entry_size * type_map_count; if (sizeof(header) + data_size > file_size) { log_error (LOG_ASSEMBLY, "typemap: index file is too small, expected %u, found %u bytes", data_size + sizeof(header), file_size); return nullptr; @@ -562,9 +660,8 @@ EmbeddedAssemblies::typemap_load_index (TypeMapIndexHeader &header, size_t file_ } uint8_t *p = data; - for (size_t i = 0; i < module_count; i++) { - memcpy (modules[i].module_uuid, p, UUID_SIZE); - modules[i].assembly_name = reinterpret_cast(p + UUID_SIZE); + for (size_t i = 0; i < type_map_count; i++) { + type_maps[i].assembly_name = reinterpret_cast(p); p += entry_size; } @@ -585,8 +682,8 @@ EmbeddedAssemblies::typemap_load_index (int dir_fd, const char *dir_path, const goto cleanup; } - module_count = header.entry_count; - modules = new TypeMapModule[module_count](); + type_map_count = header.entry_count; + type_maps = new TypeMap[type_map_count](); data = typemap_load_index (header, file_size, fd); cleanup: @@ -597,72 +694,72 @@ EmbeddedAssemblies::typemap_load_index (int dir_fd, const char *dir_path, const } bool -EmbeddedAssemblies::typemap_load_file (BinaryTypeMapHeader &header, const char *dir_path, const char *file_path, int file_fd, TypeMapModule &module) +EmbeddedAssemblies::typemap_load_file (BinaryTypeMapHeader &header, const char *dir_path, const char *file_path, int file_fd, TypeMap &module) { size_t alloc_size = ADD_WITH_OVERFLOW_CHECK (size_t, header.assembly_name_length, 1); module.assembly_name = new char[alloc_size]; ssize_t nread = do_read (file_fd, module.assembly_name, header.assembly_name_length); + if (nread != header.assembly_name_length) { + log_error (LOG_ASSEMBLY, "tyemap: failed to read map assembly name from '%s/%s': %s", dir_path, file_path, strerror (errno)); + return false; + } + module.assembly_name [header.assembly_name_length] = 0; module.entry_count = header.entry_count; log_debug ( LOG_ASSEMBLY, - "typemap: '%s/%s':: entry count == %u; duplicate entry count == %u; Java type name field width == %u; MVID == %s; assembly name length == %u; assembly name == %s", - dir_path, file_path, header.entry_count, header.duplicate_count, header.java_name_width, - MonoGuidString (header.module_uuid).get (), header.assembly_name_length, module.assembly_name + "typemap: '%s/%s':: entry count == %u; Java name field width == %u; Managed name width == %u; assembly name length == %u; assembly name == %s", + dir_path, file_path, header.entry_count, header.java_name_width, header.managed_name_width, header.assembly_name_length, module.assembly_name ); - alloc_size = MULTIPLY_WITH_OVERFLOW_CHECK (size_t, header.java_name_width + 4, header.entry_count); - module.java_name_width = header.java_name_width; - module.java_map = new uint8_t[alloc_size]; - nread = do_read (file_fd, module.java_map, alloc_size); - if (nread != static_cast(alloc_size)) { - log_error (LOG_ASSEMBLY, "typemap: failed to read %u bytes (java-to-managed) from module file %s/%s. %s", alloc_size, dir_path, file_path, strerror (errno)); - return false; - } + // [name][index] + size_t java_entry_size = header.java_name_width + sizeof(uint32_t); + size_t managed_entry_size = header.managed_name_width + sizeof(uint32_t); + size_t data_size = ADD_WITH_OVERFLOW_CHECK ( + size_t, + header.entry_count * java_entry_size, + header.entry_count * managed_entry_size + ); - module.map = new TypeMapModuleEntry[header.entry_count]; - alloc_size = MULTIPLY_WITH_OVERFLOW_CHECK (size_t, sizeof(TypeMapModuleEntry), header.entry_count); - nread = do_read (file_fd, module.map, alloc_size); - if (nread != static_cast(alloc_size)) { - log_error (LOG_ASSEMBLY, "typemap: failed to read %u bytes (managed-to-java) from module file %s/%s. %s", alloc_size, dir_path, file_path, strerror (errno)); + module.data = new uint8_t [data_size]; + nread = do_read (file_fd, module.data, data_size); + if (nread != data_size) { + log_error (LOG_ASSEMBLY, "tyemap: failed to read map data from '%s/%s': %s", dir_path, file_path, strerror (errno)); return false; } - // alloc_size = module.java_name_width + 1; - // auto chars = new char[alloc_size](); - // uint8_t *p = module.java_map; - // log_debug (LOG_ASSEMBLY, "Java entries in %s/%s", dir_path, file_path); - // for (size_t i = 0; i < module.entry_count; i++) { - // memcpy (chars, p, module.java_name_width); - // uint32_t token = *reinterpret_cast(p + module.java_name_width); - // log_debug (LOG_ASSEMBLY, " %04u: %s; %u (0x%x)", i, chars, token, token); - // p += module.java_name_width + 4; - // } - // delete[] chars; - - // log_debug (LOG_ASSEMBLY, "Managed entries in %s/%s", dir_path, file_path); - // for (size_t i = 0; i < module.entry_count; i++) { - // log_debug (LOG_ASSEMBLY, " %04u: token %u (0x%x); index %u", i, module.map[i].type_token_id, module.map[i].type_token_id, module.map[i].java_map_index); - // } - - if (header.duplicate_count == 0) - return true; - - module.duplicate_map = new TypeMapModuleEntry[header.duplicate_count]; - alloc_size = MULTIPLY_WITH_OVERFLOW_CHECK (size_t, sizeof(TypeMapModuleEntry), header.duplicate_count); - nread = do_read (file_fd, module.duplicate_map, alloc_size); - if (nread != static_cast(alloc_size)) { - log_error (LOG_ASSEMBLY, "typemap: failed to read %u bytes (managed-to-java duplicates) from module file %s/%s. %s", alloc_size, dir_path, file_path, strerror (errno)); - return false; + module.java_to_managed = new TypeMapEntry [module.entry_count]; + module.managed_to_java = new TypeMapEntry [module.entry_count]; + + uint8_t *java_start = module.data; + uint8_t *managed_start = module.data + (module.entry_count * java_entry_size); + uint8_t *java_pos = java_start; + uint8_t *managed_pos = managed_start; + TypeMapEntry *cur; + + for (size_t i = 0; i < module.entry_count; i++) { + cur = &module.java_to_managed[i]; + cur->from = reinterpret_cast(java_pos); + + uint32_t idx = *(reinterpret_cast(java_pos + header.java_name_width)); + cur->to = reinterpret_cast(managed_start + (managed_entry_size * idx)); + java_pos += java_entry_size; + + cur = &module.managed_to_java[i]; + cur->from = reinterpret_cast(managed_pos); + + idx = *(reinterpret_cast(managed_pos + header.managed_name_width)); + cur->to = reinterpret_cast(java_start + (java_entry_size * idx)); + managed_pos += managed_entry_size; } return true; } bool -EmbeddedAssemblies::typemap_load_file (int dir_fd, const char *dir_path, const char *file_path, TypeMapModule &module) +EmbeddedAssemblies::typemap_load_file (int dir_fd, const char *dir_path, const char *file_path, TypeMap &module) { log_debug (LOG_ASSEMBLY, "typemap: loading TypeMap file '%s/%s'", dir_path, file_path); @@ -671,11 +768,10 @@ EmbeddedAssemblies::typemap_load_file (int dir_fd, const char *dir_path, const c size_t file_size; int fd = -1; - module.java_map = nullptr; - module.map = nullptr; - module.duplicate_map = nullptr; + module.java_to_managed = nullptr; + module.managed_to_java = nullptr; - if (!typemap_read_header (dir_fd, "TypeMap", dir_path, file_path, MODULE_MAGIC, header, file_size, fd)) { + if (!typemap_read_header (dir_fd, "TypeMap", dir_path, file_path, MODULE_MAGIC_NAMES, header, file_size, fd)) { ret = false; goto cleanup; } @@ -687,12 +783,10 @@ EmbeddedAssemblies::typemap_load_file (int dir_fd, const char *dir_path, const c close (fd); if (!ret) { - delete[] module.java_map; - module.java_map = nullptr; - delete[] module.map; - module.map = nullptr; - delete[] module.duplicate_map; - module.duplicate_map = nullptr; + delete[] module.java_to_managed; + module.java_to_managed = nullptr; + delete[] module.managed_to_java; + module.managed_to_java = nullptr; } return ret; @@ -706,6 +800,7 @@ EmbeddedAssemblies::try_load_typemaps_from_directory (const char *path) return; } + log_warn (LOG_ASSEMBLY, "typemap: %s (\"%s\")", __PRETTY_FUNCTION__, path); simple_pointer_guard dir_path (utils.path_combine (path, "typemaps")); monodroid_dir_t *dir; if ((dir = utils.monodroid_opendir (dir_path)) == nullptr) { @@ -730,9 +825,9 @@ EmbeddedAssemblies::try_load_typemaps_from_directory (const char *path) exit (FATAL_EXIT_NO_ASSEMBLIES); // TODO: use a new error code here } - for (size_t i = 0; i < module_count; i++) { - TypeMapModule &module = modules[i]; - if (!typemap_load_file (dir_fd, dir_path, module.assembly_name, module)) { + for (size_t i = 0; i < type_map_count; i++) { + TypeMap *module = &type_maps[i]; + if (!typemap_load_file (dir_fd, dir_path, module->assembly_name, *module)) { continue; } } diff --git a/src/monodroid/jni/embedded-assemblies.hh b/src/monodroid/jni/embedded-assemblies.hh index f000bab413b..e758d5e4832 100644 --- a/src/monodroid/jni/embedded-assemblies.hh +++ b/src/monodroid/jni/embedded-assemblies.hh @@ -41,9 +41,11 @@ namespace xamarin::android::internal { #if defined (DEBUG) || !defined (ANDROID) void try_load_typemaps_from_directory (const char *path); #endif + // TODO: `managed_type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed + const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid, MonoString *managed_type_name); + void install_preload_hooks (); MonoReflectionType* typemap_java_to_managed (MonoString *java_type); - const char* typemap_managed_to_java (const uint8_t *mvid, const int32_t token); /* returns current number of *all* assemblies found from all invocations */ template @@ -66,6 +68,9 @@ namespace xamarin::android::internal { void set_assemblies_prefix (const char *prefix); private: + // TODO: `managed_type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed + const char* typemap_managed_to_java (MonoType *type, MonoClass *klass, const uint8_t *mvid, MonoString *managed_type_name); + MonoReflectionType* typemap_java_to_managed (const char *java_type_name); 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); MonoAssembly* open_from_bundles (MonoAssemblyName* aname, bool ref_only); @@ -74,9 +79,10 @@ namespace xamarin::android::internal { bool typemap_read_header (int dir_fd, const char *file_type, const char *dir_path, const char *file_path, uint32_t expected_magic, H &header, size_t &file_size, int &fd); uint8_t* typemap_load_index (int dir_fd, const char *dir_path, const char *index_path); uint8_t* typemap_load_index (TypeMapIndexHeader &header, size_t file_size, int index_fd); - bool typemap_load_file (int dir_fd, const char *dir_path, const char *file_path, TypeMapModule &module); - bool typemap_load_file (BinaryTypeMapHeader &header, const char *dir_path, const char *file_path, int file_fd, TypeMapModule &module); + 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); #endif // DEBUG || !ANDROID bool register_debug_symbols_for_assembly (const char *entry_name, MonoBundledAssembly *assembly, const mono_byte *debug_contents, int debug_size); @@ -104,11 +110,12 @@ namespace xamarin::android::internal { template const Entry* binary_search (const Key *key, const Entry *base, size_t nmemb, size_t extra_size = 0); +#if defined (DEBUG) || !defined (ANDROID) + static int compare_type_name (const char *type_name, const TypeMapEntry *entry); +#else static int compare_mvid (const uint8_t *mvid, const TypeMapModule *module); - static int compare_type_token (const int32_t *token, const TypeMapModuleEntry *entry); + static int compare_type_token (const uint32_t *token, const TypeMapModuleEntry *entry); static int compare_java_name (const char *java_name, const TypeMapJava *entry); -#if defined (DEBUG) || !defined (ANDROID) - static int compare_java_name (const char *java_name, const uint8_t *java_map); #endif private: @@ -118,8 +125,8 @@ namespace xamarin::android::internal { #if defined (DEBUG) || !defined (ANDROID) TypeMappingInfo *java_to_managed_maps; TypeMappingInfo *managed_to_java_maps; - TypeMapModule *modules; - size_t module_count; + TypeMap *type_maps; + size_t type_map_count; #endif // DEBUG || !ANDROID const char *assemblies_prefix_override = nullptr; }; diff --git a/src/monodroid/jni/external-api.cc b/src/monodroid/jni/external-api.cc index c49a58308af..a835d4a8ca3 100644 --- a/src/monodroid/jni/external-api.cc +++ b/src/monodroid/jni/external-api.cc @@ -158,12 +158,6 @@ _monodroid_get_display_dpi (float *x_dpi, float *y_dpi) return monodroidRuntime.get_display_dpi (x_dpi, y_dpi); } -MONO_API const char * -monodroid_typemap_managed_to_java (const uint8_t *mvid, const int32_t token) -{ - return embeddedAssemblies.typemap_managed_to_java (mvid, token); -} - MONO_API int monodroid_embedded_assemblies_set_assemblies_prefix (const char *prefix) { embeddedAssemblies.set_assemblies_prefix (prefix); diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 8ab70212529..37fd6f9b2b6 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -178,6 +178,9 @@ namespace xamarin::android::internal static void thread_end (MonoProfiler *prof, uintptr_t tid); static MonoReflectionType* typemap_java_to_managed (MonoString *java_type_name); + // TODO: `type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed + static const char* typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid, MonoString *type_name); + #if defined (DEBUG) void set_debug_env_vars (void); diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index a669016e5bd..38ce201d318 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -921,6 +921,7 @@ void MonodroidRuntime::init_android_runtime (MonoDomain *domain, 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)); struct JnienvInitializeArgs init = {}; init.javaVm = osBridge.get_jvm (); @@ -1411,6 +1412,13 @@ MonodroidRuntime::typemap_java_to_managed (MonoString *java_type_name) return embeddedAssemblies.typemap_java_to_managed (java_type_name); } +// TODO: `type_name` is temporary, until https://github.com/mono/mono/issues/19377 is fixed +const char* +MonodroidRuntime::typemap_managed_to_java (MonoReflectionType *type, const uint8_t *mvid, MonoString *type_name) +{ + return embeddedAssemblies.typemap_managed_to_java (type, mvid, type_name); +} + inline void MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 6cbef89d428..c20e643c909 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -8,18 +8,19 @@ #include "monodroid.h" -static constexpr uint32_t MODULE_MAGIC = 0x4D544158; // 'XATM', little-endian +static constexpr uint32_t MODULE_MAGIC_MVID = 0x4D544158; // 'XATM', little-endian +static constexpr uint32_t MODULE_MAGIC_NAMES = 0x53544158; // 'XATS', little-endian static constexpr uint32_t MODULE_INDEX_MAGIC = 0x49544158; // 'XATI', little-endian -static constexpr uint8_t MODULE_FORMAT_VERSION = 1; // Keep in sync with the value in src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +static constexpr uint8_t MODULE_FORMAT_VERSION = 2; // Keep in sync with the value in src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +#if defined (DEBUG) || !defined (ANDROID) struct BinaryTypeMapHeader { uint32_t magic; uint32_t version; - uint8_t module_uuid[16]; uint32_t entry_count; - uint32_t duplicate_count; uint32_t java_name_width; + uint32_t managed_name_width; uint32_t assembly_name_length; }; @@ -31,9 +32,25 @@ struct TypeMapIndexHeader uint32_t module_file_name_width; }; +struct TypeMapEntry +{ + const char *from; + const char *to; +}; + +// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +struct TypeMap +{ + uint32_t entry_count; + char *assembly_name; + uint8_t *data; + TypeMapEntry *java_to_managed; + TypeMapEntry *managed_to_java; +}; +#else struct TypeMapModuleEntry { - int32_t type_token_id; + uint32_t type_token_id; uint32_t java_map_index; }; @@ -53,9 +70,10 @@ struct TypeMapModule struct TypeMapJava { uint32_t module_index; - int32_t type_token_id; + uint32_t type_token_id; uint8_t java_name[]; }; +#endif struct ApplicationConfig { @@ -73,11 +91,15 @@ struct ApplicationConfig const char *android_package_name; }; +#if defined (DEBUG) || !defined (ANDROID) +MONO_API const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +#else MONO_API const uint32_t map_module_count; MONO_API const uint32_t java_type_count; MONO_API const uint32_t java_name_width; MONO_API const TypeMapModule map_modules[]; MONO_API const TypeMapJava map_java[]; +#endif MONO_API ApplicationConfig application_config; MONO_API const char* app_environment_variables[];