Skip to content

bpo-45056: Remove trailing unused constants from co_consts #28109

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 6 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Diff view
11 changes: 11 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,17 @@ def test_merge_code_attrs(self):
self.assertIs(f1.__code__.co_linetable, f2.__code__.co_linetable)
self.assertIs(f1.__code__.co_code, f2.__code__.co_code)

# Stripping unused constants is not a strict requirement for the
# Python semantics, it's a more an implementation detail.
@support.cpython_only
def test_strip_unused_consts(self):
# Python 3.10rc1 appended None to co_consts when None is not used
# at all. See bpo-45056.
def f1():
"docstring"
return 42
self.assertEqual(f1.__code__.co_consts, ("docstring", 42))

# This is a regression test for a CPython specific peephole optimizer
# implementation bug present in a few releases. It's assertion verifies
# that peephole optimization was actually done though that isn't an
Expand Down
6 changes: 1 addition & 5 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,10 +702,7 @@ def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs):
if sys.flags.optimize:
code_info_consts = "0: None"
else:
code_info_consts = (
"""0: 'Formatted details of methods, functions, or code.'
1: None"""
)
code_info_consts = "0: 'Formatted details of methods, functions, or code.'"

code_info_code_info = f"""\
Name: code_info
Expand Down Expand Up @@ -828,7 +825,6 @@ def f(c=c):
Constants:
0: 0
1: 1
2: None
Names:
0: x"""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Compiler now removes trailing unused constants from co_consts.
33 changes: 33 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -7573,6 +7573,9 @@ normalize_basic_block(basicblock *bb);
static int
optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts);

static int
trim_unused_consts(struct compiler *c, struct assembler *a, PyObject *consts);

/* Duplicates exit BBs, so that line numbers can be propagated to them */
static int
duplicate_exits_without_lineno(struct compiler *c);
Expand Down Expand Up @@ -7870,6 +7873,9 @@ assemble(struct compiler *c, int addNone)
if (duplicate_exits_without_lineno(c)) {
return NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, I am curious why this isn’t “go to error”.

}
if (trim_unused_consts(c, &a, consts)) {
goto error;
}
propagate_line_numbers(&a);
guarantee_lineno_for_exits(&a, c->u->u_firstlineno);
int maxdepth = stackdepth(c);
Expand Down Expand Up @@ -8599,6 +8605,33 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
return 0;
}

// Remove trailing unused constants.
static int
trim_unused_consts(struct compiler *c, struct assembler *a, PyObject *consts)
{
assert(PyList_CheckExact(consts));

// The first constant may be docstring; keep it always.
int max_const_index = 0;
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
for (int i = 0; i < b->b_iused; i++) {
if (b->b_instr[i].i_opcode == LOAD_CONST &&
b->b_instr[i].i_oparg > max_const_index) {
max_const_index = b->b_instr[i].i_oparg;
}
}
}
if (max_const_index+1 < PyList_GET_SIZE(consts)) {
//fprintf(stderr, "removing trailing consts: max=%d, size=%d\n",
// max_const_index, (int)PyList_GET_SIZE(consts));
if (PyList_SetSlice(consts, max_const_index+1,
PyList_GET_SIZE(consts), NULL) < 0) {
return 1;
}
}
return 0;
}

static inline int
is_exit_without_lineno(basicblock *b) {
return b->b_exit && b->b_instr[0].i_lineno < 0;
Expand Down
4,093 changes: 2,046 additions & 2,047 deletions Python/frozen_modules/importlib__bootstrap.h

Large diffs are not rendered by default.

Loading