Created
August 13, 2025 17:50
-
-
Save nascheme/b7a565790f07ad67c1fbf01e32059f14 to your computer and use it in GitHub Desktop.
Explicit rooting of references for Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdbool.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <limits.h> | |
| #include <assert.h> | |
| typedef size_t Py_ssize_t; | |
| #define PY_SSIZE_T_MAX ULONG_MAX | |
| /* dummy PyObject structure */ | |
| typedef struct { | |
| int data; | |
| } PyObject; | |
| /* dummy thread state */ | |
| typedef struct { | |
| struct _PyRoot_Stack_Cell_Struct *stack_roots; | |
| } PyThreadState; | |
| /* used for a object return value that must go into a rooted location. */ | |
| typedef PyObject *PyRoot; | |
| /* used for a object parameter that is already rooted, maybe on a | |
| * higher stack frame, as a global or as an object property */ | |
| typedef PyObject *PyHandle; | |
| /* stack allocated structure to hold local roots. Linked into thread state | |
| * in LIFO fashion */ | |
| typedef struct _PyRoot_Stack_Cell_Struct { | |
| struct _PyRoot_Stack_Cell_Struct *prev; | |
| PyRoot obj; | |
| } _PyRoot_Stack_Cell; | |
| #define _PyRoot_Decl(name) \ | |
| _PyRoot_Stack_Cell _PyRoot_stack_var_ ## name = {0}; | |
| static void | |
| _PyRoot_Link(PyThreadState *tstate, _PyRoot_Stack_Cell *c) | |
| { | |
| /* push this cell on top of local variable roots stack */ | |
| c->prev = tstate->stack_roots; | |
| tstate->stack_roots = c; | |
| } | |
| static PyRoot | |
| _PyRoot_Unlink(PyThreadState *tstate, _PyRoot_Stack_Cell *c) | |
| { | |
| /* require LIFO, this cell must be top of stack */ | |
| assert(tstate->stack_roots == c); | |
| tstate->stack_roots = c->prev; | |
| PyRoot rv = (PyRoot)c->obj; | |
| c->obj = NULL; | |
| return rv; | |
| } | |
| /* Declare and init a new C stack root 'name' */ | |
| #define PyRoot_New(name) _PyRoot_Decl(name); _PyRoot_Link(tstate, &(_PyRoot_stack_var_ ## name)) | |
| /* remove a C stack root, must be LIFO with PyRoot_New() */ | |
| #define PyRoot_Del(name) (_PyRoot_Unlink(tstate, &(_PyRoot_stack_var_ ## name))) | |
| /* need this to hook in pre/post methods for write barriers */ | |
| #define PyRoot_Set(name, val) ((_PyRoot_stack_var_ ## name).obj = val) | |
| /* Convert a PyHandle to a PyRoot. This is used for return values, to enforce | |
| that the caller has a properly "rooted" location to store the value. | |
| */ | |
| PyRoot | |
| PyHandle_AsRoot(PyHandle h) | |
| { | |
| return (PyRoot)h; | |
| } | |
| /* Convert a PyRoot value to a PyHandle, typically to pass as argument */ | |
| #define PyRoot_AsHandle(name) ((PyHandle)((_PyRoot_stack_var_ ## name).obj)) | |
| static inline bool | |
| PyHandle_IsNull(PyHandle h) | |
| { | |
| return h == NULL; | |
| } | |
| static inline bool | |
| _PyRoot_IsNull(_PyRoot_Stack_Cell *c) | |
| { | |
| return c->obj == NULL; | |
| } | |
| #define PyRoot_IsNull(name) _PyRoot_IsNull(&(_PyRoot_stack_var_ ## name)) | |
| PyThreadState *tstate; | |
| /* Dummy functions follow to flesh out example code */ | |
| static int | |
| PyTuple_CheckExact(PyHandle v) | |
| { | |
| return (uintptr_t)v == 1; | |
| } | |
| static int | |
| PyList_CheckExact(PyHandle v) | |
| { | |
| return (uintptr_t)v == 2; | |
| } | |
| static PyRoot | |
| PyList_AsTuple(PyHandle v) | |
| { | |
| return PyHandle_AsRoot(v); | |
| } | |
| static PyRoot | |
| PyObject_GetIter(PyHandle v) | |
| { | |
| return PyHandle_AsRoot(v); | |
| } | |
| static Py_ssize_t | |
| PyObject_LengthHint(PyHandle v, Py_ssize_t hint) | |
| { | |
| return hint; | |
| } | |
| static PyRoot | |
| PyTuple_New(Py_ssize_t n) | |
| { | |
| return (PyRoot)(n); | |
| } | |
| static PyRoot | |
| PyIter_Next(PyHandle v) | |
| { | |
| return (PyRoot)(v); | |
| } | |
| static bool | |
| PyErr_Occurred(void) | |
| { | |
| return tstate == NULL; | |
| } | |
| static PyRoot | |
| PyErr_NoMemory(void) | |
| { | |
| return NULL; | |
| } | |
| static void | |
| PyTuple_SET_ITEM(PyHandle obj, Py_ssize_t i, PyHandle val) | |
| { | |
| } | |
| PyRoot | |
| PySequence_Tuple(PyHandle v) | |
| { | |
| if (PyHandle_IsNull(v)) { | |
| return NULL; | |
| } | |
| /* Special-case the common tuple and list cases, for efficiency. */ | |
| if (PyTuple_CheckExact(v)) { | |
| /* Note that we can't know whether it's safe to return | |
| a tuple *subclass* instance as-is, hence the restriction | |
| to exact tuples here. In contrast, lists always make | |
| a copy, so there's no need for exactness below. */ | |
| return PyHandle_AsRoot(v); | |
| } | |
| if (PyList_CheckExact(v)) { | |
| return PyList_AsTuple(v); | |
| } | |
| /* create new stack roots */ | |
| PyRoot_New(result); | |
| PyRoot_New(item); /* hoisted from loop for performance */ | |
| PyRoot_New(it); /* iter(v) */ | |
| /* Get iterator. */ | |
| PyRoot_Set(it, PyObject_GetIter(v)); | |
| if (PyRoot_IsNull(it)) | |
| goto Fail; | |
| /* Guess result size and allocate space. */ | |
| Py_ssize_t n = PyObject_LengthHint(v, 10); | |
| if (n == -1) | |
| goto Fail; | |
| PyRoot_Set(result, PyTuple_New(n)); | |
| if (PyRoot_IsNull(result)) | |
| goto Fail; | |
| /* Fill the tuple. */ | |
| for (Py_ssize_t j = 0; ; ++j) { | |
| PyRoot_Set(item, PyIter_Next(PyRoot_AsHandle(it))); | |
| if (PyRoot_IsNull(item)) { | |
| if (PyErr_Occurred()) | |
| goto Fail; | |
| break; | |
| } | |
| if (j >= n) { | |
| size_t newn = (size_t)n; | |
| /* The over-allocation strategy can grow a bit faster | |
| than for lists because unlike lists the | |
| over-allocation isn't permanent -- we reclaim | |
| the excess before the end of this routine. | |
| So, grow by ten and then add 25%. | |
| */ | |
| newn += 10u; | |
| newn += newn >> 2; | |
| if (newn > PY_SSIZE_T_MAX) { | |
| /* Check for overflow */ | |
| PyErr_NoMemory(); | |
| goto Fail; | |
| } | |
| n = (Py_ssize_t)newn; | |
| } | |
| PyTuple_SET_ITEM(PyRoot_AsHandle(result), j, PyRoot_AsHandle(item)); | |
| } | |
| PyRoot_Del(it); | |
| PyRoot_Del(item); | |
| return PyRoot_Del(result); | |
| Fail: | |
| PyRoot_Del(it); | |
| PyRoot_Del(item); | |
| PyRoot_Del(result); | |
| return NULL; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment