Skip to content

Commit

Permalink
GH-128682: Spill the stack pointer in labels, as well as instructions (
Browse files Browse the repository at this point in the history
  • Loading branch information
markshannon authored Feb 4, 2025
1 parent d3c54f3 commit 2effea4
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 102 deletions.
2 changes: 1 addition & 1 deletion Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ extern int _Py_uop_frame_pop(JitOptContext *ctx);

PyAPI_FUNC(PyObject *) _Py_uop_symbols_test(PyObject *self, PyObject *ignored);

PyAPI_FUNC(int) _PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, _PyStackRef *stack_pointer, _PyExecutorObject **exec_ptr, int chain_depth);
PyAPI_FUNC(int) _PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, _PyExecutorObject **exec_ptr, int chain_depth);

static inline int is_terminator(const _PyUOpInstruction *uop)
{
Expand Down
90 changes: 85 additions & 5 deletions Lib/test/test_generated_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def run_cases_test(self, input: str, expected: str):
instructions, labels_with_prelude_and_postlude = rest.split(tier1_generator.INSTRUCTION_END_MARKER)
_, labels_with_postlude = labels_with_prelude_and_postlude.split(tier1_generator.LABEL_START_MARKER)
labels, _ = labels_with_postlude.split(tier1_generator.LABEL_END_MARKER)
actual = instructions + labels
actual = instructions.strip() + "\n\n " + labels.strip()
# if actual.strip() != expected.strip():
# print("Actual:")
# print(actual)
Expand Down Expand Up @@ -652,6 +652,9 @@ def test_cache_effect(self):

def test_suppress_dispatch(self):
input = """
label(somewhere) {
}
inst(OP, (--)) {
goto somewhere;
}
Expand All @@ -663,6 +666,11 @@ def test_suppress_dispatch(self):
INSTRUCTION_STATS(OP);
goto somewhere;
}
somewhere:
{
}
"""
self.run_cases_test(input, output)

Expand Down Expand Up @@ -1768,9 +1776,15 @@ def test_kill_in_wrong_order(self):

def test_complex_label(self):
input = """
label(other_label) {
}
label(other_label2) {
}
label(my_label) {
// Comment
do_thing()
do_thing();
if (complex) {
goto other_label;
}
Expand All @@ -1779,10 +1793,22 @@ def test_complex_label(self):
"""

output = """
other_label:
{
}
other_label2:
{
}
my_label:
{
// Comment
do_thing()
_PyFrame_SetStackPointer(frame, stack_pointer);
do_thing();
stack_pointer = _PyFrame_GetStackPointer(frame);
if (complex) {
goto other_label;
}
Expand All @@ -1791,6 +1817,60 @@ def test_complex_label(self):
"""
self.run_cases_test(input, output)

def test_spilled_label(self):
input = """
spilled label(one) {
RELOAD_STACK();
goto two;
}
label(two) {
SAVE_STACK();
goto one;
}
"""

output = """
one:
{
/* STACK SPILLED */
stack_pointer = _PyFrame_GetStackPointer(frame);
goto two;
}
two:
{
_PyFrame_SetStackPointer(frame, stack_pointer);
goto one;
}
"""
self.run_cases_test(input, output)


def test_incorrect_spills(self):
input1 = """
spilled label(one) {
goto two;
}
label(two) {
}
"""

input2 = """
spilled label(one) {
}
label(two) {
goto one;
}
"""
with self.assertRaisesRegex(SyntaxError, ".*reload.*"):
self.run_cases_test(input1, "")
with self.assertRaisesRegex(SyntaxError, ".*spill.*"):
self.run_cases_test(input2, "")


def test_multiple_labels(self):
input = """
label(my_label_1) {
Expand All @@ -1802,7 +1882,7 @@ def test_multiple_labels(self):
label(my_label_2) {
// Comment
do_thing2();
goto my_label_3;
goto my_label_1;
}
"""

Expand All @@ -1818,7 +1898,7 @@ def test_multiple_labels(self):
{
// Comment
do_thing2();
goto my_label_3;
goto my_label_1;
}
"""

Expand Down
45 changes: 24 additions & 21 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2808,7 +2808,7 @@ dummy_func(
start--;
}
_PyExecutorObject *executor;
int optimized = _PyOptimizer_Optimize(frame, start, stack_pointer, &executor, 0);
int optimized = _PyOptimizer_Optimize(frame, start, &executor, 0);
if (optimized <= 0) {
this_instr[1].counter = restart_backoff_counter(counter);
ERROR_IF(optimized < 0, error);
Expand Down Expand Up @@ -5033,7 +5033,7 @@ dummy_func(
}
else {
int chain_depth = current_executor->vm_data.chain_depth + 1;
int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor, chain_depth);
int optimized = _PyOptimizer_Optimize(frame, target, &executor, chain_depth);
if (optimized <= 0) {
exit->temperature = restart_backoff_counter(temperature);
if (optimized < 0) {
Expand Down Expand Up @@ -5134,7 +5134,7 @@ dummy_func(
exit->temperature = advance_backoff_counter(exit->temperature);
GOTO_TIER_ONE(target);
}
int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor, 0);
int optimized = _PyOptimizer_Optimize(frame, target, &executor, 0);
if (optimized <= 0) {
exit->temperature = restart_backoff_counter(exit->temperature);
if (optimized < 0) {
Expand Down Expand Up @@ -5242,48 +5242,49 @@ dummy_func(
goto exception_unwind;
}

label(exception_unwind) {
spilled label(exception_unwind) {
/* We can't use frame->instr_ptr here, as RERAISE may have set it */
int offset = INSTR_OFFSET()-1;
int level, handler, lasti;
if (get_exception_handler(_PyFrame_GetCode(frame), offset, &level, &handler, &lasti) == 0) {
int handled = get_exception_handler(_PyFrame_GetCode(frame), offset, &level, &handler, &lasti);
if (handled == 0) {
// No handlers, so exit.
assert(_PyErr_Occurred(tstate));

/* Pop remaining stack entries. */
_PyStackRef *stackbase = _PyFrame_Stackbase(frame);
while (stack_pointer > stackbase) {
PyStackRef_XCLOSE(POP());
while (frame->stackpointer > stackbase) {
_PyStackRef ref = _PyFrame_StackPop(frame);
PyStackRef_XCLOSE(ref);
}
assert(STACK_LEVEL() == 0);
_PyFrame_SetStackPointer(frame, stack_pointer);
monitor_unwind(tstate, frame, next_instr-1);
goto exit_unwind;
}

assert(STACK_LEVEL() >= level);
_PyStackRef *new_top = _PyFrame_Stackbase(frame) + level;
while (stack_pointer > new_top) {
PyStackRef_XCLOSE(POP());
assert(frame->stackpointer >= new_top);
while (frame->stackpointer > new_top) {
_PyStackRef ref = _PyFrame_StackPop(frame);
PyStackRef_XCLOSE(ref);
}
if (lasti) {
int frame_lasti = _PyInterpreterFrame_LASTI(frame);
PyObject *lasti = PyLong_FromLong(frame_lasti);
if (lasti == NULL) {
goto exception_unwind;
}
PUSH(PyStackRef_FromPyObjectSteal(lasti));
_PyFrame_StackPush(frame, PyStackRef_FromPyObjectSteal(lasti));
}

/* Make the raw exception data
available to the handler,
so a program can emulate the
Python main loop. */
PyObject *exc = _PyErr_GetRaisedException(tstate);
PUSH(PyStackRef_FromPyObjectSteal(exc));
_PyFrame_StackPush(frame, PyStackRef_FromPyObjectSteal(exc));
next_instr = _PyFrame_GetBytecode(frame) + handler;

if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
int err = monitor_handled(tstate, frame, next_instr, exc);
if (err < 0) {
goto exception_unwind;
}
/* Resume normal execution */
Expand All @@ -5292,10 +5293,11 @@ dummy_func(
lltrace_resume_frame(frame);
}
#endif
RELOAD_STACK();
DISPATCH();
}

label(exit_unwind) {
spilled label(exit_unwind) {
assert(_PyErr_Occurred(tstate));
_Py_LeaveRecursiveCallPy(tstate);
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
Expand All @@ -5311,16 +5313,16 @@ dummy_func(
return NULL;
}
next_instr = frame->instr_ptr;
stack_pointer = _PyFrame_GetStackPointer(frame);
RELOAD_STACK();
goto error;
}

label(start_frame) {
if (_Py_EnterRecursivePy(tstate)) {
spilled label(start_frame) {
int too_deep = _Py_EnterRecursivePy(tstate);
if (too_deep) {
goto exit_unwind;
}
next_instr = frame->instr_ptr;
stack_pointer = _PyFrame_GetStackPointer(frame);

#ifdef LLTRACE
{
Expand All @@ -5339,6 +5341,7 @@ dummy_func(
assert(!_PyErr_Occurred(tstate));
#endif

RELOAD_STACK();
DISPATCH();
}

Expand Down
4 changes: 2 additions & 2 deletions Python/executor_cases.c.h

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

Loading

0 comments on commit 2effea4

Please sign in to comment.