[mono] Implement GC descriptor for structs with InlineArray attribute (#84097)
authorMilos Kotlar <kotlarmilos@gmail.com>
Thu, 30 Mar 2023 13:58:22 +0000 (15:58 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Mar 2023 13:58:22 +0000 (15:58 +0200)
* Implement GC descriptor for structs with InlineArrayAttribute and add Mono GC test

* Enable GC test on CoreCLR

* Add warning when field_iter too large

src/mono/mono/metadata/object.c
src/tests/Loader/classloader/InlineArray/InlineArrayValid.cs

index 90b2cf2..c157ffc 100644 (file)
@@ -874,72 +874,77 @@ compute_class_bitmap (MonoClass *klass, gsize *bitmap, int size, int offset, int
                                /* special/collectible static */
                                continue;
 
-                       pos = field_offset / TARGET_SIZEOF_VOID_P;
-                       pos += offset;
-
-                       type = mono_type_get_underlying_type (field->type);
-
-                       switch (type->type) {
-                       case MONO_TYPE_U:
-                       case MONO_TYPE_I:
-                       case MONO_TYPE_PTR:
-                       case MONO_TYPE_FNPTR:
-                               break;
-                       case MONO_TYPE_STRING:
-                       case MONO_TYPE_SZARRAY:
-                       case MONO_TYPE_CLASS:
-                       case MONO_TYPE_OBJECT:
-                       case MONO_TYPE_ARRAY:
-                               g_assert ((m_field_get_offset (field) % wordsize) == 0);
-
-                               g_assert (pos < GINT_TO_UINT32(size) || pos <= GINT_TO_UINT32(max_size));
-                               bitmap [pos / BITMAP_EL_SIZE] |= ((gsize)1) << (pos % BITMAP_EL_SIZE);
-                               *max_set = MAX (GINT_TO_UINT32(*max_set), pos);
-                               break;
-                       case MONO_TYPE_GENERICINST:
-                               if (!mono_type_generic_inst_is_valuetype (type)) {
+                       guint32 field_iter = 1;
+                       guint32 field_instance_offset = field_offset;
+                       // If struct has InlineArray attribute, iterate `length` times to set a bitmap
+                       if (m_class_is_inlinearray (p))
+                               field_iter = m_class_inlinearray_value (p);
+
+                       if (field_iter > 500)
+                               g_warning ("Large number of iterations detected when creating a GC bitmap, might affect performance.");
+
+                       while (field_iter) {
+                               pos = field_instance_offset / TARGET_SIZEOF_VOID_P;
+                               pos += offset;
+
+                               type = mono_type_get_underlying_type (field->type);
+
+                               switch (type->type) {
+                               case MONO_TYPE_U:
+                               case MONO_TYPE_I:
+                               case MONO_TYPE_PTR:
+                               case MONO_TYPE_FNPTR:
+                                       break;
+                               case MONO_TYPE_STRING:
+                               case MONO_TYPE_SZARRAY:
+                               case MONO_TYPE_CLASS:
+                               case MONO_TYPE_OBJECT:
+                               case MONO_TYPE_ARRAY:
                                        g_assert ((m_field_get_offset (field) % wordsize) == 0);
 
+                                       g_assert (pos < GINT_TO_UINT32(size) || pos <= GINT_TO_UINT32(max_size));
                                        bitmap [pos / BITMAP_EL_SIZE] |= ((gsize)1) << (pos % BITMAP_EL_SIZE);
                                        *max_set = MAX (GINT_TO_UINT32(*max_set), pos);
                                        break;
-                               } else {
-                                       /* fall through */
+                               case MONO_TYPE_GENERICINST:
+                                       if (!mono_type_generic_inst_is_valuetype (type)) {
+                                               g_assert ((m_field_get_offset (field) % wordsize) == 0);
+
+                                               bitmap [pos / BITMAP_EL_SIZE] |= ((gsize)1) << (pos % BITMAP_EL_SIZE);
+                                               *max_set = MAX (GINT_TO_UINT32(*max_set), pos);
+                                               break;
+                                       } else {
+                                               /* fall through */
+                                       }
+                               case MONO_TYPE_TYPEDBYREF:
+                               case MONO_TYPE_VALUETYPE: {
+                                       MonoClass *fclass = mono_class_from_mono_type_internal (field->type);
+                                       if (m_class_has_references (fclass)) {
+                                               /* remove the object header */
+                                               compute_class_bitmap (fclass, bitmap, size, pos - MONO_OBJECT_HEADER_BITS, max_set, FALSE);
+                                       }
+                                       break;
                                }
-                       case MONO_TYPE_TYPEDBYREF:
-                       case MONO_TYPE_VALUETYPE: {
-                               MonoClass *fclass = mono_class_from_mono_type_internal (field->type);
-                               if (m_class_has_references (fclass)) {
-                                       /* remove the object header */
-                                       compute_class_bitmap (fclass, bitmap, size, pos - MONO_OBJECT_HEADER_BITS, max_set, FALSE);
+                               case MONO_TYPE_I1:
+                               case MONO_TYPE_U1:
+                               case MONO_TYPE_I2:
+                               case MONO_TYPE_U2:
+                               case MONO_TYPE_I4:
+                               case MONO_TYPE_U4:
+                               case MONO_TYPE_I8:
+                               case MONO_TYPE_U8:
+                               case MONO_TYPE_R4:
+                               case MONO_TYPE_R8:
+                               case MONO_TYPE_BOOLEAN:
+                               case MONO_TYPE_CHAR:
+                                       break;
+                               default:
+                                       g_error ("compute_class_bitmap: Invalid type %x for field %s:%s\n", type->type, mono_type_get_full_name (m_field_get_parent (field)), field->name);
+                                       break;
                                }
-                               break;
-                       }
-                       case MONO_TYPE_I1:
-                       case MONO_TYPE_U1:
-                       case MONO_TYPE_I2:
-                       case MONO_TYPE_U2:
-                       case MONO_TYPE_I4:
-                       case MONO_TYPE_U4:
-                       case MONO_TYPE_I8:
-                       case MONO_TYPE_U8:
-                       case MONO_TYPE_R4:
-                       case MONO_TYPE_R8:
-                       case MONO_TYPE_BOOLEAN:
-                       case MONO_TYPE_CHAR:
-                               break;
-                       default:
-                               g_error ("compute_class_bitmap: Invalid type %x for field %s:%s\n", type->type, mono_type_get_full_name (m_field_get_parent (field)), field->name);
-                               break;
-                       }
-
-                       // A struct with the inline array attribute is handled in the same way as an array
-                       if (m_class_is_inlinearray (klass)) {
-                               g_assert ((m_field_get_offset (field) % wordsize) == 0);
 
-                               g_assert (pos < GINT_TO_UINT32(size) || pos <= GINT_TO_UINT32(max_size));
-                               bitmap [pos / BITMAP_EL_SIZE] |= ((gsize)1) << (pos % BITMAP_EL_SIZE);
-                               *max_set = MAX (GINT_TO_UINT32(*max_set), pos);
+                               field_instance_offset += field_offset;
+                               field_iter--;
                        }
                }
                if (static_fields)
index 705f32e..82ebaa0 100644 (file)
@@ -172,6 +172,21 @@ unsafe class Validate
 
         [UnscopedRef]
         public ref (object o, short s) this[int i] => ref Unsafe.Add(ref element, i);
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static ObjShortArr CreateArray(int recCount) {
+            if (recCount > 0) {
+                return CreateArray(recCount-1);
+            } else {
+                var arr = new ObjShortArr();
+                for (short i = 0; i < ObjShortArr.Length; i++)
+                {
+                    arr[i].o = i;
+                    arr[i].s = (short)(i + 1);
+                }
+                return arr;
+            }
+        }
     }
 
     [Fact]
@@ -384,4 +399,35 @@ unsafe class Validate
             Assert.Equal(1, *gcSeriesPtr);
         }
     }
+
+     // ====================== MonoGCDesc ==========================================================
+
+    class Holder {
+        public ObjShortArr arr;
+    }
+
+    static Holder CreateArray() {
+        var arr = ObjShortArr.CreateArray(100);
+        var holder = new Holder();
+        holder.arr = arr;
+        return holder;
+    }
+
+    [Fact]
+    public static void MonoGCDescOpt()
+    {
+        Console.WriteLine($"{nameof(MonoGCDescOpt)}...");
+
+        var holder = CreateArray();
+
+        GC.Collect(2, GCCollectionMode.Forced, true, true);
+
+        MakeGarbage();
+
+        for (short i = 0; i < ObjShortArr.Length; i++)
+        {
+            Assert.Equal(i, holder.arr[i].o);
+            Assert.Equal(i + 1, holder.arr[i].s);
+        }
+    }
 }