Skip to content

Commit 1668070

Browse files
authored
[NET6] Call JNIEnv.Initialize() directly, via function pointer (#6880)
Context: dotnet/runtime@1cfa6d6 With the .NET6 Mono runtime we are able to use the [`[UnmanagedCallersOnly]`][0] attribute to mark certain methods as directly callable from native code. This is faster than going through `mono_runtime_invoke()`, as there is nearly no marshaling of parameters performed. Since `Android.Runtime.JNIEnv.Initialize()` is called only by our native runtime, we can use this approach here. Additionally, remove some Reflection use by looking up and saving managed tokens of the `Android.Runtime.JNIEnv` class as well as the two methods `JNIEnv.Initialize()` and `JNIEnv.RegisterJniNatives()`. This saves time by allowing us to not look for the above methods via their names during process startup. Time savings aren't spectacular in the overall startup time, but the "native to managed" phase (essentially a call to `JNIEnv.Initialize`) paired with the reflection savings gives us a speedup of 0.1ms here on a Pixel 6 Pro (savings might be bigger on slower devices). Additionally, update our `DotNetCoreCLI@2` use in `setup-test-environment.yaml` to work around a "bizarre" CI error: "C:\Program Files\dotnet\dotnet.exe" restore C:\a\_work\1\s\xamarin-android\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\Xamarin.Android.Build.Tests.csproj --configfile C:\a\_work\1\Nuget\tempNuGet_5963650.config --verbosity Detailed --configfile C:\a\_work\1\s\xamarin-android/NuGet.config -bl:C:\a\_work\1\s\xamarin-android/bin/TestRelease/restore-Xamarin.Android.Build.Tests.binlog Option '--configfile' only accepts a single argument but 2 were provided. It appears that `restoreArguments: --configfile …` can no longer be used with the [`DotNetCoreCLI@2`][1] task, and instead the `nugetConfigPath` and `feedsToUse: config` inputs must be used instead. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0 [1]: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/dotnet-core-cli?view=azure-devops
1 parent 83dfd15 commit 1668070

File tree

11 files changed

+170
-30
lines changed

11 files changed

+170
-30
lines changed

build-tools/automation/yaml-templates/setup-test-environment.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ steps:
6161
inputs:
6262
command: restore
6363
projects: ${{ parameters.xaSourcePath }}/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj
64-
restoreArguments: --configfile ${{ parameters.xaSourcePath }}/NuGet.config -bl:${{ parameters.xaSourcePath }}/bin/Test${{ parameters.configuration }}/restore-Xamarin.Android.Build.Tests.binlog
64+
restoreArguments: -bl:${{ parameters.xaSourcePath }}/bin/Test${{ parameters.configuration }}/restore-Xamarin.Android.Build.Tests.binlog
65+
nugetConfigPath: ${{ parameters.xaSourcePath }}/NuGet.config
66+
feedsToUse: config
6567

6668
- task: DotNetCoreCLI@2
6769
displayName: build Xamarin.Android.Tools.BootstrapTasks.csproj

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In
140140
((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods);
141141
}
142142

143+
#if NETCOREAPP
144+
[UnmanagedCallersOnly]
145+
#endif
143146
internal static unsafe void Initialize (JnienvInitializeArgs* args)
144147
{
145148
bool logTiming = (args->logCategories & (uint)LogCategories.Timing) != 0;

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

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
using System;
44
using System.Collections.Generic;
55
using System.IO;
6-
using System.Reflection;
6+
using System.Reflection.Metadata;
7+
using System.Reflection.Metadata.Ecma335;
8+
using System.Reflection.PortableExecutable;
79
using System.Text;
810
using Microsoft.Build.Framework;
911
using Microsoft.Build.Utilities;
@@ -328,9 +330,22 @@ void AddEnvironment ()
328330
}
329331
}
330332

333+
int android_runtime_jnienv_class_token = -1;
334+
int jnienv_initialize_method_token = -1;
335+
int jnienv_registerjninatives_method_token = -1;
331336
foreach (var assembly in ResolvedAssemblies) {
332337
updateNameWidth (assembly);
333338
updateAssemblyCount (assembly);
339+
340+
if (android_runtime_jnienv_class_token != -1) {
341+
continue;
342+
}
343+
344+
if (!assembly.ItemSpec.EndsWith ("Mono.Android.dll", StringComparison.OrdinalIgnoreCase)) {
345+
continue;
346+
}
347+
348+
GetRequiredTokens (assembly.ItemSpec, out android_runtime_jnienv_class_token, out jnienv_initialize_method_token, out jnienv_registerjninatives_method_token);
334349
}
335350

336351
if (!UseAssemblyStore) {
@@ -409,6 +424,9 @@ void AddEnvironment ()
409424
MonoComponents = monoComponents,
410425
NativeLibraries = uniqueNativeLibraries,
411426
HaveAssemblyStore = UseAssemblyStore,
427+
AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token,
428+
JNIEnvInitializeToken = jnienv_initialize_method_token,
429+
JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token,
412430
};
413431
appConfigAsmGen.Init ();
414432

@@ -446,5 +464,71 @@ string ValidAssemblerString (string s)
446464
return s.Replace ("\"", "\\\"");
447465
}
448466
}
467+
468+
void GetRequiredTokens (string assemblyFilePath, out int android_runtime_jnienv_class_token, out int jnienv_initialize_method_token, out int jnienv_registerjninatives_method_token)
469+
{
470+
using (var pe = new PEReader (File.OpenRead (assemblyFilePath))) {
471+
GetRequiredTokens (pe.GetMetadataReader (), out android_runtime_jnienv_class_token, out jnienv_initialize_method_token, out jnienv_registerjninatives_method_token);
472+
}
473+
474+
if (android_runtime_jnienv_class_token == -1 || jnienv_initialize_method_token == -1 || jnienv_registerjninatives_method_token == -1) {
475+
throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnv method tokens");
476+
}
477+
}
478+
479+
void GetRequiredTokens (MetadataReader reader, out int android_runtime_jnienv_class_token, out int jnienv_initialize_method_token, out int jnienv_registerjninatives_method_token)
480+
{
481+
android_runtime_jnienv_class_token = -1;
482+
jnienv_initialize_method_token = -1;
483+
jnienv_registerjninatives_method_token = -1;
484+
485+
TypeDefinition? typeDefinition = null;
486+
487+
foreach (TypeDefinitionHandle typeHandle in reader.TypeDefinitions) {
488+
TypeDefinition td = reader.GetTypeDefinition (typeHandle);
489+
if (!TypeMatches (td)) {
490+
continue;
491+
}
492+
493+
typeDefinition = td;
494+
android_runtime_jnienv_class_token = MetadataTokens.GetToken (reader, typeHandle);
495+
break;
496+
}
497+
498+
if (typeDefinition == null) {
499+
return;
500+
}
501+
502+
foreach (MethodDefinitionHandle methodHandle in typeDefinition.Value.GetMethods ()) {
503+
MethodDefinition md = reader.GetMethodDefinition (methodHandle);
504+
string name = reader.GetString (md.Name);
505+
506+
if (jnienv_initialize_method_token == -1 && String.Compare (name, "Initialize", StringComparison.Ordinal) == 0) {
507+
jnienv_initialize_method_token = MetadataTokens.GetToken (reader, methodHandle);
508+
} else if (jnienv_registerjninatives_method_token == -1 && String.Compare (name, "RegisterJniNatives", StringComparison.Ordinal) == 0) {
509+
jnienv_registerjninatives_method_token = MetadataTokens.GetToken (reader, methodHandle);
510+
}
511+
512+
if (jnienv_initialize_method_token != -1 && jnienv_registerjninatives_method_token != -1) {
513+
break;
514+
}
515+
}
516+
517+
518+
bool TypeMatches (TypeDefinition td)
519+
{
520+
string ns = reader.GetString (td.Namespace);
521+
if (String.Compare (ns, "Android.Runtime", StringComparison.Ordinal) != 0) {
522+
return false;
523+
}
524+
525+
string name = reader.GetString (td.Name);
526+
if (String.Compare (name, "JNIEnv", StringComparison.Ordinal) != 0) {
527+
return false;
528+
}
529+
530+
return true;
531+
}
532+
}
449533
}
450534
}

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,13 @@ public sealed class ApplicationConfig
5656
public uint bundled_assembly_name_width;
5757
public uint number_of_assembly_store_files;
5858
public uint number_of_dso_cache_entries;
59+
public uint android_runtime_jnienv_class_token;
60+
public uint jnienv_initialize_method_token;
61+
public uint jnienv_registerjninatives_method_token;
5962
public uint mono_components_mask;
6063
public string android_package_name;
6164
};
62-
const uint ApplicationConfigFieldCount = 19;
65+
const uint ApplicationConfigFieldCount = 22;
6366

6467
const string ApplicationConfigSymbolName = "application_config";
6568
const string AppEnvironmentVariablesSymbolName = "app_environment_variables";
@@ -282,12 +285,27 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile)
282285
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
283286
break;
284287

285-
case 17: // mono_components_mask: uint32_t / .word | .long
288+
case 17: // android_runtime_jnienv_class_token: uint32_t / .word | .long
289+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
290+
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
291+
break;
292+
293+
case 18: // jnienv_initialize_method_token: uint32_t / .word | .long
294+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
295+
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
296+
break;
297+
298+
case 19: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
299+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
300+
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
301+
break;
302+
303+
case 20: // mono_components_mask: uint32_t / .word | .long
286304
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
287305
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
288306
break;
289307

290-
case 18: // android_package_name: string / [pointer type]
308+
case 21: // android_package_name: string / [pointer type]
291309
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
292310
pointers.Add (field [1].Trim ());
293311
break;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Xamarin.Android.Tasks
1919
// uint64_t | ulong
2020
// int64_t | long
2121
// char* | string
22-
//
22+
//
2323
// Names should be the same as in the above struct, but it's not a requirement
2424
// (they will be used only to generate comments in the native code)
2525
sealed class ApplicationConfig
@@ -41,6 +41,9 @@ sealed class ApplicationConfig
4141
public uint bundled_assembly_name_width;
4242
public uint number_of_assembly_store_files;
4343
public uint number_of_dso_cache_entries;
44+
public uint android_runtime_jnienv_class_token;
45+
public uint jnienv_initialize_method_token;
46+
public uint jnienv_registerjninatives_method_token;
4447
public uint mono_components_mask;
4548
public string android_package_name = String.Empty;
4649
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ sealed class XamarinAndroidBundledAssembly
166166
public int NumberOfAssembliesInApk { get; set; }
167167
public int NumberOfAssemblyStoresInApks { get; set; }
168168
public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL
169+
public int AndroidRuntimeJNIEnvToken { get; set; }
170+
public int JNIEnvInitializeToken { get; set; }
171+
public int JNIEnvRegisterJniNativesToken { get; set; }
169172
public MonoComponent MonoComponents { get; set; }
170173
public PackageNamingPolicy PackageNamingPolicy { get; set; }
171174
public List<ITaskItem> NativeLibraries { get; set; }
@@ -204,6 +207,9 @@ public override void Init ()
204207
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
205208
number_of_assembly_store_files = (uint)NumberOfAssemblyStoresInApks,
206209
number_of_dso_cache_entries = (uint)dsoCache.Count,
210+
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
211+
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
212+
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
207213
mono_components_mask = (uint)MonoComponents,
208214
android_package_name = AndroidPackageName,
209215
};

src/monodroid/jni/application_dso_stub.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ const ApplicationConfig application_config = {
5858
.bundled_assembly_name_width = 0,
5959
.number_of_assembly_store_files = 2,
6060
.number_of_dso_cache_entries = 2,
61+
.android_runtime_jnienv_class_token = 1,
62+
.jnienv_initialize_method_token = 2,
63+
.jnienv_registerjninatives_method_token = 3,
6164
.mono_components_mask = MonoComponent::None,
6265
.android_package_name = android_package_name,
6366
};

src/monodroid/jni/cpp-util.hh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,8 @@
1919
#include "platform-compat.hh"
2020

2121
static inline void
22-
do_abort_unless (bool condition, const char* fmt, ...)
22+
do_abort_unless (const char* fmt, ...)
2323
{
24-
if (XA_LIKELY (condition)) {
25-
return;
26-
}
27-
2824
va_list ap;
2925

3026
va_start (ap, fmt);
@@ -39,7 +35,11 @@ do_abort_unless (bool condition, const char* fmt, ...)
3935
std::abort ();
4036
}
4137

42-
#define abort_unless(_condition_, _fmt_, ...) do_abort_unless (_condition_, "%s:%d (%s): " _fmt_, __FILE__, __LINE__, __FUNCTION__, ## __VA_ARGS__)
38+
#define abort_unless(_condition_, _fmt_, ...) \
39+
if (XA_UNLIKELY (!(_condition_))) { \
40+
do_abort_unless ("%s:%d (%s): " _fmt_, __FILE__, __LINE__, __FUNCTION__, ## __VA_ARGS__); \
41+
}
42+
4343
#define abort_if_invalid_pointer_argument(_ptr_) abort_unless ((_ptr_) != nullptr, "Parameter '%s' must be a valid pointer", #_ptr_)
4444
#define abort_if_negative_integer_argument(_arg_) abort_unless ((_arg_) > 0, "Parameter '%s' must be larger than 0", #_arg_)
4545

src/monodroid/jni/monodroid-glue-internal.hh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ namespace xamarin::android::internal
126126
int jniAddNativeMethodRegistrationAttributePresent;
127127
};
128128

129+
#if defined (NET6)
130+
using jnienv_initialize_fn = void (*) (JnienvInitializeArgs*);
131+
#endif
132+
129133
private:
130134
static constexpr char base_apk_name[] = "/base.apk";
131135
static constexpr size_t SMALL_STRING_PARSE_BUFFER_LEN = 50;

src/monodroid/jni/monodroid-glue.cc

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
#include <mono/utils/mono-dl-fallback.h>
3434
#include <mono/utils/mono-logger.h>
3535

36+
#if defined (NET6)
37+
#include <mono/metadata/mono-private-unstable.h>
38+
#endif
39+
3640
#include "mono_android_Runtime.h"
3741

3842
#if defined (DEBUG) && !defined (WINDOWS)
@@ -1089,20 +1093,24 @@ MonodroidRuntime::init_android_runtime (
10891093
);
10901094
}
10911095

1092-
// TODO: try looking up the method by its token
10931096
MonoClass *runtime;
1097+
MonoMethod *method;
1098+
1099+
if constexpr (is_running_on_desktop) {
10941100
#if defined (NET6)
1095-
runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);
1101+
runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);
10961102
#else
1097-
runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);
1098-
#endif
1099-
MonoMethod *method = mono_class_get_method_from_name (runtime, "Initialize", 1);
1100-
1101-
if (method == nullptr) {
1102-
log_fatal (LOG_DEFAULT, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.Initialize!");
1103-
exit (FATAL_EXIT_MISSING_INIT);
1103+
runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);
1104+
#endif // def NET6
1105+
method = mono_class_get_method_from_name (runtime, "Initialize", 1);
1106+
} else {
1107+
runtime = mono_class_get (image, application_config.android_runtime_jnienv_class_token);
1108+
method = mono_get_method (image, application_config.jnienv_initialize_method_token, runtime);
11041109
}
11051110

1111+
abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnv class!");
1112+
abort_unless (method != nullptr, "INTERNAL ERROR: Unable to find the Android.Runtime.JNIEnv.Initialize method!");
1113+
11061114
MonoAssembly *ji_assm;
11071115
#if defined (NET6)
11081116
ji_assm = utils.monodroid_load_assembly (default_alc, SharedConstants::JAVA_INTEROP_ASSEMBLY_NAME);
@@ -1126,12 +1134,14 @@ MonodroidRuntime::init_android_runtime (
11261134
* so always make sure we have the freshest handle to the method.
11271135
*/
11281136
if (registerType == nullptr || is_running_on_desktop) {
1129-
registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5);
1130-
}
1131-
if (registerType == nullptr) {
1132-
log_fatal (LOG_DEFAULT, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives!");
1133-
exit (FATAL_EXIT_CANNOT_FIND_JNIENV);
1137+
if constexpr (is_running_on_desktop) {
1138+
registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5);
1139+
} else {
1140+
registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime);
1141+
}
11341142
}
1143+
abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives!");
1144+
11351145
MonoClass *android_runtime_jnienv = runtime;
11361146
MonoClassField *bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast<char*> ("BridgeProcessing"));
11371147
if (android_runtime_jnienv ==nullptr || bridge_processing_field == nullptr) {
@@ -1154,14 +1164,18 @@ MonodroidRuntime::init_android_runtime (
11541164
if (XA_UNLIKELY (utils.should_log (LOG_TIMING)))
11551165
partial_time.mark_start ();
11561166

1167+
#if defined (NET6) && defined (ANDROID)
1168+
MonoError error;
1169+
auto initialize = reinterpret_cast<jnienv_initialize_fn> (mono_method_get_unmanaged_callers_only_ftnptr (method, &error));
1170+
abort_unless (initialize != nullptr, "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize method");
1171+
initialize (&init);
1172+
#else // def NET6 && def ANDROID
11571173
void *args [] = {
11581174
&init,
11591175
};
1160-
#if defined (NET6)
1161-
mono_runtime_invoke (method, nullptr, args, nullptr);
1162-
#else // def NET6
1176+
11631177
utils.monodroid_runtime_invoke (domain, method, nullptr, args, nullptr);
1164-
#endif // ndef NET6
1178+
#endif // ndef NET6 && ndef ANDROID
11651179

11661180
if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) {
11671181
partial_time.mark_end ();

0 commit comments

Comments
 (0)