[mono][aot] Add support for deferred failures during AOT compilation (#86554)
authorMilos Kotlar <kotlarmilos@gmail.com>
Wed, 31 May 2023 09:32:00 +0000 (11:32 +0200)
committerGitHub <noreply@github.com>
Wed, 31 May 2023 09:32:00 +0000 (11:32 +0200)
* Add deferred failures to the AOT comiler

* Move type_load_failure_callback to mono-error.c

* Add default mono_class_set_deferred_type_load_failure_callback

* Disable failing test in llvm AOT configurations

13 files changed:
src/mono/mono/metadata/class-accessors.c
src/mono/mono/metadata/class-getters.h
src/mono/mono/metadata/class-init.c
src/mono/mono/metadata/class-internals.h
src/mono/mono/metadata/class-private-definition.h
src/mono/mono/metadata/class.c
src/mono/mono/mini/aot-compiler.c
src/mono/mono/mini/aot-runtime.c
src/mono/mono/mini/driver.c
src/mono/mono/mini/mini-runtime.c
src/mono/mono/utils/mono-error-internals.h
src/mono/mono/utils/mono-error.c
src/tests/issues.targets

index 4cc08da..f74b2dc 100644 (file)
@@ -579,6 +579,23 @@ mono_class_set_failure (MonoClass *klass, MonoErrorBoxed *boxed_error)
 }
 
 /**
+ * mono_class_set_deferred_failure:
+ * \param klass class in which the failure was detected
+ * This method marks the class with a deferred failure, indicating that a failure was detected but it will be processed during AOT runtime..
+ * Note that only the first failure is kept.
+ *
+ * LOCKING: Acquires the loader lock.
+ */
+void
+mono_class_set_deferred_failure (MonoClass *klass)
+{
+       mono_loader_lock ();
+       klass->has_deferred_failure = 1;
+       mono_loader_unlock ();
+}
+
+/**
  * mono_class_set_nonblittable:
  * \param klass class which will be marked as not blittable.
  *
index 9eafbb4..c0e1505 100644 (file)
@@ -49,6 +49,7 @@ MONO_CLASS_GETTER(m_class_is_simd_type, gboolean, , MonoClass, simd_type)
 MONO_CLASS_GETTER(m_class_is_has_finalize_inited, gboolean, , MonoClass, has_finalize_inited)
 MONO_CLASS_GETTER(m_class_is_fields_inited, gboolean, , MonoClass, fields_inited)
 MONO_CLASS_GETTER(m_class_has_failure, gboolean, , MonoClass, has_failure)
+MONO_CLASS_GETTER(m_class_has_deferred_failure, gboolean, , MonoClass, has_deferred_failure)
 MONO_CLASS_GETTER(m_class_has_weak_fields, gboolean, , MonoClass, has_weak_fields)
 MONO_CLASS_GETTER(m_class_has_dim_conflicts, gboolean, , MonoClass, has_dim_conflicts)
 MONO_CLASS_GETTER(m_class_get_parent, MonoClass *, , MonoClass, parent)
index dca64ca..6ecaca9 100644 (file)
@@ -305,7 +305,7 @@ mono_class_setup_fields (MonoClass *klass)
        }
 
        if (m_class_is_inlinearray (klass) && m_class_inlinearray_value (klass) <= 0)
-               mono_class_set_type_load_failure (klass, "Inline array length property must be positive.");
+               mono_class_set_deferred_type_load_failure_callback (klass, "Inline array length property must be positive.");
 
        /* Get the real size */
        explicit_size = mono_metadata_packing_from_typedef (klass->image, klass->type_token, &packing_size, &real_size);
@@ -376,8 +376,8 @@ mono_class_setup_fields (MonoClass *klass)
                                break;
                        }
                        if (m_class_is_inlinearray (klass)) {
-                               mono_class_set_type_load_failure (klass, "Inline array struct must not have explicit layout.");
-                               break;
+                               if (mono_class_set_deferred_type_load_failure_callback (klass, "Inline array struct must not have explicit layout."))
+                                       break;
                        }
                }
                if (mono_type_has_exceptions (field->type)) {
@@ -2276,12 +2276,15 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_
                                if (m_class_is_inlinearray (klass)) {
                                        // Limit the max size of array instance to 1MiB
                                        const guint32 struct_max_size = 1024 * 1024;
+                                       guint32 initial_size = size;
                                        // If size overflows, it returns 0
                                        size *= m_class_inlinearray_value (klass);
                                        inlined_fields++;
                                        if(size == 0 || size > struct_max_size) {
-                                               mono_class_set_type_load_failure (klass, "Inline array struct size out of bounds, abnormally large.");
-                                               break;
+                                               if (mono_class_set_deferred_type_load_failure_callback (klass, "Inline array struct size out of bounds, abnormally large."))
+                                                       break;
+                                               else
+                                                       size = initial_size;
                                        }
                                }
 
@@ -2312,7 +2315,7 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_
                        }
                }
                if (m_class_is_inlinearray (klass) && inlined_fields != 1)
-                       mono_class_set_type_load_failure (klass, "Inline array struct must have a single field.");
+                       mono_class_set_deferred_type_load_failure_callback (klass, "Inline array struct must have a single field.");
                break;
        case TYPE_ATTRIBUTE_EXPLICIT_LAYOUT: {
                real_size = 0;
@@ -2993,6 +2996,14 @@ mono_class_init_internal (MonoClass *klass)
 
        has_cached_info = mono_class_get_cached_class_info (klass, &cached_info);
 
+       /*
+        * If the class has a deferred failure, ignore the cached info and
+        * let the runtime go on the slow path of trying to setup the class
+        * layout at runtime.
+       */
+       if (has_cached_info && cached_info.has_deferred_failure)
+               has_cached_info = FALSE;
+
        /* Compute instance size etc. */
        init_sizes_with_info (klass, has_cached_info ? &cached_info : NULL);
        if (mono_class_has_failure (klass))
index 7915be4..68f83b1 100644 (file)
@@ -578,6 +578,7 @@ typedef struct MonoCachedClassInfo {
        guint no_special_static_fields : 1;
        guint is_generic_container : 1;
        guint has_weak_fields : 1;
+       guint has_deferred_failure : 1;
        guint32 cctor_token;
        MonoImage *finalize_image;
        guint32 finalize_token;
@@ -1062,9 +1063,6 @@ mono_register_jit_icall_info (MonoJitICallInfo *info, T func, const char *name,
 
 #define mono_register_jit_icall(func, sig, no_wrapper) (mono_register_jit_icall_info (&mono_get_jit_icall_info ()->func, func, #func, (sig), (no_wrapper), NULL))
 
-gboolean
-mono_class_set_type_load_failure (MonoClass *klass, const char * fmt, ...) MONO_ATTR_FORMAT_PRINTF(2,3);
-
 MonoException*
 mono_class_get_exception_for_failure (MonoClass *klass);
 
@@ -1268,6 +1266,9 @@ mono_error_set_for_class_failure (MonoError *orerror, const MonoClass *klass);
 gboolean
 mono_class_has_failure (const MonoClass *klass);
 
+gboolean
+mono_class_has_deferred_failure (const MonoClass *klass);
+
 /* Kind specific accessors */
 MONO_COMPONENT_API MonoGenericClass*
 mono_class_get_generic_class (MonoClass *klass);
@@ -1429,6 +1430,9 @@ mono_class_find_enum_basetype (MonoClass *klass, MonoError *error);
 gboolean
 mono_class_set_failure (MonoClass *klass, MonoErrorBoxed *boxed_error);
 
+void
+mono_class_set_deferred_failure (MonoClass *klass);
+
 gboolean
 mono_class_set_type_load_failure_causedby_class (MonoClass *klass, const MonoClass *caused_by, const gchar* msg);
 
index 08139a2..ee104bb 100644 (file)
@@ -83,6 +83,7 @@ struct _MonoClass {
        guint has_weak_fields : 1; /* class has weak reference fields */
        guint has_dim_conflicts : 1; /* Class has conflicting default interface methods */
        guint any_field_has_auto_layout : 1; /* a field in this type's layout uses auto-layout */
+       guint has_deferred_failure : 1;
 
        MonoClass  *parent;
        MonoClass  *nested_in;
index 5dc37fc..3b371bf 100644 (file)
@@ -5943,36 +5943,11 @@ mono_class_has_failure (const MonoClass *klass)
        return m_class_has_failure ((MonoClass*)klass) != 0;
 }
 
-
-/**
- * mono_class_set_type_load_failure:
- * \param klass class in which the failure was detected
- * \param fmt \c printf -style error message string.
- *
- * Collect detected failure informaion in the class for later processing.
- * The error is stored as a MonoErrorBoxed as with mono_error_set_type_load_class()
- * Note that only the first failure is kept.
- *
- * LOCKING: Acquires the loader lock.
- *
- * \returns FALSE if a failure was already set on the class, or TRUE otherwise.
- */
 gboolean
-mono_class_set_type_load_failure (MonoClass *klass, const char * fmt, ...)
+mono_class_has_deferred_failure (const MonoClass *klass)
 {
-       ERROR_DECL (prepare_error);
-       va_list args;
-
-       if (mono_class_has_failure (klass))
-               return FALSE;
-
-       va_start (args, fmt);
-       mono_error_vset_type_load_class (prepare_error, klass, fmt, args);
-       va_end (args);
-
-       MonoErrorBoxed *box = mono_error_box (prepare_error, m_class_get_image (klass));
-       mono_error_cleanup (prepare_error);
-       return mono_class_set_failure (klass, box);
+       g_assert (klass != NULL);
+       return m_class_has_deferred_failure ((MonoClass*)klass) != 0;
 }
 
 /**
index 33d4e9d..d164793 100644 (file)
@@ -7880,7 +7880,7 @@ emit_klass_info (MonoAotCompile *acfg, guint32 token)
        } else {
                gboolean has_nested = mono_class_get_nested_classes_property (klass) != NULL;
                encode_value (m_class_get_vtable_size (klass), p, &p);
-               encode_value ((m_class_has_weak_fields (klass) << 9) | (mono_class_is_gtd (klass) ? (1 << 8) : 0) | (no_special_static << 7) | (m_class_has_static_refs (klass) << 6) | (m_class_has_references (klass) << 5) | ((m_class_is_blittable (klass) << 4) | (has_nested ? 1 : 0) << 3) | (m_class_has_cctor (klass) << 2) | (m_class_has_finalize (klass) << 1) | m_class_is_ghcimpl (klass), p, &p);
+               encode_value ((m_class_has_deferred_failure (klass) << 10) | (m_class_has_weak_fields (klass) << 9) | (mono_class_is_gtd (klass) ? (1 << 8) : 0) | (no_special_static << 7) | (m_class_has_static_refs (klass) << 6) | (m_class_has_references (klass) << 5) | ((m_class_is_blittable (klass) << 4) | (has_nested ? 1 : 0) << 3) | (m_class_has_cctor (klass) << 2) | (m_class_has_finalize (klass) << 1) | m_class_is_ghcimpl (klass), p, &p);
                if (m_class_has_cctor (klass))
                        encode_method_ref (acfg, mono_class_get_cctor (klass), p, &p);
                if (m_class_has_finalize (klass))
index 6da9d27..280d05b 100644 (file)
@@ -2508,6 +2508,7 @@ decode_cached_class_info (MonoAotModule *module, MonoCachedClassInfo *info, guin
        info->no_special_static_fields = (flags >> 7) & 0x1;
        info->is_generic_container = (flags >> 8) & 0x1;
        info->has_weak_fields = (flags >> 9) & 0x1;
+       info->has_deferred_failure = (flags >> 10) & 0x1;
 
        if (info->has_cctor) {
                res = decode_method_ref (module, &ref, buf, &buf, error);
index a5c95bd..2d21c98 100644 (file)
@@ -1400,7 +1400,7 @@ main_thread_handler (gpointer user_data)
                MonoAssembly **assemblies;
 
                assemblies = g_new0 (MonoAssembly*, main_args->argc);
-
+               set_failure_type (DEFERRED_FAILURE);
                /* Treat the other arguments as assemblies to compile too */
                for (i = 0; i < main_args->argc; ++i) {
                        assembly = mono_domain_assembly_open_internal (mono_alc_get_default (), main_args->argv [i]);
@@ -1428,6 +1428,7 @@ main_thread_handler (gpointer user_data)
                return;
        }
 
+       set_failure_type (IMMEDIATE_FAILURE);
        assembly = mono_domain_assembly_open_internal (mono_alc_get_default (), main_args->file);
        if (!assembly){
                fprintf (stderr, "Can not open image %s\n", main_args->file);
index 019fc1a..c44cf55 100644 (file)
@@ -1547,7 +1547,6 @@ mono_resolve_patch_target_ext (MonoMemoryManager *mem_manager, MonoMethod *metho
                break;
        case MONO_PATCH_INFO_VTABLE:
                target = mono_class_vtable_checked (patch_info->data.klass, error);
-               mono_error_assert_ok (error);
                break;
        case MONO_PATCH_INFO_DELEGATE_INFO: {
                MonoDelegateClassMethodPair *del_tramp = patch_info->data.del_tramp;
index 6ed41ef..272d87b 100644 (file)
@@ -319,6 +319,36 @@ mono_error_set_specific (MonoError *error, int error_code, const char *missing_m
 void
 mono_error_set_first_argument (MonoError *oerror, const char *first_argument);
 
+typedef enum {
+    DEFERRED_FAILURE, // Used during AOT compilation to defer failure for execution
+    IMMEDIATE_FAILURE // Used during runtime to indicate that the failure should be reported
+} FailureType;
+
+void
+set_failure_type (FailureType failure_type);
+
+/**
+ * TypeLoadFailureCallback:
+ * @param klass: Class in which the failure was detected.
+ * @param fmt: printf-style error message string.
+ *
+ * The callback is responsible for processing the failure information provided by the @klass parameter and the error message format string @fmt.
+ * If a deferred failure occurs, the callback should return FALSE to let the AOT compiler proceed with the class layout setup.
+ * Otherwise, if the callback returns TRUE, it indicates that the failure should be reported.
+ *
+ * @returns: TRUE if the failure is handled and the runtime should not proceed with class setup, FALSE if the failure should be deferred for runtime class setup.
+ * 
+ */
+typedef gboolean (*TypeLoadFailureCallback)(MonoClass *klass, const char * fmt, ...) MONO_ATTR_FORMAT_PRINTF(2,3);
+
+extern TypeLoadFailureCallback mono_class_set_deferred_type_load_failure_callback;
+
+gboolean
+mono_class_set_deferred_type_load_failure (MonoClass *klass, const char * fmt, ...);
+
+gboolean
+mono_class_set_type_load_failure (MonoClass *klass, const char * fmt, ...);
+
 #if HOST_WIN32
 #if HOST_X86 || HOST_AMD64
 
index 821fc33..3cb2cf7 100644 (file)
@@ -30,6 +30,8 @@
        va_end (args); \
 } while (0)
 
+TypeLoadFailureCallback mono_class_set_deferred_type_load_failure_callback = mono_class_set_type_load_failure;
+
 static void
 mono_error_set_generic_errorv (MonoError *oerror, const char *name_space, const char *name, const char *msg_format, va_list args);
 
@@ -908,3 +910,73 @@ mono_error_set_first_argument (MonoError *oerror, const char *first_argument)
        to->first_argument = g_strdup (first_argument);
        to->flags |= MONO_ERROR_FREE_STRINGS;
 }
+
+/**
+ * mono_class_set_deferred_type_load_failure:
+ * \param klass class in which the failure was detected
+ * \param fmt \c printf -style error message string.
+ *
+ * Sets a deferred failure in the class and prints a warning message. 
+ * The deferred failure allows the runtime to attempt setting up the class layout at runtime.
+ *
+ * LOCKING: Acquires the loader lock.
+ *
+ * \returns FALSE
+ */
+gboolean
+mono_class_set_deferred_type_load_failure (MonoClass *klass, const char * fmt, ...)
+{
+       if (!mono_class_has_deferred_failure (klass)) {
+               va_list args;
+
+               va_start (args, fmt);
+               g_warning ("Warning: %s", fmt, args);
+               va_end (args);
+
+               mono_class_set_deferred_failure (klass);
+       }
+
+       return FALSE;
+}
+
+/**
+ * mono_class_set_type_load_failure:
+ * \param klass class in which the failure was detected
+ * \param fmt \c printf -style error message string.
+ *
+ * Collect detected failure informaion in the class for later processing.
+ * The error is stored as a MonoErrorBoxed as with mono_error_set_type_load_class()
+ * Note that only the first failure is kept.
+ *
+ * LOCKING: Acquires the loader lock.
+ *
+ * \returns TRUE
+ */
+gboolean
+mono_class_set_type_load_failure (MonoClass *klass, const char * fmt, ...)
+{
+       if (!mono_class_has_failure (klass)) {
+               ERROR_DECL (prepare_error);
+               va_list args;
+
+               va_start (args, fmt);
+               mono_error_vset_type_load_class (prepare_error, klass, fmt, args);
+               va_end (args);
+
+               MonoErrorBoxed *box = mono_error_box (prepare_error, m_class_get_image (klass));
+               mono_error_cleanup (prepare_error);
+               mono_class_set_failure (klass, box);
+       }
+
+       return TRUE;
+}
+
+void set_failure_type(FailureType failure_type) {
+       if (failure_type == DEFERRED_FAILURE) {
+               mono_class_set_deferred_type_load_failure_callback = mono_class_set_deferred_type_load_failure;
+       } else if (failure_type == IMMEDIATE_FAILURE) {
+               mono_class_set_deferred_type_load_failure_callback = mono_class_set_type_load_failure;
+       } else {
+               g_assert_not_reached ();
+       }
+}
\ No newline at end of file
index 9c28713..33d95e4 100644 (file)
         <ExcludeList Include = "$(XunitTestBinBase)/Interop/NativeLibrary/API/NativeLibraryTests/**">
             <Issue>Needs coreclr build</Issue>
         </ExcludeList>
+        <ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/InlineArray/InlineArrayInvalid/**">
+            <Issue>https://github.com/dotnet/runtime/issues/86327</Issue>
+        </ExcludeList>
     </ItemGroup>
 
     <ItemGroup Condition="'$(RuntimeFlavor)' == 'mono' and ('$(RuntimeVariant)' == 'llvmfullaot' or '$(RuntimeVariant)' == 'llvmaot' or '$(TargetOS)' == 'ios') and '$(TargetArchitecture)' == 'arm64'">