◐ Shell
clean mode source ↗

[3.14] gh-149242: Heap size should be added at end of the struct by DinoV · Pull Request #149241 · python/cpython

Not modifying the offsets of existing members seems prudent, can't really hurt. However, I think since we have changed other members of the struct, we need to have a different patch, the recent changes to this struct are as follows:

 struct _gc_runtime_state {
     /* List of objects that still need to be cleaned up, singly linked
      * via their gc headers' gc_prev pointers.  */
     PyObject *trash_delete_later;
     /* Current call-stack depth of tp_dealloc calls. */
     int trash_delete_nesting;
 
     /* Is automatic collection enabled? */
     int enabled;
     int debug;
     /* linked lists of container objects */
+#ifndef Py_GIL_DISABLED
+    struct gc_generation generations[NUM_GENERATIONS];
+    PyGC_Head *generation0;
+#else
     struct gc_generation young;
     struct gc_generation old[2];
+#endif
     /* a permanent generation which won't be collected */
     struct gc_generation permanent_generation;
     struct gc_generation_stats generation_stats[NUM_GENERATIONS];
     /* true if we are currently running the collector */
     int collecting;
     /* list of uncollectable objects */
     PyObject *garbage;
     /* a list of callbacks to be invoked when collection is performed */
     PyObject *callbacks;
 
+    /* The number of live objects. */
     Py_ssize_t heap_size;
-    Py_ssize_t work_to_do;
-    /* Which of the old spaces is the visited space */
-    int visited_space;
-    int phase;
 
-#ifdef Py_GIL_DISABLED
     /* This is the number of objects that survived the last full
        collection. It approximates the number of long lived objects
        tracked by the GC.
 
        (by "full collection", we mean a collection of the oldest
        generation). */
     Py_ssize_t long_lived_total;
     /* This is the number of objects that survived all "non-full"
        collections, and are awaiting to undergo a full collection for
        the first time. */
     Py_ssize_t long_lived_pending;
 
+#ifdef Py_GIL_DISABLED
     /* True if gc.freeze() has been used. */
     int freeze_active;
 
     /* Memory usage of the process (RSS + swap) after last GC. */
     Py_ssize_t last_mem;
 
     /* This accumulates the new object count whenever collection is deferred
        due to the RSS increase condition not being meet.  Reset on collection. */
     Py_ssize_t deferred_count;
 
     /* Mutex held for gc_should_collect_mem_usage(). */
     PyMutex mutex;
 #endif
 };

I would think we want something like the following, to preserve as many offsets as we can:

--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -210,13 +210,10 @@ struct _gc_runtime_state {
     int enabled;
     int debug;
     /* linked lists of container objects */
-#ifndef Py_GIL_DISABLED
-    struct gc_generation generations[NUM_GENERATIONS];
-    PyGC_Head *generation0;
-#else
+    /* Note this is only used for the Py_GIL_DISABLED build, retained to preserve
+     * other member offsets. */
     struct gc_generation young;
     struct gc_generation old[2];
-#endif
     /* a permanent generation which won't be collected */
     struct gc_generation permanent_generation;
     struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -230,6 +227,11 @@ struct _gc_runtime_state {
     /* The number of live objects. */
     Py_ssize_t heap_size;
 
+    /* dummy members to preserve other offsets */
+    Py_ssize_t dummy1; /* was work_to_do */
+    int dummy2; /* was visited_space */
+    int dummy3; /* was phase */
+
     /* This is the number of objects that survived the last full
        collection. It approximates the number of long lived objects
        tracked by the GC.
@@ -255,6 +257,10 @@ struct _gc_runtime_state {
 
     /* Mutex held for gc_should_collect_mem_usage(). */
     PyMutex mutex;
+#else
+    /* linked lists of container objects */
+    struct gc_generation generations[NUM_GENERATIONS];
+    PyGC_Head *generation0;
 #endif
 };