Skip to content

Commit b1e179a

Browse files
committed
New code to perform managed <-> java lookups (typemap)
Xamarin.Assembly needs to "translate" managed types to Java types and vice versa in order to provide a bridge between the two world. So far it has been done using a straightforward (and fast) method of performing the lookups - all the type pairs were stored in two tables of the same size, with all type names padded to the width of the longest name so that the `bsearch` C function can be used to quickly perform a binary search over the data set. This approach works very well at the expense of data size (shorter strings are 0-padded to the maximum width) and a slightly degraded performace because of the requirement to perform string comparisons. Furthermore, the lookup required that reflection is used to obtain full managed type name (when translating from managed to Java) or to get a `Type` instance from type name (when translating from Java to managed). For Release builds all the above data is placed in the `libxamarin-app.so` library, for Debug builds it is also placed in two files - one for each direction of lookup, described above. This commit is a slight improvement over the above scheme. It eliminates reflection from the process by using managed type tokens (which are integers) and using UUID/Guid of the module in which the type is found. This allows us to perform the binary search over the set of 20 bytes (16 bytes for the UUID and 4 bytes for the token ID) for managed to Java lookups and a single string comparison + binary search over a set of integers for the Java to managed lookup. Java type names must still be used because Java doesn't provide any equivalent to the .NET's type token and module UUID. Those names are still 0-padded to the width of the longest name but there are no longer duplicated. Managed type names are eliminated completely. If Xamarin.Android Instant Run is not used (which is the case for OSS code) for Debug builds, the operation is performed in the same way for both Release and Debug builds. If, however, Instant Run is in effect, the type maps are stored in several files with the .typemap extension - one per **module**. The files contain both the Java to managed maps as well as managed to Java maps (which use indexes into the Java to managed maps). All of those files are loaded during Debug app startup and used to construct a dataset which is the searched during all the lookups. TODO: - Performance changes - Size changes: - monodroid PR
1 parent 8d7557a commit b1e179a

30 files changed

+1593
-648
lines changed

.external

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
xamarin/monodroid:master@0f6725edfa09559afad58233d43f385122e31e8d
1+
grendello/monodroid:new-typemap@efe3956ce2e557b5f8286f984731e43c6ee402f7
22
mono/mono:2019-10@18920a83f423fb864a2263948737681968f5b2c8

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
234234
foreach (var simpleRef in base.GetSimpleReferences (type)) {
235235
yield return simpleRef;
236236
}
237-
var j = JNIEnv.monodroid_typemap_managed_to_java (type.FullName + ", " + type.Assembly.GetName ().Name);
237+
var j = JNIEnv.monodroid_typemap_managed_to_java (type.Module.ModuleVersionId.ToByteArray (), type.MetadataToken);
238238
if (j != IntPtr.Zero) {
239239
yield return Marshal.PtrToStringAnsi (j);
240240
}

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,13 +632,13 @@ public static string GetClassNameFromInstance (IntPtr jobject)
632632
}
633633

634634
[DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)]
635-
internal static extern IntPtr monodroid_typemap_managed_to_java (string managed);
635+
internal static extern IntPtr monodroid_typemap_managed_to_java (byte[] mvid, int token);
636636

637637
public static string GetJniName (Type type)
638638
{
639639
if (type == null)
640640
throw new ArgumentNullException ("type");
641-
var java = monodroid_typemap_managed_to_java (type.FullName + ", " + type.Assembly.GetName ().Name);
641+
var java = monodroid_typemap_managed_to_java (type.Module.ModuleVersionId.ToByteArray (), type.MetadataToken);
642642
return java == IntPtr.Zero
643643
? JavaNativeTypeManager.ToJniName (type)
644644
: Marshal.PtrToStringAnsi (java);

src/Mono.Android/Java.Interop/TypeManager.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Reflection;
44
using System.Linq;
5+
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67
using Java.Interop.Tools.TypeNameMappings;
78

@@ -203,22 +204,22 @@ static Exception CreateJavaLocationException ()
203204
return new JavaLocationException (loc.ToString ());
204205
}
205206

206-
[DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)]
207-
internal static extern IntPtr monodroid_typemap_java_to_managed (string java);
207+
[MethodImplAttribute(MethodImplOptions.InternalCall)]
208+
static extern Type monodroid_typemap_java_to_managed (string java_type_name);
208209

209210
internal static Type GetJavaToManagedType (string class_name)
210211
{
211-
var t = monodroid_typemap_java_to_managed (class_name);
212-
if (t != IntPtr.Zero)
213-
return Type.GetType (Marshal.PtrToStringAnsi (t));
212+
Type type = monodroid_typemap_java_to_managed (class_name);
213+
if (type != null)
214+
return type;
214215

215216
if (!JNIEnv.IsRunningOnDesktop) {
216217
return null;
217218
}
218219

219220
__TypeRegistrations.RegisterPackages ();
220221

221-
var type = (Type) null;
222+
type = null;
222223
int ls = class_name.LastIndexOf ('/');
223224
var package = ls >= 0 ? class_name.Substring (0, ls) : "";
224225
List<Converter<string, Type>> mappers;

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public class GenerateJavaStubs : AndroidTask
4242
[Required]
4343
public string [] SupportedAbis { get; set; }
4444

45+
[Required]
46+
public bool GenerateNativeAssembly { get; set; }
47+
4548
public string ManifestTemplate { get; set; }
4649
public string[] MergedManifestDocuments { get; set; }
4750

@@ -55,6 +58,13 @@ public class GenerateJavaStubs : AndroidTask
5558

5659
public string AndroidSdkPlatform { get; set; }
5760
public string OutputDirectory { get; set; }
61+
62+
[Required]
63+
public string NativeOutputDirectory { get; set; }
64+
65+
[Output]
66+
public string[] GeneratedBinaryTypeMaps { get; set; }
67+
5868
public string MergedAndroidManifestOutput { get; set; }
5969

6070
public bool EmbedAssemblies { get; set; }
@@ -75,11 +85,12 @@ public class GenerateJavaStubs : AndroidTask
7585

7686
public override bool RunTask ()
7787
{
88+
var typemapGen = new GenerateTypeMaps ();
7889
try {
7990
// We're going to do 3 steps here instead of separate tasks so
8091
// we can share the list of JLO TypeDefinitions between them
8192
using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true)) {
82-
Run (res);
93+
Run (res, typemapGen);
8394
}
8495
} catch (XamarinAndroidException e) {
8596
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
@@ -92,14 +103,13 @@ public override bool RunTask ()
92103
// by ensuring that the target outputs have been deleted.
93104
Files.DeleteFile (MergedAndroidManifestOutput, Log);
94105
Files.DeleteFile (AcwMapFile, Log);
95-
Files.DeleteFile (Path.Combine (OutputDirectory, "typemap.jm"), Log);
96-
Files.DeleteFile (Path.Combine (OutputDirectory, "typemap.mj"), Log);
106+
typemapGen.CleanupOnError (NativeOutputDirectory);
97107
}
98108

99109
return !Log.HasLoggedErrors;
100110
}
101111

102-
void Run (DirectoryAssemblyResolver res)
112+
void Run (DirectoryAssemblyResolver res, GenerateTypeMaps typemapGen)
103113
{
104114
PackageNamingPolicy pnp;
105115
JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
@@ -141,14 +151,16 @@ void Run (DirectoryAssemblyResolver res)
141151
assemblies.Add (asm.ItemSpec);
142152
}
143153

154+
// Step 0 - Generate type maps (this should eventually live in a separate task)
155+
typemapGen.Run (Log, res, ResolvedAssemblies, NativeOutputDirectory, SupportedAbis, GenerateNativeAssembly);
156+
GeneratedBinaryTypeMaps = typemapGen.GeneratedBinaryTypeMaps;
157+
144158
// Step 1 - Find all the JLO types
145159
var scanner = new JavaTypeScanner (this.CreateTaskLogger ()) {
146160
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
147161
};
148162
var all_java_types = scanner.GetJavaTypes (assemblies, res);
149163

150-
WriteTypeMappings (all_java_types);
151-
152164
var java_types = all_java_types
153165
.Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t))
154166
.ToArray ();
@@ -300,76 +312,5 @@ void SaveResource (string resource, string filename, string destDir, Func<string
300312
template = applyTemplate (template);
301313
MonoAndroidHelper.CopyIfStringChanged (template, Path.Combine (destDir, filename));
302314
}
303-
304-
void WriteTypeMappings (List<TypeDefinition> types)
305-
{
306-
void logger (TraceLevel level, string value) => Log.LogDebugMessage (value);
307-
TypeNameMapGenerator createTypeMapGenerator () => UseSharedRuntime ?
308-
new TypeNameMapGenerator (types, logger) :
309-
new TypeNameMapGenerator (ResolvedAssemblies.Select (p => p.ItemSpec), logger);
310-
using (var gen = createTypeMapGenerator ()) {
311-
using (var ms = new MemoryStream ()) {
312-
UpdateWhenChanged (Path.Combine (OutputDirectory, "typemap.jm"), "jm", ms, gen.WriteJavaToManaged);
313-
UpdateWhenChanged (Path.Combine (OutputDirectory, "typemap.mj"), "mj", ms, gen.WriteManagedToJava);
314-
}
315-
}
316-
}
317-
318-
void UpdateWhenChanged (string path, string type, MemoryStream ms, Action<Stream> generator)
319-
{
320-
if (!EmbedAssemblies) {
321-
ms.SetLength (0);
322-
generator (ms);
323-
MonoAndroidHelper.CopyIfStreamChanged (ms, path);
324-
}
325-
326-
string dataFilePath = $"{path}.inc";
327-
using (var stream = new NativeAssemblyDataStream ()) {
328-
if (EmbedAssemblies) {
329-
generator (stream);
330-
stream.EndOfFile ();
331-
MonoAndroidHelper.CopyIfStreamChanged (stream, dataFilePath);
332-
} else {
333-
stream.EmptyFile ();
334-
}
335-
336-
var generatedFiles = new List <ITaskItem> ();
337-
string mappingFieldName = $"{type}_typemap";
338-
string dataFileName = Path.GetFileName (dataFilePath);
339-
NativeAssemblerTargetProvider asmTargetProvider;
340-
var utf8Encoding = new UTF8Encoding (false);
341-
foreach (string abi in SupportedAbis) {
342-
ms.SetLength (0);
343-
switch (abi.Trim ()) {
344-
case "armeabi-v7a":
345-
asmTargetProvider = new ARMNativeAssemblerTargetProvider (is64Bit: false);
346-
break;
347-
348-
case "arm64-v8a":
349-
asmTargetProvider = new ARMNativeAssemblerTargetProvider (is64Bit: true);
350-
break;
351-
352-
case "x86":
353-
asmTargetProvider = new X86NativeAssemblerTargetProvider (is64Bit: false);
354-
break;
355-
356-
case "x86_64":
357-
asmTargetProvider = new X86NativeAssemblerTargetProvider (is64Bit: true);
358-
break;
359-
360-
default:
361-
throw new InvalidOperationException ($"Unknown ABI {abi}");
362-
}
363-
364-
var asmgen = new TypeMappingNativeAssemblyGenerator (asmTargetProvider, stream, dataFileName, stream.MapByteCount, mappingFieldName);
365-
asmgen.EmbedAssemblies = EmbedAssemblies;
366-
string asmFileName = $"{path}.{abi.Trim ()}.s";
367-
using (var sw = new StreamWriter (ms, utf8Encoding, bufferSize: 8192, leaveOpen: true)) {
368-
asmgen.Write (sw, dataFileName);
369-
MonoAndroidHelper.CopyIfStreamChanged (ms, asmFileName);
370-
}
371-
}
372-
}
373-
}
374315
}
375316
}

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public class GeneratePackageManagerJava : AndroidTask
5858
[Required]
5959
public bool EnablePreloadAssembliesDefault { get; set; }
6060

61+
[Required]
62+
public bool InstantRunEnabled { get; set; }
63+
6164
public string BoundExceptionType { get; set; }
6265

6366
public string PackageNamingPolicy { get; set; }
@@ -252,7 +255,8 @@ void AddEnvironment ()
252255
foreach (string abi in SupportedAbis) {
253256
ms.SetLength (0);
254257
NativeAssemblerTargetProvider asmTargetProvider;
255-
string asmFileName = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}.s");
258+
string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}");
259+
string asmFilePath = $"{baseAsmFilePath}.s";
256260
switch (abi.Trim ()) {
257261
case "armeabi-v7a":
258262
asmTargetProvider = new ARMNativeAssemblerTargetProvider (false);
@@ -274,7 +278,7 @@ void AddEnvironment ()
274278
throw new InvalidOperationException ($"Unknown ABI {abi}");
275279
}
276280

277-
var asmgen = new ApplicationConfigNativeAssemblyGenerator (asmTargetProvider, environmentVariables, systemProperties) {
281+
var asmgen = new ApplicationConfigNativeAssemblyGenerator (asmTargetProvider, baseAsmFilePath, environmentVariables, systemProperties) {
278282
IsBundledApp = IsBundledApplication,
279283
UsesMonoAOT = usesMonoAOT,
280284
UsesMonoLLVM = EnableLLVM,
@@ -284,11 +288,12 @@ void AddEnvironment ()
284288
BrokenExceptionTransitions = brokenExceptionTransitions,
285289
PackageNamingPolicy = pnp,
286290
BoundExceptionType = boundExceptionType,
291+
InstantRunEnabled = InstantRunEnabled,
287292
};
288293

289294
using (var sw = new StreamWriter (ms, utf8Encoding, bufferSize: 8192, leaveOpen: true)) {
290-
asmgen.Write (sw, asmFileName);
291-
MonoAndroidHelper.CopyIfStreamChanged (ms, asmFileName);
295+
asmgen.Write (sw);
296+
MonoAndroidHelper.CopyIfStreamChanged (ms, asmFilePath);
292297
}
293298

294299
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
7+
using Microsoft.Build.Framework;
8+
using Microsoft.Build.Utilities;
9+
10+
using Java.Interop.Tools.Cecil;
11+
using Java.Interop.Tools.Diagnostics;
12+
using Xamarin.Android.Tools;
13+
14+
namespace Xamarin.Android.Tasks
15+
{
16+
public class GenerateTypeMaps : AndroidTask
17+
{
18+
public override string TaskPrefix => "GTM";
19+
20+
[Required]
21+
public ITaskItem[] ResolvedAssemblies { get; set; }
22+
23+
[Required]
24+
public ITaskItem[] ResolvedUserAssemblies { get; set; }
25+
26+
[Required]
27+
public ITaskItem [] FrameworkDirectories { get; set; }
28+
29+
[Required]
30+
public string[] SupportedAbis { get; set; }
31+
32+
[Required]
33+
public string OutputDirectory { get; set; }
34+
35+
[Required]
36+
public bool GenerateNativeAssembly { get; set; }
37+
38+
[Output]
39+
public string[] GeneratedBinaryTypeMaps { get; set; }
40+
41+
public bool ErrorOnCustomJavaObject { get; set; }
42+
43+
public override bool RunTask ()
44+
{
45+
try {
46+
var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true);
47+
foreach (var dir in FrameworkDirectories) {
48+
if (Directory.Exists (dir.ItemSpec))
49+
res.SearchDirectories.Add (dir.ItemSpec);
50+
}
51+
52+
Run (Log, res, ResolvedAssemblies, OutputDirectory, SupportedAbis, GenerateNativeAssembly);
53+
} catch (XamarinAndroidException e) {
54+
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
55+
if (MonoAndroidHelper.LogInternalExceptions)
56+
Log.LogMessage (e.ToString ());
57+
}
58+
59+
if (Log.HasLoggedErrors) {
60+
CleanupOnError (OutputDirectory);
61+
return false;
62+
}
63+
64+
return true;
65+
}
66+
67+
public void CleanupOnError (string outputDirectory)
68+
{
69+
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
70+
// by ensuring that the target outputs have been deleted.
71+
Files.DeleteFile (Path.Combine (outputDirectory, "typemap.index"), Log);
72+
foreach (string file in Directory.EnumerateFiles (outputDirectory, "*.typemap")) {
73+
Files.DeleteFile (file, Log);
74+
}
75+
}
76+
77+
public void Run (TaskLoggingHelper log, DirectoryAssemblyResolver res, ITaskItem[] resolvedAssemblies, string outputDirectory, string[] supportedAbis, bool generateNativeAssembly)
78+
{
79+
var interestingAssemblies = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
80+
81+
foreach (ITaskItem assembly in resolvedAssemblies) {
82+
res.Load (assembly.ItemSpec);
83+
if (String.Compare ("MonoAndroid", assembly.GetMetadata ("TargetFrameworkIdentifier"), StringComparison.Ordinal) != 0)
84+
continue;
85+
if (interestingAssemblies.Contains (assembly.ItemSpec))
86+
continue;
87+
interestingAssemblies.Add (assembly.ItemSpec);
88+
}
89+
90+
var tmg = new TypeMapGenerator ((string message) => log.LogDebugMessage (message), supportedAbis);
91+
if (!tmg.Generate (res, interestingAssemblies, outputDirectory, generateNativeAssembly))
92+
throw new XamarinAndroidException (99999, "Failed to generate type maps");
93+
GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray ();
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)