-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
gh-106149: Simplify stack depth calculation. Replace asserts by exceptions. #107255
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
Changes from all commits
962e87c
1cea1e4
ead648f
a2fa3cb
e546717
fce0919
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -615,23 +615,28 @@ make_cfg_traversal_stack(basicblock *entryblock) { | |
return stack; | ||
} | ||
|
||
Py_LOCAL_INLINE(void) | ||
Py_LOCAL_INLINE(int) | ||
stackdepth_push(basicblock ***sp, basicblock *b, int depth) | ||
{ | ||
assert(b->b_startdepth < 0 || b->b_startdepth == depth); | ||
if (!(b->b_startdepth < 0 || b->b_startdepth == depth)) { | ||
PyErr_Format(PyExc_ValueError, "Invalid CFG, inconsistent stackdepth"); | ||
return ERROR; | ||
} | ||
if (b->b_startdepth < depth && b->b_startdepth < 100) { | ||
assert(b->b_startdepth < 0); | ||
b->b_startdepth = depth; | ||
*(*sp)++ = b; | ||
} | ||
return SUCCESS; | ||
} | ||
|
||
/* Find the flow path that needs the largest stack. We assume that | ||
* cycles in the flow graph have no net effect on the stack depth. | ||
*/ | ||
int | ||
_PyCfg_Stackdepth(basicblock *entryblock, int code_flags) | ||
_PyCfg_Stackdepth(cfg_builder *g) | ||
{ | ||
basicblock *entryblock = g->g_entryblock; | ||
for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | ||
b->b_startdepth = INT_MIN; | ||
} | ||
|
@@ -640,42 +645,54 @@ _PyCfg_Stackdepth(basicblock *entryblock, int code_flags) | |
return ERROR; | ||
} | ||
|
||
|
||
int stackdepth = -1; | ||
int maxdepth = 0; | ||
basicblock **sp = stack; | ||
if (code_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) { | ||
stackdepth_push(&sp, entryblock, 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused about this. Why is it 1 if the RETURN_GENERATOR+POP_TOP add up to 0? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it's somehow related to the fact that stack_effect() returns 0 for RETURN_GENERATOR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added some prints to the old and new versions, to see how the stackdepth calculation proceeds. So here is the output for Old:
New:
So the old code looks at those two instructions which are now added after the stackdepth calculation. And indeed stackdepth(RETURN_GENERATOR) returns 0, so the additional 1 in the initialisation compensated for that. So the old code is a messy hack. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wondered the same thing! Thanks for tracking this down. Separately, is there any reason not to fix num_pushed metadata for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that the stack effect is derived from the bytecodes.c file, from the signature of the bytecode. If we add stuff to the signature the code generator will do other stuff with it. |
||
} else { | ||
stackdepth_push(&sp, entryblock, 0); | ||
if (stackdepth_push(&sp, entryblock, 0) < 0) { | ||
goto error; | ||
} | ||
|
||
while (sp != stack) { | ||
basicblock *b = *--sp; | ||
int depth = b->b_startdepth; | ||
assert(depth >= 0); | ||
basicblock *next = b->b_next; | ||
for (int i = 0; i < b->b_iused; i++) { | ||
cfg_instr *instr = &b->b_instr[i]; | ||
int effect = PyCompile_OpcodeStackEffectWithJump(instr->i_opcode, instr->i_oparg, 0); | ||
int effect = PyCompile_OpcodeStackEffectWithJump( | ||
instr->i_opcode, instr->i_oparg, 0); | ||
if (effect == PY_INVALID_STACK_EFFECT) { | ||
PyErr_Format(PyExc_SystemError, | ||
"compiler PyCompile_OpcodeStackEffectWithJump(opcode=%d, arg=%i) failed", | ||
"Invalid stack effect for opcode=%d, arg=%i", | ||
instr->i_opcode, instr->i_oparg); | ||
return ERROR; | ||
goto error; | ||
} | ||
int new_depth = depth + effect; | ||
assert(new_depth >= 0); /* invalid code or bug in stackdepth() */ | ||
if (new_depth < 0) { | ||
PyErr_Format(PyExc_ValueError, | ||
"Invalid CFG, stack underflow"); | ||
goto error; | ||
} | ||
if (new_depth > maxdepth) { | ||
maxdepth = new_depth; | ||
} | ||
if (HAS_TARGET(instr->i_opcode)) { | ||
effect = PyCompile_OpcodeStackEffectWithJump(instr->i_opcode, instr->i_oparg, 1); | ||
assert(effect != PY_INVALID_STACK_EFFECT); | ||
effect = PyCompile_OpcodeStackEffectWithJump( | ||
instr->i_opcode, instr->i_oparg, 1); | ||
if (effect == PY_INVALID_STACK_EFFECT) { | ||
PyErr_Format(PyExc_SystemError, | ||
"Invalid stack effect for opcode=%d, arg=%i", | ||
instr->i_opcode, instr->i_oparg); | ||
goto error; | ||
} | ||
int target_depth = depth + effect; | ||
assert(target_depth >= 0); /* invalid code or bug in stackdepth() */ | ||
if (target_depth > maxdepth) { | ||
maxdepth = target_depth; | ||
} | ||
stackdepth_push(&sp, instr->i_target, target_depth); | ||
if (stackdepth_push(&sp, instr->i_target, target_depth) < 0) { | ||
goto error; | ||
} | ||
} | ||
depth = new_depth; | ||
assert(!IS_ASSEMBLER_OPCODE(instr->i_opcode)); | ||
|
@@ -689,11 +706,15 @@ _PyCfg_Stackdepth(basicblock *entryblock, int code_flags) | |
} | ||
if (next != NULL) { | ||
assert(BB_HAS_FALLTHROUGH(b)); | ||
stackdepth_push(&sp, next, depth); | ||
if (stackdepth_push(&sp, next, depth) < 0) { | ||
goto error; | ||
} | ||
} | ||
} | ||
stackdepth = maxdepth; | ||
error: | ||
PyMem_Free(stack); | ||
return maxdepth; | ||
return stackdepth; | ||
} | ||
|
||
static int | ||
|
@@ -2009,7 +2030,7 @@ mark_cold(basicblock *entryblock) { | |
|
||
|
||
static int | ||
push_cold_blocks_to_end(cfg_builder *g, int code_flags) { | ||
push_cold_blocks_to_end(cfg_builder *g) { | ||
basicblock *entryblock = g->g_entryblock; | ||
if (entryblock->b_next == NULL) { | ||
/* single basicblock, no need to reorder */ | ||
|
@@ -2251,7 +2272,7 @@ resolve_line_numbers(cfg_builder *g, int firstlineno) | |
|
||
int | ||
_PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache, | ||
int code_flags, int nlocals, int nparams, int firstlineno) | ||
int nlocals, int nparams, int firstlineno) | ||
{ | ||
assert(cfg_builder_check(g)); | ||
/** Preprocessing **/ | ||
|
@@ -2268,7 +2289,7 @@ _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache, | |
g->g_entryblock, nlocals, nparams)); | ||
insert_superinstructions(g); | ||
|
||
RETURN_IF_ERROR(push_cold_blocks_to_end(g, code_flags)); | ||
RETURN_IF_ERROR(push_cold_blocks_to_end(g)); | ||
RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); | ||
return SUCCESS; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this explanation is accurate? This function is a generator:
Here is the disassembly of it:
I think if
RETURN_CONST
didn't exist, it would be enough to say "all functions must return a value, which always must go on the stack before it is returned." But withRETURN_CONST
that's not true either. It really does seem like theStopIteration
handler is what we are relying on here.