Skip to content

Commit 2091fb2

Browse files
authored
gh-107901: make compiler inline basic blocks with no line number and no fallthrough (#114750)
1 parent 41fde89 commit 2091fb2

File tree

3 files changed

+108
-39
lines changed

3 files changed

+108
-39
lines changed

Lib/test/test_compile.py

+50-12
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,17 @@ async def test(aseq):
11041104
code_lines = self.get_code_lines(test.__code__)
11051105
self.assertEqual(expected_lines, code_lines)
11061106

1107+
def check_line_numbers(self, code, opnames=None):
1108+
# Check that all instructions whose op matches opnames
1109+
# have a line number. opnames can be a single name, or
1110+
# a sequence of names. If it is None, match all ops.
1111+
1112+
if isinstance(opnames, str):
1113+
opnames = (opnames, )
1114+
for inst in dis.Bytecode(code):
1115+
if opnames and inst.opname in opnames:
1116+
self.assertIsNotNone(inst.positions.lineno)
1117+
11071118
def test_line_number_synthetic_jump_multiple_predecessors(self):
11081119
def f():
11091120
for x in it:
@@ -1113,25 +1124,52 @@ def f():
11131124
except OSError:
11141125
pass
11151126

1116-
# Ensure that all JUMP_BACKWARDs have line number
1117-
code = f.__code__
1118-
for inst in dis.Bytecode(code):
1119-
if inst.opname == 'JUMP_BACKWARD':
1120-
self.assertIsNotNone(inst.positions.lineno)
1127+
self.check_line_numbers(f.__code__, 'JUMP_BACKWARD')
11211128

1122-
def test_lineno_of_backward_jump(self):
1129+
def test_line_number_synthetic_jump_multiple_predecessors_nested(self):
1130+
def f():
1131+
for x in it:
1132+
try:
1133+
X = 3
1134+
except OSError:
1135+
try:
1136+
if C3:
1137+
X = 4
1138+
except OSError:
1139+
pass
1140+
return 42
1141+
1142+
self.check_line_numbers(f.__code__, 'JUMP_BACKWARD')
1143+
1144+
def test_line_number_synthetic_jump_multiple_predecessors_more_nested(self):
1145+
def f():
1146+
for x in it:
1147+
try:
1148+
X = 3
1149+
except OSError:
1150+
try:
1151+
if C3:
1152+
if C4:
1153+
X = 4
1154+
except OSError:
1155+
try:
1156+
if C3:
1157+
if C4:
1158+
X = 5
1159+
except OSError:
1160+
pass
1161+
return 42
1162+
1163+
self.check_line_numbers(f.__code__, 'JUMP_BACKWARD')
1164+
1165+
def test_lineno_of_backward_jump_conditional_in_loop(self):
11231166
# Issue gh-107901
11241167
def f():
11251168
for i in x:
11261169
if y:
11271170
pass
11281171

1129-
linenos = list(inst.positions.lineno
1130-
for inst in dis.get_instructions(f.__code__)
1131-
if inst.opname == 'JUMP_BACKWARD')
1132-
1133-
self.assertTrue(len(linenos) > 0)
1134-
self.assertTrue(all(l is not None for l in linenos))
1172+
self.check_line_numbers(f.__code__, 'JUMP_BACKWARD')
11351173

11361174
def test_big_dict_literal(self):
11371175
# The compiler has a flushing point in "compiler_dict" that calls compiles

Lib/test/test_monitoring.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1466,9 +1466,8 @@ def func():
14661466
('branch', 'func', 4, 4),
14671467
('line', 'func', 5),
14681468
('line', 'meth', 1),
1469-
('jump', 'func', 5, 5),
1470-
('jump', 'func', 5, '[offset=114]'),
1471-
('branch', 'func', '[offset=120]', '[offset=124]'),
1469+
('jump', 'func', 5, '[offset=118]'),
1470+
('branch', 'func', '[offset=122]', '[offset=126]'),
14721471
('line', 'get_events', 11)])
14731472

14741473
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
@@ -1482,9 +1481,8 @@ def func():
14821481
('line', 'func', 5),
14831482
('line', 'meth', 1),
14841483
('return', 'meth', None),
1485-
('jump', 'func', 5, 5),
1486-
('jump', 'func', 5, '[offset=114]'),
1487-
('branch', 'func', '[offset=120]', '[offset=124]'),
1484+
('jump', 'func', 5, '[offset=118]'),
1485+
('branch', 'func', '[offset=122]', '[offset=126]'),
14881486
('return', 'func', None),
14891487
('line', 'get_events', 11)])
14901488

Python/flowgraph.c

+54-21
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,14 @@ basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc)
212212
}
213213

214214
static inline int
215-
basicblock_append_instructions(basicblock *target, basicblock *source)
215+
basicblock_append_instructions(basicblock *to, basicblock *from)
216216
{
217-
for (int i = 0; i < source->b_iused; i++) {
218-
int n = basicblock_next_instr(target);
217+
for (int i = 0; i < from->b_iused; i++) {
218+
int n = basicblock_next_instr(to);
219219
if (n < 0) {
220220
return ERROR;
221221
}
222-
target->b_instr[n] = source->b_instr[i];
222+
to->b_instr[n] = from->b_instr[i];
223223
}
224224
return SUCCESS;
225225
}
@@ -292,9 +292,9 @@ static void
292292
dump_basicblock(const basicblock *b)
293293
{
294294
const char *b_return = basicblock_returns(b) ? "return " : "";
295-
fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, %s\n",
295+
fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, preds: %d %s\n",
296296
b->b_label.id, b->b_except_handler, b->b_cold, b->b_warm, BB_NO_FALLTHROUGH(b), b, b->b_iused,
297-
b->b_startdepth, b_return);
297+
b->b_startdepth, b->b_predecessors, b_return);
298298
if (b->b_instr) {
299299
int i;
300300
for (i = 0; i < b->b_iused; i++) {
@@ -1165,15 +1165,26 @@ remove_redundant_jumps(cfg_builder *g) {
11651165
return changes;
11661166
}
11671167

1168+
static inline bool
1169+
basicblock_has_no_lineno(basicblock *b) {
1170+
for (int i = 0; i < b->b_iused; i++) {
1171+
if (b->b_instr[i].i_loc.lineno >= 0) {
1172+
return false;
1173+
}
1174+
}
1175+
return true;
1176+
}
1177+
11681178
/* Maximum size of basic block that should be copied in optimizer */
11691179
#define MAX_COPY_SIZE 4
11701180

1171-
/* If this block ends with an unconditional jump to a small exit block, then
1181+
/* If this block ends with an unconditional jump to a small exit block or
1182+
* a block that has no line numbers (and no fallthrough), then
11721183
* remove the jump and extend this block with the target.
11731184
* Returns 1 if extended, 0 if no change, and -1 on error.
11741185
*/
11751186
static int
1176-
inline_small_exit_blocks(basicblock *bb) {
1187+
basicblock_inline_small_or_no_lineno_blocks(basicblock *bb) {
11771188
cfg_instr *last = basicblock_last_instr(bb);
11781189
if (last == NULL) {
11791190
return 0;
@@ -1182,14 +1193,46 @@ inline_small_exit_blocks(basicblock *bb) {
11821193
return 0;
11831194
}
11841195
basicblock *target = last->i_target;
1185-
if (basicblock_exits_scope(target) && target->b_iused <= MAX_COPY_SIZE) {
1196+
bool small_exit_block = (basicblock_exits_scope(target) &&
1197+
target->b_iused <= MAX_COPY_SIZE);
1198+
bool no_lineno_no_fallthrough = (basicblock_has_no_lineno(target) &&
1199+
!BB_HAS_FALLTHROUGH(target));
1200+
if (small_exit_block || no_lineno_no_fallthrough) {
1201+
assert(is_jump(last));
1202+
int removed_jump_opcode = last->i_opcode;
11861203
INSTR_SET_OP0(last, NOP);
11871204
RETURN_IF_ERROR(basicblock_append_instructions(bb, target));
1205+
if (no_lineno_no_fallthrough) {
1206+
last = basicblock_last_instr(bb);
1207+
if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode) &&
1208+
removed_jump_opcode == JUMP)
1209+
{
1210+
/* Make sure we don't lose eval breaker checks */
1211+
last->i_opcode = JUMP;
1212+
}
1213+
}
1214+
target->b_predecessors--;
11881215
return 1;
11891216
}
11901217
return 0;
11911218
}
11921219

1220+
static int
1221+
inline_small_or_no_lineno_blocks(basicblock *entryblock) {
1222+
bool changes;
1223+
do {
1224+
changes = false;
1225+
for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
1226+
int res = basicblock_inline_small_or_no_lineno_blocks(b);
1227+
RETURN_IF_ERROR(res);
1228+
if (res) {
1229+
changes = true;
1230+
}
1231+
}
1232+
} while(changes); /* every change removes a jump, ensuring convergence */
1233+
return changes;
1234+
}
1235+
11931236
// Attempt to eliminate jumps to jumps by updating inst to jump to
11941237
// target->i_target using the provided opcode. Return whether or not the
11951238
// optimization was successful.
@@ -1804,19 +1847,14 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstl
18041847
{
18051848
assert(PyDict_CheckExact(const_cache));
18061849
RETURN_IF_ERROR(check_cfg(g));
1807-
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
1808-
RETURN_IF_ERROR(inline_small_exit_blocks(b));
1809-
}
1850+
RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock));
18101851
RETURN_IF_ERROR(remove_unreachable(g->g_entryblock));
18111852
RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno));
18121853
RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts));
18131854
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
18141855
RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts));
18151856
}
18161857
RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock));
1817-
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
1818-
RETURN_IF_ERROR(inline_small_exit_blocks(b));
1819-
}
18201858
RETURN_IF_ERROR(remove_unreachable(g->g_entryblock));
18211859

18221860
int removed_nops, removed_jumps;
@@ -2333,12 +2371,7 @@ convert_pseudo_ops(cfg_builder *g)
23332371
static inline bool
23342372
is_exit_or_eval_check_without_lineno(basicblock *b) {
23352373
if (basicblock_exits_scope(b) || basicblock_has_eval_break(b)) {
2336-
for (int i = 0; i < b->b_iused; i++) {
2337-
if (b->b_instr[i].i_loc.lineno >= 0) {
2338-
return false;
2339-
}
2340-
}
2341-
return true;
2374+
return basicblock_has_no_lineno(b);
23422375
}
23432376
else {
23442377
return false;

0 commit comments

Comments
 (0)