Skip to main content

Python/pyarena.c

Source:

cpython 3.14 @ ab2d84fe1023/Python/pyarena.c

PyArena is a simple bump allocator used during compilation. AST nodes are allocated from it in bulk; the entire arena is freed at once when compilation completes.

Map​

LinesSymbolRole
1-50PyArena_NewAllocate arena and first block
51-100PyArena_FreeFree all blocks and tracked pointers
101-150PyArena_MallocBump-allocate bytes from current block
151-200PyArena_AddPyObjectRegister a Python object pointer for GC safety

Reading​

PyArena structure​

// CPython: Python/pyarena.c:18 _PyArena
struct _PyArena {
block *a_head; /* current (most recently created) block */
block *a_cur; /* same as a_head (block list is LIFO) */
PyObject **a_objects; /* array of registered PyObject * pointers */
Py_ssize_t a_nobjects;
Py_ssize_t a_szobjects;
};

typedef struct _block {
size_t ab_size; /* total capacity */
size_t ab_offset; /* bytes already allocated */
struct _block *ab_next;
uint8_t ab_mem[1]; /* variable-length storage */
} block;

PyArena_New​

// CPython: Python/pyarena.c:55 PyArena_New
PyArena *
PyArena_New(void)
{
PyArena *arena = (PyArena *)PyMem_Malloc(sizeof(PyArena));
if (!arena) return PyErr_NoMemory();
arena->a_head = block_new(DEFAULT_BLOCK_SIZE);
arena->a_cur = arena->a_head;
arena->a_objects = NULL;
arena->a_nobjects = 0;
arena->a_szobjects = 0;
return arena;
}

DEFAULT_BLOCK_SIZE is 8 KB. A new block is chained when the current one fills up.

PyArena_Malloc​

// CPython: Python/pyarena.c:110 PyArena_Malloc
void *
PyArena_Malloc(PyArena *arena, size_t size)
{
/* Round up to pointer alignment */
size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
block *b = arena->a_cur;
if (b->ab_offset + size > b->ab_size) {
/* Overflow: allocate a new block at least big enough */
size_t newsize = b->ab_size * 2;
if (newsize < size) newsize = size + sizeof(block);
block *newb = block_new(newsize);
if (!newb) return PyErr_NoMemory();
newb->ab_next = arena->a_head;
arena->a_head = arena->a_cur = newb;
b = newb;
}
void *p = b->ab_mem + b->ab_offset;
b->ab_offset += size;
return p;
}

Allocations are not individually freed. The offset never decreases.

PyArena_AddPyObject​

// CPython: Python/pyarena.c:160 PyArena_AddPyObject
int
PyArena_AddPyObject(PyArena *arena, PyObject *obj)
{
/* Keep obj alive for the arena's lifetime and protect from GC */
if (arena->a_nobjects == arena->a_szobjects) {
/* Grow the pointer array */
Py_ssize_t newsz = arena->a_szobjects + 128;
arena->a_objects = PyMem_Realloc(arena->a_objects,
newsz * sizeof(PyObject *));
arena->a_szobjects = newsz;
}
Py_INCREF(obj);
arena->a_objects[arena->a_nobjects++] = obj;
return 0;
}

String literals and identifier names are Python objects allocated outside the arena. AddPyObject keeps a reference so they are not collected while the arena lives.

gopy notes​

The gopy compiler uses Go's GC for AST node lifetimes, so there is no direct PyArena equivalent. The compiler package allocates AST node structs as Go heap values; the Go GC collects them when the compilation function returns. PyArena_AddPyObject has no equivalent because Go strings are values, not GC-tracked pointers.