From 733e308158ed9a8c040fee4d9c2a587ba1921538 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 14 Apr 2025 11:02:22 +0100 Subject: [PATCH] Allow 'peek' variables to be modified --- Include/internal/pycore_uop_metadata.h | 2 +- Lib/test/test_generated_cases.py | 23 ++++++-- Python/bytecodes.c | 27 ++++----- Python/executor_cases.c.h | 39 +++++-------- Python/generated_cases.c.h | 68 ++++++++-------------- Python/optimizer_cases.c.h | 6 -- Tools/cases_generator/generators_common.py | 3 - Tools/cases_generator/stack.py | 36 +++++++----- Tools/cases_generator/tier1_generator.py | 2 +- 9 files changed, 90 insertions(+), 116 deletions(-) diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 8fa50ff2c291c4..ab26543a26fa98 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -1081,7 +1081,7 @@ int _PyUop_num_popped(int opcode, int oparg) case _CALL_KW_NON_PY: return 3 + oparg; case _MAKE_CALLARGS_A_TUPLE: - return 2; + return 0; case _MAKE_FUNCTION: return 1; case _SET_FUNCTION_ATTRIBUTE: diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 302e69de285ca8..5b120f28131d51 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -1862,13 +1862,28 @@ def test_multiple_labels(self): def test_reassigning_live_inputs(self): input = """ - inst(OP, (in -- )) { + inst(OP, (in -- in)) { in = 0; - DEAD(in); } """ - with self.assertRaises(SyntaxError): - self.run_cases_test(input, "") + + output = """ + TARGET(OP) { + #if Py_TAIL_CALL_INTERP + int opcode = OP; + (void)(opcode); + #endif + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(OP); + _PyStackRef in; + in = stack_pointer[-1]; + in = 0; + stack_pointer[-1] = in; + DISPATCH(); + } + """ + self.run_cases_test(input, output) def test_reassigning_dead_inputs(self): input = """ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index e9dced654d1492..95786c91371e98 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4725,15 +4725,9 @@ dummy_func( _CALL_KW_NON_PY + _CHECK_PERIODIC; - op(_MAKE_CALLARGS_A_TUPLE, (func, unused, callargs, kwargs_in -- func, unused, tuple, kwargs_out)) { + op(_MAKE_CALLARGS_A_TUPLE, (func, unused, callargs, kwargs -- func, unused, callargs, kwargs)) { PyObject *callargs_o = PyStackRef_AsPyObjectBorrow(callargs); - if (PyTuple_CheckExact(callargs_o)) { - tuple = callargs; - kwargs_out = kwargs_in; - DEAD(kwargs_in); - DEAD(callargs); - } - else { + if (!PyTuple_CheckExact(callargs_o)) { int err = _Py_Check_ArgsIterable(tstate, PyStackRef_AsPyObjectBorrow(func), callargs_o); if (err < 0) { ERROR_NO_POP(); @@ -4742,10 +4736,9 @@ dummy_func( if (tuple_o == NULL) { ERROR_NO_POP(); } - kwargs_out = kwargs_in; - DEAD(kwargs_in); - PyStackRef_CLOSE(callargs); - tuple = PyStackRef_FromPyObjectSteal(tuple_o); + _PyStackRef temp = callargs; + callargs = PyStackRef_FromPyObjectSteal(tuple_o); + PyStackRef_CLOSE(temp); } } @@ -4965,11 +4958,11 @@ dummy_func( macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP; - pure inst(SWAP, (bottom[1], unused[oparg-2], top[1] -- - bottom[1], unused[oparg-2], top[1])) { - _PyStackRef temp = bottom[0]; - bottom[0] = top[0]; - top[0] = temp; + pure inst(SWAP, (bottom, unused[oparg-2], top -- + bottom, unused[oparg-2], top)) { + _PyStackRef temp = bottom; + bottom = top; + top = temp; assert(oparg >= 2); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 938a80fe665c0b..9bfb13e2d9773f 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -6350,20 +6350,12 @@ } case _MAKE_CALLARGS_A_TUPLE: { - _PyStackRef kwargs_in; _PyStackRef callargs; _PyStackRef func; - _PyStackRef tuple; - _PyStackRef kwargs_out; - kwargs_in = stack_pointer[-1]; callargs = stack_pointer[-2]; func = stack_pointer[-4]; PyObject *callargs_o = PyStackRef_AsPyObjectBorrow(callargs); - if (PyTuple_CheckExact(callargs_o)) { - tuple = callargs; - kwargs_out = kwargs_in; - } - else { + if (!PyTuple_CheckExact(callargs_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_Check_ArgsIterable(tstate, PyStackRef_AsPyObjectBorrow(func), callargs_o); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -6376,17 +6368,14 @@ if (tuple_o == NULL) { JUMP_TO_ERROR(); } - kwargs_out = kwargs_in; - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); + _PyStackRef temp = callargs; + callargs = PyStackRef_FromPyObjectSteal(tuple_o); + stack_pointer[-2] = callargs; _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(callargs); + PyStackRef_CLOSE(temp); stack_pointer = _PyFrame_GetStackPointer(frame); - tuple = PyStackRef_FromPyObjectSteal(tuple_o); - stack_pointer += 2; } - stack_pointer[-2] = tuple; - stack_pointer[-1] = kwargs_out; + stack_pointer[-2] = callargs; break; } @@ -6631,15 +6620,17 @@ } case _SWAP: { - _PyStackRef *top; - _PyStackRef *bottom; + _PyStackRef top; + _PyStackRef bottom; oparg = CURRENT_OPARG(); - top = &stack_pointer[-1]; - bottom = &stack_pointer[-2 - (oparg-2)]; - _PyStackRef temp = bottom[0]; - bottom[0] = top[0]; - top[0] = temp; + top = stack_pointer[-1]; + bottom = stack_pointer[-2 - (oparg-2)]; + _PyStackRef temp = bottom; + bottom = top; + top = temp; assert(oparg >= 2); + stack_pointer[-2 - (oparg-2)] = bottom; + stack_pointer[-1] = top; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 97bffce8d82767..6fe647d6197a07 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2330,9 +2330,6 @@ opcode = CALL_FUNCTION_EX; _PyStackRef func; _PyStackRef callargs; - _PyStackRef kwargs_in; - _PyStackRef tuple; - _PyStackRef kwargs_out; _PyStackRef func_st; _PyStackRef null; _PyStackRef callargs_st; @@ -2340,15 +2337,10 @@ _PyStackRef result; // _MAKE_CALLARGS_A_TUPLE { - kwargs_in = stack_pointer[-1]; callargs = stack_pointer[-2]; func = stack_pointer[-4]; PyObject *callargs_o = PyStackRef_AsPyObjectBorrow(callargs); - if (PyTuple_CheckExact(callargs_o)) { - tuple = callargs; - kwargs_out = kwargs_in; - } - else { + if (!PyTuple_CheckExact(callargs_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_Check_ArgsIterable(tstate, PyStackRef_AsPyObjectBorrow(func), callargs_o); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -2361,20 +2353,18 @@ if (tuple_o == NULL) { JUMP_TO_LABEL(error); } - kwargs_out = kwargs_in; - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); + _PyStackRef temp = callargs; + callargs = PyStackRef_FromPyObjectSteal(tuple_o); + stack_pointer[-2] = callargs; _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(callargs); + PyStackRef_CLOSE(temp); stack_pointer = _PyFrame_GetStackPointer(frame); - tuple = PyStackRef_FromPyObjectSteal(tuple_o); - stack_pointer += 2; } } // _DO_CALL_FUNCTION_EX { - kwargs_st = kwargs_out; - callargs_st = tuple; + kwargs_st = stack_pointer[-1]; + callargs_st = callargs; null = stack_pointer[-3]; func_st = func; (void)null; @@ -2390,7 +2380,6 @@ PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; stack_pointer[-2] = callargs_st; - stack_pointer[-1] = kwargs_st; _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, @@ -2456,7 +2445,6 @@ PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); assert(kwargs == NULL || PyDict_CheckExact(kwargs)); stack_pointer[-2] = callargs_st; - stack_pointer[-1] = kwargs_st; _PyFrame_SetStackPointer(frame, stack_pointer); result_o = PyObject_Call(func, callargs, kwargs); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -6289,9 +6277,6 @@ opcode = INSTRUMENTED_CALL_FUNCTION_EX; _PyStackRef func; _PyStackRef callargs; - _PyStackRef kwargs_in; - _PyStackRef tuple; - _PyStackRef kwargs_out; _PyStackRef func_st; _PyStackRef null; _PyStackRef callargs_st; @@ -6299,15 +6284,10 @@ _PyStackRef result; // _MAKE_CALLARGS_A_TUPLE { - kwargs_in = stack_pointer[-1]; callargs = stack_pointer[-2]; func = stack_pointer[-4]; PyObject *callargs_o = PyStackRef_AsPyObjectBorrow(callargs); - if (PyTuple_CheckExact(callargs_o)) { - tuple = callargs; - kwargs_out = kwargs_in; - } - else { + if (!PyTuple_CheckExact(callargs_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_Check_ArgsIterable(tstate, PyStackRef_AsPyObjectBorrow(func), callargs_o); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -6320,20 +6300,18 @@ if (tuple_o == NULL) { JUMP_TO_LABEL(error); } - kwargs_out = kwargs_in; - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); + _PyStackRef temp = callargs; + callargs = PyStackRef_FromPyObjectSteal(tuple_o); + stack_pointer[-2] = callargs; _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(callargs); + PyStackRef_CLOSE(temp); stack_pointer = _PyFrame_GetStackPointer(frame); - tuple = PyStackRef_FromPyObjectSteal(tuple_o); - stack_pointer += 2; } } // _DO_CALL_FUNCTION_EX { - kwargs_st = kwargs_out; - callargs_st = tuple; + kwargs_st = stack_pointer[-1]; + callargs_st = callargs; null = stack_pointer[-3]; func_st = func; (void)null; @@ -6349,7 +6327,6 @@ PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; stack_pointer[-2] = callargs_st; - stack_pointer[-1] = kwargs_st; _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, @@ -6415,7 +6392,6 @@ PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); assert(kwargs == NULL || PyDict_CheckExact(kwargs)); stack_pointer[-2] = callargs_st; - stack_pointer[-1] = kwargs_st; _PyFrame_SetStackPointer(frame, stack_pointer); result_o = PyObject_Call(func, callargs, kwargs); stack_pointer = _PyFrame_GetStackPointer(frame); @@ -11358,14 +11334,16 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(SWAP); - _PyStackRef *bottom; - _PyStackRef *top; - top = &stack_pointer[-1]; - bottom = &stack_pointer[-2 - (oparg-2)]; - _PyStackRef temp = bottom[0]; - bottom[0] = top[0]; - top[0] = temp; + _PyStackRef bottom; + _PyStackRef top; + top = stack_pointer[-1]; + bottom = stack_pointer[-2 - (oparg-2)]; + _PyStackRef temp = bottom; + bottom = top; + top = temp; assert(oparg >= 2); + stack_pointer[-2 - (oparg-2)] = bottom; + stack_pointer[-1] = top; DISPATCH(); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 0c617137a8893d..6a20cef906242b 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2047,12 +2047,6 @@ } case _MAKE_CALLARGS_A_TUPLE: { - JitOptSymbol *tuple; - JitOptSymbol *kwargs_out; - tuple = sym_new_not_null(ctx); - kwargs_out = sym_new_not_null(ctx); - stack_pointer[-2] = tuple; - stack_pointer[-1] = kwargs_out; break; } diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index ca6104705fb3f2..9ba0767cba35a0 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -500,9 +500,6 @@ def emit_SimpleStmt( if tkn in local_stores: for var in storage.inputs: if var.name == tkn.text: - if var.in_local or var.in_memory(): - msg = f"Cannot assign to already defined input variable '{tkn.text}'" - raise analysis_error(msg, tkn) var.in_local = True var.memory_offset = None break diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 14d06948ba1602..ff1e2ea74dbab8 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -283,7 +283,7 @@ def clear(self, out: CWriter) -> None: self.base_offset = self.logical_sp def push(self, var: Local) -> None: - assert(var not in self.variables) + assert(var not in self.variables), var self.variables.append(var) self.logical_sp = self.logical_sp.push(var.item) @@ -325,6 +325,7 @@ def save_variables(self, out: CWriter) -> None: var_offset = var_offset.push(var.item) def flush(self, out: CWriter) -> None: + self._print(out) self.save_variables(out) self._save_physical_sp(out) out.start_line() @@ -432,12 +433,14 @@ class Storage: stack: Stack inputs: list[Local] outputs: list[Local] + peeks: int check_liveness: bool spilled: int = 0 @staticmethod def needs_defining(var: Local) -> bool: return ( + not var.item.peek and not var.in_local and not var.is_array() and var.name != "unused" @@ -454,7 +457,7 @@ def is_live(var: Local) -> bool: ) def clear_inputs(self, reason:str) -> None: - while self.inputs: + while len(self.inputs) > self.peeks: tos = self.inputs.pop() if self.is_live(tos) and self.check_liveness: raise StackError( @@ -464,14 +467,14 @@ def clear_inputs(self, reason:str) -> None: def clear_dead_inputs(self) -> None: live = "" - while self.inputs: + while len(self.inputs) > self.peeks: tos = self.inputs[-1] if self.is_live(tos): live = tos.name break self.inputs.pop() self.stack.drop(tos.item, self.check_liveness) - for var in self.inputs: + for var in self.inputs[self.peeks:]: if not self.is_live(var): raise StackError( f"Input '{var.name}' is not live, but '{live}' is" @@ -493,8 +496,8 @@ def _push_defined_outputs(self) -> None: f"Expected '{undefined}' to be defined before '{out.name}'" else: undefined = out.name - while self.outputs and not self.needs_defining(self.outputs[0]): - out = self.outputs.pop(0) + while len(self.outputs) > self.peeks and not self.needs_defining(self.outputs[0]): + out = self.outputs.pop(self.peeks) self.stack.push(out) def locals_cached(self) -> bool: @@ -541,12 +544,9 @@ def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = True) - local = stack.pop(input, out) if input.peek: peeks.append(local) - else: - inputs.append(local) + inputs.append(local) inputs.reverse() peeks.reverse() - for peek in peeks: - stack.push(peek) offset = stack.logical_sp - stack.physical_sp for ouput in uop.stack.outputs: if ouput.is_array() and ouput.used and not ouput.peek: @@ -555,8 +555,8 @@ def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = True) - offset = offset.push(ouput) for var in inputs: stack.push(var) - outputs = [ Local.undefined(var) for var in uop.stack.outputs if not var.peek ] - return Storage(stack, inputs, outputs, check_liveness) + outputs = peeks + [ Local.undefined(var) for var in uop.stack.outputs if not var.peek ] + return Storage(stack, inputs, outputs, len(peeks), check_liveness) @staticmethod def copy_list(arg: list[Local]) -> list[Local]: @@ -568,7 +568,7 @@ def copy(self) -> "Storage": inputs = [ variables[var.name] for var in self.inputs] assert [v.name for v in inputs] == [v.name for v in self.inputs], (inputs, self.inputs) return Storage( - new_stack, inputs, self.copy_list(self.outputs), + new_stack, inputs, self.copy_list(self.outputs), self.peeks, self.check_liveness, self.spilled ) @@ -602,6 +602,8 @@ def merge(self, other: "Storage", out: CWriter) -> None: other.clear_dead_inputs() if len(self.inputs) != len(other.inputs) and self.check_liveness: diff = self.inputs[-1] if len(self.inputs) > len(other.inputs) else other.inputs[-1] + self._print(out) + other._print(out) raise StackError(f"Unmergeable inputs. Differing state of '{diff.name}'") for var, other_var in zip(self.inputs, other.inputs): if var.in_local != other_var.in_local: @@ -624,11 +626,11 @@ def push_outputs(self) -> None: if self.spilled: raise StackError(f"Unbalanced stack spills") self.clear_inputs("at the end of the micro-op") - if self.inputs and self.check_liveness: + if len(self.inputs) > self.peeks and self.check_liveness: raise StackError(f"Input variable '{self.inputs[-1].name}' is still live") self._push_defined_outputs() if self.outputs: - for out in self.outputs: + for out in self.outputs[self.peeks:]: if self.needs_defining(out): raise StackError(f"Output variable '{self.outputs[0].name}' is not defined") self.stack.push(out) @@ -641,6 +643,10 @@ def as_comment(self) -> str: outputs = ", ".join([var.compact_str() for var in self.outputs]) return f"{stack_comment[:-2]}{next_line}inputs: {inputs} outputs: {outputs}*/" + def _print(self, out: CWriter) -> None: + if PRINT_STACKS: + out.emit(self.as_comment() + "\n") + def close_inputs(self, out: CWriter) -> None: tmp_defined = False diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 5a49c239ed1aa7..32dc346d5e891a 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -204,7 +204,7 @@ def generate_tier1_labels( # Emit tail-callable labels as function defintions for name, label in analysis.labels.items(): emitter.emit(f"LABEL({name})\n") - storage = Storage(Stack(), [], [], False) + storage = Storage(Stack(), [], [], 0, False) if label.spilled: storage.spilled = 1 emitter.emit_tokens(label, storage, None)