From b1df62c4b0b1eb18f03c47e181d235773a9ac9ed Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Wed, 8 Jul 2026 15:50:36 +0200 Subject: [PATCH 1/7] Test work-around --- mono/metadata/class-init.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index 4666924367c9..e9ddd925dd47 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -27,6 +27,9 @@ #include #include #include +#if defined HOST_ARM64 +#include +#endif #ifdef MONO_CLASS_DEF_PRIVATE /* Class initialization gets to see the fields of MonoClass */ #define REALLY_INCLUDE_CLASS_DEF 1 @@ -3771,8 +3774,22 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) error_init (error); +#if defined HOST_ARM64 && !defined(_MSC_VER) +#ifndef DISABLE_REMOTING + int byte_offset_from_min_align = 3; + int bit_offset = 0; +#else + int byte_offset_from_min_align = 2; + int bit_offset = 6; +#endif + const atomic_uint_least8_t* addr_of_containing_byte = &((const atomic_uint_least8_t*)&klass->min_align)[byte_offset_from_min_align]; + gint8 containing_byte = atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); + if ((containing_byte >> bit_offset) & 1 /* klass->interfaces_inited */) + return; +#else if (klass->interfaces_inited) return; +#endif if (klass->rank == 1 && m_class_get_byval_arg (klass)->type != MONO_TYPE_ARRAY) { MonoType *args [1]; From e6b2914ef3cd4439bb5fbb77d3740a9e7ac3190e Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Fri, 10 Jul 2026 09:22:24 +0200 Subject: [PATCH 2/7] Add correctness assert --- mono/metadata/class-init.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index e9ddd925dd47..c246a5ad6900 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -27,7 +27,8 @@ #include #include #include -#if defined HOST_ARM64 +#define ASSERT_IT_WORKS 1 +#if defined HOST_ARM64 && !defined(_MSC_VER) #include #endif #ifdef MONO_CLASS_DEF_PRIVATE @@ -3784,7 +3785,11 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) #endif const atomic_uint_least8_t* addr_of_containing_byte = &((const atomic_uint_least8_t*)&klass->min_align)[byte_offset_from_min_align]; gint8 containing_byte = atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); - if ((containing_byte >> bit_offset) & 1 /* klass->interfaces_inited */) + int is_inited = (containing_byte >> bit_offset) & 1; /* klass->interfaces_inited */ +#if defined ASSERT_IT_WORKS + g_assert(is_inited == klass->interfaces_inited); +#endif + if (is_inited) return; #else if (klass->interfaces_inited) From 1646194b537183b0205e27cacedafe78e3fe3080 Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Fri, 10 Jul 2026 09:22:36 +0200 Subject: [PATCH 3/7] Without assert --- mono/metadata/class-init.c | 1 - 1 file changed, 1 deletion(-) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index c246a5ad6900..9d5892ea93d6 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -27,7 +27,6 @@ #include #include #include -#define ASSERT_IT_WORKS 1 #if defined HOST_ARM64 && !defined(_MSC_VER) #include #endif From 6b098b5406da771d11a74d98cfd289d20db65395 Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Fri, 10 Jul 2026 11:03:08 +0200 Subject: [PATCH 4/7] Add a general assert --- mono/metadata/class-init.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index 9d5892ea93d6..035caf3a166b 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -27,6 +27,7 @@ #include #include #include +#define ASSERT_IT_WORKS 1 #if defined HOST_ARM64 && !defined(_MSC_VER) #include #endif @@ -3786,6 +3787,10 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) gint8 containing_byte = atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); int is_inited = (containing_byte >> bit_offset) & 1; /* klass->interfaces_inited */ #if defined ASSERT_IT_WORKS + MonoClass tester = { .interfaces_inited = TRUE }; + gint8 test_byte = atomic_load_explicit(&((const atomic_uint_least8_t*)&tester.min_align)[byte_offset_from_min_align], memory_order_acquire); + int test_bit = (test_byte >> bit_offset) & 1; + g_assert(test_bit == tester.interfaces_inited); g_assert(is_inited == klass->interfaces_inited); #endif if (is_inited) From d389ef41c701d4154e180f3b13585c8d747570dc Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Fri, 10 Jul 2026 11:15:04 +0200 Subject: [PATCH 5/7] Add Windows ARM64 impl --- mono/metadata/class-init.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index 035caf3a166b..d17084ca2827 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -27,10 +27,13 @@ #include #include #include -#define ASSERT_IT_WORKS 1 -#if defined HOST_ARM64 && !defined(_MSC_VER) +#if defined HOST_ARM64 +#if defined(_MSC_VER) +#include +#else #include #endif +#endif #ifdef MONO_CLASS_DEF_PRIVATE /* Class initialization gets to see the fields of MonoClass */ #define REALLY_INCLUDE_CLASS_DEF 1 @@ -3775,7 +3778,12 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) error_init (error); -#if defined HOST_ARM64 && !defined(_MSC_VER) +#if defined HOST_ARM64 +#if defined(_MSC_VER) +#define ATOMIC_U8_PTR unsigned __int8 volatile* +#else +#define ATOMIC_U8_PTR const atomic_uint_least8_t* +#endif #ifndef DISABLE_REMOTING int byte_offset_from_min_align = 3; int bit_offset = 0; @@ -3783,18 +3791,17 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) int byte_offset_from_min_align = 2; int bit_offset = 6; #endif - const atomic_uint_least8_t* addr_of_containing_byte = &((const atomic_uint_least8_t*)&klass->min_align)[byte_offset_from_min_align]; - gint8 containing_byte = atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); - int is_inited = (containing_byte >> bit_offset) & 1; /* klass->interfaces_inited */ -#if defined ASSERT_IT_WORKS - MonoClass tester = { .interfaces_inited = TRUE }; - gint8 test_byte = atomic_load_explicit(&((const atomic_uint_least8_t*)&tester.min_align)[byte_offset_from_min_align], memory_order_acquire); - int test_bit = (test_byte >> bit_offset) & 1; - g_assert(test_bit == tester.interfaces_inited); - g_assert(is_inited == klass->interfaces_inited); + ATOMIC_U8_PTR addr_of_containing_byte = &((ATOMIC_U8_PTR) &klass->min_align)[byte_offset_from_min_align]; + gint8 containing_byte = +#if defined(_MSC_VER) + __ldar8(addr_of_containing_byte); +#else + atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); #endif + int is_inited = (containing_byte >> bit_offset) & 1; /* klass->interfaces_inited */ if (is_inited) return; +#undef ATOMIC_U8_PTR #else if (klass->interfaces_inited) return; From c8d4b8ab7e3fdbafd243501e1d0a0d2e80471917 Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Tue, 14 Jul 2026 11:13:10 +0200 Subject: [PATCH 6/7] Add comment --- mono/metadata/class-init.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index d17084ca2827..aba1393c2f79 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -28,7 +28,7 @@ #include #include #if defined HOST_ARM64 -#if defined(_MSC_VER) +#if defined _MSC_VER #include #else #include @@ -3778,12 +3778,38 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) error_init (error); + // ======================================= NOTE! ======================================= + // This is a hacky fix for a bug that is technically present many places in the codebase. + // + // The issue is: + // Below we normally check if `klass->interfaces_inited` (bitfield flag) is set. + // If that's the case, we assume the interfaces for this class have been initialized + // and `klass->interface_count` and `klass->interfaces` are set and valid. + // + // If it's not set, we prepare the values and then enter the mono_loader_lock, + // set the values, memory fence and then set the `klass->interfaces_inited` flag. + // + // On strong memory ordering architectures (like x86) this is enough to guarantee that + // if a thread observes `klass->interfaces_inited` to be set, then the interface data + // is present and valid. + // + // But on weak memory ordering architectures (like ARM64) this is not guaranteed, and + // we need at minimum a load-acquire to ensure the correct observed ordering here. + // + // This is technically an issue everywhere that uses this pattern, but has only been + // consistently observed here. + // + // The fix: + // An additional issue is that because `klass->interfaces_inited` is a bitfield, we + // can't get a pointer to it (required for load-acquires). So instead we have to do + // the following ugly hack: #if defined HOST_ARM64 -#if defined(_MSC_VER) +#if defined _MSC_VER #define ATOMIC_U8_PTR unsigned __int8 volatile* #else #define ATOMIC_U8_PTR const atomic_uint_least8_t* #endif + // NOTE: The layout defined in `class-private-definition.h` is affected by DISABLE_REMOTING #ifndef DISABLE_REMOTING int byte_offset_from_min_align = 3; int bit_offset = 0; @@ -3791,13 +3817,17 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) int byte_offset_from_min_align = 2; int bit_offset = 6; #endif + // We get the address of the closest addressable field in the class, which is `min_align` + // From there we can count how many bytes away the byte containing our bit is: ATOMIC_U8_PTR addr_of_containing_byte = &((ATOMIC_U8_PTR) &klass->min_align)[byte_offset_from_min_align]; - gint8 containing_byte = -#if defined(_MSC_VER) + // Load it using a load-acquire: + guint8 containing_byte = +#if defined _MSC_VER __ldar8(addr_of_containing_byte); #else atomic_load_explicit(addr_of_containing_byte, memory_order_acquire); #endif + // Then extract the specific bit from that byte: int is_inited = (containing_byte >> bit_offset) & 1; /* klass->interfaces_inited */ if (is_inited) return; From 06c860d167ee1c2c322f5ad62445dd0128c14ff0 Mon Sep 17 00:00:00 2001 From: Marco Persson Date: Fri, 17 Jul 2026 13:45:18 +0200 Subject: [PATCH 7/7] Fix offsets on Windows --- mono/metadata/class-init.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/mono/metadata/class-init.c b/mono/metadata/class-init.c index aba1393c2f79..c50dd62ccc65 100644 --- a/mono/metadata/class-init.c +++ b/mono/metadata/class-init.c @@ -3810,13 +3810,40 @@ mono_class_setup_interfaces (MonoClass *klass, MonoError *error) #define ATOMIC_U8_PTR const atomic_uint_least8_t* #endif // NOTE: The layout defined in `class-private-definition.h` is affected by DISABLE_REMOTING +#if defined _MSC_VER +#ifndef DISABLE_REMOTING + int byte_offset_from_min_align = 6; + int bit_offset = 0; +#else + int byte_offset_from_min_align = 5; + int bit_offset = 6; +#endif +#else #ifndef DISABLE_REMOTING int byte_offset_from_min_align = 3; int bit_offset = 0; #else int byte_offset_from_min_align = 2; int bit_offset = 6; +#endif #endif + // ====================== CORRECTNESS CHECK ====================== + // Enable this define to verify: +#if defined ARM64_RACE_CONDITION_CORRECTNESS_ASSERT + MonoClass tester = { .interfaces_inited = TRUE }; + ATOMIC_U8_PTR test_addr = &((ATOMIC_U8_PTR) &tester.min_align)[byte_offset_from_min_align]; + gint8 test_byte = +#if defined _MSC_VER + __ldar8(test_addr); +#else + atomic_load_explicit(test_addr, memory_order_acquire); +#endif + int test_bit = (test_byte >> bit_offset) & 1; + g_assert(test_bit == tester.interfaces_inited); +#endif + // =============================================================== + + // We get the address of the closest addressable field in the class, which is `min_align` // From there we can count how many bytes away the byte containing our bit is: ATOMIC_U8_PTR addr_of_containing_byte = &((ATOMIC_U8_PTR) &klass->min_align)[byte_offset_from_min_align]; @@ -4205,4 +4232,4 @@ mono_classes_cleanup (void) mono_bitset_free (global_interface_bitset); global_interface_bitset = NULL; mono_os_mutex_destroy (&classes_mutex); -} +} \ No newline at end of file