Skip to content
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

GH-128682: Spill the stack pointer in labels, as well as instructions #129618

Merged
merged 7 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member Author

@markshannon markshannon Feb 3, 2025

Choose a reason for hiding this comment

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

The stack_pointer parameter was redundant. In fact, since _PyOptimizer_Optimize escapes, the code generator objects to the use of stack_pointer as an argument when called from the interpreter.


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
Loading