Skip to content

Commit 9f799ab

Browse files
authored
gh-87092: Make jump target label equal to the offset of the target in the instructions sequence (#102093)
1 parent 6b2d7c0 commit 9f799ab

File tree

4 files changed

+111
-108
lines changed

4 files changed

+111
-108
lines changed

Lib/test/support/bytecode_helper.py

+36-47
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,13 @@ class CompilationStepTestCase(unittest.TestCase):
5050
HAS_TARGET = set(dis.hasjrel + dis.hasjabs + dis.hasexc)
5151
HAS_ARG_OR_TARGET = HAS_ARG.union(HAS_TARGET)
5252

53-
def setUp(self):
54-
self.last_label = 0
55-
56-
def Label(self):
57-
self.last_label += 1
58-
return self.last_label
53+
class Label:
54+
pass
5955

6056
def assertInstructionsMatch(self, actual_, expected_):
6157
# get two lists where each entry is a label or
62-
# an instruction tuple. Compare them, while mapping
63-
# each actual label to a corresponding expected label
64-
# based on their locations.
58+
# an instruction tuple. Normalize the labels to the
59+
# instruction count of the target, and compare the lists.
6560

6661
self.assertIsInstance(actual_, list)
6762
self.assertIsInstance(expected_, list)
@@ -82,39 +77,35 @@ def assertInstructionsMatch(self, actual_, expected_):
8277
act = act[:len(exp)]
8378
self.assertEqual(exp, act)
8479

80+
def resolveAndRemoveLabels(self, insts):
81+
idx = 0
82+
res = []
83+
for item in insts:
84+
assert isinstance(item, (self.Label, tuple))
85+
if isinstance(item, self.Label):
86+
item.value = idx
87+
else:
88+
idx += 1
89+
res.append(item)
90+
91+
return res
92+
8593
def normalize_insts(self, insts):
8694
""" Map labels to instruction index.
87-
Remove labels which are not used as jump targets.
8895
Map opcodes to opnames.
8996
"""
90-
labels_map = {}
91-
targets = set()
92-
idx = 1
93-
for item in insts:
94-
assert isinstance(item, (int, tuple))
95-
if isinstance(item, tuple):
96-
opcode, oparg, *_ = item
97-
if dis.opmap.get(opcode, opcode) in self.HAS_TARGET:
98-
targets.add(oparg)
99-
idx += 1
100-
elif isinstance(item, int):
101-
assert item not in labels_map, "label reused"
102-
labels_map[item] = idx
103-
97+
insts = self.resolveAndRemoveLabels(insts)
10498
res = []
10599
for item in insts:
106-
if isinstance(item, int) and item in targets:
107-
if not res or labels_map[item] != res[-1]:
108-
res.append(labels_map[item])
109-
elif isinstance(item, tuple):
110-
opcode, oparg, *loc = item
111-
opcode = dis.opmap.get(opcode, opcode)
112-
if opcode in self.HAS_TARGET:
113-
arg = labels_map[oparg]
114-
else:
115-
arg = oparg if opcode in self.HAS_TARGET else None
116-
opcode = dis.opname[opcode]
117-
res.append((opcode, arg, *loc))
100+
assert isinstance(item, tuple)
101+
opcode, oparg, *loc = item
102+
opcode = dis.opmap.get(opcode, opcode)
103+
if isinstance(oparg, self.Label):
104+
arg = oparg.value
105+
else:
106+
arg = oparg if opcode in self.HAS_ARG else None
107+
opcode = dis.opname[opcode]
108+
res.append((opcode, arg, *loc))
118109
return res
119110

120111

@@ -129,20 +120,18 @@ class CfgOptimizationTestCase(CompilationStepTestCase):
129120

130121
def complete_insts_info(self, insts):
131122
# fill in omitted fields in location, and oparg 0 for ops with no arg.
132-
instructions = []
123+
res = []
133124
for item in insts:
134-
if isinstance(item, int):
135-
instructions.append(item)
136-
else:
137-
assert isinstance(item, tuple)
138-
inst = list(reversed(item))
139-
opcode = dis.opmap[inst.pop()]
140-
oparg = inst.pop() if opcode in self.HAS_ARG_OR_TARGET else 0
141-
loc = inst + [-1] * (4 - len(inst))
142-
instructions.append((opcode, oparg, *loc))
143-
return instructions
125+
assert isinstance(item, tuple)
126+
inst = list(reversed(item))
127+
opcode = dis.opmap[inst.pop()]
128+
oparg = inst.pop() if opcode in self.HAS_ARG_OR_TARGET else 0
129+
loc = inst + [-1] * (4 - len(inst))
130+
res.append((opcode, oparg, *loc))
131+
return res
144132

145133
def get_optimized(self, insts, consts):
134+
insts = self.normalize_insts(insts)
146135
insts = self.complete_insts_info(insts)
147136
insts = optimize_cfg(insts, consts)
148137
return insts, consts

Lib/test/test_compiler_codegen.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ def test_for_loop(self):
3737
('GET_ITER', None, 1),
3838
loop_lbl := self.Label(),
3939
('FOR_ITER', exit_lbl := self.Label(), 1),
40-
('STORE_NAME', None, 1),
40+
('STORE_NAME', 1, 1),
4141
('PUSH_NULL', None, 2),
42-
('LOAD_NAME', None, 2),
43-
('LOAD_NAME', None, 2),
44-
('CALL', None, 2),
42+
('LOAD_NAME', 2, 2),
43+
('LOAD_NAME', 1, 2),
44+
('CALL', 1, 2),
4545
('POP_TOP', None),
4646
('JUMP', loop_lbl),
4747
exit_lbl,

Lib/test/test_peepholer.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,7 @@ def cfg_optimization_test(self, insts, expected_insts,
984984
if expected_consts is None:
985985
expected_consts = consts
986986
opt_insts, opt_consts = self.get_optimized(insts, consts)
987+
expected_insts = self.normalize_insts(expected_insts)
987988
self.assertInstructionsMatch(opt_insts, expected_insts)
988989
self.assertEqual(opt_consts, expected_consts)
989990

@@ -996,11 +997,11 @@ def test_conditional_jump_forward_non_const_condition(self):
996997
('LOAD_CONST', 3, 14),
997998
]
998999
expected = [
999-
('LOAD_NAME', '1', 11),
1000+
('LOAD_NAME', 1, 11),
10001001
('POP_JUMP_IF_TRUE', lbl := self.Label(), 12),
1001-
('LOAD_CONST', '2', 13),
1002+
('LOAD_CONST', 2, 13),
10021003
lbl,
1003-
('LOAD_CONST', '3', 14)
1004+
('LOAD_CONST', 3, 14)
10041005
]
10051006
self.cfg_optimization_test(insts, expected, consts=list(range(5)))
10061007

@@ -1018,7 +1019,7 @@ def test_conditional_jump_forward_const_condition(self):
10181019
expected = [
10191020
('NOP', None, 11),
10201021
('NOP', None, 12),
1021-
('LOAD_CONST', '3', 14)
1022+
('LOAD_CONST', 3, 14)
10221023
]
10231024
self.cfg_optimization_test(insts, expected, consts=list(range(5)))
10241025

@@ -1031,9 +1032,9 @@ def test_conditional_jump_backward_non_const_condition(self):
10311032
]
10321033
expected = [
10331034
lbl := self.Label(),
1034-
('LOAD_NAME', '1', 11),
1035+
('LOAD_NAME', 1, 11),
10351036
('POP_JUMP_IF_TRUE', lbl, 12),
1036-
('LOAD_CONST', '2', 13)
1037+
('LOAD_CONST', 2, 13)
10371038
]
10381039
self.cfg_optimization_test(insts, expected, consts=list(range(5)))
10391040

Python/compile.c

+64-51
Original file line numberDiff line numberDiff line change
@@ -9704,56 +9704,77 @@ instructions_to_cfg(PyObject *instructions, cfg_builder *g)
97049704
{
97059705
assert(PyList_Check(instructions));
97069706

9707-
Py_ssize_t instr_size = PyList_GET_SIZE(instructions);
9708-
for (Py_ssize_t i = 0; i < instr_size; i++) {
9707+
Py_ssize_t num_insts = PyList_GET_SIZE(instructions);
9708+
bool *is_target = PyMem_Calloc(num_insts, sizeof(bool));
9709+
if (is_target == NULL) {
9710+
return ERROR;
9711+
}
9712+
for (Py_ssize_t i = 0; i < num_insts; i++) {
97099713
PyObject *item = PyList_GET_ITEM(instructions, i);
9710-
if (PyLong_Check(item)) {
9711-
int lbl_id = PyLong_AsLong(item);
9714+
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 6) {
9715+
PyErr_SetString(PyExc_ValueError, "expected a 6-tuple");
9716+
goto error;
9717+
}
9718+
int opcode = PyLong_AsLong(PyTuple_GET_ITEM(item, 0));
9719+
if (PyErr_Occurred()) {
9720+
goto error;
9721+
}
9722+
if (HAS_TARGET(opcode)) {
9723+
int oparg = PyLong_AsLong(PyTuple_GET_ITEM(item, 1));
97129724
if (PyErr_Occurred()) {
9713-
return ERROR;
9725+
goto error;
97149726
}
9715-
if (lbl_id <= 0 || lbl_id > instr_size) {
9716-
/* expect label in a reasonable range */
9727+
if (oparg < 0 || oparg >= num_insts) {
97179728
PyErr_SetString(PyExc_ValueError, "label out of range");
9718-
return ERROR;
9729+
goto error;
97199730
}
9720-
jump_target_label lbl = {lbl_id};
9731+
is_target[oparg] = true;
9732+
}
9733+
}
9734+
9735+
for (int i = 0; i < num_insts; i++) {
9736+
if (is_target[i]) {
9737+
jump_target_label lbl = {i};
97219738
RETURN_IF_ERROR(cfg_builder_use_label(g, lbl));
97229739
}
9723-
else {
9724-
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 6) {
9725-
PyErr_SetString(PyExc_ValueError, "expected a 6-tuple");
9726-
return ERROR;
9727-
}
9728-
int opcode = PyLong_AsLong(PyTuple_GET_ITEM(item, 0));
9729-
if (PyErr_Occurred()) {
9730-
return ERROR;
9731-
}
9732-
int oparg = PyLong_AsLong(PyTuple_GET_ITEM(item, 1));
9733-
if (PyErr_Occurred()) {
9734-
return ERROR;
9735-
}
9736-
location loc;
9737-
loc.lineno = PyLong_AsLong(PyTuple_GET_ITEM(item, 2));
9738-
if (PyErr_Occurred()) {
9739-
return ERROR;
9740-
}
9741-
loc.end_lineno = PyLong_AsLong(PyTuple_GET_ITEM(item, 3));
9742-
if (PyErr_Occurred()) {
9743-
return ERROR;
9744-
}
9745-
loc.col_offset = PyLong_AsLong(PyTuple_GET_ITEM(item, 4));
9746-
if (PyErr_Occurred()) {
9747-
return ERROR;
9748-
}
9749-
loc.end_col_offset = PyLong_AsLong(PyTuple_GET_ITEM(item, 5));
9750-
if (PyErr_Occurred()) {
9751-
return ERROR;
9752-
}
9753-
RETURN_IF_ERROR(cfg_builder_addop(g, opcode, oparg, loc));
9740+
PyObject *item = PyList_GET_ITEM(instructions, i);
9741+
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 6) {
9742+
PyErr_SetString(PyExc_ValueError, "expected a 6-tuple");
9743+
goto error;
9744+
}
9745+
int opcode = PyLong_AsLong(PyTuple_GET_ITEM(item, 0));
9746+
if (PyErr_Occurred()) {
9747+
goto error;
9748+
}
9749+
int oparg = PyLong_AsLong(PyTuple_GET_ITEM(item, 1));
9750+
if (PyErr_Occurred()) {
9751+
goto error;
9752+
}
9753+
location loc;
9754+
loc.lineno = PyLong_AsLong(PyTuple_GET_ITEM(item, 2));
9755+
if (PyErr_Occurred()) {
9756+
goto error;
97549757
}
9758+
loc.end_lineno = PyLong_AsLong(PyTuple_GET_ITEM(item, 3));
9759+
if (PyErr_Occurred()) {
9760+
goto error;
9761+
}
9762+
loc.col_offset = PyLong_AsLong(PyTuple_GET_ITEM(item, 4));
9763+
if (PyErr_Occurred()) {
9764+
goto error;
9765+
}
9766+
loc.end_col_offset = PyLong_AsLong(PyTuple_GET_ITEM(item, 5));
9767+
if (PyErr_Occurred()) {
9768+
goto error;
9769+
}
9770+
RETURN_IF_ERROR(cfg_builder_addop(g, opcode, oparg, loc));
97559771
}
9772+
9773+
PyMem_Free(is_target);
97569774
return SUCCESS;
9775+
error:
9776+
PyMem_Free(is_target);
9777+
return ERROR;
97579778
}
97589779

97599780
static PyObject *
@@ -9763,20 +9784,12 @@ cfg_to_instructions(cfg_builder *g)
97639784
if (instructions == NULL) {
97649785
return NULL;
97659786
}
9766-
int lbl = 1;
9787+
int lbl = 0;
97679788
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
9768-
b->b_label = lbl++;
9789+
b->b_label = lbl;
9790+
lbl += b->b_iused;
97699791
}
97709792
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
9771-
PyObject *lbl = PyLong_FromLong(b->b_label);
9772-
if (lbl == NULL) {
9773-
goto error;
9774-
}
9775-
if (PyList_Append(instructions, lbl) != 0) {
9776-
Py_DECREF(lbl);
9777-
goto error;
9778-
}
9779-
Py_DECREF(lbl);
97809793
for (int i = 0; i < b->b_iused; i++) {
97819794
struct instr *instr = &b->b_instr[i];
97829795
location loc = instr->i_loc;

0 commit comments

Comments
 (0)