From 766c28d5d310bd2c9094702edc791ce71c41f939 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 22 Jun 2026 21:14:50 +0200 Subject: [PATCH 1/3] [mono] AOT-compile marshalling wrappers for layout classes with class-typed fields can_marshal_struct() in the AOT compiler did not handle MONO_TYPE_CLASS fields, so a [StructLayout] class whose fields are themselves marshalable layout classes (e.g. MINMAXINFO with two POINT fields) was considered not marshalable. Its StructureToPtr/PtrToStructure wrappers were therefore not emitted into the AOT image, and calling Marshal.StructureToPtr/PtrToStructure under full-AOT failed with 'Attempting to JIT compile method ... while running in aot-only mode'. Handle MONO_TYPE_CLASS the same way as MONO_TYPE_VALUETYPE by recursing into can_marshal_struct; the existing auto-layout check keeps non-layout class fields conservative. Re-enables the JIT/Regression/JitBlue/WPF_3226 test on Mono. --- src/mono/mono/mini/aot-compiler.c | 10 ++++++++++ .../JitBlue/WPF_3226/CSharpRepro/WPF_3226.cs | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 8e9cdc6120f4bd..b560a214a28065 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -4584,6 +4584,16 @@ can_marshal_struct (MonoClass *klass) if (!m_class_is_enumtype (mono_class_from_mono_type_internal (field->type)) && !can_marshal_struct (mono_class_from_mono_type_internal (field->type))) can_marshal = FALSE; break; + case MONO_TYPE_CLASS: + /* + * A field whose type is a non-auto-layout (sequential/explicit) class is marshalled + * as an embedded struct, the same way the runtime marshalling code handles it. Allow it + * if the nested class is itself marshallable; otherwise the StructureToPtr/PtrToStructure + * wrappers are not AOT compiled and full-AOT fails with a JIT-in-aot-only error. + */ + if (!can_marshal_struct (mono_class_from_mono_type_internal (field->type))) + can_marshal = FALSE; + break; case MONO_TYPE_SZARRAY: { gboolean has_mspec = FALSE; diff --git a/src/tests/JIT/Regression/JitBlue/WPF_3226/CSharpRepro/WPF_3226.cs b/src/tests/JIT/Regression/JitBlue/WPF_3226/CSharpRepro/WPF_3226.cs index 51106edc3fb893..d958abe234da5e 100644 --- a/src/tests/JIT/Regression/JitBlue/WPF_3226/CSharpRepro/WPF_3226.cs +++ b/src/tests/JIT/Regression/JitBlue/WPF_3226/CSharpRepro/WPF_3226.cs @@ -38,7 +38,6 @@ static void WmGetMinMaxInfo(IntPtr lParam) Marshal.StructureToPtr(mmi, lParam, true); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/129508", TestRuntimes.Mono)] [Fact] public unsafe static int TestEntryPoint() { From 3b8fb68fc3ae4f6cbbd76b5072a1e80e330f5e08 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 22 Jun 2026 23:49:54 +0200 Subject: [PATCH 2/3] Guard can_marshal_struct against recursion on cyclic layout types The new MONO_TYPE_CLASS handling recurses into class-typed fields, but unlike value types a reference-class field can point back to its declaring class (directly or indirectly). On a self-referential or cyclic [StructLayout] class this recursed without bound and overflowed the stack, crashing the AOT compiler (observed while AOT-compiling framework assemblies such as System.Net.Security and System.Private.CoreLib). Add a recursion-depth guard: such types cannot be marshalled as a flat struct anyway, so treat them as non-marshalable instead of overflowing. --- src/mono/mono/mini/aot-compiler.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index b560a214a28065..e792d4a9521360 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -4545,7 +4545,7 @@ get_runtime_invoke (MonoAotCompile *acfg, MonoMethod *method, gboolean virtual_) } static gboolean -can_marshal_struct (MonoClass *klass) +can_marshal_struct_internal (MonoClass *klass, int depth) { MonoClassField *field; gboolean can_marshal = TRUE; @@ -4555,6 +4555,15 @@ can_marshal_struct (MonoClass *klass) if (mono_class_is_auto_layout (klass)) return FALSE; + /* + * Guard against runaway recursion for self-referential or cyclic layout types: a + * reference-class field can point back to its declaring class, directly or indirectly. + * Such a type cannot be marshalled as a flat struct anyway, so treat it as + * non-marshalable instead of overflowing the stack. + */ + if (depth > 32) + return FALSE; + info = mono_marshal_load_type_info (klass); /* Only allow a few field types to avoid asserts in the marshalling code */ @@ -4581,7 +4590,7 @@ can_marshal_struct (MonoClass *klass) case MONO_TYPE_STRING: break; case MONO_TYPE_VALUETYPE: - if (!m_class_is_enumtype (mono_class_from_mono_type_internal (field->type)) && !can_marshal_struct (mono_class_from_mono_type_internal (field->type))) + if (!m_class_is_enumtype (mono_class_from_mono_type_internal (field->type)) && !can_marshal_struct_internal (mono_class_from_mono_type_internal (field->type), depth + 1)) can_marshal = FALSE; break; case MONO_TYPE_CLASS: @@ -4591,7 +4600,7 @@ can_marshal_struct (MonoClass *klass) * if the nested class is itself marshallable; otherwise the StructureToPtr/PtrToStructure * wrappers are not AOT compiled and full-AOT fails with a JIT-in-aot-only error. */ - if (!can_marshal_struct (mono_class_from_mono_type_internal (field->type))) + if (!can_marshal_struct_internal (mono_class_from_mono_type_internal (field->type), depth + 1)) can_marshal = FALSE; break; case MONO_TYPE_SZARRAY: { @@ -4621,6 +4630,12 @@ can_marshal_struct (MonoClass *klass) return can_marshal; } +static gboolean +can_marshal_struct (MonoClass *klass) +{ + return can_marshal_struct_internal (klass, 0); +} + /* Create a ref shared instantiation */ static void create_ref_shared_inst (MonoAotCompile *acfg, MonoMethod *method, MonoGenericContext *ctx) From 609a3c0d55c16d62f639bd0504c08a5dc64ed6e6 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 13:25:36 +0200 Subject: [PATCH 3/3] [mono] Fix 'marshallable' typo in can_marshal_struct comment Use the codebase's standard spelling 'marshalable'. --- src/mono/mono/mini/aot-compiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index e792d4a9521360..465062d04c7f9d 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -4597,7 +4597,7 @@ can_marshal_struct_internal (MonoClass *klass, int depth) /* * A field whose type is a non-auto-layout (sequential/explicit) class is marshalled * as an embedded struct, the same way the runtime marshalling code handles it. Allow it - * if the nested class is itself marshallable; otherwise the StructureToPtr/PtrToStructure + * if the nested class is itself marshalable; otherwise the StructureToPtr/PtrToStructure * wrappers are not AOT compiled and full-AOT fails with a JIT-in-aot-only error. */ if (!can_marshal_struct_internal (mono_class_from_mono_type_internal (field->type), depth + 1))