Skip to content

Commit 22b8d77

Browse files
authored
GH-100719: Remove redundant gi_code field from generator object. (GH-100749)
1 parent 572223f commit 22b8d77

File tree

9 files changed

+116
-61
lines changed

9 files changed

+116
-61
lines changed

Include/cpython/genobject.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ extern "C" {
1313
and coroutine objects. */
1414
#define _PyGenObject_HEAD(prefix) \
1515
PyObject_HEAD \
16-
/* The code object backing the generator */ \
17-
PyCodeObject *prefix##_code; \
1816
/* List of weak reference. */ \
1917
PyObject *prefix##_weakreflist; \
2018
/* Name of the generator. */ \
@@ -28,7 +26,7 @@ extern "C" {
2826
char prefix##_running_async; \
2927
/* The frame */ \
3028
int8_t prefix##_frame_state; \
31-
PyObject *prefix##_iframe[1];
29+
PyObject *prefix##_iframe[1]; \
3230

3331
typedef struct {
3432
/* The gi_ prefix is intended to remind of generator-iterator. */
@@ -46,6 +44,7 @@ PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(PyFrameObject *,
4644
PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *);
4745
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
4846
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
47+
PyAPI_FUNC(PyCodeObject *) PyGen_GetCode(PyGenObject *gen);
4948

5049

5150
/* --- PyCoroObject ------------------------------------------------------- */

Include/internal/pycore_frame.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ _PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
209209
* frames like the ones in generators and coroutines.
210210
*/
211211
void
212-
_PyFrame_Clear(_PyInterpreterFrame * frame);
212+
_PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
213213

214214
int
215215
_PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);

Lib/test/test_capi/test_misc.py

+5
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,11 @@ def test_pendingcalls_non_threaded(self):
12131213
self.pendingcalls_submit(l, n)
12141214
self.pendingcalls_wait(l, n)
12151215

1216+
def test_gen_get_code(self):
1217+
def genf(): yield
1218+
gen = genf()
1219+
self.assertEqual(_testcapi.gen_get_code(gen), gen.gi_code)
1220+
12161221

12171222
class SubinterpreterTest(unittest.TestCase):
12181223

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1460,7 +1460,7 @@ def bar(cls):
14601460
check(bar, size('PP'))
14611461
# generator
14621462
def get_gen(): yield 1
1463-
check(get_gen(), size('P2P4P4c7P2ic??2P'))
1463+
check(get_gen(), size('PP4P4c7P2ic??2P'))
14641464
# iterator
14651465
check(iter('abc'), size('lP'))
14661466
# callable-iterator
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Remove gi_code field from generator (and coroutine and async generator)
2+
objects as it is redundant. The frame already includes a reference to the
3+
code object.

Modules/_testcapimodule.c

+11
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,16 @@ eval_get_func_desc(PyObject *self, PyObject *func)
30763076
return PyUnicode_FromString(PyEval_GetFuncDesc(func));
30773077
}
30783078

3079+
static PyObject *
3080+
gen_get_code(PyObject *self, PyObject *gen)
3081+
{
3082+
if (!PyGen_Check(gen)) {
3083+
PyErr_SetString(PyExc_TypeError, "argument must be a generator object");
3084+
return NULL;
3085+
}
3086+
return (PyObject *)PyGen_GetCode((PyGenObject *)gen);
3087+
}
3088+
30793089
static PyObject *
30803090
eval_eval_code_ex(PyObject *mod, PyObject *pos_args)
30813091
{
@@ -3657,6 +3667,7 @@ static PyMethodDef TestMethods[] = {
36573667
{"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL},
36583668
{"eval_get_func_name", eval_get_func_name, METH_O, NULL},
36593669
{"eval_get_func_desc", eval_get_func_desc, METH_O, NULL},
3670+
{"gen_get_code", gen_get_code, METH_O, NULL},
36603671
{"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},
36613672
{"test_code_api", test_code_api, METH_NOARGS, NULL},
36623673
{"settrace_to_record", settrace_to_record, METH_O, NULL},

Objects/genobject.c

+55-17
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ static const char *NON_INIT_CORO_MSG = "can't send non-None value to a "
2424
static const char *ASYNC_GEN_IGNORED_EXIT_MSG =
2525
"async generator ignored GeneratorExit";
2626

27+
/* Returns a borrowed reference */
28+
static inline PyCodeObject *
29+
_PyGen_GetCode(PyGenObject *gen) {
30+
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)(gen->gi_iframe);
31+
return frame->f_code;
32+
}
33+
34+
PyCodeObject *
35+
PyGen_GetCode(PyGenObject *gen) {
36+
assert(PyGen_Check(gen));
37+
PyCodeObject *res = _PyGen_GetCode(gen);
38+
Py_INCREF(res);
39+
return res;
40+
}
41+
2742
static inline int
2843
exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg)
2944
{
@@ -34,7 +49,6 @@ exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg)
3449
static int
3550
gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
3651
{
37-
Py_VISIT(gen->gi_code);
3852
Py_VISIT(gen->gi_name);
3953
Py_VISIT(gen->gi_qualname);
4054
if (gen->gi_frame_state < FRAME_CLEARED) {
@@ -88,8 +102,8 @@ _PyGen_Finalize(PyObject *self)
88102

89103
/* If `gen` is a coroutine, and if it was never awaited on,
90104
issue a RuntimeWarning. */
91-
if (gen->gi_code != NULL &&
92-
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
105+
assert(_PyGen_GetCode(gen) != NULL);
106+
if (_PyGen_GetCode(gen)->co_flags & CO_COROUTINE &&
93107
gen->gi_frame_state == FRAME_CREATED)
94108
{
95109
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
@@ -137,12 +151,12 @@ gen_dealloc(PyGenObject *gen)
137151
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
138152
gen->gi_frame_state = FRAME_CLEARED;
139153
frame->previous = NULL;
140-
_PyFrame_Clear(frame);
154+
_PyFrame_ClearExceptCode(frame);
141155
}
142-
if (((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE) {
156+
if (_PyGen_GetCode(gen)->co_flags & CO_COROUTINE) {
143157
Py_CLEAR(((PyCoroObject *)gen)->cr_origin_or_finalizer);
144158
}
145-
Py_CLEAR(gen->gi_code);
159+
Py_DECREF(_PyGen_GetCode(gen));
146160
Py_CLEAR(gen->gi_name);
147161
Py_CLEAR(gen->gi_qualname);
148162
_PyErr_ClearExcState(&gen->gi_exc_state);
@@ -332,7 +346,7 @@ _PyGen_yf(PyGenObject *gen)
332346
/* Return immediately if the frame didn't start yet. SEND
333347
always come after LOAD_CONST: a code object should not start
334348
with SEND */
335-
assert(_PyCode_CODE(gen->gi_code)[0].op.code != SEND);
349+
assert(_PyCode_CODE(_PyGen_GetCode(gen))[0].op.code != SEND);
336350
return NULL;
337351
}
338352
_Py_CODEUNIT next = frame->prev_instr[1];
@@ -767,6 +781,21 @@ gen_getframe(PyGenObject *gen, void *Py_UNUSED(ignored))
767781
return _gen_getframe(gen, "gi_frame");
768782
}
769783

784+
static PyObject *
785+
_gen_getcode(PyGenObject *gen, const char *const name)
786+
{
787+
if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) {
788+
return NULL;
789+
}
790+
return Py_NewRef(_PyGen_GetCode(gen));
791+
}
792+
793+
static PyObject *
794+
gen_getcode(PyGenObject *gen, void *Py_UNUSED(ignored))
795+
{
796+
return _gen_getcode(gen, "gi_code");
797+
}
798+
770799
static PyGetSetDef gen_getsetlist[] = {
771800
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
772801
PyDoc_STR("name of the generator")},
@@ -777,11 +806,11 @@ static PyGetSetDef gen_getsetlist[] = {
777806
{"gi_running", (getter)gen_getrunning, NULL, NULL},
778807
{"gi_frame", (getter)gen_getframe, NULL, NULL},
779808
{"gi_suspended", (getter)gen_getsuspended, NULL, NULL},
809+
{"gi_code", (getter)gen_getcode, NULL, NULL},
780810
{NULL} /* Sentinel */
781811
};
782812

783813
static PyMemberDef gen_memberlist[] = {
784-
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY|PY_AUDIT_READ},
785814
{NULL} /* Sentinel */
786815
};
787816

@@ -790,7 +819,7 @@ gen_sizeof(PyGenObject *gen, PyObject *Py_UNUSED(ignored))
790819
{
791820
Py_ssize_t res;
792821
res = offsetof(PyGenObject, gi_iframe) + offsetof(_PyInterpreterFrame, localsplus);
793-
PyCodeObject *code = gen->gi_code;
822+
PyCodeObject *code = _PyGen_GetCode(gen);
794823
res += _PyFrame_NumSlotsForCodeObject(code) * sizeof(PyObject *);
795824
return PyLong_FromSsize_t(res);
796825
}
@@ -878,7 +907,6 @@ make_gen(PyTypeObject *type, PyFunctionObject *func)
878907
return NULL;
879908
}
880909
gen->gi_frame_state = FRAME_CLEARED;
881-
gen->gi_code = (PyCodeObject *)Py_NewRef(func->func_code);
882910
gen->gi_weakreflist = NULL;
883911
gen->gi_exc_state.exc_value = NULL;
884912
gen->gi_exc_state.previous_item = NULL;
@@ -960,20 +988,18 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
960988
f->f_frame = frame;
961989
frame->owner = FRAME_OWNED_BY_GENERATOR;
962990
assert(PyObject_GC_IsTracked((PyObject *)f));
963-
gen->gi_code = PyFrame_GetCode(f);
964-
Py_INCREF(gen->gi_code);
965991
Py_DECREF(f);
966992
gen->gi_weakreflist = NULL;
967993
gen->gi_exc_state.exc_value = NULL;
968994
gen->gi_exc_state.previous_item = NULL;
969995
if (name != NULL)
970996
gen->gi_name = Py_NewRef(name);
971997
else
972-
gen->gi_name = Py_NewRef(gen->gi_code->co_name);
998+
gen->gi_name = Py_NewRef(_PyGen_GetCode(gen)->co_name);
973999
if (qualname != NULL)
9741000
gen->gi_qualname = Py_NewRef(qualname);
9751001
else
976-
gen->gi_qualname = Py_NewRef(gen->gi_code->co_qualname);
1002+
gen->gi_qualname = Py_NewRef(_PyGen_GetCode(gen)->co_qualname);
9771003
_PyObject_GC_TRACK(gen);
9781004
return (PyObject *)gen;
9791005
}
@@ -1001,7 +1027,7 @@ static int
10011027
gen_is_coroutine(PyObject *o)
10021028
{
10031029
if (PyGen_CheckExact(o)) {
1004-
PyCodeObject *code = (PyCodeObject *)((PyGenObject*)o)->gi_code;
1030+
PyCodeObject *code = _PyGen_GetCode((PyGenObject*)o);
10051031
if (code->co_flags & CO_ITERABLE_COROUTINE) {
10061032
return 1;
10071033
}
@@ -1110,6 +1136,12 @@ cr_getframe(PyCoroObject *coro, void *Py_UNUSED(ignored))
11101136
return _gen_getframe((PyGenObject *)coro, "cr_frame");
11111137
}
11121138

1139+
static PyObject *
1140+
cr_getcode(PyCoroObject *coro, void *Py_UNUSED(ignored))
1141+
{
1142+
return _gen_getcode((PyGenObject *)coro, "cr_code");
1143+
}
1144+
11131145

11141146
static PyGetSetDef coro_getsetlist[] = {
11151147
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
@@ -1120,12 +1152,12 @@ static PyGetSetDef coro_getsetlist[] = {
11201152
PyDoc_STR("object being awaited on, or None")},
11211153
{"cr_running", (getter)cr_getrunning, NULL, NULL},
11221154
{"cr_frame", (getter)cr_getframe, NULL, NULL},
1155+
{"cr_code", (getter)cr_getcode, NULL, NULL},
11231156
{"cr_suspended", (getter)cr_getsuspended, NULL, NULL},
11241157
{NULL} /* Sentinel */
11251158
};
11261159

11271160
static PyMemberDef coro_memberlist[] = {
1128-
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY|PY_AUDIT_READ},
11291161
{"cr_origin", T_OBJECT, offsetof(PyCoroObject, cr_origin_or_finalizer), READONLY},
11301162
{NULL} /* Sentinel */
11311163
};
@@ -1514,6 +1546,12 @@ ag_getframe(PyAsyncGenObject *ag, void *Py_UNUSED(ignored))
15141546
return _gen_getframe((PyGenObject *)ag, "ag_frame");
15151547
}
15161548

1549+
static PyObject *
1550+
ag_getcode(PyGenObject *gen, void *Py_UNUSED(ignored))
1551+
{
1552+
return _gen_getcode(gen, "ag__code");
1553+
}
1554+
15171555
static PyGetSetDef async_gen_getsetlist[] = {
15181556
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
15191557
PyDoc_STR("name of the async generator")},
@@ -1522,13 +1560,13 @@ static PyGetSetDef async_gen_getsetlist[] = {
15221560
{"ag_await", (getter)coro_get_cr_await, NULL,
15231561
PyDoc_STR("object being awaited on, or None")},
15241562
{"ag_frame", (getter)ag_getframe, NULL, NULL},
1563+
{"ag_code", (getter)ag_getcode, NULL, NULL},
15251564
{NULL} /* Sentinel */
15261565
};
15271566

15281567
static PyMemberDef async_gen_memberlist[] = {
15291568
{"ag_running", T_BOOL, offsetof(PyAsyncGenObject, ag_running_async),
15301569
READONLY},
1531-
{"ag_code", T_OBJECT, offsetof(PyAsyncGenObject, ag_code), READONLY|PY_AUDIT_READ},
15321570
{NULL} /* Sentinel */
15331571
};
15341572

Python/ceval.c

+36-37
Original file line numberDiff line numberDiff line change
@@ -1604,41 +1604,6 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
16041604
return -1;
16051605
}
16061606

1607-
/* Consumes references to func, locals and all the args */
1608-
static _PyInterpreterFrame *
1609-
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
1610-
PyObject *locals, PyObject* const* args,
1611-
size_t argcount, PyObject *kwnames)
1612-
{
1613-
PyCodeObject * code = (PyCodeObject *)func->func_code;
1614-
CALL_STAT_INC(frames_pushed);
1615-
_PyInterpreterFrame *frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
1616-
if (frame == NULL) {
1617-
goto fail;
1618-
}
1619-
_PyFrame_Initialize(frame, func, locals, code, 0);
1620-
PyObject **localsarray = &frame->localsplus[0];
1621-
if (initialize_locals(tstate, func, localsarray, args, argcount, kwnames)) {
1622-
assert(frame->owner != FRAME_OWNED_BY_GENERATOR);
1623-
_PyEvalFrameClearAndPop(tstate, frame);
1624-
return NULL;
1625-
}
1626-
return frame;
1627-
fail:
1628-
/* Consume the references */
1629-
for (size_t i = 0; i < argcount; i++) {
1630-
Py_DECREF(args[i]);
1631-
}
1632-
if (kwnames) {
1633-
Py_ssize_t kwcount = PyTuple_GET_SIZE(kwnames);
1634-
for (Py_ssize_t i = 0; i < kwcount; i++) {
1635-
Py_DECREF(args[i+argcount]);
1636-
}
1637-
}
1638-
PyErr_NoMemory();
1639-
return NULL;
1640-
}
1641-
16421607
static void
16431608
clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
16441609
{
@@ -1649,7 +1614,8 @@ clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
16491614
tstate->datastack_top);
16501615
tstate->c_recursion_remaining--;
16511616
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
1652-
_PyFrame_Clear(frame);
1617+
_PyFrame_ClearExceptCode(frame);
1618+
Py_DECREF(frame->f_code);
16531619
tstate->c_recursion_remaining++;
16541620
_PyThreadState_PopFrame(tstate, frame);
16551621
}
@@ -1665,7 +1631,7 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
16651631
gen->gi_exc_state.previous_item = NULL;
16661632
tstate->c_recursion_remaining--;
16671633
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
1668-
_PyFrame_Clear(frame);
1634+
_PyFrame_ClearExceptCode(frame);
16691635
tstate->c_recursion_remaining++;
16701636
frame->previous = NULL;
16711637
}
@@ -1681,6 +1647,39 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame)
16811647
}
16821648
}
16831649

1650+
/* Consumes references to func, locals and all the args */
1651+
static _PyInterpreterFrame *
1652+
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
1653+
PyObject *locals, PyObject* const* args,
1654+
size_t argcount, PyObject *kwnames)
1655+
{
1656+
PyCodeObject * code = (PyCodeObject *)func->func_code;
1657+
CALL_STAT_INC(frames_pushed);
1658+
_PyInterpreterFrame *frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
1659+
if (frame == NULL) {
1660+
goto fail;
1661+
}
1662+
_PyFrame_Initialize(frame, func, locals, code, 0);
1663+
if (initialize_locals(tstate, func, frame->localsplus, args, argcount, kwnames)) {
1664+
assert(frame->owner == FRAME_OWNED_BY_THREAD);
1665+
clear_thread_frame(tstate, frame);
1666+
return NULL;
1667+
}
1668+
return frame;
1669+
fail:
1670+
/* Consume the references */
1671+
for (size_t i = 0; i < argcount; i++) {
1672+
Py_DECREF(args[i]);
1673+
}
1674+
if (kwnames) {
1675+
Py_ssize_t kwcount = PyTuple_GET_SIZE(kwnames);
1676+
for (Py_ssize_t i = 0; i < kwcount; i++) {
1677+
Py_DECREF(args[i+argcount]);
1678+
}
1679+
}
1680+
PyErr_NoMemory();
1681+
return NULL;
1682+
}
16841683

16851684
PyObject *
16861685
_PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,

0 commit comments

Comments
 (0)