Skip to content

Commit

Permalink
gh-104615: don't make unsafe swaps in apply_static_swaps (#104620)
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm authored May 18, 2023
1 parent dcdc90d commit 0589c6a
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 20 deletions.
3 changes: 2 additions & 1 deletion Include/internal/pycore_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ PyAPI_FUNC(PyObject*) _PyCompile_CodeGen(

PyAPI_FUNC(PyObject*) _PyCompile_OptimizeCfg(
PyObject *instructions,
PyObject *consts);
PyObject *consts,
int nlocals);

PyAPI_FUNC(PyCodeObject*)
_PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename,
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

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

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(newline)
STRUCT_FOR_ID(newlines)
STRUCT_FOR_ID(next)
STRUCT_FOR_ID(nlocals)
STRUCT_FOR_ID(node_depth)
STRUCT_FOR_ID(node_offset)
STRUCT_FOR_ID(ns)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

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

3 changes: 3 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

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

4 changes: 2 additions & 2 deletions Lib/test/support/bytecode_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ def generate_code(self, ast):

class CfgOptimizationTestCase(CompilationStepTestCase):

def get_optimized(self, insts, consts):
def get_optimized(self, insts, consts, nlocals=0):
insts = self.normalize_insts(insts)
insts = self.complete_insts_info(insts)
insts = optimize_cfg(insts, consts)
insts = optimize_cfg(insts, consts, nlocals)
return insts, consts

class AssemblerTestCase(CompilationStepTestCase):
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,24 @@ def foo(param, lambda_exp):
""")
compile(code, "<test>", "exec")

def test_apply_static_swaps(self):
def f(x, y):
a, a = x, y
return a
self.assertEqual(f("x", "y"), "y")

def test_apply_static_swaps_2(self):
def f(x, y, z):
a, b, a = x, y, z
return a
self.assertEqual(f("x", "y", "z"), "z")

def test_apply_static_swaps_3(self):
def f(x, y, z):
a, a, b = x, y, z
return a
self.assertEqual(f("x", "y", "z"), "y")


@requires_debug_ranges()
class TestSourcePositions(unittest.TestCase):
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ def test_nested_listcomp_in_lambda(self):
"""
self._check_in_scopes(code, {"z": 1, "out": [(3, 2, 1)]})

def test_assign_to_comp_iter_var_in_outer_function(self):
code = """
a = [1 for a in [0]]
"""
self._check_in_scopes(code, {"a": [1]}, scopes=["function"])


__test__ = {'doctests' : doctests}

Expand Down
20 changes: 17 additions & 3 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,13 +971,14 @@ def trace(frame, event, arg):
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")


class DirectiCfgOptimizerTests(CfgOptimizationTestCase):
class DirectCfgOptimizerTests(CfgOptimizationTestCase):

def cfg_optimization_test(self, insts, expected_insts,
consts=None, expected_consts=None):
consts=None, expected_consts=None,
nlocals=0):
if expected_consts is None:
expected_consts = consts
opt_insts, opt_consts = self.get_optimized(insts, consts)
opt_insts, opt_consts = self.get_optimized(insts, consts, nlocals)
expected_insts = self.normalize_insts(expected_insts)
self.assertInstructionsMatch(opt_insts, expected_insts)
self.assertEqual(opt_consts, expected_consts)
Expand Down Expand Up @@ -1058,6 +1059,19 @@ def test_conditional_jump_backward_const_condition(self):
]
self.cfg_optimization_test(insts, expected_insts, consts=list(range(5)))

def test_no_unsafe_static_swap(self):
# We can't change order of two stores to the same location
insts = [
('LOAD_CONST', 0, 1),
('LOAD_CONST', 1, 2),
('LOAD_CONST', 2, 3),
('SWAP', 3, 4),
('STORE_FAST', 1, 4),
('STORE_FAST', 1, 4),
('POP_TOP', 0, 4),
('RETURN_VALUE', 5)
]
self.cfg_optimization_test(insts, 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 @@
Fix wrong ordering of assignments in code like ``a, a = x, y``. Contributed by
Carl Meyer.
7 changes: 4 additions & 3 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -616,16 +616,17 @@ _testinternalcapi.optimize_cfg -> object
instructions: object
consts: object
nlocals: int
Apply compiler optimizations to an instruction list.
[clinic start generated code]*/

static PyObject *
_testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions,
PyObject *consts)
/*[clinic end generated code: output=5412aeafca683c8b input=7e8a3de86ebdd0f9]*/
PyObject *consts, int nlocals)
/*[clinic end generated code: output=57c53c3a3dfd1df0 input=6a96d1926d58d7e5]*/
{
return _PyCompile_OptimizeCfg(instructions, consts);
return _PyCompile_OptimizeCfg(instructions, consts, nlocals);
}

static int
Expand Down
23 changes: 14 additions & 9 deletions Modules/clinic/_testinternalcapi.c.h

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

4 changes: 2 additions & 2 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -7996,7 +7996,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
}

PyObject *
_PyCompile_OptimizeCfg(PyObject *instructions, PyObject *consts)
_PyCompile_OptimizeCfg(PyObject *instructions, PyObject *consts, int nlocals)
{
PyObject *res = NULL;
PyObject *const_cache = PyDict_New();
Expand All @@ -8008,7 +8008,7 @@ _PyCompile_OptimizeCfg(PyObject *instructions, PyObject *consts)
if (instructions_to_cfg(instructions, &g) < 0) {
goto error;
}
int code_flags = 0, nlocals = 0, nparams = 0, firstlineno = 1;
int code_flags = 0, nparams = 0, firstlineno = 1;
if (_PyCfg_OptimizeCodeUnit(&g, consts, const_cache, code_flags, nlocals,
nparams, firstlineno) < 0) {
goto error;
Expand Down
22 changes: 22 additions & 0 deletions Python/flowgraph.c
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,11 @@ swaptimize(basicblock *block, int *ix)
(opcode) == STORE_FAST_MAYBE_NULL || \
(opcode) == POP_TOP)

#define STORES_TO(instr) \
(((instr).i_opcode == STORE_FAST || \
(instr).i_opcode == STORE_FAST_MAYBE_NULL) \
? (instr).i_oparg : -1)

static int
next_swappable_instruction(basicblock *block, int i, int lineno)
{
Expand Down Expand Up @@ -1344,6 +1349,23 @@ apply_static_swaps(basicblock *block, int i)
return;
}
}
// The reordering is not safe if the two instructions to be swapped
// store to the same location, or if any intervening instruction stores
// to the same location as either of them.
int store_j = STORES_TO(block->b_instr[j]);
int store_k = STORES_TO(block->b_instr[k]);
if (store_j >= 0 || store_k >= 0) {
if (store_j == store_k) {
return;
}
for (int idx = j + 1; idx < k; idx++) {
int store_idx = STORES_TO(block->b_instr[idx]);
if (store_idx >= 0 && (store_idx == store_j || store_idx == store_k)) {
return;
}
}
}

// Success!
INSTR_SET_OP0(swap, NOP);
cfg_instr temp = block->b_instr[j];
Expand Down

0 comments on commit 0589c6a

Please sign in to comment.