Skip to content

Commit c7ec0ac

Browse files
[Mono.Android] JNI handles are now in a "control block" (#10179)
Originally from: #10125 Context: dotnet/java-interop#1339 These are changes to Mono's GC bridge implementation, such that the same code can be used on both Mono and CoreCLR. The base `JavaObject` type has changes such as: ```diff --[NonSerialized] IntPtr handle; --[NonSerialized] JniObjectReferenceType handle_type; --[NonSerialized] IntPtr weak_handle; --[NonSerialized] int refs_added; ++unsafe JniObjectReferenceControlBlock* jniObjectReferenceControlBlock; ``` And the new `JniObjectReferenceControlBlock` struct is defined as: internal struct JniObjectReferenceControlBlock { public IntPtr handle; public int handle_type; public IntPtr weak_handle; public int refs_added; } * Each `JavaObject` allocation now has a `NativeMemory.AllocZeroed()` call for this struct. * However, we only have to read a single field now using Mono's native embedding API: ```diff --info->handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle")); --info->handle_type = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle_type")); --info->refs_added = mono_class_get_field_from_name (info->klass, const_cast<char*> ("refs_added")); --info->weak_handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("weak_handle")); ++info->jniObjectReferenceControlBlock = mono_class_get_field_from_name (info->klass, const_cast<char*> ("jniObjectReferenceControlBlock")); ``` We are hoping this results in ~the same performance as before, with the flexibility to support multiple GC bridge implementations and runtimes. Other changes: * Fix SIGSEGV when using the new control block We were using it incorrectly - there's no need to fetch/set values of managed object fields. The correct thing to do is to read/write them on the control block itself. Co-authored-by: Marek Habersack <[email protected]>
1 parent cfa4209 commit c7ec0ac

File tree

5 files changed

+98
-71
lines changed

5 files changed

+98
-71
lines changed

src/Microsoft.Android.Sdk.ILLink/PreserveLists/Java.Interop.xml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@
22
<linker>
33
<assembly fullname="Java.Interop">
44
<type fullname="Java.Interop.JavaObject">
5-
<field name="handle" />
6-
<field name="handle_type" />
7-
<field name="refs_added" />
5+
<field name="jniObjectReferenceControlBlock" />
86
</type>
97
<type fullname="Java.Interop.JavaException">
10-
<field name="handle" />
11-
<field name="handle_type" />
12-
<field name="refs_added" />
8+
<field name="jniObjectReferenceControlBlock" />
139
</type>
1410
</assembly>
1511
</linker>

src/native/mono/monodroid/monodroid-glue.cc

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -793,22 +793,15 @@ MonodroidRuntime::create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks
793793
MonodroidRuntime::lookup_bridge_info (MonoClass *klass, const OSBridge::MonoJavaGCBridgeType *type, OSBridge::MonoJavaGCBridgeInfo *info) noexcept
794794
{
795795
info->klass = klass;
796-
info->handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle"));
797-
info->handle_type = mono_class_get_field_from_name (info->klass, const_cast<char*> ("handle_type"));
798-
info->refs_added = mono_class_get_field_from_name (info->klass, const_cast<char*> ("refs_added"));
799-
info->key_handle = mono_class_get_field_from_name (info->klass, const_cast<char*> ("key_handle"));
796+
info->jniObjectReferenceControlBlock = mono_class_get_field_from_name (info->klass, const_cast<char*>("jniObjectReferenceControlBlock"));
800797

801-
// key_handle is optional, as Java.Interop.JavaObject doesn't currently have it
802-
if (info->klass == nullptr || info->handle == nullptr || info->handle_type == nullptr || info->refs_added == nullptr) {
798+
if (info->klass == nullptr || info->jniObjectReferenceControlBlock == nullptr) {
803799
Helpers::abort_application (
804-
Util::monodroid_strdup_printf (
805-
"The type `%s.%s` is missing required instance fields! handle=%p handle_type=%p refs_added=%p key_handle=%p",
800+
std::format (
801+
"The type `{}.{} is missing required instance fields! jniObjectReferenceControlBlock={:p}",
806802
type->_namespace,
807803
type->_typename,
808-
info->handle,
809-
info->handle_type,
810-
info->refs_added,
811-
info->key_handle
804+
static_cast<void*>(info->jniObjectReferenceControlBlock)
812805
)
813806
);
814807
}

src/native/mono/monodroid/osbridge.cc

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ const uint32_t OSBridge::NUM_GC_BRIDGE_TYPES = NUM_XA_GC_BRIDGE_TYPES + NUM_J
4242
OSBridge::MonoJavaGCBridgeInfo OSBridge::mono_java_gc_bridge_info [NUM_GC_BRIDGE_TYPES];
4343

4444
OSBridge::MonoJavaGCBridgeInfo OSBridge::empty_bridge_info = {
45-
nullptr,
46-
nullptr,
47-
nullptr,
4845
nullptr,
4946
nullptr
5047
};
@@ -76,9 +73,14 @@ OSBridge::clear_mono_java_gc_bridge_info ()
7673
for (uint32_t c = 0; c < NUM_GC_BRIDGE_TYPES; c++) {
7774
MonoJavaGCBridgeInfo *info = &mono_java_gc_bridge_info [c];
7875
info->klass = nullptr;
79-
info->handle = nullptr;
80-
info->handle_type = nullptr;
81-
info->refs_added = nullptr;
76+
auto control_block = reinterpret_cast<JniObjectReferenceControlBlock*>(info->jniObjectReferenceControlBlock);
77+
if (control_block == nullptr) [[unlikely]] {
78+
continue;
79+
}
80+
control_block->handle = nullptr;
81+
control_block->handle_type = 0;
82+
control_block->weak_handle = nullptr;
83+
control_block->refs_added = 0;
8284
}
8385
}
8486

@@ -102,6 +104,19 @@ OSBridge::get_gc_bridge_index (MonoClass *klass)
102104
: -1;
103105
}
104106

107+
OSBridge::JniObjectReferenceControlBlock*
108+
OSBridge::get_gc_control_block_for_object (MonoObject *obj)
109+
{
110+
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (obj);
111+
if (bridge_info == nullptr) {
112+
return nullptr;
113+
}
114+
115+
JniObjectReferenceControlBlock *control_block = nullptr;
116+
mono_field_get_value (obj, bridge_info->jniObjectReferenceControlBlock, &control_block);
117+
return control_block;
118+
}
119+
105120
OSBridge::MonoJavaGCBridgeInfo *
106121
OSBridge::get_gc_bridge_info_for_class (MonoClass *klass)
107122
{
@@ -456,15 +471,15 @@ OSBridge::monodroid_disable_gc_hooks ()
456471
mono_bool
457472
OSBridge::take_global_ref_jni (JNIEnv *env, MonoObject *obj)
458473
{
459-
jobject handle, weak;
460474
int type = JNIGlobalRefType;
461475

462-
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (obj);
463-
if (bridge_info == nullptr)
476+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (obj);
477+
if (control_block == nullptr) {
464478
return 0;
479+
}
465480

466-
mono_field_get_value (obj, bridge_info->handle, &weak);
467-
handle = env->NewGlobalRef (weak);
481+
jobject weak = control_block->handle;
482+
jobject handle = env->NewGlobalRef (weak);
468483
if (gref_log) {
469484
fprintf (gref_log, "*try_take_global obj=%p -> wref=%p handle=%p\n", obj, weak, handle);
470485
fflush (gref_log);
@@ -476,8 +491,8 @@ OSBridge::take_global_ref_jni (JNIEnv *env, MonoObject *obj)
476491
" at [[gc:take_global_ref_jni]]", 0);
477492
} else if (Logger::gc_spew_enabled ()) [[unlikely]] {
478493
void *key_handle = nullptr;
479-
if (bridge_info->key_handle) {
480-
mono_field_get_value (obj, bridge_info->key_handle, &key_handle);
494+
if (control_block->weak_handle != nullptr) {
495+
key_handle = control_block->weak_handle;
481496
}
482497

483498
MonoClass *klass = mono_object_get_class (obj);
@@ -491,8 +506,8 @@ OSBridge::take_global_ref_jni (JNIEnv *env, MonoObject *obj)
491506
free (message);
492507
}
493508

494-
mono_field_set_value (obj, bridge_info->handle, &handle);
495-
mono_field_set_value (obj, bridge_info->handle_type, &type);
509+
control_block->handle = handle;
510+
control_block->handle_type = type;
496511

497512
_monodroid_weak_gref_delete (weak, get_object_ref_type (env, weak),
498513
"finalizer", gettid (), " at [[gc:take_global_ref_jni]]", 0);
@@ -504,26 +519,26 @@ OSBridge::take_global_ref_jni (JNIEnv *env, MonoObject *obj)
504519
mono_bool
505520
OSBridge::take_weak_global_ref_jni (JNIEnv *env, MonoObject *obj)
506521
{
507-
jobject handle, weak;
508522
int type = JNIWeakGlobalRefType;
509523

510-
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (obj);
511-
if (bridge_info == nullptr)
524+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (obj);
525+
if (control_block == nullptr) {
512526
return 0;
527+
}
513528

514-
mono_field_get_value (obj, bridge_info->handle, &handle);
529+
jobject handle = control_block->handle;
515530
if (gref_log) {
516531
fprintf (gref_log, "*take_weak obj=%p; handle=%p\n", obj, handle);
517532
fflush (gref_log);
518533
}
519534

520-
weak = env->NewWeakGlobalRef (handle);
535+
jobject weak = env->NewWeakGlobalRef (handle);
521536
_monodroid_weak_gref_new (handle, get_object_ref_type (env, handle),
522537
weak, get_object_ref_type (env, weak),
523538
"finalizer", gettid (), " at [[gc:take_weak_global_ref_jni]]", 0);
524539

525-
mono_field_set_value (obj, bridge_info->handle, &weak);
526-
mono_field_set_value (obj, bridge_info->handle_type, &type);
540+
control_block->handle = weak;
541+
control_block->handle_type = type;
527542

528543
_monodroid_gref_log_delete (handle, get_object_ref_type (env, handle),
529544
"finalizer", gettid (), " at [[gc:take_weak_global_ref_jni]]", 0);
@@ -558,14 +573,22 @@ OSBridge::gc_bridge_class_kind (MonoClass *klass)
558573
mono_bool
559574
OSBridge::gc_is_bridge_object (MonoObject *object)
560575
{
561-
void *handle;
576+
if (object == nullptr) [[unlikely]] {
577+
log_debug (LOG_GC, "gc_is_bridge_object was passed a NULL object pointer");
578+
return FALSE;
579+
}
562580

563-
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (object);
564-
if (bridge_info == nullptr)
565-
return 0;
581+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (object);
582+
if (control_block == nullptr) {
583+
return FALSE;
584+
}
566585

567-
mono_field_get_value (object, bridge_info->handle, &handle);
568-
if (handle == nullptr) {
586+
if (control_block->handle == nullptr) {
587+
log_warn (LOG_GC, "gc_is_bridge_object: control block's handle is NULL");
588+
return FALSE;
589+
}
590+
591+
if (control_block->handle == nullptr) {
569592
#if DEBUG
570593
MonoClass *mclass = mono_object_get_class (object);
571594
log_info (LOG_GC,
@@ -574,10 +597,10 @@ OSBridge::gc_is_bridge_object (MonoObject *object)
574597
optional_string (mono_class_get_name (mclass))
575598
);
576599
#endif
577-
return 0;
600+
return FALSE;
578601
}
579602

580-
return 1;
603+
return TRUE;
581604
}
582605

583606
// Add a reference from an IGCUserPeer jobject to another jobject
@@ -603,11 +626,17 @@ OSBridge::add_reference_jobject (JNIEnv *env, jobject handle, jobject reffed_han
603626
mono_bool
604627
OSBridge::load_reference_target (OSBridge::AddReferenceTarget target, OSBridge::MonoJavaGCBridgeInfo** bridge_info, jobject *handle)
605628
{
629+
if (handle == nullptr) [[unlikely]] {
630+
return FALSE;
631+
}
632+
606633
if (target.is_mono_object) {
607-
*bridge_info = get_gc_bridge_info_for_object (target.obj);
608-
if (!*bridge_info)
634+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (target.obj);
635+
if (control_block == nullptr) {
609636
return FALSE;
610-
mono_field_get_value (target.obj, (*bridge_info)->handle, handle);
637+
}
638+
639+
*handle = control_block->handle;
611640
} else {
612641
*handle = target.jobj;
613642
}
@@ -648,8 +677,11 @@ OSBridge::add_reference (JNIEnv *env, OSBridge::AddReferenceTarget target, OSBri
648677
// Flag MonoObjects so they can be cleared in gc_cleanup_after_java_collection.
649678
// Java temporaries do not need this because the entire GCUserPeer is discarded.
650679
if (success && target.is_mono_object) {
651-
int ref_val = 1;
652-
mono_field_set_value (target.obj, bridge_info->refs_added, &ref_val);
680+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (target.obj);
681+
if (control_block == nullptr) {
682+
return FALSE;
683+
}
684+
control_block->refs_added = 1;
653685
}
654686

655687
#if DEBUG
@@ -867,15 +899,15 @@ OSBridge::gc_cleanup_after_java_collection (JNIEnv *env, int num_sccs, MonoGCBri
867899
sccs [i]->is_alive = 0;
868900

869901
for (j = 0; j < sccs [i]->num_objs; j++) {
870-
MonoJavaGCBridgeInfo *bridge_info;
871-
872902
obj = sccs [i]->objs [j];
873903

874-
bridge_info = get_gc_bridge_info_for_object (obj);
875-
if (bridge_info == nullptr)
904+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (obj);
905+
if (control_block == nullptr) {
876906
continue;
877-
mono_field_get_value (obj, bridge_info->handle, &jref);
878-
if (jref) {
907+
}
908+
909+
jref = control_block->handle;
910+
if (jref != nullptr) {
879911
alive++;
880912
if (j > 0) {
881913
abort_unless (
@@ -884,8 +916,8 @@ OSBridge::gc_cleanup_after_java_collection (JNIEnv *env, int num_sccs, MonoGCBri
884916
);
885917
}
886918
sccs [i]->is_alive = 1;
887-
mono_field_get_value (obj, bridge_info->refs_added, &refs_added);
888-
if (refs_added) {
919+
refs_added = control_block->refs_added;
920+
if (refs_added != 0) {
889921
jclass java_class = env->GetObjectClass (jref);
890922
clear_method_id = env->GetMethodID (java_class, "monodroidClearReferences", "()V");
891923
env->DeleteLocalRef (java_class);
@@ -951,13 +983,13 @@ OSBridge::gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xre
951983
for (j = 0; j < sccs [i]->num_objs; ++j) {
952984
MonoObject *obj = sccs [i]->objs [j];
953985

954-
MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (obj);
986+
JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (obj);
955987
jobject handle = 0;
956988
void *key_handle = nullptr;
957-
if (bridge_info != nullptr) {
958-
mono_field_get_value (obj, bridge_info->handle, &handle);
959-
if (bridge_info->key_handle != nullptr) {
960-
mono_field_get_value (obj, bridge_info->key_handle, &key_handle);
989+
if (control_block != nullptr) {
990+
handle = control_block->handle;
991+
if (control_block->weak_handle != nullptr) {
992+
key_handle = control_block->weak_handle;
961993
}
962994
}
963995
MonoClass *klass = mono_object_get_class (obj);

src/native/mono/monodroid/osbridge.hh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ namespace xamarin::android::internal
3737
struct MonoJavaGCBridgeInfo
3838
{
3939
MonoClass *klass;
40-
MonoClassField *handle;
41-
MonoClassField *handle_type;
42-
MonoClassField *refs_added;
43-
MonoClassField *key_handle;
40+
MonoClassField *jniObjectReferenceControlBlock;
41+
};
42+
43+
struct JniObjectReferenceControlBlock
44+
{
45+
jobject handle;
46+
int handle_type;
47+
jobject weak_handle;
48+
int refs_added;
4449
};
4550

4651
// add_reference can work with objects which are either MonoObjects with java peers, or raw jobjects
@@ -127,6 +132,7 @@ namespace xamarin::android::internal
127132

128133
private:
129134
int get_gc_bridge_index (MonoClass *klass);
135+
JniObjectReferenceControlBlock* get_gc_control_block_for_object (MonoObject *obj);
130136
MonoJavaGCBridgeInfo* get_gc_bridge_info_for_class (MonoClass *klass);
131137
MonoJavaGCBridgeInfo* get_gc_bridge_info_for_object (MonoObject *object);
132138
char get_object_ref_type (JNIEnv *env, void *handle);

0 commit comments

Comments
 (0)