diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs index 68ab3e6bd6e..08863825ef9 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs @@ -37,13 +37,14 @@ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) var xml = new TypeMapObjectsXmlFile { AssemblyName = assembly.Name.Name, + AssemblyMvid = assembly.MainModule.Mvid, }; if (Debug) { - var (javaToManaged, managedToJava, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context); + var (typeMapDebugSets, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context, needUniqueAssemblies: false); - xml.JavaToManagedDebugEntries.AddRange (javaToManaged); - xml.ManagedToJavaDebugEntries.AddRange (managedToJava); + xml.JavaToManagedDebugEntries.AddRange (typeMapDebugSets.JavaToManaged); + xml.ManagedToJavaDebugEntries.AddRange (typeMapDebugSets.ManagedToJava); xml.FoundJniNativeRegistration = foundJniNativeRegistration; } else { var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs index 32e9806f3a1..9d815c33d1b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs @@ -38,6 +38,7 @@ public void BuildLibraryWithAssetPack ([Values (true, false)] bool isRelease) [Category ("SmokeTests")] [TestCase (false, AndroidRuntime.MonoVM)] [TestCase (true, AndroidRuntime.MonoVM)] + [TestCase (false, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.NativeAOT)] public void BuildApplicationWithAssetPackThatHasInvalidName (bool isRelease, AndroidRuntime runtime) @@ -67,6 +68,7 @@ public void BuildApplicationWithAssetPackThatHasInvalidName (bool isRelease, And [Category ("SmokeTests")] [TestCase (false, AndroidRuntime.MonoVM)] [TestCase (true, AndroidRuntime.MonoVM)] + [TestCase (false, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.NativeAOT)] public void BuildApplicationWithAssetPackOutsideProjectDirectory (bool isRelease, AndroidRuntime runtime) @@ -117,6 +119,7 @@ public void BuildApplicationWithAssetPackOutsideProjectDirectory (bool isRelease [Category ("SmokeTests")] [TestCase (false, AndroidRuntime.MonoVM)] [TestCase (true, AndroidRuntime.MonoVM)] + [TestCase (false, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.NativeAOT)] public void BuildApplicationWithAssetPackOverrides (bool isRelease, AndroidRuntime runtime) @@ -160,6 +163,7 @@ public void BuildApplicationWithAssetPackOverrides (bool isRelease, AndroidRunti [Category ("SmokeTests")] [TestCase (false, AndroidRuntime.MonoVM)] [TestCase (true, AndroidRuntime.MonoVM)] + [TestCase (false, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.CoreCLR)] [TestCase (true, AndroidRuntime.NativeAOT)] public void BuildApplicationWithAssetPack (bool isRelease, AndroidRuntime runtime) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.TestCaseSource.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.TestCaseSource.cs index a7f5cf2fc49..593d68009cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.TestCaseSource.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.TestCaseSource.cs @@ -132,6 +132,13 @@ public partial class BuildTest : BaseTest /* usesAssemblyStore */ true, /* runtime */ AndroidRuntime.CoreCLR, }, + new object [] { + /* runtimeIdentifiers */ "android-arm64", + /* isRelease */ false, + /* aot */ false, + /* usesAssemblyStore */ true, + /* runtime */ AndroidRuntime.CoreCLR, + }, new object [] { /* runtimeIdentifiers */ "android-arm64", /* isRelease */ true, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs index 0273e253d5e..fc6b4930af4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs @@ -7,24 +7,27 @@ using ReleaseGenerationState = Xamarin.Android.Tasks.TypeMapGenerator.ReleaseGenerationState; using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry; using TypeMapReleaseEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapReleaseEntry; +using TypeMapDebugDataSets = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugDataSets; +using TypeMapDebugAssembly = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugAssembly; namespace Xamarin.Android.Tasks; // Converts types from Mono.Cecil to the format used by the typemap generator. class TypeMapCecilAdapter { - public static (List javaToManaged, List managedToJava) GetDebugNativeEntries (NativeCodeGenState state) + public static TypeMapDebugDataSets GetDebugNativeEntries (NativeCodeGenState state, bool needUniqueAssemblies) { - var (javaToManaged, managedToJava, foundJniNativeRegistration) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache); + var (dataSets, foundJniNativeRegistration) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache, needUniqueAssemblies); state.JniAddNativeMethodRegistrationAttributePresent = foundJniNativeRegistration; - return (javaToManaged, managedToJava); + return dataSets; } - public static (List javaToManaged, List managedToJava, bool foundJniNativeRegistration) GetDebugNativeEntries (List types, TypeDefinitionCache cache) + public static (TypeMapDebugDataSets dataSets, bool foundJniNativeRegistration) GetDebugNativeEntries (List types, TypeDefinitionCache cache, bool needUniqueAssemblies) { var javaDuplicates = new Dictionary> (StringComparer.Ordinal); + var uniqueAssemblies = needUniqueAssemblies ? new Dictionary (StringComparer.OrdinalIgnoreCase) : null; var javaToManaged = new List (); var managedToJava = new List (); var foundJniNativeRegistration = false; @@ -37,11 +40,31 @@ public static (List javaToManaged, List ma javaToManaged.Add (entry); managedToJava.Add (entry); + + if (uniqueAssemblies == null) { + continue; + } + + string? asmName = td.Module.Assembly.Name.Name; + if (String.IsNullOrEmpty (asmName) || uniqueAssemblies.ContainsKey (asmName)) { + continue; + } + + var asmInfo = new TypeMapDebugAssembly { + MVID = td.Module.Mvid, + Name = asmName, + }; + asmInfo.MVIDBytes = asmInfo.MVID.ToByteArray (); + uniqueAssemblies.Add (asmName, asmInfo); } SyncDebugDuplicates (javaDuplicates); - return (javaToManaged, managedToJava, foundJniNativeRegistration); + return (new TypeMapDebugDataSets { + JavaToManaged = javaToManaged, + ManagedToJava = managedToJava, + UniqueAssemblies = uniqueAssemblies != null ? new List (uniqueAssemblies.Values) : null + }, foundJniNativeRegistration); } public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenState state) @@ -204,7 +227,7 @@ static bool JniAddNativeMethodRegistrationAttributeFound (bool alreadyFound, Typ if (alreadyFound || !javaType.HasCustomAttributes) { return alreadyFound; } - + foreach (CustomAttribute ca in javaType.CustomAttributes) { if (string.Equals ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal)) { return true; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs index 9511edc39fe..56b33164d33 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs @@ -83,12 +83,27 @@ public override string ToString () } } + internal sealed class TypeMapDebugAssembly + { + public Guid MVID; + public byte[] MVIDBytes; + public string Name; + } + + internal sealed class TypeMapDebugDataSets + { + public List JavaToManaged ; + public List ManagedToJava; + public List? UniqueAssemblies; + } + // Widths include the terminating nul character but not the padding! internal sealed class ModuleDebugData { public uint EntryCount; public List JavaToManagedMap; public List ManagedToJavaMap; + public List? UniqueAssemblies; } internal sealed class ReleaseGenerationState @@ -138,18 +153,23 @@ public void Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAt void GenerateDebugNativeAssembly (string outputDirectory) { - (var javaToManaged, var managedToJava) = state.GetDebugNativeEntries (); + TypeMapDebugDataSets dataSets = state.GetDebugNativeEntries (needUniqueAssemblies: runtime == AndroidRuntime.CoreCLR); var data = new ModuleDebugData { - EntryCount = (uint)javaToManaged.Count, - JavaToManagedMap = javaToManaged, - ManagedToJavaMap = managedToJava, + EntryCount = (uint)dataSets.JavaToManaged.Count, + JavaToManagedMap = dataSets.JavaToManaged, + ManagedToJavaMap = dataSets.ManagedToJava, + UniqueAssemblies = dataSets.UniqueAssemblies, }; data.JavaToManagedMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.JavaName, b.JavaName, StringComparison.Ordinal)); data.ManagedToJavaMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.ManagedName, b.ManagedName, StringComparison.Ordinal)); - var composer = new TypeMappingDebugNativeAssemblyGenerator (log, data); + LLVMIR.LlvmIrComposer composer = runtime switch { + AndroidRuntime.MonoVM => new TypeMappingDebugNativeAssemblyGenerator (log, data), + AndroidRuntime.CoreCLR => new TypeMappingDebugNativeAssemblyGeneratorCLR (log, data), + _ => throw new NotSupportedException ($"Internal error: unsupported runtime {runtime}") + }; GenerateNativeAssembly (composer, composer.Construct (), outputDirectory); } @@ -216,7 +236,7 @@ interface ITypeMapGeneratorAdapter { AndroidTargetArch TargetArch { get; } bool JniAddNativeMethodRegistrationAttributePresent { get; set; } - (List javaToManaged, List managedToJava) GetDebugNativeEntries (); + TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies); ReleaseGenerationState GetReleaseGenerationState (); } @@ -236,9 +256,9 @@ public bool JniAddNativeMethodRegistrationAttributePresent { set => state.JniAddNativeMethodRegistrationAttributePresent = value; } - public (List javaToManaged, List managedToJava) GetDebugNativeEntries () + public TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies) { - return TypeMapCecilAdapter.GetDebugNativeEntries (state); + return TypeMapCecilAdapter.GetDebugNativeEntries (state, needUniqueAssemblies); } public ReleaseGenerationState GetReleaseGenerationState () @@ -259,12 +279,22 @@ public TypeMapObjectsFileAdapter (AndroidTargetArch targetArch) public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } - public (List javaToManaged, List managedToJava) GetDebugNativeEntries () + public TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies) { var javaToManaged = new List (); var managedToJava = new List (); + var uniqueAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase); foreach (var xml in XmlFiles) { + if (!uniqueAssemblies.ContainsKey (xml.AssemblyName)) { + var assm = new TypeMapDebugAssembly { + MVID = xml.AssemblyMvid, + MVIDBytes = xml.AssemblyMvid.ToByteArray (), + Name = xml.AssemblyName, + }; + uniqueAssemblies.Add (xml.AssemblyName, assm); + } + javaToManaged.AddRange (xml.JavaToManagedDebugEntries); managedToJava.AddRange (xml.ManagedToJavaDebugEntries); } @@ -273,7 +303,11 @@ public TypeMapObjectsFileAdapter (AndroidTargetArch targetArch) GroupDuplicateDebugEntries (javaToManaged); GroupDuplicateDebugEntries (managedToJava); - return (javaToManaged, managedToJava); + return new TypeMapDebugDataSets { + JavaToManaged = javaToManaged, + ManagedToJava = managedToJava, + UniqueAssemblies = uniqueAssemblies.Values.ToList (), + }; } void GroupDuplicateDebugEntries (List debugEntries) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs index 5af39a6d0d2..9eb190dabc0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs @@ -25,6 +25,7 @@ class TypeMapObjectsXmlFile static readonly TypeMapObjectsXmlFile unscanned = new TypeMapObjectsXmlFile { WasScanned = false }; public string? AssemblyName { get; set; } + public Guid AssemblyMvid { get; set; } = Guid.Empty; public bool FoundJniNativeRegistration { get; set; } public List JavaToManagedDebugEntries { get; } = []; public List ManagedToJavaDebugEntries { get; } = []; @@ -58,6 +59,10 @@ void Export (XmlWriter xml) xml.WriteStartElement ("api"); xml.WriteAttributeString ("type", HasDebugEntries ? "debug" : "release"); xml.WriteAttributeStringIfNotDefault ("assembly-name", AssemblyName); + + if (AssemblyMvid != Guid.Empty) { + xml.WriteAttributeString ("mvid", AssemblyMvid.ToString ("N")); + } xml.WriteAttributeStringIfNotDefault ("found-jni-native-registration", FoundJniNativeRegistration); if (HasDebugEntries) @@ -173,11 +178,13 @@ public static TypeMapObjectsXmlFile Import (string filename) var type = root.GetRequiredAttribute ("type"); var assemblyName = root.GetAttributeOrDefault ("assembly-name", (string?)null); + var mvid = Guid.Parse (root.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())); var foundJniNativeRegistration = root.GetAttributeOrDefault ("found-jni-native-registration", false); var file = new TypeMapObjectsXmlFile { WasScanned = true, AssemblyName = assemblyName, + AssemblyMvid = mvid, FoundJniNativeRegistration = foundJniNativeRegistration, }; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs index 9b41c756387..60e52b0e06d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs @@ -13,7 +13,7 @@ class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer { 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 + const string TypeMapSymbol = "type_map"; // MUST match src/native/mono/xamarin-app-stub/xamarin-app.hh sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider { @@ -66,11 +66,11 @@ public override string GetComment (object data, string fieldName) var entry = EnsureType (data); if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) { - return $"from: entry.from"; + return $"from: {entry.from}"; } if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) { - return $"to: entry.to"; + return $"to: {entry.to}"; } return String.Empty; @@ -78,7 +78,7 @@ public override string GetComment (object data, string fieldName) } // Order of fields and their type must correspond *exactly* to that in - // src/monodroid/jni/xamarin-app.hh TypeMapEntry structure + // src/native/mono/xamarin-app-stub/xamarin-app.hh TypeMapEntry structure [NativeAssemblerStructContextDataProvider (typeof (TypeMapEntryContextDataProvider))] sealed class TypeMapEntry { @@ -87,7 +87,7 @@ sealed class TypeMapEntry }; // Order of fields and their type must correspond *exactly* to that in - // src/monodroid/jni/xamarin-app.hh TypeMap structure + // src/native/mono/xamarin-app-stub/xamarin-app.hh TypeMap structure [NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))] sealed class TypeMap { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs new file mode 100644 index 00000000000..2257754a2d5 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Build.Utilities; + +using Xamarin.Android.Tasks.LLVMIR; + +namespace Xamarin.Android.Tasks; + +class TypeMappingDebugNativeAssemblyGeneratorCLR : LlvmIrComposer +{ + const string JavaToManagedSymbol = "map_java_to_managed"; + const string ManagedToJavaSymbol = "map_managed_to_java"; + + // These names MUST match src/native/clr/include/xamarin-app.hh + const string TypeMapSymbol = "type_map"; + const string UniqueAssembliesSymbol = "type_map_unique_assemblies"; + const string AssemblyNamesBlobSymbol = "type_map_assembly_names_blob"; + + sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override ulong GetBufferSize (object data, string fieldName) + { + var map_module = EnsureType (data); + return fieldName switch { + "java_to_managed" => map_module.entry_count, + "managed_to_java" => map_module.entry_count, + _ => 0 + }; + } + + public override string? GetPointedToSymbolName (object data, string fieldName) + { + var map_module = EnsureType (data); + + if (String.Compare ("java_to_managed", fieldName, StringComparison.Ordinal) == 0) { + return map_module.JavaToManagedCount == 0 ? null : JavaToManagedSymbol; + } + + if (String.Compare ("managed_to_java", fieldName, StringComparison.Ordinal) == 0) { + return map_module.ManagedToJavaCount == 0 ? null : ManagedToJavaSymbol; + } + + return base.GetPointedToSymbolName (data, fieldName); + } + } + + sealed class TypeMapEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) { + return $"from: {entry.from}"; + } + + if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) { + return $"to: {entry.to}"; + } + + return String.Empty; + } + } + + sealed class TypeMapAssemblyContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("mvid_hash", fieldName, StringComparison.Ordinal) == 0) { + return $" MVID: {entry.MVID}"; + } + + if (String.Compare ("name_offset", fieldName, StringComparison.Ordinal) == 0) { + return $" {entry.Name}"; + } + + return String.Empty; + } + } + + // Order of fields and their type must correspond *exactly* to that in + // src/native/clr/include/xamarin-app.hh TypeMapEntry structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapEntryContextDataProvider))] + sealed class TypeMapEntry + { + [NativeAssembler (UsesDataProvider = true)] + public string from = String.Empty; + + [NativeAssembler (UsesDataProvider = true)] + public string? to; + }; + + // Order of fields and their type must correspond *exactly* to that in + // src/native/clr/include/xamarin-app.hh TypeMap structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))] + sealed class TypeMap + { + [NativeAssembler (Ignore = true)] + public int JavaToManagedCount; + + [NativeAssembler (Ignore = true)] + public int ManagedToJavaCount; + + public uint entry_count; + public ulong unique_assemblies_count; + public ulong assembly_names_blob_size; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? java_to_managed = null; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public TypeMapEntry? managed_to_java = null; + }; + + // Order of fields and their type must correspond *exactly* to that in + // src/native/clr/include/xamarin-app.hh TypeMapAssembly structure + [NativeAssemblerStructContextDataProvider (typeof (TypeMapAssemblyContextDataProvider))] + sealed class TypeMapAssembly + { + [NativeAssembler (Ignore = true)] + public string Name = String.Empty; + + [NativeAssembler (Ignore = true)] + public Guid MVID; + + [NativeAssembler (UsesDataProvider = true, NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] + public ulong mvid_hash; + public ulong name_length; + + [NativeAssembler (UsesDataProvider = true)] + public ulong name_offset; + } + + readonly TypeMapGenerator.ModuleDebugData data; + StructureInfo? typeMapEntryStructureInfo; + StructureInfo? typeMapStructureInfo; + StructureInfo? typeMapAssemblyStructureInfo; + List> javaToManagedMap; + List> managedToJavaMap; + List> uniqueAssemblies; + StructureInstance? type_map; + + public TypeMappingDebugNativeAssemblyGeneratorCLR (TaskLoggingHelper log, TypeMapGenerator.ModuleDebugData data) + : base (log) + { + if (data.UniqueAssemblies == null || data.UniqueAssemblies.Count == 0) { + throw new InvalidOperationException ("Internal error: set of unique assemblies must be provided."); + } + + this.data = data; + + javaToManagedMap = new (); + managedToJavaMap = new (); + uniqueAssemblies = new (); + } + + protected override void Construct (LlvmIrModule module) + { + module.DefaultStringGroup = "tmd"; + + if (data.UniqueAssemblies == null) { + throw new InvalidOperationException ("Internal error: unique assemblies collection must be present"); + } + + MapStructures (module); + + if (data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { + var m2j = new TypeMapEntry { + from = entry.ManagedName, + to = entry.JavaName, + }; + managedToJavaMap.Add (new StructureInstance (typeMapEntryStructureInfo, m2j)); + } + } + + if (data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0) { + foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { + TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; + + var j2m = new TypeMapEntry { + from = entry.JavaName, + to = managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedName, + }; + javaToManagedMap.Add (new StructureInstance (typeMapEntryStructureInfo, j2m)); + } + } + + // CoreCLR supports only 64-bit targets, so we can make things simpler by hashing the MVIDs here instead of + // in a callback during code generation + var assemblyNamesBlob = new List (); + foreach (TypeMapGenerator.TypeMapDebugAssembly asm in data.UniqueAssemblies) { + byte[] nameBytes = MonoAndroidHelper.Utf8StringToBytes (asm.Name); + var entry = new TypeMapAssembly { + Name = asm.Name, + MVID = asm.MVID, + + mvid_hash = MonoAndroidHelper.GetXxHash (asm.MVIDBytes, is64Bit: true), + name_length = (ulong)nameBytes.Length, // without the trailing NUL + name_offset = (ulong)assemblyNamesBlob.Count, + }; + uniqueAssemblies.Add (new StructureInstance (typeMapAssemblyStructureInfo, entry)); + assemblyNamesBlob.AddRange (nameBytes); + assemblyNamesBlob.Add (0); + } + uniqueAssemblies.Sort ((StructureInstance a, StructureInstance b) => { + if (a.Instance == null) { + return b.Instance == null ? 0 : -1; + } + + if (b.Instance == null) { + return 1; + } + + return a.Instance.mvid_hash.CompareTo (b.Instance.mvid_hash); + }); + + var map = new TypeMap { + JavaToManagedCount = data.JavaToManagedMap == null ? 0 : data.JavaToManagedMap.Count, + ManagedToJavaCount = data.ManagedToJavaMap == null ? 0 : data.ManagedToJavaMap.Count, + + entry_count = data.EntryCount, + unique_assemblies_count = (ulong)data.UniqueAssemblies.Count, + assembly_names_blob_size = (ulong)assemblyNamesBlob.Count, + }; + type_map = new StructureInstance (typeMapStructureInfo, map); + module.AddGlobalVariable (TypeMapSymbol, type_map, LlvmIrVariableOptions.GlobalConstant); + + if (managedToJavaMap.Count > 0) { + module.AddGlobalVariable (ManagedToJavaSymbol, managedToJavaMap, LlvmIrVariableOptions.LocalConstant); + } + + if (javaToManagedMap.Count > 0) { + module.AddGlobalVariable (JavaToManagedSymbol, javaToManagedMap, LlvmIrVariableOptions.LocalConstant); + } + + module.AddGlobalVariable (UniqueAssembliesSymbol, uniqueAssemblies, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable (AssemblyNamesBlobSymbol, assemblyNamesBlob, LlvmIrVariableOptions.GlobalConstant); + } + + void MapStructures (LlvmIrModule module) + { + typeMapAssemblyStructureInfo = module.MapStructure (); + typeMapEntryStructureInfo = module.MapStructure (); + typeMapStructureInfo = module.MapStructure (); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index b48988a65e7..9333ac64189 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -338,10 +338,21 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - - <_AndroidAotStripLibraries Condition=" '$(_AndroidAotStripLibraries)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True + false true + <_AndroidUseAssemblyStore>$(AndroidUseAssemblyStore) + + + + + <_AndroidUseAssemblyStore Condition="'$(EmbedAssembliesIntoApk)' == 'true' ">true + <_AndroidUseAssemblyStore Condition="'$(EmbedAssembliesIntoApk)' != 'true' ">false + + + + <_AndroidAotStripLibraries Condition=" '$(_AndroidAotStripLibraries)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True True False True @@ -1728,7 +1739,7 @@ because xbuild doesn't support framework reference assemblies. .so;$(AndroidStoreUncompressedFileExtensions) .dex;$(AndroidStoreUncompressedFileExtensions) - .blob;$(AndroidStoreUncompressedFileExtensions) + .blob;$(AndroidStoreUncompressedFileExtensions) @@ -1888,7 +1899,7 @@ because xbuild doesn't support framework reference assemblies. PackageNamingPolicy="$(AndroidPackageNamingPolicy)" BoundExceptionType="$(AndroidBoundExceptionType)" RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)" - UseAssemblyStore="$(AndroidUseAssemblyStore)" + UseAssemblyStore="$(_AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" EnableManagedMarshalMethodsLookup="$(_AndroidUseManagedMarshalMethodsLookup)" CustomBundleConfigFile="$(AndroidBundleConfigurationFile)" @@ -2302,7 +2313,7 @@ because xbuild doesn't support framework reference assemblies. ResolvedFrameworkAssemblies="@(_BuildApkResolvedFrameworkAssemblies)" ResolvedUserAssemblies="@(_BuildApkResolvedUserAssemblies)" SupportedAbis="@(_BuildTargetAbis)" - UseAssemblyStore="$(AndroidUseAssemblyStore)"> + UseAssemblyStore="$(_AndroidUseAssemblyStore)"> @@ -2313,7 +2324,7 @@ because xbuild doesn't support framework reference assemblies. IntermediateOutputPath="$(IntermediateOutputPath)" ResolvedAssemblies="@(_BuildApkAssembliesToAddToArchive)" SupportedAbis="@(_BuildTargetAbis)" - UseAssemblyStore="$(AndroidUseAssemblyStore)" + UseAssemblyStore="$(_AndroidUseAssemblyStore)" RuntimePackLibraryDirectories="@(_RuntimePackLibraryDirectory)"> diff --git a/src/native/clr/host/assembly-store.cc b/src/native/clr/host/assembly-store.cc index db5dfc94328..402584d09df 100644 --- a/src/native/clr/host/assembly-store.cc +++ b/src/native/clr/host/assembly-store.cc @@ -176,7 +176,17 @@ auto AssemblyStore::find_assembly_store_entry (hash_t hash, const AssemblyStoreI auto AssemblyStore::open_assembly (std::string_view const& name, int64_t &size) noexcept -> void* { hash_t name_hash = xxhash::hash (name.data (), name.length ()); - log_debug (LOG_ASSEMBLY, "assembly_store_open_from_bundles: looking for bundled name: '{}' (hash {:x})", optional_string (name.data ()), name_hash); + log_debug (LOG_ASSEMBLY, "AssemblyStore::open_assembly: looking for bundled name: '{}' (hash {:x})", optional_string (name.data ()), name_hash); + + if constexpr (Constants::is_debug_build) { + // TODO: implement filesystem lookup here + + // In fastdev mode we might not have any assembly store. + if (assembly_store_hashes == nullptr) { + log_warn (LOG_ASSEMBLY, "Assembly store not registered. Unable to look up assembly '{}'", name); + return nullptr; + } + } const AssemblyStoreIndexEntry *hash_entry = find_assembly_store_entry (name_hash, assembly_store_hashes, assembly_store.index_entry_count); if (hash_entry == nullptr) { diff --git a/src/native/clr/host/typemap.cc b/src/native/clr/host/typemap.cc index b2ce3bff8cd..43194203261 100644 --- a/src/native/clr/host/typemap.cc +++ b/src/native/clr/host/typemap.cc @@ -64,10 +64,64 @@ namespace { } #if defined(DEBUG) +[[gnu::always_inline]] +auto TypeMapper::typemap_type_to_type_debug (const char *typeName, const TypeMapEntry *map, std::string_view const& from_name, std::string_view const& to_name) noexcept -> const char* +{ + log_debug (LOG_ASSEMBLY, "Looking up {} type '{}'", from_name, optional_string (typeName)); + auto equal = [](TypeMapEntry const& entry, const char *key) -> bool { + if (entry.from == nullptr) { + return 1; + } + + return strcmp (entry.from, key) == 0; + }; + + auto less_than = [](TypeMapEntry const& entry, const char *key) -> bool { + if (entry.from == nullptr) { + return 1; + } + + return strcmp (entry.from, key) < 0; + }; + + ssize_t idx = Search::binary_search (typeName, map, type_map.entry_count); + if (idx >= 0) [[likely]] { + log_debug ( + LOG_ASSEMBLY, + "{} type '{}' maps to {} type '{}'", + from_name, + optional_string (typeName), + to_name, + optional_string (type_map.managed_to_java[idx].to) + ); + return type_map.managed_to_java[idx].to; + } + + return nullptr; +} + [[gnu::always_inline]] auto TypeMapper::typemap_managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char* { - Helpers::abort_application ("TypeMap support for Debug builds not implemented yet"sv); + dynamic_local_path_string full_type_name; + full_type_name.append (typeName); + + hash_t mvid_hash = xxhash::hash (mvid, 16z); // we must hope managed land called us with valid data + + auto equal = [](TypeMapAssembly const& entry, hash_t key) -> bool { return entry.mvid_hash == key; }; + auto less_than = [](TypeMapAssembly const& entry, hash_t key) -> bool { return entry.mvid_hash < key; }; + ssize_t idx = Search::binary_search (mvid_hash, type_map_unique_assemblies, type_map.unique_assemblies_count); + + if (idx >= 0) [[likely]] { + TypeMapAssembly const& assm = type_map_unique_assemblies[idx]; + full_type_name.append (", "sv); + full_type_name.append (&type_map_assembly_names_blob[assm.name_offset], assm.name_length); + log_debug (LOG_ASSEMBLY, "Fixed-up type name: '{}'", full_type_name.get ()); + } else { + log_warn (LOG_ASSEMBLY, "Unable to look up assembly name for type '{}', trying without it.", typeName); + } + + return typemap_type_to_type_debug (full_type_name.get (), type_map.managed_to_java, MANAGED, JAVA); } #endif // def DEBUG @@ -210,6 +264,7 @@ auto TypeMapper::typemap_managed_to_java_release (const char *typeName, const ui [[gnu::flatten]] auto TypeMapper::typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char* { + log_debug (LOG_ASSEMBLY, "typemap_managed_to_java: looking up type '{}'", optional_string (typeName)); if (FastTiming::enabled ()) [[unlikely]] { internal_timing.start_event (TimingEventKind::ManagedToJava); } @@ -235,9 +290,12 @@ auto TypeMapper::typemap_managed_to_java (const char *typeName, const uint8_t *m #if defined(DEBUG) [[gnu::flatten]] -auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +auto TypeMapper::typemap_java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool { - Helpers::abort_application ("typemap_java_to_managed not implemented for debug builds yet"); + // FIXME: this is currently VERY broken + *assembly_name = nullptr; + *managed_type_token_id = 0; + return typemap_type_to_type_debug (java_type_name, type_map.java_to_managed, JAVA, MANAGED); } #else // def DEBUG @@ -253,12 +311,8 @@ auto TypeMapper::find_java_to_managed_entry (hash_t name_hash) noexcept -> const } [[gnu::flatten]] -auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +auto TypeMapper::typemap_java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool { - if (FastTiming::enabled ()) [[unlikely]] { - internal_timing.start_event (TimingEventKind::JavaToManaged); - } - if (java_type_name == nullptr || assembly_name == nullptr || managed_type_token_id == nullptr) [[unlikely]] { if (java_type_name == nullptr) { log_warn ( @@ -316,10 +370,33 @@ auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const optional_string (*assembly_name) ); + return true; +} +#endif // ndef DEBUG + +[[gnu::flatten]] +auto TypeMapper::typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool +{ + log_debug (LOG_ASSEMBLY, "typemap_java_to_managed: looking up type '{}'", optional_string (java_type_name)); + if (FastTiming::enabled ()) [[unlikely]] { + internal_timing.start_event (TimingEventKind::JavaToManaged); + } + + if (java_type_name == nullptr) [[unlikely]] { + log_warn (LOG_ASSEMBLY, "typemap: type name not specified in typemap_java_to_managed"); + return false; + } + + bool ret; +#if defined(RELEASE) + ret = typemap_java_to_managed_release (java_type_name, assembly_name, managed_type_token_id); +#else + ret = typemap_java_to_managed_debug (java_type_name, assembly_name, managed_type_token_id); +#endif + if (FastTiming::enabled ()) [[unlikely]] { internal_timing.end_event (); } - return true; + return ret; } -#endif // ndef DEBUG diff --git a/src/native/clr/include/host/typemap.hh b/src/native/clr/include/host/typemap.hh index f695f0ce5d4..fa15db569cf 100644 --- a/src/native/clr/include/host/typemap.hh +++ b/src/native/clr/include/host/typemap.hh @@ -1,6 +1,7 @@ #pragma once #include +#include #include "../runtime-base/logger.hh" #include @@ -9,9 +10,12 @@ namespace xamarin::android { class TypeMapper { + static constexpr std::string_view MANAGED { "Managed" }; + static constexpr std::string_view JAVA { "Java" }; + public: static auto typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept -> const char*; - static auto typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_doken_id) noexcept -> bool; + static auto typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; private: #if defined(RELEASE) @@ -19,10 +23,13 @@ namespace xamarin::android { static auto find_module_entry (const uint8_t *mvid, const TypeMapModule *entries, size_t entry_count) noexcept -> const TypeMapModule*; static auto find_managed_to_java_map_entry (hash_t name_hash, const TypeMapModuleEntry *map, size_t entry_count) noexcept -> const TypeMapModuleEntry*; static auto typemap_managed_to_java_release (const char *typeName, const uint8_t *mvid) noexcept -> const char*; + static auto typemap_java_to_managed_release (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; static auto find_java_to_managed_entry (hash_t name_hash) noexcept -> const TypeMapJava*; #else + static auto typemap_type_to_type_debug (const char *typeName, const TypeMapEntry *map, std::string_view const& from_name, std::string_view const& to_name) noexcept -> const char*; static auto typemap_managed_to_java_debug (const char *typeName, const uint8_t *mvid) noexcept -> const char*; + static auto typemap_java_to_managed_debug (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept -> bool; #endif }; } diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 5b2ad4cd963..b8717e5e7eb 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -62,15 +62,22 @@ struct TypeMapEntry const char *to; }; -// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs +// MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs struct TypeMap { uint32_t entry_count; - char *assembly_name; - uint8_t *data; + uint64_t unique_assemblies_count; + uint64_t assembly_names_blob_size; const TypeMapEntry *java_to_managed; const TypeMapEntry *managed_to_java; }; + +struct TypeMapAssembly +{ + xamarin::android::hash_t mvid_hash; + uint64_t name_length; + uint64_t name_offset; // into the assembly names blob +}; #else struct TypeMapModuleEntry { @@ -312,7 +319,9 @@ extern "C" { [[gnu::visibility("default")]] extern const uint64_t format_tag; #if defined (DEBUG) - [[gnu::visibility("default")]] extern const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs + [[gnu::visibility("default")]] extern const TypeMap type_map; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs + [[gnu::visibility("default")]] extern const TypeMapAssembly type_map_unique_assemblies[]; + [[gnu::visibility("default")]] extern const char type_map_assembly_names_blob[]; #else [[gnu::visibility("default")]] extern const uint32_t managed_to_java_map_module_count; [[gnu::visibility("default")]] extern const uint32_t java_type_count; diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc index e16929d2c3a..146414e0dc4 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -15,12 +15,15 @@ static TypeMapEntry managed_to_java[] = {}; // MUST match src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs const TypeMap type_map = { - 0, - nullptr, - nullptr, - java_to_managed, - managed_to_java + .entry_count = 0, + .unique_assemblies_count = 0, + .assembly_names_blob_size = 0, + .java_to_managed = java_to_managed, + .managed_to_java = managed_to_java, }; + +const TypeMapAssembly type_map_unique_assemblies[] = {}; +const char type_map_assembly_names_blob[] = {}; #else const uint32_t managed_to_java_map_module_count = 0; const uint32_t java_type_count = 0; diff --git a/src/native/common/include/shared/xxhash.hh b/src/native/common/include/shared/xxhash.hh index 32365aceab0..a2760604862 100644 --- a/src/native/common/include/shared/xxhash.hh +++ b/src/native/common/include/shared/xxhash.hh @@ -163,8 +163,8 @@ namespace xamarin::android class xxhash64 final { public: - [[gnu::always_inline]] - static auto hash (const char *p, size_t len) noexcept -> XXH64_hash_t + template [[gnu::always_inline]] + static auto hash (const T *p, size_t len) noexcept -> XXH64_hash_t { return XXH3_64bits (static_cast(p), len); } diff --git a/src/native/common/runtime-base/timing-internal.cc b/src/native/common/runtime-base/timing-internal.cc index 30cc690821e..ebf8d111893 100644 --- a/src/native/common/runtime-base/timing-internal.cc +++ b/src/native/common/runtime-base/timing-internal.cc @@ -26,15 +26,16 @@ void FastTiming::really_initialize (bool log_immediately) noexcept open_sequences.push (0); open_sequences.pop (); + // Options in `debug.mono.timing` are relevant only when immediate logging is disabled + if (immediate_logging) { + return; + } + dynamic_local_property_string value; if (AndroidSystem::monodroid_get_system_property (Constants::DEBUG_MONO_TIMING, value) != 0) { internal_timing.parse_options (value); } - if (immediate_logging) { - return; - } - log_write ( LOG_TIMING, LogLevel::Info, diff --git a/src/native/native.targets b/src/native/native.targets index 10bb1f372f1..df70d1cab5f 100644 --- a/src/native/native.targets +++ b/src/native/native.targets @@ -38,6 +38,7 @@ <_ConfigureRuntimesInputs Include="common\libstub\CMakeLists.txt" /> <_ConfigureRuntimesInputs Include="common\libunwind\CMakeLists.txt" /> <_ConfigureRuntimesInputs Include="common\lz4\CMakeLists.txt" /> + <_ConfigureRuntimesInputs Include="common\runtime-base\CMakeLists.txt" /> <_ConfigureRuntimesOutputs Include="@(AndroidSupportedTargetJitAbi->'$(FlavorIntermediateOutputPath)\%(AndroidRID)-Debug\CMakeCache.txt')" /> <_ConfigureRuntimesOutputs Include="@(AndroidSupportedTargetJitAbi->'$(FlavorIntermediateOutputPath)\%(AndroidRID)-Release\CMakeCache.txt')" /> @@ -171,6 +172,7 @@ <_RuntimeSources Include="common\archive-dso-stub\*.cc" /> <_RuntimeSources Include="common\include\**\*.hh" /> <_RuntimeSources Include="common\libstub\*.cc;common\libstub\*.hh" /> + <_RuntimeSources Include="common\runtime-base\*.cc" /> <_RuntimeSources Include="$(LZ4SourceFullPath)\lib\lz4.c;$(LZ4SourceFullPath)\lib\lz4.h" />