Skip to content

Commit 2ab27c4

Browse files
bpo-43693: Un-revert commits 2c1e258 and b2bf2bc. (gh-26577)
These were reverted in gh-26530 (commit 17c4edc) due to refleaks. * 2c1e258 - Compute deref offsets in compiler (gh-25152) * b2bf2bc - Add new internal code objects fields: co_fastlocalnames and co_fastlocalkinds. (gh-26388) This change fixes the refleaks. https://bugs.python.org/issue43693
1 parent 001eb52 commit 2ab27c4

23 files changed

+6041
-5707
lines changed

Doc/library/dis.rst

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,17 +1058,24 @@ All of the following opcodes use their arguments.
10581058

10591059
.. opcode:: LOAD_CLOSURE (i)
10601060

1061-
Pushes a reference to the cell contained in slot *i* of the cell and free
1062-
variable storage. The name of the variable is ``co_cellvars[i]`` if *i* is
1063-
less than the length of *co_cellvars*. Otherwise it is ``co_freevars[i -
1064-
len(co_cellvars)]``.
1061+
Pushes a reference to the cell contained in slot ``i`` of the "fast locals"
1062+
storage. The name of the variable is ``co_fastlocalnames[i]``.
1063+
1064+
Note that ``LOAD_CLOSURE`` is effectively an alias for ``LOAD_FAST``.
1065+
It exists to keep bytecode a little more readable.
1066+
1067+
.. versionchanged:: 3.11
1068+
``i`` is no longer offset by the length of ``co_varnames``.
10651069

10661070

10671071
.. opcode:: LOAD_DEREF (i)
10681072

1069-
Loads the cell contained in slot *i* of the cell and free variable storage.
1073+
Loads the cell contained in slot ``i`` of the "fast locals" storage.
10701074
Pushes a reference to the object the cell contains on the stack.
10711075

1076+
.. versionchanged:: 3.11
1077+
``i`` is no longer offset by the length of ``co_varnames``.
1078+
10721079

10731080
.. opcode:: LOAD_CLASSDEREF (i)
10741081

@@ -1078,20 +1085,29 @@ All of the following opcodes use their arguments.
10781085

10791086
.. versionadded:: 3.4
10801087

1088+
.. versionchanged:: 3.11
1089+
``i`` is no longer offset by the length of ``co_varnames``.
1090+
10811091

10821092
.. opcode:: STORE_DEREF (i)
10831093

1084-
Stores TOS into the cell contained in slot *i* of the cell and free variable
1094+
Stores TOS into the cell contained in slot ``i`` of the "fast locals"
10851095
storage.
10861096

1097+
.. versionchanged:: 3.11
1098+
``i`` is no longer offset by the length of ``co_varnames``.
1099+
10871100

10881101
.. opcode:: DELETE_DEREF (i)
10891102

1090-
Empties the cell contained in slot *i* of the cell and free variable storage.
1103+
Empties the cell contained in slot ``i`` of the "fast locals" storage.
10911104
Used by the :keyword:`del` statement.
10921105

10931106
.. versionadded:: 3.2
10941107

1108+
.. versionchanged:: 3.11
1109+
``i`` is no longer offset by the length of ``co_varnames``.
1110+
10951111

10961112
.. opcode:: RAISE_VARARGS (argc)
10971113

Include/cpython/code.h

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#endif
44

55
typedef uint16_t _Py_CODEUNIT;
6+
// Each oparg must fit in the second half of _Py_CODEUNIT, hence 8 bits.
7+
#define _Py_MAX_OPARG 255
68

79
#ifdef WORDS_BIGENDIAN
810
# define _Py_OPCODE(word) ((word) >> 8)
@@ -16,6 +18,11 @@ typedef uint16_t _Py_CODEUNIT;
1618

1719
typedef struct _PyOpcache _PyOpcache;
1820

21+
22+
// These are duplicated from pycore_code.h.
23+
typedef unsigned char _PyLocalsPlusKind;
24+
typedef _PyLocalsPlusKind *_PyLocalsPlusKinds;
25+
1926
/* Bytecode object */
2027
struct PyCodeObject {
2128
PyObject_HEAD
@@ -47,7 +54,9 @@ struct PyCodeObject {
4754
// The hottest fields (in the eval loop) are grouped here at the top.
4855
PyObject *co_consts; /* list (constants used) */
4956
PyObject *co_names; /* list of strings (names used) */
50-
_Py_CODEUNIT *co_firstinstr; /* Pointer to first instruction, used for quickening */
57+
_Py_CODEUNIT *co_firstinstr; /* Pointer to first instruction, used for quickening.
58+
Unlike the other "hot" fields, this one is
59+
actually derived from co_code. */
5160
PyObject *co_exceptiontable; /* Byte string encoding exception handling table */
5261
int co_flags; /* CO_..., see below */
5362
int co_warmup; /* Warmup counter for quickening */
@@ -59,9 +68,8 @@ struct PyCodeObject {
5968
int co_stacksize; /* #entries needed for evaluation stack */
6069
int co_firstlineno; /* first source line number */
6170
PyObject *co_code; /* instruction opcodes */
62-
PyObject *co_varnames; /* tuple of strings (local variable names) */
63-
PyObject *co_cellvars; /* tuple of strings (cell variable names) */
64-
PyObject *co_freevars; /* tuple of strings (free variable names) */
71+
PyObject *co_localsplusnames; /* tuple mapping offsets to names */
72+
_PyLocalsPlusKinds co_localspluskinds; /* array mapping to local kinds */
6573
PyObject *co_filename; /* unicode (where it was loaded from) */
6674
PyObject *co_name; /* unicode (name, for reference) */
6775
PyObject *co_linetable; /* string (encoding addr<->lineno mapping) See
@@ -70,11 +78,15 @@ struct PyCodeObject {
7078
/* These fields are set with computed values on new code objects. */
7179

7280
int *co_cell2arg; /* Maps cell vars which are arguments. */
73-
// These are redundant but offer some performance benefit.
81+
// redundant values (derived from co_localsplusnames and co_localspluskinds)
7482
int co_nlocalsplus; /* number of local + cell + free variables */
7583
int co_nlocals; /* number of local variables */
7684
int co_ncellvars; /* number of cell variables */
7785
int co_nfreevars; /* number of free variables */
86+
// lazily-computed values
87+
PyObject *co_varnames; /* tuple of strings (local variable names) */
88+
PyObject *co_cellvars; /* tuple of strings (cell variable names) */
89+
PyObject *co_freevars; /* tuple of strings (free variable names) */
7890

7991
/* The remaining fields are zeroed out on new code objects. */
8092

@@ -152,7 +164,7 @@ struct PyCodeObject {
152164
PyAPI_DATA(PyTypeObject) PyCode_Type;
153165

154166
#define PyCode_Check(op) Py_IS_TYPE(op, &PyCode_Type)
155-
#define PyCode_GetNumFree(op) (PyTuple_GET_SIZE((op)->co_freevars))
167+
#define PyCode_GetNumFree(op) ((op)->co_nfreevars)
156168

157169
/* Public interface */
158170
PyAPI_FUNC(PyCodeObject *) PyCode_New(

Include/internal/pycore_code.h

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,58 @@ int _Py_Quicken(PyCodeObject *code);
150150

151151
extern Py_ssize_t _Py_QuickenedCount;
152152

153+
154+
/* "Locals plus" for a code object is the set of locals + cell vars +
155+
* free vars. This relates to variable names as well as offsets into
156+
* the "fast locals" storage array of execution frames. The compiler
157+
* builds the list of names, their offsets, and the corresponding
158+
* kind of local.
159+
*
160+
* Those kinds represent the source of the initial value and the
161+
* variable's scope (as related to closures). A "local" is an
162+
* argument or other variable defined in the current scope. A "free"
163+
* variable is one that is defined in an outer scope and comes from
164+
* the function's closure. A "cell" variable is a local that escapes
165+
* into an inner function as part of a closure, and thus must be
166+
* wrapped in a cell. Any "local" can also be a "cell", but the
167+
* "free" kind is mutually exclusive with both.
168+
*/
169+
170+
// We would use an enum if C let us specify the storage type.
171+
typedef unsigned char _PyLocalsPlusKind;
172+
/* Note that these all fit within _PyLocalsPlusKind, as do combinations. */
173+
// Later, we will use the smaller numbers to differentiate the different
174+
// kinds of locals (e.g. pos-only arg, varkwargs, local-only).
175+
#define CO_FAST_LOCAL 0x20
176+
#define CO_FAST_CELL 0x40
177+
#define CO_FAST_FREE 0x80
178+
179+
typedef _PyLocalsPlusKind *_PyLocalsPlusKinds;
180+
181+
static inline int
182+
_PyCode_InitLocalsPlusKinds(int num, _PyLocalsPlusKinds *pkinds)
183+
{
184+
if (num == 0) {
185+
*pkinds = NULL;
186+
return 0;
187+
}
188+
_PyLocalsPlusKinds kinds = PyMem_NEW(_PyLocalsPlusKind, num);
189+
if (kinds == NULL) {
190+
PyErr_NoMemory();
191+
return -1;
192+
}
193+
*pkinds = kinds;
194+
return 0;
195+
}
196+
197+
static inline void
198+
_PyCode_ClearLocalsPlusKinds(_PyLocalsPlusKinds kinds)
199+
{
200+
if (kinds != NULL) {
201+
PyMem_Free(kinds);
202+
}
203+
}
204+
153205
struct _PyCodeConstructor {
154206
/* metadata */
155207
PyObject *filename;
@@ -166,13 +218,13 @@ struct _PyCodeConstructor {
166218
PyObject *names;
167219

168220
/* mapping frame offsets to information */
169-
PyObject *varnames;
170-
PyObject *cellvars;
171-
PyObject *freevars;
221+
PyObject *localsplusnames;
222+
_PyLocalsPlusKinds localspluskinds;
172223

173224
/* args (within varnames) */
174225
int argcount;
175226
int posonlyargcount;
227+
// XXX Replace argcount with posorkwargcount (argcount - posonlyargcount).
176228
int kwonlyargcount;
177229

178230
/* needed to create the frame */
@@ -199,6 +251,11 @@ PyAPI_FUNC(PyCodeObject *) _PyCode_New(struct _PyCodeConstructor *);
199251

200252
int _PyCode_InitOpcache(PyCodeObject *co);
201253

254+
/* Getters for internal PyCodeObject data. */
255+
PyAPI_FUNC(PyObject *) _PyCode_GetVarnames(PyCodeObject *);
256+
PyAPI_FUNC(PyObject *) _PyCode_GetCellvars(PyCodeObject *);
257+
PyAPI_FUNC(PyObject *) _PyCode_GetFreevars(PyCodeObject *);
258+
202259

203260
#ifdef __cplusplus
204261
}

Lib/ctypes/test/test_values.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ class struct_frozen(Structure):
8080
continue
8181
items.append((entry.name.decode("ascii"), entry.size))
8282

83-
expected = [("__hello__", 138),
84-
("__phello__", -138),
85-
("__phello__.spam", 138),
83+
expected = [("__hello__", 128),
84+
("__phello__", -128),
85+
("__phello__.spam", 128),
8686
]
8787
self.assertEqual(items, expected, "PyImport_FrozenModules example "
8888
"in Doc/library/ctypes.rst may be out of date")

Lib/dis.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,15 @@ def get_instructions(x, *, first_line=None):
273273
the disassembled code object.
274274
"""
275275
co = _get_code_object(x)
276-
cell_names = co.co_cellvars + co.co_freevars
277276
linestarts = dict(findlinestarts(co))
278277
if first_line is not None:
279278
line_offset = first_line - co.co_firstlineno
280279
else:
281280
line_offset = 0
282-
return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
283-
co.co_consts, cell_names, linestarts,
284-
line_offset)
281+
return _get_instructions_bytes(co.co_code,
282+
co._varname_from_oparg,
283+
co.co_names, co.co_consts,
284+
linestarts, line_offset)
285285

286286
def _get_const_info(const_index, const_list):
287287
"""Helper to get optional details about const references
@@ -295,16 +295,16 @@ def _get_const_info(const_index, const_list):
295295
argval = const_list[const_index]
296296
return argval, repr(argval)
297297

298-
def _get_name_info(name_index, name_list):
298+
def _get_name_info(name_index, get_name, **extrainfo):
299299
"""Helper to get optional details about named references
300300
301301
Returns the dereferenced name as both value and repr if the name
302302
list is defined.
303303
Otherwise returns the name index and its repr().
304304
"""
305305
argval = name_index
306-
if name_list is not None:
307-
argval = name_list[name_index]
306+
if get_name is not None:
307+
argval = get_name(name_index, **extrainfo)
308308
argrepr = argval
309309
else:
310310
argrepr = repr(argval)
@@ -336,8 +336,10 @@ def parse_exception_table(code):
336336
except StopIteration:
337337
return entries
338338

339-
def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
340-
cells=None, linestarts=None, line_offset=0, exception_entries=()):
339+
def _get_instructions_bytes(code, varname_from_oparg=None,
340+
names=None, constants=None,
341+
linestarts=None, line_offset=0,
342+
exception_entries=()):
341343
"""Iterate over the instructions in a bytecode string.
342344
343345
Generates a sequence of Instruction namedtuples giving the details of each
@@ -346,6 +348,7 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
346348
arguments.
347349
348350
"""
351+
get_name = None if names is None else names.__getitem__
349352
labels = set(findlabels(code))
350353
for start, end, target, _, _ in exception_entries:
351354
for i in range(start, end):
@@ -368,20 +371,18 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
368371
if op in hasconst:
369372
argval, argrepr = _get_const_info(arg, constants)
370373
elif op in hasname:
371-
argval, argrepr = _get_name_info(arg, names)
374+
argval, argrepr = _get_name_info(arg, get_name)
372375
elif op in hasjabs:
373376
argval = arg*2
374377
argrepr = "to " + repr(argval)
375378
elif op in hasjrel:
376379
argval = offset + 2 + arg*2
377380
argrepr = "to " + repr(argval)
378-
elif op in haslocal:
379-
argval, argrepr = _get_name_info(arg, varnames)
381+
elif op in haslocal or op in hasfree:
382+
argval, argrepr = _get_name_info(arg, varname_from_oparg)
380383
elif op in hascompare:
381384
argval = cmp_op[arg]
382385
argrepr = argval
383-
elif op in hasfree:
384-
argval, argrepr = _get_name_info(arg, cells)
385386
elif op == FORMAT_VALUE:
386387
argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3]
387388
argval = (argval, bool(arg & 0x4))
@@ -398,11 +399,11 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
398399

399400
def disassemble(co, lasti=-1, *, file=None):
400401
"""Disassemble a code object."""
401-
cell_names = co.co_cellvars + co.co_freevars
402402
linestarts = dict(findlinestarts(co))
403403
exception_entries = parse_exception_table(co)
404-
_disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
405-
co.co_consts, cell_names, linestarts, file=file,
404+
_disassemble_bytes(co.co_code, lasti,
405+
co._varname_from_oparg,
406+
co.co_names, co.co_consts, linestarts, file=file,
406407
exception_entries=exception_entries)
407408

408409
def _disassemble_recursive(co, *, file=None, depth=None):
@@ -416,8 +417,8 @@ def _disassemble_recursive(co, *, file=None, depth=None):
416417
print("Disassembly of %r:" % (x,), file=file)
417418
_disassemble_recursive(x, file=file, depth=depth)
418419

419-
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
420-
constants=None, cells=None, linestarts=None,
420+
def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
421+
names=None, constants=None, linestarts=None,
421422
*, file=None, line_offset=0, exception_entries=()):
422423
# Omit the line number column entirely if we have no line number info
423424
show_lineno = bool(linestarts)
@@ -434,8 +435,8 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
434435
offset_width = len(str(maxoffset))
435436
else:
436437
offset_width = 4
437-
for instr in _get_instructions_bytes(code, varnames, names,
438-
constants, cells, linestarts,
438+
for instr in _get_instructions_bytes(code, varname_from_oparg, names,
439+
constants, linestarts,
439440
line_offset=line_offset, exception_entries=exception_entries):
440441
new_source_line = (show_lineno and
441442
instr.starts_line is not None and
@@ -517,16 +518,16 @@ def __init__(self, x, *, first_line=None, current_offset=None):
517518
else:
518519
self.first_line = first_line
519520
self._line_offset = first_line - co.co_firstlineno
520-
self._cell_names = co.co_cellvars + co.co_freevars
521521
self._linestarts = dict(findlinestarts(co))
522522
self._original_object = x
523523
self.current_offset = current_offset
524524
self.exception_entries = parse_exception_table(co)
525525

526526
def __iter__(self):
527527
co = self.codeobj
528-
return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
529-
co.co_consts, self._cell_names,
528+
return _get_instructions_bytes(co.co_code,
529+
co._varname_from_oparg,
530+
co.co_names, co.co_consts,
530531
self._linestarts,
531532
line_offset=self._line_offset,
532533
exception_entries=self.exception_entries)
@@ -554,9 +555,9 @@ def dis(self):
554555
else:
555556
offset = -1
556557
with io.StringIO() as output:
557-
_disassemble_bytes(co.co_code, varnames=co.co_varnames,
558+
_disassemble_bytes(co.co_code,
559+
varname_from_oparg=co._varname_from_oparg,
558560
names=co.co_names, constants=co.co_consts,
559-
cells=self._cell_names,
560561
linestarts=self._linestarts,
561562
line_offset=self._line_offset,
562563
file=output,

0 commit comments

Comments
 (0)