8000 bpo-46753: Add the empty tuple to the _PyRuntimeState.global_objects. by ericsnowcurrently · Pull Request #31345 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-46753: Add the empty tuple to the _PyRuntimeState.global_objects. #31345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7fef0be
Add the empty tuple to the _PyRuntimeState.global_objects.
ericsnowcurrently Feb 14, 2022
9f0aa44
Use the global empty tuple.
ericsnowcurrently Feb 14, 2022
21ce6aa
Merge branch 'main' into global-objects-empty-tuple
ericsnowcurrently Feb 15, 2022
ba1b4cc
Leave space for the empty GC head.
ericsnowcurrently Feb 15, 2022
67ebfd6
Revert "Leave space for the empty GC head."
ericsnowcurrently Feb 15, 2022
df35d70
Add PyTuple_Type.tp_is_gc().
ericsnowcurrently Feb 15, 2022
24ca51c
Inline tuple_get_empty().
ericsnowcurrently Feb 15, 2022
55b8eb0
Switch back to allocated an unused PyGC_Head for the empty tuple.
ericsnowcurrently Feb 15, 2022
ab721be
Skip tupledealloc() if it's the empty tuple.
ericsnowcurrently Feb 15, 2022
7b89727
Return the empty tuple when resizing to 0.
ericsnowcurrently Feb 16, 2022
2fedc9c
Use the empty tuple when appropriate in deepfreeze.c.
ericsnowcurrently Feb 16, 2022
dd0a1a2
Disassociate the empty tuple from the freelist logic.
ericsnowcurrently Feb 16, 2022
5e729d8
Merge branch 'main' into global-objects-empty-tuple
ericsnowcurrently Feb 23, 2022
deddeb5
Allow deallocating an empty tuple if a subclass.
ericsnowcurrently Feb 23, 2022
efed1d1
Return a new reference from tuple_get_empty().
ericsnowcurrently Feb 23, 2022
923c8cc
Add _PyGC_Head_UNUSED.
ericsnowcurrently Feb 24, 2022
631d12b
Clean up tupledealloc() a little.
ericsnowcurrently Feb 24, 2022
3264e8d
8000 Drop get_tuple_state().
ericsnowcurrently Feb 24, 2022
9dfee07
Consolidate the freelist code.
ericsnowcurrently Feb 24, 2022
e272804
Merge branch 'main' into global-objects-empty-tuple
ericsnowcurrently Feb 25, 2022
597f1bd
Do not use _Py_NewRef().
ericsnowcurrently Feb 25, 2022
fa2edad
Calling tupledealloc() on the empty singleton is an error.
ericsnowcurrently Feb 25, 2022
f4acc36
Tweak _PyTuple_Resize().
ericsnowcurrently Feb 25, 2022
460ae61
Roll back the global singleton part.
ericsnowcurrently Feb 25, 2022
c163aca
Re-apply the global singleton part.
ericsnowcurrently Feb 25, 2022
4ac3d66
Make sure struct _Py_tuple_state is never empty.
ericsnowcurrently Feb 28, 2022
11a9311
Clarify about freelists a little.
ericsnowcurrently Feb 28, 2022
fdc82d2
Fix a preprocessor condition.
ericsnowcurrently Feb 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
8000
Diff view
4 changes: 4 additions & 0 deletions Include/internal/pycore_global_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_gc.h" // PyGC_Head
#include "pycore_global_strings.h" // struct _Py_global_strings


Expand Down Expand Up @@ -40,6 +41,9 @@ struct _Py_global_objects {
} bytes_characters[256];

struct _Py_global_strings strings;

PyGC_Head _tuple_empty_gc_not_used;
PyTupleObject tuple_empty;
} singletons;
};

Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,10 @@ extern "C" {
INIT_ID(zipimporter), \
}, \
}, \
\
.tuple_empty = { \
.ob_base = _PyVarObject_IMMORTAL_INIT(&PyTuple_Type, 0) \
}, \
}, \
}
/* End auto-generated code */
Expand Down
14 changes: 6 additions & 8 deletions Include/internal/pycore_tuple.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ extern "C" {

/* runtime lifecycle */

extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *);
extern PyStatus _PyTuple_InitTypes(PyInterpreterState *);
extern void _PyTuple_Fini(PyInterpreterState *);

Expand All @@ -23,8 +22,8 @@ extern void _PyTuple_Fini(PyInterpreterState *);
#ifndef WITH_FREELISTS
// without freelists
// for tuples only store empty tuple singleton
# define PyTuple_MAXSAVESIZE 1
# define PyTuple_MAXFREELIST 1
# define PyTuple_MAXSAVESIZE 0
# define PyTuple_MAXFREELIST 0
#endif

/* Speed optimization to avoid frequent malloc/free of small tuples */
Expand All @@ -39,11 +38,10 @@ extern void _PyTuple_Fini(PyInterpreterState *);

struct _Py_tuple_state {
#if PyTuple_MAXSAVESIZE > 0
/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists,
entry 0 is the empty tuple () of which at most one instance
will be allocated. */
PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
int numfree[PyTuple_MAXSAVESIZE];
/* Each entry up to PyTuple_MAXSAVESIZE is a free list.
The empty tuple is handled separately, hence declaring one fewer. */
PyTupleObject *free_list[PyTuple_MAXSAVESIZE - 1];
int numfree[PyTuple_MAXSAVESIZE - 1];
#endif
};

Expand Down
10000
153 changes: 44 additions & 109 deletions Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ _PyTuple_DebugMallocStats(FILE *out)
char buf[128];
PyOS_snprintf(buf, sizeof(buf),
"free %d-sized PyTupleObject", i);
_PyDebugAllocatorStats(out, buf, state->numfree[i],
_PyDebugAllocatorStats(out, buf, state->numfree[i-1],
_PyObject_VAR_SIZE(&PyTuple_Type, i));
}
#endif
Expand All @@ -57,27 +57,22 @@ static PyTupleObject *
tuple_alloc(Py_ssize_t size)
{
PyTupleObject *op;
#if PyTuple_MAXSAVESIZE > 0
// If Python is built with the empty tuple singleton,
// tuple_alloc(0) must not be called.
assert(size != 0);
#endif
if (size < 0) {
/* The empty tuple is statically allocated. */
if (size <= 0) {
PyErr_BadInternalCall();
return NULL;
}

// Check for max save size > 1. Empty tuple singleton is special case.
// Check for max save size > 1.
#if PyTuple_MAXSAVESIZE > 1
struct _Py_tuple_state *state = get_tuple_state();
#ifdef Py_DEBUG
// tuple_alloc() must not be called after _PyTuple_Fini()
assert(state->numfree[0] != -1);
assert(state->numfree[0] >= 0);
#endif
if (size < PyTuple_MAXSAVESIZE && (op = state->free_list[size]) != NULL) {
assert(size != 0);
state->free_list[size] = (PyTupleObject *) op->ob_item[0];
state->numfree[size]--;
if (size < PyTuple_MAXSAVESIZE && (op = state->free_list[size-1]) != NULL) {
state->free_list[size-1] = (PyTupleObject *) op->ob_item[0];
state->numfree[size-1]--;
/* Inlined _PyObject_InitVar() without _PyType_HasFeature() test */
#ifdef Py_TRACE_REFS
Py_SET_SIZE(op, size);
Expand All @@ -100,58 +95,20 @@ tuple_alloc(Py_ssize_t size)
return op;
}

static int
tuple_create_empty_tuple_singleton(struct _Py_tuple_state *state)
{
#if PyTuple_MAXSAVESIZE > 0
assert(state->free_list[0] == NULL);

PyTupleObject *op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, 0);
if (op == NULL) {
return -1;
}
// The empty tuple singleton is not tracked by the GC.
// It does not contain any Python object.

state->free_list[0] = op;
state->numfree[0]++;

assert(state->numfree[0] == 1);
#endif
return 0;
}


static PyObject *
static inline PyObject *
tuple_get_empty(void)
{
#if PyTuple_MAXSAVESIZE > 0
struct _Py_tuple_state *state = get_tuple_state();
PyTupleObject *op = state->free_list[0];
// tuple_get_empty() must not be called before _PyTuple_Init()
// or after _PyTuple_Fini()
assert(op != NULL);
#ifdef Py_DEBUG
assert(state->numfree[0] != -1);
#endif

Py_INCREF(op);
return (PyObject *) op;
#else
return PyTuple_New(0);
#endif
return _Py_NewRef((PyObject *)&_Py_SINGLETON(tuple_empty));
}


PyObject *
PyTuple_New(Py_ssize_t size)
{
PyTupleObject *op;
#if PyTuple_MAXSAVESIZE > 0
if (size == 0) {
return tuple_get_empty();
}
#endif
op = tuple_alloc(size);
if (op == NULL) {
return NULL;
Expand Down Expand Up @@ -265,40 +222,34 @@ PyTuple_Pack(Py_ssize_t n, ...)
static void
tupledealloc(PyTupleObject *op)
{
Py_ssize_t len = Py_SIZE(op);
/* The empty tuple is statically allocated. */
if (op == &_Py_SINGLETON(tuple_empty)) {
return;
}
Py_ssize_t len = Py_SIZE(op);
/* tuple subclasses have their own empty instances. */
assert(len > 0 || !PyTuple_CheckExact(op));
PyObject_GC_UnTrack(op);
Py_TRASHCAN_BEGIN(op, tupledealloc)
if (len > 0) {
Py_ssize_t i = len;
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}

Py_ssize_t i = len;
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}
#if PyTuple_MAXSAVESIZE > 0
struct _Py_tuple_state *state = get_tuple_state();
struct _Py_tuple_state *state = get_tuple_state();
#ifdef Py_DEBUG
// tupledealloc() must not be called after _PyTuple_Fini()
assert(state->numfree[0] != -1);
// tupledealloc() must not be called after _PyTuple_Fini()
assert(state->numfree[0] >= 0);
#endif
if (len < PyTuple_MAXSAVESIZE
&& state->numfree[len] < PyTuple_MAXFREELIST
&& Py_IS_TYPE(op, &PyTuple_Type))
{
op->ob_item[0] = (PyObject *) state->free_list[len];
state->numfree[len]++;
state->free_list[len] = op;
goto done; /* return */
}
#endif
}
#if defined(Py_DEBUG) && PyTuple_MAXSAVESIZE > 0
else {
assert(len == 0);
struct _Py_tuple_state *state = get_tuple_state();
// The empty tuple singleton must only be deallocated by
// _PyTuple_Fini(): not before, not after
if (op == state->free_list[0] && state->numfree[0] != 0) {
_Py_FatalRefcountError("deallocating the empty tuple singleton");
}
if (len < (PyTuple_MAXSAVESIZE - 1)
&& state->numfree[len-1] < PyTuple_MAXFREELIST
&& Py_IS_TYPE(op, &PyTuple_Type))
{
op->ob_item[0] = (PyObject *) state->free_list[len-1];
state->numfree[len-1]++;
state->free_list[len-1] = op;
goto done; /* return */
}
#endif
Py_TYPE(op)->tp_free((PyObject *)op);
Expand Down Expand Up @@ -838,6 +789,7 @@ tuple_subtype_new(PyTypeObject *type, PyObject *iterable)
if (tmp == NULL)
return NULL;
assert(PyTuple_Check(tmp));
/* This may allocate an empty tuple that is not the global one. */
newobj = type->tp_alloc(type, n = PyTuple_GET_SIZE(tmp));
if (newobj == NULL) {
Py_DECREF(tmp);
Expand Down Expand Up @@ -1047,6 +999,10 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
for (i = newsize; i < oldsize; i++) {
Py_CLEAR(v->ob_item[i]);
}
if (newsize == 0) {
*pv = tuple_get_empty();
return 0;
}
sv = PyObject_GC_Resize(PyTupleObject, v, newsize);
if (sv == NULL) {
*pv = NULL;
Expand All @@ -1069,31 +1025,19 @@ _PyTuple_ClearFreeList(PyInterpreterState *interp)
#if PyTuple_MAXSAVESIZE > 0
struct _Py_tuple_state *state = &interp->tuple;
for (Py_ssize_t i = 1; i < PyTuple_MAXSAVESIZE; i++) {
PyTupleObject *p = state->free_list[i];
state->free_list[i] = NULL;
state->numfree[i] = 0;
PyTupleObject *p = state->free_list[i-1];
state->free_list[i-1] = NULL;
state->numfree[i-1] = 0;
while (p) {
PyTupleObject *q = p;
p = (PyTupleObject *)(p->ob_item[0]);
PyObject_GC_Del(q);
}
}
// the empty tuple singleton is only cleared by _PyTuple_Fini()
#endif
}


PyStatus
_PyTuple_InitGlobalObjects(PyInterpreterState *interp)
{
struct _Py_tuple_state *state = &interp->tuple;
if (tuple_create_empty_tuple_singleton(state) < 0) {
return _PyStatus_NO_MEMORY();
}
return _PyStatus_OK();
}


PyStatus
_PyTuple_InitTypes(PyInterpreterState *interp)
{
Expand All @@ -1116,19 +1060,10 @@ void
_PyTuple_Fini(PyInterpreterState *interp)
{
#if PyTuple_MAXSAVESIZE > 0
struct _Py_tuple_state *state = &interp->tuple;
// The empty tuple singleton must not be tracked by the GC
assert(!_PyObject_GC_IS_TRACKED(state->free_list[0]));

#ifdef Py_DEBUG
state->numfree[0] = 0;
#endif
Py_CLEAR(state->free_list[0]);
#ifdef Py_DEBUG
state->numfree[0] = -1;
#endif

_PyTuple_ClearFreeList(interp);
for (Py_ssize_t i = 1; i < PyTuple_MAXSAVESIZE; i++) {
interp->tuple.numfree[i-1] = -1;
}
#endif
}

Expand Down
5 changes: 0 additions & 5 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -682,11 +682,6 @@ pycore_init_global_objects(PyInterpreterState *interp)

_PyUnicode_InitState(interp);

status = _PyTuple_InitGlobalObjects(interp);
if (_PyStatus_EXCEPTION(status)) {
return status;
}

return _PyStatus_OK();
}

Expand Down
2 changes: 2 additions & 0 deletions Tools/scripts/deepfreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
return f"& {name}.ob_base"

def generate_tuple(self, name: str, t: Tuple[object, ...]) -> str:
if len(t) == 0:
return f"(PyObject *)& _Py_SINGLETON(tuple_empty)"
items = [self.generate(f"{name}_{i}", it) for i, it in enumerate(t)]
self.write("static")
with self.indent():
Expand Down
3 changes: 3 additions & 0 deletions Tools/scripts/generate_global_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ def generate_runtime_init(identifiers, strings):
for name in sorted(identifiers):
assert name.isidentifier(), name
printer.write(f'INIT_ID({name}),')
printer.write('')
with printer.block('.tuple_empty =', ','):
printer.write('.ob_base = _PyVarObject_IMMORTAL_INIT(&PyTuple_Type, 0)')
printer.write(END)
printer.write(after)

Expand Down
0