Skip to content

Commit b7bd81c

Browse files
committed
Add backtracking
1 parent 80b6d4d commit b7bd81c

1 file changed

Lines changed: 211 additions & 12 deletions

File tree

Python/immutability.c

Lines changed: 211 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
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

668866
error:
867+
fail_freeze(&freeze_state);
669868
result = -1;
670869

671870
finally:

0 commit comments

Comments
 (0)