Skip to content

Commit 288f107

Browse files
authored
[CoreCLR] support for Debug managed-to-Java typemaps (#10065)
Context: 684ede6 Context: b11471b Context: 7117414 Context: c227042 Context: a0a837a Context: ea38c0c From b11471b: > `Java.Interop.JniRuntime.JniTypeManager` supports bi-directional > mapping between JNI type signatures and managed `Type`s: > > * `GetTypeSignature(Type)` returns the JNI type signature that > corresponds to the `System.Type`. > * `GetType(JniTypeSignature)` returns the `Type` that corresponds > to a JNI type signature. We have had *multiple* ways to perform such mappings, including: * Fully managed (Release config): Processes trimmed assemblies, creates app-global mappings within `Mono.Android.dll`. Required for NativeAOT, optional with CoreCLR, MonoVM. Commits 684ede6, b11471b * MonoVM Instant Run (Debug config): Per-assembly mapping data, fast deployed to to the target device. Removed in commits 8298b6f, 17e2e8e. * MonoVM libxamarin-app.so (Debug config): Processes *un-trimmed* assemblies, creates app-global *string-based* type mappings within LLVM-IR, so that mappings don't need to be rebuilt as often when assemblies are changed. Commits 7117414, c227042 * MonoVM libxamarin-app.so (Release config): Processes trimmed assemblies, creates app-global *assembly-based* type mappings within LLVM-IR, relying on assembly name hashes, assembly MVIDs, and type token IDs. Commits c227042 * CoreCLR libxamarin-app.so (Release config): Processes trimmed assemblies, creates app-global *assembly-based* type mappings within LLVM-IR, relying on assembly name hashes, assembly MVIDs, and type token IDs. Commits a0a837a, ea38c0c "Missing" from this list is support for Debug config CoreCLR builds. Begin implementing managed-to-Java lookups for Debug config CoreCLR builds by 1. Updating `libxamarin-app.so` generation to emit data similar to the "MonoVM libxamarin-app.so (Debug config)" data structures, & 2. Update `clr_typemap_managed_to_java()` / `TypeMapper::typemap_managed_to_java()` within `libmono-android.debug.so` to use the Debug config data in (1). This allows (mostly) untrimmed assemblies to be used with typemap data. Additionally, the managed-to-Java lookup tables use the assembly-qualified managed type name; introduce a step to "fix up" the passed managed type name by appending the assembly name, which is obtained by searching for MVID match in the MVID-to-name lookup table.
1 parent 651244c commit 288f107

File tree

17 files changed

+503
-57
lines changed

17 files changed

+503
-57
lines changed

src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
3737

3838
var xml = new TypeMapObjectsXmlFile {
3939
AssemblyName = assembly.Name.Name,
40+
AssemblyMvid = assembly.MainModule.Mvid,
4041
};
4142

4243
if (Debug) {
43-
var (javaToManaged, managedToJava, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context);
44+
var (typeMapDebugSets, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context, needUniqueAssemblies: false);
4445

45-
xml.JavaToManagedDebugEntries.AddRange (javaToManaged);
46-
xml.ManagedToJavaDebugEntries.AddRange (managedToJava);
46+
xml.JavaToManagedDebugEntries.AddRange (typeMapDebugSets.JavaToManaged);
47+
xml.ManagedToJavaDebugEntries.AddRange (typeMapDebugSets.ManagedToJava);
4748
xml.FoundJniNativeRegistration = foundJniNativeRegistration;
4849
} else {
4950
var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration);

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public void BuildLibraryWithAssetPack ([Values (true, false)] bool isRelease)
3838
[Category ("SmokeTests")]
3939
[TestCase (false, AndroidRuntime.MonoVM)]
4040
[TestCase (true, AndroidRuntime.MonoVM)]
41+
[TestCase (false, AndroidRuntime.CoreCLR)]
4142
[TestCase (true, AndroidRuntime.CoreCLR)]
4243
[TestCase (true, AndroidRuntime.NativeAOT)]
4344
public void BuildApplicationWithAssetPackThatHasInvalidName (bool isRelease, AndroidRuntime runtime)
@@ -67,6 +68,7 @@ public void BuildApplicationWithAssetPackThatHasInvalidName (bool isRelease, And
6768
[Category ("SmokeTests")]
6869
[TestCase (false, AndroidRuntime.MonoVM)]
6970
[TestCase (true, AndroidRuntime.MonoVM)]
71+
[TestCase (false, AndroidRuntime.CoreCLR)]
7072
[TestCase (true, AndroidRuntime.CoreCLR)]
7173
[TestCase (true, AndroidRuntime.NativeAOT)]
7274
public void BuildApplicationWithAssetPackOutsideProjectDirectory (bool isRelease, AndroidRuntime runtime)
@@ -117,6 +119,7 @@ public void BuildApplicationWithAssetPackOutsideProjectDirectory (bool isRelease
117119
[Category ("SmokeTests")]
118120
[TestCase (false, AndroidRuntime.MonoVM)]
119121
[TestCase (true, AndroidRuntime.MonoVM)]
122+
[TestCase (false, AndroidRuntime.CoreCLR)]
120123
[TestCase (true, AndroidRuntime.CoreCLR)]
121124
[TestCase (true, AndroidRuntime.NativeAOT)]
122125
public void BuildApplicationWithAssetPackOverrides (bool isRelease, AndroidRuntime runtime)
@@ -160,6 +163,7 @@ public void BuildApplicationWithAssetPackOverrides (bool isRelease, AndroidRunti
160163
[Category ("SmokeTests")]
161164
[TestCase (false, AndroidRuntime.MonoVM)]
162165
[TestCase (true, AndroidRuntime.MonoVM)]
166+
[TestCase (false, AndroidRuntime.CoreCLR)]
163167
[TestCase (true, AndroidRuntime.CoreCLR)]
164168
[TestCase (true, AndroidRuntime.NativeAOT)]
165169
public void BuildApplicationWithAssetPack (bool isRelease, AndroidRuntime runtime)

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.TestCaseSource.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ public partial class BuildTest : BaseTest
132132
/* usesAssemblyStore */ true,
133133
/* runtime */ AndroidRuntime.CoreCLR,
134134
},
135+
new object [] {
136+
/* runtimeIdentifiers */ "android-arm64",
137+
/* isRelease */ false,
138+
/* aot */ false,
139+
/* usesAssemblyStore */ true,
140+
/* runtime */ AndroidRuntime.CoreCLR,
141+
},
135142
new object [] {
136143
/* runtimeIdentifiers */ "android-arm64",
137144
/* isRelease */ true,

src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,27 @@
77
using ReleaseGenerationState = Xamarin.Android.Tasks.TypeMapGenerator.ReleaseGenerationState;
88
using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry;
99
using TypeMapReleaseEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapReleaseEntry;
10+
using TypeMapDebugDataSets = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugDataSets;
11+
using TypeMapDebugAssembly = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugAssembly;
1012

1113
namespace Xamarin.Android.Tasks;
1214

1315
// Converts types from Mono.Cecil to the format used by the typemap generator.
1416
class TypeMapCecilAdapter
1517
{
16-
public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries (NativeCodeGenState state)
18+
public static TypeMapDebugDataSets GetDebugNativeEntries (NativeCodeGenState state, bool needUniqueAssemblies)
1719
{
18-
var (javaToManaged, managedToJava, foundJniNativeRegistration) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache);
20+
var (dataSets, foundJniNativeRegistration) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache, needUniqueAssemblies);
1921

2022
state.JniAddNativeMethodRegistrationAttributePresent = foundJniNativeRegistration;
2123

22-
return (javaToManaged, managedToJava);
24+
return dataSets;
2325
}
2426

25-
public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava, bool foundJniNativeRegistration) GetDebugNativeEntries (List<TypeDefinition> types, TypeDefinitionCache cache)
27+
public static (TypeMapDebugDataSets dataSets, bool foundJniNativeRegistration) GetDebugNativeEntries (List<TypeDefinition> types, TypeDefinitionCache cache, bool needUniqueAssemblies)
2628
{
2729
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
30+
var uniqueAssemblies = needUniqueAssemblies ? new Dictionary<string, TypeMapDebugAssembly> (StringComparer.OrdinalIgnoreCase) : null;
2831
var javaToManaged = new List<TypeMapDebugEntry> ();
2932
var managedToJava = new List<TypeMapDebugEntry> ();
3033
var foundJniNativeRegistration = false;
@@ -37,11 +40,31 @@ public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> ma
3740

3841
javaToManaged.Add (entry);
3942
managedToJava.Add (entry);
43+
44+
if (uniqueAssemblies == null) {
45+
continue;
46+
}
47+
48+
string? asmName = td.Module.Assembly.Name.Name;
49+
if (String.IsNullOrEmpty (asmName) || uniqueAssemblies.ContainsKey (asmName)) {
50+
continue;
51+
}
52+
53+
var asmInfo = new TypeMapDebugAssembly {
54+
MVID = td.Module.Mvid,
55+
Name = asmName,
56+
};
57+
asmInfo.MVIDBytes = asmInfo.MVID.ToByteArray ();
58+
uniqueAssemblies.Add (asmName, asmInfo);
4059
}
4160

4261
SyncDebugDuplicates (javaDuplicates);
4362

44-
return (javaToManaged, managedToJava, foundJniNativeRegistration);
63+
return (new TypeMapDebugDataSets {
64+
JavaToManaged = javaToManaged,
65+
ManagedToJava = managedToJava,
66+
UniqueAssemblies = uniqueAssemblies != null ? new List<TypeMapDebugAssembly> (uniqueAssemblies.Values) : null
67+
}, foundJniNativeRegistration);
4568
}
4669

4770
public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenState state)
@@ -204,7 +227,7 @@ static bool JniAddNativeMethodRegistrationAttributeFound (bool alreadyFound, Typ
204227
if (alreadyFound || !javaType.HasCustomAttributes) {
205228
return alreadyFound;
206229
}
207-
230+
208231
foreach (CustomAttribute ca in javaType.CustomAttributes) {
209232
if (string.Equals ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal)) {
210233
return true;

src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,27 @@ public override string ToString ()
8383
}
8484
}
8585

86+
internal sealed class TypeMapDebugAssembly
87+
{
88+
public Guid MVID;
89+
public byte[] MVIDBytes;
90+
public string Name;
91+
}
92+
93+
internal sealed class TypeMapDebugDataSets
94+
{
95+
public List<TypeMapDebugEntry> JavaToManaged ;
96+
public List<TypeMapDebugEntry> ManagedToJava;
97+
public List<TypeMapDebugAssembly>? UniqueAssemblies;
98+
}
99+
86100
// Widths include the terminating nul character but not the padding!
87101
internal sealed class ModuleDebugData
88102
{
89103
public uint EntryCount;
90104
public List<TypeMapDebugEntry> JavaToManagedMap;
91105
public List<TypeMapDebugEntry> ManagedToJavaMap;
106+
public List<TypeMapDebugAssembly>? UniqueAssemblies;
92107
}
93108

94109
internal sealed class ReleaseGenerationState
@@ -138,18 +153,23 @@ public void Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAt
138153

139154
void GenerateDebugNativeAssembly (string outputDirectory)
140155
{
141-
(var javaToManaged, var managedToJava) = state.GetDebugNativeEntries ();
156+
TypeMapDebugDataSets dataSets = state.GetDebugNativeEntries (needUniqueAssemblies: runtime == AndroidRuntime.CoreCLR);
142157

143158
var data = new ModuleDebugData {
144-
EntryCount = (uint)javaToManaged.Count,
145-
JavaToManagedMap = javaToManaged,
146-
ManagedToJavaMap = managedToJava,
159+
EntryCount = (uint)dataSets.JavaToManaged.Count,
160+
JavaToManagedMap = dataSets.JavaToManaged,
161+
ManagedToJavaMap = dataSets.ManagedToJava,
162+
UniqueAssemblies = dataSets.UniqueAssemblies,
147163
};
148164

149165
data.JavaToManagedMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.JavaName, b.JavaName, StringComparison.Ordinal));
150166
data.ManagedToJavaMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.ManagedName, b.ManagedName, StringComparison.Ordinal));
151167

152-
var composer = new TypeMappingDebugNativeAssemblyGenerator (log, data);
168+
LLVMIR.LlvmIrComposer composer = runtime switch {
169+
AndroidRuntime.MonoVM => new TypeMappingDebugNativeAssemblyGenerator (log, data),
170+
AndroidRuntime.CoreCLR => new TypeMappingDebugNativeAssemblyGeneratorCLR (log, data),
171+
_ => throw new NotSupportedException ($"Internal error: unsupported runtime {runtime}")
172+
};
153173
GenerateNativeAssembly (composer, composer.Construct (), outputDirectory);
154174
}
155175

@@ -216,7 +236,7 @@ interface ITypeMapGeneratorAdapter
216236
{
217237
AndroidTargetArch TargetArch { get; }
218238
bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
219-
(List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries ();
239+
TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies);
220240
ReleaseGenerationState GetReleaseGenerationState ();
221241
}
222242

@@ -236,9 +256,9 @@ public bool JniAddNativeMethodRegistrationAttributePresent {
236256
set => state.JniAddNativeMethodRegistrationAttributePresent = value;
237257
}
238258

239-
public (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries ()
259+
public TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies)
240260
{
241-
return TypeMapCecilAdapter.GetDebugNativeEntries (state);
261+
return TypeMapCecilAdapter.GetDebugNativeEntries (state, needUniqueAssemblies);
242262
}
243263

244264
public ReleaseGenerationState GetReleaseGenerationState ()
@@ -259,12 +279,22 @@ public TypeMapObjectsFileAdapter (AndroidTargetArch targetArch)
259279

260280
public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
261281

262-
public (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries ()
282+
public TypeMapDebugDataSets GetDebugNativeEntries (bool needUniqueAssemblies)
263283
{
264284
var javaToManaged = new List<TypeMapDebugEntry> ();
265285
var managedToJava = new List<TypeMapDebugEntry> ();
286+
var uniqueAssemblies = new Dictionary<string, TypeMapDebugAssembly> (StringComparer.OrdinalIgnoreCase);
266287

267288
foreach (var xml in XmlFiles) {
289+
if (!uniqueAssemblies.ContainsKey (xml.AssemblyName)) {
290+
var assm = new TypeMapDebugAssembly {
291+
MVID = xml.AssemblyMvid,
292+
MVIDBytes = xml.AssemblyMvid.ToByteArray (),
293+
Name = xml.AssemblyName,
294+
};
295+
uniqueAssemblies.Add (xml.AssemblyName, assm);
296+
}
297+
268298
javaToManaged.AddRange (xml.JavaToManagedDebugEntries);
269299
managedToJava.AddRange (xml.ManagedToJavaDebugEntries);
270300
}
@@ -273,7 +303,11 @@ public TypeMapObjectsFileAdapter (AndroidTargetArch targetArch)
273303
GroupDuplicateDebugEntries (javaToManaged);
274304
GroupDuplicateDebugEntries (managedToJava);
275305

276-
return (javaToManaged, managedToJava);
306+
return new TypeMapDebugDataSets {
307+
JavaToManaged = javaToManaged,
308+
ManagedToJava = managedToJava,
309+
UniqueAssemblies = uniqueAssemblies.Values.ToList (),
310+
};
277311
}
278312

279313
void GroupDuplicateDebugEntries (List<TypeMapDebugEntry> debugEntries)

src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class TypeMapObjectsXmlFile
2525
static readonly TypeMapObjectsXmlFile unscanned = new TypeMapObjectsXmlFile { WasScanned = false };
2626

2727
public string? AssemblyName { get; set; }
28+
public Guid AssemblyMvid { get; set; } = Guid.Empty;
2829
public bool FoundJniNativeRegistration { get; set; }
2930
public List<TypeMapDebugEntry> JavaToManagedDebugEntries { get; } = [];
3031
public List<TypeMapDebugEntry> ManagedToJavaDebugEntries { get; } = [];
@@ -58,6 +59,10 @@ void Export (XmlWriter xml)
5859
xml.WriteStartElement ("api");
5960
xml.WriteAttributeString ("type", HasDebugEntries ? "debug" : "release");
6061
xml.WriteAttributeStringIfNotDefault ("assembly-name", AssemblyName);
62+
63+
if (AssemblyMvid != Guid.Empty) {
64+
xml.WriteAttributeString ("mvid", AssemblyMvid.ToString ("N"));
65+
}
6166
xml.WriteAttributeStringIfNotDefault ("found-jni-native-registration", FoundJniNativeRegistration);
6267

6368
if (HasDebugEntries)
@@ -173,11 +178,13 @@ public static TypeMapObjectsXmlFile Import (string filename)
173178

174179
var type = root.GetRequiredAttribute ("type");
175180
var assemblyName = root.GetAttributeOrDefault ("assembly-name", (string?)null);
181+
var mvid = Guid.Parse (root.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ()));
176182
var foundJniNativeRegistration = root.GetAttributeOrDefault ("found-jni-native-registration", false);
177183

178184
var file = new TypeMapObjectsXmlFile {
179185
WasScanned = true,
180186
AssemblyName = assemblyName,
187+
AssemblyMvid = mvid,
181188
FoundJniNativeRegistration = foundJniNativeRegistration,
182189
};
183190

src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class TypeMappingDebugNativeAssemblyGenerator : LlvmIrComposer
1313
{
1414
const string JavaToManagedSymbol = "map_java_to_managed";
1515
const string ManagedToJavaSymbol = "map_managed_to_java";
16-
const string TypeMapSymbol = "type_map"; // MUST match src/monodroid/xamarin-app.hh
16+
const string TypeMapSymbol = "type_map"; // MUST match src/native/mono/xamarin-app-stub/xamarin-app.hh
1717

1818
sealed class TypeMapContextDataProvider : NativeAssemblerStructContextDataProvider
1919
{
@@ -66,19 +66,19 @@ public override string GetComment (object data, string fieldName)
6666
var entry = EnsureType<TypeMapEntry> (data);
6767

6868
if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) {
69-
return $"from: entry.from";
69+
return $"from: {entry.from}";
7070
}
7171

7272
if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) {
73-
return $"to: entry.to";
73+
return $"to: {entry.to}";
7474
}
7575

7676
return String.Empty;
7777
}
7878
}
7979

8080
// Order of fields and their type must correspond *exactly* to that in
81-
// src/monodroid/jni/xamarin-app.hh TypeMapEntry structure
81+
// src/native/mono/xamarin-app-stub/xamarin-app.hh TypeMapEntry structure
8282
[NativeAssemblerStructContextDataProvider (typeof (TypeMapEntryContextDataProvider))]
8383
sealed class TypeMapEntry
8484
{
@@ -87,7 +87,7 @@ sealed class TypeMapEntry
8787
};
8888

8989
// Order of fields and their type must correspond *exactly* to that in
90-
// src/monodroid/jni/xamarin-app.hh TypeMap structure
90+
// src/native/mono/xamarin-app-stub/xamarin-app.hh TypeMap structure
9191
[NativeAssemblerStructContextDataProvider (typeof (TypeMapContextDataProvider))]
9292
sealed class TypeMap
9393
{

0 commit comments

Comments
 (0)