Skip to content

Commit b28f3ad

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 b28f3ad

30 files changed

+1563
-644
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: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ void Run (DirectoryAssemblyResolver res)
147147
};
148148
var all_java_types = scanner.GetJavaTypes (assemblies, res);
149149

150-
WriteTypeMappings (all_java_types);
151-
152150
var java_types = all_java_types
153151
.Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t))
154152
.ToArray ();
@@ -300,76 +298,5 @@ void SaveResource (string resource, string filename, string destDir, Func<string
300298
template = applyTemplate (template);
301299
MonoAndroidHelper.CopyIfStringChanged (template, Path.Combine (destDir, filename));
302300
}
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-
}
374301
}
375302
}

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

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ public sealed class ApplicationConfig
2222
public bool uses_assembly_preload;
2323
public bool is_a_bundled_app;
2424
public bool broken_exception_transitions;
25+
public bool instant_run_enabled;
2526
public byte bound_stream_io_exception_type;
2627
public uint package_naming_policy;
2728
public uint environment_variable_count;
2829
public uint system_property_count;
2930
public string android_package_name;
3031
};
31-
const uint ApplicationConfigFieldCount = 10;
32+
const uint ApplicationConfigFieldCount = 11;
3233

3334
static readonly object ndkInitLock = new object ();
3435
static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' };
@@ -49,10 +50,11 @@ public sealed class ApplicationConfig
4950
"app_environment_variables",
5051
"app_system_properties",
5152
"application_config",
52-
"jm_typemap",
53-
"jm_typemap_header",
54-
"mj_typemap",
55-
"mj_typemap_header",
53+
"map_modules",
54+
"map_module_count",
55+
"java_type_count",
56+
"java_name_width",
57+
"map_java",
5658
"mono_aot_mode_name",
5759
};
5860

@@ -136,27 +138,32 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
136138
ret.broken_exception_transitions = ConvertFieldToBool ("broken_exception_transitions", envFile, i, field [1]);
137139
break;
138140

139-
case 5: // bound_stream_io_exception_type: byte / .byte
141+
case 5: // instant_run_enabled: bool / .byte
142+
AssertFieldType (envFile, ".byte", field [0], i);
143+
ret.instant_run_enabled = ConvertFieldToBool ("instant_run_enabled", envFile, i, field [1]);
144+
break;
145+
146+
case 6: // bound_stream_io_exception_type: byte / .byte
140147
AssertFieldType (envFile, ".byte", field [0], i);
141148
ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile, i, field [1]);
142149
break;
143150

144-
case 6: // package_naming_policy: uint32_t / .word | .long
151+
case 7: // package_naming_policy: uint32_t / .word | .long
145152
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
146153
ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile, i, field [1]);
147154
break;
148155

149-
case 7: // environment_variable_count: uint32_t / .word | .long
156+
case 8: // environment_variable_count: uint32_t / .word | .long
150157
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
151158
ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile, i, field [1]);
152159
break;
153160

154-
case 8: // system_property_count: uint32_t / .word | .long
161+
case 9: // system_property_count: uint32_t / .word | .long
155162
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
156163
ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile, i, field [1]);
157164
break;
158165

159-
case 9: // android_package_name: string / [pointer type]
166+
case 10: // android_package_name: string / [pointer type]
160167
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
161168
pointers.Add (field [1].Trim ());
162169
break;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ namespace Xamarin.Android.Tasks
55
{
66
class ARMNativeAssemblerTargetProvider : NativeAssemblerTargetProvider
77
{
8+
const string ARMV7a = "armeabi-v7a";
9+
const string ARMV8a = "arm64-v8a";
10+
811
public override bool Is64Bit { get; }
912
public override string PointerFieldType { get; }
1013
public override string TypePrefix { get; }
14+
public override string AbiName => Is64Bit ? ARMV8a : ARMV7a;
15+
public override uint MapModulesAlignBits => Is64Bit ? 3u : 2u;
16+
public override uint MapJavaAlignBits { get; } = 2;
1117

1218
public ARMNativeAssemblerTargetProvider (bool is64Bit)
1319
{

0 commit comments

Comments
 (0)