Skip to content

Commit ab4a691

Browse files
committed
Issue #21523: Fix over-pessimistic computation of the stack effect of some opcodes in the compiler.
This also fixes a quadratic compilation time issue noticeable when compiling code with a large number of "and" and "or" operators.
1 parent cc79837 commit ab4a691

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

Lib/test/test_compile.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import unittest
23
import sys
34
import _ast
@@ -519,8 +520,46 @@ def test_compile_ast(self):
519520
self.assertRaises(TypeError, compile, ast, '<ast>', 'exec')
520521

521522

523+
class TestStackSize(unittest.TestCase):
524+
# These tests check that the computed stack size for a code object
525+
# stays within reasonable bounds (see issue #21523 for an example
526+
# dysfunction).
527+
N = 100
528+
529+
def check_stack_size(self, code):
530+
# To assert that the alleged stack size is not O(N), we
531+
# check that it is smaller than log(N).
532+
if isinstance(code, str):
533+
code = compile(code, "<foo>", "single")
534+
max_size = math.ceil(math.log(len(code.co_code)))
535+
self.assertLessEqual(code.co_stacksize, max_size)
536+
537+
def test_and(self):
538+
self.check_stack_size("x and " * self.N + "x")
539+
540+
def test_or(self):
541+
self.check_stack_size("x or " * self.N + "x")
542+
543+
def test_and_or(self):
544+
self.check_stack_size("x and x or " * self.N + "x")
545+
546+
def test_chained_comparison(self):
547+
self.check_stack_size("x < " * self.N + "x")
548+
549+
def test_if_else(self):
550+
self.check_stack_size("x if x else " * self.N + "x")
551+
552+
def test_binop(self):
553+
self.check_stack_size("x + " * self.N + "x")
554+
555+
def test_func_and(self):
556+
code = "def f(x):\n"
557+
code += " x and x\n" * self.N
558+
self.check_stack_size(code)
559+
560+
522561
def test_main():
523-
test_support.run_unittest(TestSpecifics)
562+
test_support.run_unittest(__name__)
524563

525564
if __name__ == "__main__":
526-
test_main()
565+
unittest.main()

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 2.7.8?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #21523: Fix over-pessimistic computation of the stack effect of
14+
some opcodes in the compiler. This also fixes a quadratic compilation
15+
time issue noticeable when compiling code with a large number of "and"
16+
and "or" operators.
17+
1318
Library
1419
-------
1520

Python/compile.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3483,12 +3483,16 @@ stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth)
34833483
target_depth = depth;
34843484
if (instr->i_opcode == FOR_ITER) {
34853485
target_depth = depth-2;
3486-
} else if (instr->i_opcode == SETUP_FINALLY ||
3487-
instr->i_opcode == SETUP_EXCEPT) {
3486+
}
3487+
else if (instr->i_opcode == SETUP_FINALLY ||
3488+
instr->i_opcode == SETUP_EXCEPT) {
34883489
target_depth = depth+3;
34893490
if (target_depth > maxdepth)
34903491
maxdepth = target_depth;
34913492
}
3493+
else if (instr->i_opcode == JUMP_IF_TRUE_OR_POP ||
3494+
instr->i_opcode == JUMP_IF_FALSE_OR_POP)
3495+
depth = depth - 1;
34923496
maxdepth = stackdepth_walk(c, instr->i_target,
34933497
target_depth, maxdepth);
34943498
if (instr->i_opcode == JUMP_ABSOLUTE ||

0 commit comments

Comments
 (0)