Skip to content

gh-104635: Eliminate redundant STORE_FAST instructions in the compiler #105040

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

Closed
wants to merge 13 commits into from
Closed
8 changes: 4 additions & 4 deletions Include/internal/pycore_opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ extern "C" {
(opcode) == LOAD_FAST__LOAD_CONST || \
(opcode) == LOAD_CONST__LOAD_FAST || \
(opcode) == STORE_FAST__LOAD_FAST || \
(opcode) == STORE_FAST__STORE_FAST)
(opcode) == STORE_FAST__STORE_FAST) || \
(opcode) == POP_TOP__POP_TOP || \
(opcode) == POP_TOP__STORE_FAST


#define LOG_BITS_PER_INT 5
Expand Down
2 changes: 2 additions & 0 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ def pseudo_op(name, op, real_ops):
"SEND": [
"SEND_GEN",
],
"POP_TOP": [
"POP_TOP__POP_TOP",
"POP_TOP__STORE_FAST"
]
}
_specialized_instructions = [
opcode for family in _specializations.values() for opcode in family
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ def extended_arg_quick():
%3d 2 LOAD_CONST 1 (Ellipsis)
4 EXTENDED_ARG 1
6 UNPACK_EX 256
8 STORE_FAST 0 (_)
8 POP_TOP
10 STORE_FAST 0 (_)
12 RETURN_CONST 0 (None)
"""% (extended_arg_quick.__code__.co_firstlineno,
Expand Down
52 changes: 51 additions & 1 deletion Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,57 @@ def test_no_unsafe_static_swap(self):
('POP_TOP', 0, 4),
('RETURN_VALUE', 5)
]
self.cfg_optimization_test(insts, insts, consts=list(range(3)), nlocals=1)
expected_insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('NOP', 0, 3),
('STORE_FAST', 1, 4),
('POP_TOP', 0, 4),
('RETURN_VALUE', 5)
]
self.cfg_optimization_test(insts, expected_insts, consts=list(range(3)), nlocals=1)

def test_dead_store_elimination_in_same_lineno(self):
insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('LOAD_CONST', 2, 3),
('STORE_FAST', 1, 4),
('STORE_FAST', 1, 4),
('STORE_FAST', 1, 4),
('RETURN_VALUE', 5)
]
expected_insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('NOP', 0, 3),
('POP_TOP', 0, 4),
Comment on lines +1097 to +1098
Copy link
Member

Choose a reason for hiding this comment

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

This is a cool benefit I didn't realize we'd also get! LOAD_*/POP_TOP pairs can be eliminated entirely.

Copy link
Member

Choose a reason for hiding this comment

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

Ideally this would just become LOAD_CONST NOP NOP STORE_FAST though, right?

Copy link
Member

@carljm carljm Jun 2, 2023

Choose a reason for hiding this comment

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

Yes; I think that's an orthogonal improvement to the LOAD/POP elimination optimization, allowing it to ignore intervening NOP. Or maybe it has to do rather with making it multi-pass? Or both? I haven't checked.

Copy link
Member Author

@corona10 corona10 Jun 3, 2023

Choose a reason for hiding this comment

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

Maybe we can run the loop of optimization until there are no opcode changes.
(I didn't test yet)

('STORE_FAST', 1, 4),
('RETURN_VALUE', 5)
]
self.cfg_optimization_test(insts, expected_insts, consts=list(range(3)), nlocals=1)

def test_no_dead_store_elimination_in_different_lineno(self):
insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('LOAD_CONST', 2, 3),
('STORE_FAST', 1, 4),
('STORE_FAST', 1, 5),
('STORE_FAST', 1, 6),
('RETURN_VALUE', 5)
]
expected_insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('LOAD_CONST', 2, 3),
('STORE_FAST', 1, 4),
('STORE_FAST', 1, 5),
('STORE_FAST', 1, 6),
('RETURN_VALUE', 5)
]
self.cfg_optimization_test(insts, expected_insts, consts=list(range(3)), nlocals=1)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Eliminate redundant :opcode:`STORE_FAST` instructions in the compiler.
Patch by Dong-hee Na and Carl Meyer.
2 changes: 2 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ dummy_func(
super(STORE_FAST__LOAD_FAST) = STORE_FAST + LOAD_FAST;
super(STORE_FAST__STORE_FAST) = STORE_FAST + STORE_FAST;
super(LOAD_CONST__LOAD_FAST) = LOAD_CONST + LOAD_FAST;
super(POP_TOP__POP_TOP) = POP_TOP + POP_TOP;
super(POP_TOP__STORE_FAST) = POP_TOP + STORE_FAST;

inst(POP_TOP, (value --)) {
DECREF_INPUTS();
Expand Down
22 changes: 17 additions & 5 deletions Python/flowgraph.c
Original file line number Diff line number Diff line change
Expand Up @@ -1515,15 +1515,18 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
*/
}
break;
case STORE_FAST:
if (opcode == nextop &&
oparg == bb->b_instr[i+1].i_oparg &&
bb->b_instr[i].i_loc.lineno == bb->b_instr[i+1].i_loc.lineno) {
bb->b_instr[i].i_opcode = POP_TOP;
bb->b_instr[i].i_oparg = 0;
}
break;
case SWAP:
if (oparg == 1) {
INSTR_SET_OP0(inst, NOP);
break;
}
if (swaptimize(bb, &i) < 0) {
goto error;
}
apply_static_swaps(bb, i);
break;
case KW_NAMES:
break;
Expand All @@ -1538,6 +1541,15 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
assert (!HAS_CONST(inst->i_opcode));
}
}
for (int i = 0; i < bb->b_iused; i++) {
cfg_instr *inst = &bb->b_instr[i];
if (inst->i_opcode == SWAP) {
if (swaptimize(bb, &i) < 0) {
goto error;
}
apply_static_swaps(bb, i);
}
}
return SUCCESS;
error:
return ERROR;
Expand Down
Loading