44#include <stdbool.h>
55#include <stdio.h>
66#include "pycore_descrobject.h"
7+ #include "pycore_gc.h"
78#include "pycore_object.h"
89#include "pycore_immutability.h"
910#include "pycore_list.h"
@@ -161,6 +162,199 @@ static bool is_c_wrapper(PyObject* obj){
161162 return PyCFunction_Check (obj ) || Py_IS_TYPE (obj , & _PyMethodWrapper_Type ) || Py_IS_TYPE (obj , & PyWrapperDescr_Type );
162163}
163164
165+ // Lifted fro mPython/gc.c
166+ //******************************** */
167+ #define GC_NEXT _PyGCHead_NEXT
168+ #define GC_PREV _PyGCHead_PREV
169+
170+ static inline void
171+ gc_list_init (PyGC_Head * list )
172+ {
173+ // List header must not have flags.
174+ // We can assign pointer by simple cast.
175+ list -> _gc_prev = (uintptr_t )list ;
176+ list -> _gc_next = (uintptr_t )list ;
177+ }
178+
179+ static inline int
180+ gc_list_is_empty (PyGC_Head * list )
181+ {
182+ return (list -> _gc_next == (uintptr_t )list );
183+ }
184+
185+ /* Append `node` to `list`. */
186+ static inline void
187+ gc_list_append (PyGC_Head * node , PyGC_Head * list )
188+ {
189+ assert ((list -> _gc_prev & ~_PyGC_PREV_MASK ) == 0 );
190+ PyGC_Head * last = (PyGC_Head * )list -> _gc_prev ;
191+
192+ // last <-> node
193+ _PyGCHead_SET_PREV (node , last );
194+ _PyGCHead_SET_NEXT (last , node );
195+
196+ // node <-> list
197+ _PyGCHead_SET_NEXT (node , list );
198+ list -> _gc_prev = (uintptr_t )node ;
199+ }
200+
201+ /* Move `node` from the gc list it's currently in (which is not explicitly
202+ * named here) to the end of `list`. This is semantically the same as
203+ * gc_list_remove(node) followed by gc_list_append(node, list).
204+ */
205+ static void
206+ gc_list_move (PyGC_Head * node , PyGC_Head * list )
207+ {
208+ /* Unlink from current list. */
209+ PyGC_Head * from_prev = GC_PREV (node );
210+ PyGC_Head * from_next = GC_NEXT (node );
211+ _PyGCHead_SET_NEXT (from_prev , from_next );
212+ _PyGCHead_SET_PREV (from_next , from_prev );
213+
214+ /* Relink at end of new list. */
215+ // list must not have flags. So we can skip macros.
216+ PyGC_Head * to_prev = (PyGC_Head * )list -> _gc_prev ;
217+ _PyGCHead_SET_PREV (node , to_prev );
218+ _PyGCHead_SET_NEXT (to_prev , node );
219+ list -> _gc_prev = (uintptr_t )node ;
220+ _PyGCHead_SET_NEXT (node , list );
221+ }
222+
223+ /* append list `from` onto list `to`; `from` becomes an empty list */
224+ static void
225+ gc_list_merge (PyGC_Head * from , PyGC_Head * to )
226+ {
227+ assert (from != to );
228+ if (!gc_list_is_empty (from )) {
229+ PyGC_Head * to_tail = GC_PREV (to );
230+ PyGC_Head * from_head = GC_NEXT (from );
231+ PyGC_Head * from_tail = GC_PREV (from );
232+ assert (from_head != from );
233+ assert (from_tail != from );
234+
235+ _PyGCHead_SET_NEXT (to_tail , from_head );
236+ _PyGCHead_SET_PREV (from_head , to_tail );
237+
238+ _PyGCHead_SET_NEXT (from_tail , to );
239+ _PyGCHead_SET_PREV (to , from_tail );
240+ }
241+ gc_list_init (from );
242+ }
243+
244+ struct _gc_runtime_state *
245+ get_gc_state (void )
246+ {
247+ PyInterpreterState * interp = _PyInterpreterState_GET ();
248+ return & interp -> gc ;
249+ }
250+
251+ /**
252+ * Used to track the state of an in progress freeze operation.
253+ *
254+ */
255+ struct FreezeState {
256+ #ifndef Py_GIL_DISABLED
257+ PyGC_Head visited ; // Set of objects that have been visited
258+ PyGC_Head visited_untracked ; // Set of objects that have been visited and are immortal
259+ #endif
260+ PyObject * visited_list ; // Some objects don't have GC space, so we need to track them separately.
261+ };
262+
263+
264+ //******************************** */
265+
266+
267+ void
268+ init_freeze_state (struct FreezeState * state )
269+ {
270+ #ifndef Py_GIL_DISABLED
271+ gc_list_init (& (state -> visited ));
272+ gc_list_init (& (state -> visited_untracked ));
273+ #endif
274+ state -> visited_list = NULL ;
275+ }
276+
277+ int
278+ add_visited_set (struct FreezeState * state , PyObject * op )
279+ {
280+ #ifndef Py_GIL_DISABLED
281+ if (_PyObject_IS_GC (op )) {
282+ if (_PyObject_GC_IS_TRACKED (op )) {
283+ gc_list_move (_Py_AS_GC (op ), & (state -> visited ));
284+ return 0 ;
285+ }
286+ // If the object is not tracked by the GC, we can just add it to the visited_untracked list.
287+ gc_list_append (_Py_AS_GC (op ), & (state -> visited_untracked ));
288+ return 0 ;
289+ }
290+ #endif
291+
292+ // Only create the visited_list if it is needed.
293+ if (state -> visited_list == NULL ) {
294+ state -> visited_list = PyList_New (0 );
295+ if (state -> visited_list == NULL ) {
296+ return -1 ; // Memory error
297+ }
298+ }
299+
300+ return push (state -> visited_list , op );
301+ }
302+
303+ void fail_freeze (struct FreezeState * state )
304+ {
305+ #ifndef Py_GIL_DISABLED
306+ PyGC_Head * gc ;
307+ for (gc = _PyGCHead_NEXT (& (state -> visited )); gc != & (state -> visited ); gc = _PyGCHead_NEXT (gc )) {
308+ _Py_CLEAR_IMMUTABLE (_Py_FROM_GC (gc ));
309+ }
310+ struct _gc_runtime_state * gc_state = get_gc_state ();
311+ gc_list_merge (& (state -> visited ), & (gc_state -> old [1 ].head ));
312+
313+
314+ PyGC_Head * next ;
315+ for (gc = _PyGCHead_NEXT (& (state -> visited_untracked )); gc != & (state -> visited_untracked ); gc = next ) {
316+ next = _PyGCHead_NEXT (gc );
317+ _Py_CLEAR_IMMUTABLE (_Py_FROM_GC (gc ));
318+ // Object was not tracked in the GC, so we don't need to merge it back.
319+ _PyGCHead_SET_PREV (gc , NULL );
320+ _PyGCHead_SET_NEXT (gc , NULL );
321+ }
322+ #endif
323+
324+ if (state -> visited_list == NULL ) {
325+ return ; // Nothing to do
326+ }
327+
328+ while (PyList_Size (state -> visited_list ) > 0 ) {
329+ // Pop doesn't return a newref, but we know the object is still live
330+ // as we didn't change anything.
331+ PyObject * item = pop (state -> visited_list );
332+ _Py_CLEAR_IMMUTABLE (item );
333+ }
334+
335+ // Tidy up the visited set
336+ Py_DECREF (state -> visited_list );
337+ }
338+
339+ void finish_freeze (struct FreezeState * state )
340+ {
341+ #ifndef Py_GIL_DISABLED
342+ struct _gc_runtime_state * gc_state = get_gc_state ();
343+ gc_list_merge (& (state -> visited ), & (gc_state -> old [1 ].head ));
344+
345+ PyGC_Head * gc ;
346+ PyGC_Head * next ;
347+ for (gc = _PyGCHead_NEXT (& (state -> visited_untracked )); gc != & (state -> visited_untracked ); gc = next ) {
348+ next = _PyGCHead_NEXT (gc );
349+ // Object was not tracked in the GC, so we don't need to merge it back.
350+ _PyGCHead_SET_PREV (gc , NULL );
351+ _PyGCHead_SET_NEXT (gc , NULL );
352+ }
353+ #endif
354+
355+ Py_XDECREF (state -> visited_list );
356+ }
357+
164358/**
165359 * Special function for replacing globals and builtins with a copy of just what they use.
166360 *
@@ -492,12 +686,8 @@ int _Py_DecRef_Immutable(PyObject *op)
492686
493687 assert (_Py_IMMUTABLE_FLAG_CLEAR (op -> ob_refcnt ) == 0 );
494688
495- // Clear the immutable flag so that finalisers can run correctly.
496- #if SIZEOF_VOID_P > 4
497- op -> ob_flags &= ~_Py_IMMUTABLE_FLAG ;
498- #else
499- op -> ob_refcnt = 0 ;
500- #endif
689+ _Py_CLEAR_IMMUTABLE (op );
690+
501691 return true;
502692#endif
503693}
@@ -517,6 +707,9 @@ int _PyImmutability_Freeze(PyObject* obj)
517707{
518708 PyObject * frontier = NULL ;
519709 int result = 0 ;
710+ struct FreezeState freeze_state ;
711+ // Initialize the freeze state
712+ init_freeze_state (& freeze_state );
520713
521714 struct _Py_immutability_state * state = get_immutable_state ();
522715 if (state == NULL ){
@@ -560,6 +753,10 @@ int _PyImmutability_Freeze(PyObject* obj)
560753 PyObject * item = pop (frontier );
561754 FreezableCheck check ;
562755
756+ if (_Py_IsImmutable (item )){
757+ continue ;
758+ }
759+
563760 if (item == state -> blocking_on ||
564761 item == state -> module_locks ){
565762 continue ;
@@ -593,11 +790,6 @@ int _PyImmutability_Freeze(PyObject* obj)
593790 goto error ;
594791 }
595792
596- // TODO(Immutable): mjp: This should be earlier once we have backtracking of freeze.
597- // Putting it here makes some things fail the second time they are attempted to be frozen.
598- if (_Py_IsImmutable (item )){
599- continue ;
600- }
601793#ifdef Py_DEBUG
602794 if (freeze_location != NULL ) {
603795 // TODO(Immutable): Some objects don't have attributes that can be set.
@@ -610,7 +802,12 @@ int _PyImmutability_Freeze(PyObject* obj)
610802 }
611803 }
612804#endif
613-
805+ if (add_visited_set (& freeze_state , item ) != 0 ) {
806+ // If we fail to add the item to the visited set, then we
807+ // will not be able to backtrack, so go to error case.
808+ PyErr_SetString (PyExc_RuntimeError , "Failed to add item to visited set" );
809+ goto error ;
810+ }
614811 _Py_SetImmutable (item );
615812
616813 if (is_c_wrapper (item )) {
@@ -663,9 +860,11 @@ int _PyImmutability_Freeze(PyObject* obj)
663860 }
664861 }
665862
863+ finish_freeze (& freeze_state );
666864 goto finally ;
667865
668866error :
867+ fail_freeze (& freeze_state );
669868 result = -1 ;
670869
671870finally :
0 commit comments