From 75bda2803addd6513c2187ed754ff73adbd1ff77 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 7 Aug 2024 16:13:24 +0100 Subject: [PATCH 01/14] Add copy and == support to Stack class --- Tools/cases_generator/stack.py | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index d2d598a120892d..d5f6a18baa8116 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -75,6 +75,19 @@ def condition(self) -> str | None: def is_array(self) -> bool: return self.item.is_array() + def copy(self) -> Local: + return Local(self.item, self.cached, self.in_memory, self.defined) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Local): + return NotImplemented + return ( + self.item is other.item and + self.cached is other.cached and + self.in_memory is other.in_memory and + self.defined is other.defined + ) + @dataclass class StackOffset: "The stack offset of the virtual base of the stack from the physical stack pointer" @@ -159,12 +172,17 @@ def clear(self) -> None: self.popped = [] self.pushed = [] + def __eq__(self, other: object) -> bool: + if not isinstance(other, StackOffset): + return NotImplemented + return self.to_c() == other.to_c() class StackError(Exception): pass class Stack: + def __init__(self) -> None: self.top_offset = StackOffset.empty() self.base_offset = StackOffset.empty() @@ -296,6 +314,24 @@ def peek_offset(self) -> str: def as_comment(self) -> str: return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" + def copy(self) -> Stack: + other = Stack() + other.top_offset = self.top_offset.copy() + other.base_offset = self.base_offset.copy() + other.variables = [ var.copy() for var in self.variables ] + other.defined = set(self.defined) + return other + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Stack): + return NotImplemented + return ( + self.top_offset == other.top_offset and + self.base_offset == other.base_offset and + self.variables == other.variables and + self.defined == other.defined + ) + def get_stack_effect(inst: Instruction | PseudoInstruction) -> Stack: stack = Stack() From d331371fa404a1a9989e22d3cf7edad87a0c1570 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 8 Aug 2024 11:00:16 +0100 Subject: [PATCH 02/14] blacken stack.py --- Tools/cases_generator/stack.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 7ad043122dae4b..aebc60db0a9ded 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -75,17 +75,16 @@ def condition(self) -> str | None: def is_array(self) -> bool: return self.item.is_array() - return Local(self.item, self.cached, self.in_memory, self.defined) def __eq__(self, other: object) -> bool: if not isinstance(other, Local): return NotImplemented return ( - self.item is other.item and - self.cached is other.cached and - self.in_memory is other.in_memory and - self.defined is other.defined + self.item is other.item + and self.cached is other.cached + and self.in_memory is other.in_memory + and self.defined is other.defined ) @@ -178,12 +177,12 @@ def __eq__(self, other: object) -> bool: return NotImplemented return self.to_c() == other.to_c() + class StackError(Exception): pass class Stack: - def __init__(self) -> None: self.top_offset = StackOffset.empty() self.base_offset = StackOffset.empty() @@ -366,7 +365,7 @@ def copy(self) -> Stack: other = Stack() other.top_offset = self.top_offset.copy() other.base_offset = self.base_offset.copy() - other.variables = [ var.copy() for var in self.variables ] + other.variables = [var.copy() for var in self.variables] other.defined = set(self.defined) return other @@ -374,10 +373,10 @@ def __eq__(self, other: object) -> bool: if not isinstance(other, Stack): return NotImplemented return ( - self.top_offset == other.top_offset and - self.base_offset == other.base_offset and - self.variables == other.variables and - self.defined == other.defined + self.top_offset == other.top_offset + and self.base_offset == other.base_offset + and self.variables == other.variables + and self.defined == other.defined ) From 4673d8a929988bf0f867a2e37b2a4db91fac072c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 8 Aug 2024 16:53:26 +0100 Subject: [PATCH 03/14] Cases generator: Track reachability and divergent stacks in if statements. --- Python/bytecodes.c | 18 +- Python/executor_cases.c.h | 10 +- Python/generated_cases.c.h | 142 +++++++------- Tools/cases_generator/analyzer.py | 9 - Tools/cases_generator/generators_common.py | 188 +++++++++++++++---- Tools/cases_generator/optimizer_generator.py | 3 +- Tools/cases_generator/stack.py | 29 +-- Tools/cases_generator/tier1_generator.py | 3 +- Tools/cases_generator/tier2_generator.py | 48 +++-- 9 files changed, 302 insertions(+), 148 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index b68f9327d898c2..fbaa894a30ac51 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -961,7 +961,9 @@ dummy_func( int err = _Py_call_instrumentation_arg( tstate, PY_MONITORING_EVENT_PY_RETURN, frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); - if (err) ERROR_NO_POP(); + if (err) { + ERROR_NO_POP(); + } } macro(INSTRUMENTED_RETURN_VALUE) = @@ -1154,7 +1156,9 @@ dummy_func( tstate, PY_MONITORING_EVENT_PY_YIELD, frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); LOAD_SP(); - if (err) ERROR_NO_POP(); + if (err) { + ERROR_NO_POP(); + } if (frame->instr_ptr != this_instr) { next_instr = frame->instr_ptr; DISPATCH(); @@ -1262,10 +1266,12 @@ dummy_func( DECREF_INPUTS(); ERROR_IF(true, error); } - if (PyDict_CheckExact(ns)) + if (PyDict_CheckExact(ns)) { err = PyDict_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); - else + } + else { err = PyObject_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + } DECREF_INPUTS(); ERROR_IF(err, error); } @@ -4134,7 +4140,9 @@ dummy_func( int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, frame, this_instr, func, arg); - if (err) ERROR_NO_POP(); + if (err) { + ERROR_NO_POP(); + } result = PyStackRef_FromPyObjectSteal(PyObject_Call(func, callargs, kwargs)); if (!PyFunction_Check(func) && !PyMethod_Check(func)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index f2741286d60197..893f86684ddd12 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1404,10 +1404,12 @@ PyStackRef_CLOSE(v); if (true) JUMP_TO_ERROR(); } - if (PyDict_CheckExact(ns)) - err = PyDict_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); - else - err = PyObject_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + if (PyDict_CheckExact(ns)) { + err = PyDict_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + } + else { + err = PyObject_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + } PyStackRef_CLOSE(v); if (err) JUMP_TO_ERROR(); stack_pointer += -1; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index d0aa0f9fe83182..929ecb8f6193e7 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -963,11 +963,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1289,11 +1289,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1358,11 +1358,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1426,11 +1426,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1480,11 +1480,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1529,7 +1529,9 @@ int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, frame, this_instr, func, arg); - if (err) goto error; + if (err) { + goto error; + } result = PyStackRef_FromPyObjectSteal(PyObject_Call(func, callargs, kwargs)); if (!PyFunction_Check(func) && !PyMethod_Check(func)) { if (PyStackRef_IsNull(result)) { @@ -1578,10 +1580,12 @@ assert(WITHIN_STACK_BOUNDS()); goto error; } - stack_pointer[-3 - (oparg & 1)] = result; - stack_pointer += -2 - (oparg & 1); + stack_pointer += -3 - (oparg & 1); assert(WITHIN_STACK_BOUNDS()); CHECK_EVAL_BREAKER(); + stack_pointer[0] = result; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); DISPATCH(); } @@ -1775,10 +1779,12 @@ goto error; } res = PyStackRef_FromPyObjectSteal(res_o); - stack_pointer[-3 - oparg] = res; - stack_pointer += -2 - oparg; + stack_pointer += -3 - oparg; assert(WITHIN_STACK_BOUNDS()); CHECK_EVAL_BREAKER(); + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); DISPATCH(); } @@ -1926,11 +1932,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1997,11 +2003,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -2055,11 +2061,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -2116,11 +2122,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -2189,11 +2195,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -2383,11 +2389,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-3] = res; + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-3] = res; - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -2419,11 +2425,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-3] = res; + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-3] = res; - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -3751,11 +3757,11 @@ } // _CHECK_PERIODIC { + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -4080,7 +4086,9 @@ int err = _Py_call_instrumentation_arg( tstate, PY_MONITORING_EVENT_PY_RETURN, frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); - if (err) goto error; + if (err) { + goto error; + } } // _RETURN_VALUE retval = val; @@ -4120,7 +4128,9 @@ int err = _Py_call_instrumentation_arg( tstate, PY_MONITORING_EVENT_PY_RETURN, frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); - if (err) goto error; + if (err) { + goto error; + } } // _RETURN_VALUE retval = val; @@ -4164,7 +4174,9 @@ tstate, PY_MONITORING_EVENT_PY_YIELD, frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); LOAD_SP(); - if (err) goto error; + if (err) { + goto error; + } if (frame->instr_ptr != this_instr) { next_instr = frame->instr_ptr; DISPATCH(); @@ -6538,10 +6550,12 @@ PyStackRef_CLOSE(v); if (true) goto pop_1_error; } - if (PyDict_CheckExact(ns)) - err = PyDict_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); - else - err = PyObject_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + if (PyDict_CheckExact(ns)) { + err = PyDict_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + } + else { + err = PyObject_SetItem(ns, name, PyStackRef_AsPyObjectBorrow(v)); + } PyStackRef_CLOSE(v); if (err) goto pop_1_error; stack_pointer += -1; diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index c91edff24bc665..fea4e45e049303 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -4,7 +4,6 @@ import re from typing import Optional - @dataclass class Properties: escapes: bool @@ -14,7 +13,6 @@ class Properties: oparg: bool jumps: bool eval_breaker: bool - ends_with_eval_breaker: bool needs_this: bool always_exits: bool stores_sp: bool @@ -44,7 +42,6 @@ def from_list(properties: list["Properties"]) -> "Properties": oparg=any(p.oparg for p in properties), jumps=any(p.jumps for p in properties), eval_breaker=any(p.eval_breaker for p in properties), - ends_with_eval_breaker=any(p.ends_with_eval_breaker for p in properties), needs_this=any(p.needs_this for p in properties), always_exits=any(p.always_exits for p in properties), stores_sp=any(p.stores_sp for p in properties), @@ -70,7 +67,6 @@ def infallible(self) -> bool: oparg=False, jumps=False, eval_breaker=False, - ends_with_eval_breaker=False, needs_this=False, always_exits=False, stores_sp=False, @@ -587,10 +583,6 @@ def makes_escaping_api_call(instr: parser.InstDef) -> bool: } -def eval_breaker_at_end(op: parser.InstDef) -> bool: - return op.tokens[-5].text == "CHECK_EVAL_BREAKER" - - def always_exits(op: parser.InstDef) -> bool: depth = 0 tkn_iter = iter(op.tokens) @@ -679,7 +671,6 @@ def compute_properties(op: parser.InstDef) -> Properties: oparg=oparg_used(op), jumps=variable_used(op, "JUMPBY"), eval_breaker=variable_used(op, "CHECK_EVAL_BREAKER"), - ends_with_eval_breaker=eval_breaker_at_end(op), needs_this=variable_used(op, "this_instr"), always_exits=always_exits(op), stores_sp=variable_used(op, "SYNC_SP"), diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 6ed9d836cbbabe..4a4648ef574b22 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -9,11 +9,39 @@ analysis_error, ) from cwriter import CWriter -from typing import Callable, Mapping, TextIO, Iterator +from typing import Callable, Mapping, TextIO, Iterator, Iterable from lexer import Token from stack import Stack +class TokenIterator: + + ahead: Token | None + iterator: Iterator[Token] + + def __init__(self, tkns: Iterable[Token]): + self.iterator = iter(tkns) + self.ahead = None + + def __iter__(self) -> "TokenIterator": + return self + + def __next__(self) -> Token: + if self.ahead is None: + return next(self.iterator) + else: + res = self.ahead + self.ahead = None + return res + + def peek(self) -> Token | None: + if self.ahead is None: + try: + self.ahead = next(self.iterator) + except StopIteration: + pass + return self.ahead + ROOT = Path(__file__).parent.parent.parent DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute().as_posix() @@ -47,22 +75,27 @@ def write_header( ) -def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: +def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: parens = 0 for tkn in tkn_iter: if tkn.kind == end and parens == 0: - return + return tkn if tkn.kind == "LPAREN": parens += 1 if tkn.kind == "RPAREN": parens -= 1 out.emit(tkn) + raise analysis_error(f"Expecting {end}. Reached end of file", tkn) ReplacementFunctionType = Callable[ - [Token, Iterator[Token], Uop, Stack, Instruction | None], None + [Token, TokenIterator, Uop, Stack, Instruction | None], bool ] +def always_true(tkn: Token | None) -> bool: + if tkn is None: + return False + return tkn.text == "true" or tkn.text == "1" class Emitter: out: CWriter @@ -84,13 +117,16 @@ def __init__(self, out: CWriter): def deopt_if( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, unused: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit_at("DEOPT_IF", tkn) - self.out.emit(next(tkn_iter)) + lparen = next(tkn_iter) + self.emit(lparen) + assert(lparen.kind == "LPAREN") + first_tkn = tkn_iter.peek() emit_to(self.out, tkn_iter, "RPAREN") next(tkn_iter) # Semi colon self.out.emit(", ") @@ -98,19 +134,23 @@ def deopt_if( assert inst.family is not None self.out.emit(inst.family.name) self.out.emit(");\n") + return not always_true(first_tkn) exit_if = deopt_if def error_if( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit_at("if ", tkn) - self.out.emit(next(tkn_iter)) + lparen = next(tkn_iter) + self.emit(lparen) + assert(lparen.kind == "LPAREN") + first_tkn = tkn_iter.peek() emit_to(self.out, tkn_iter, "COMMA") label = next(tkn_iter).text next(tkn_iter) # RPAREN @@ -131,33 +171,35 @@ def error_if( self.out.emit(";\n") else: self.out.emit("{\n") - stack.flush_locally(self.out) + stack.copy().flush(self.out) self.out.emit("goto ") self.out.emit(label) self.out.emit(";\n") self.out.emit("}\n") + return first_tkn.text != "true" and first_tkn.text != "1" def error_no_pop( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: next(tkn_iter) # LPAREN next(tkn_iter) # RPAREN next(tkn_iter) # Semi colon self.out.emit_at("goto error;", tkn) + return False def decref_inputs( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) @@ -176,42 +218,45 @@ def decref_inputs( self.out.emit(f"PyStackRef_XCLOSE({var.name});\n") else: self.out.emit(f"PyStackRef_CLOSE({var.name});\n") + return True def sync_sp( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) stack.flush(self.out) + return True def check_eval_breaker( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) - if not uop.properties.ends_with_eval_breaker: - self.out.emit_at("CHECK_EVAL_BREAKER();", tkn) + stack.flush(self.out) + self.out.emit_at("CHECK_EVAL_BREAKER();", tkn) + return True def py_stack_ref_from_py_object_new( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit(tkn) emit_to(self.out, tkn_iter, "SEMI") self.out.emit(";\n") @@ -219,29 +264,106 @@ def py_stack_ref_from_py_object_new( target = uop.deferred_refs[tkn] if target is None: # An assignment we don't handle, such as to a pointer or array. - return + return True # Flush the assignment to the stack. Note that we don't flush the # stack pointer here, and instead are currently relying on initializing # unused portions of the stack to NULL. stack.flush_single_var(self.out, target, uop.stack.outputs) + return True - def emit_tokens( + def _emit_if( self, + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: - tkns = uop.body[1:-1] - if not tkns: - return - tkn_iter = iter(tkns) - self.out.start_line() + ) -> tuple[bool, Token, Stack]: + """ Returns (reachable?, closing '}', stack).""" + tkn = next(tkn_iter) + assert (tkn.kind == "LPAREN") + self.out.emit(tkn) + rparen = emit_to(self.out, tkn_iter, "RPAREN") + self.emit(rparen) + if_stack = stack.copy() + reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_stack, inst, True) + maybe_else = tkn_iter.peek() + if maybe_else and maybe_else.kind == "ELSE": + self.emit(rbrace) + self.emit(next(tkn_iter)) + maybe_if = tkn_iter.peek() + if maybe_if and maybe_if.kind == "IF": + self.emit(next(tkn_iter)) + else_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, inst) + else: + else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, stack, inst, True) + if not reachable: + # Discard the if stack + reachable = else_reachable + elif not else_reachable: + # Discard the else stack + stack = if_stack + else: + stack = stack.merge(if_stack) + else: + if reachable: + stack = stack.merge(if_stack) + else: + # Discard the if stack + reachable = True + return reachable, rbrace, stack + + def _emit_block( + self, + tkn_iter: TokenIterator, + uop: Uop, + stack: Stack, + inst: Instruction | None, + emit_first_brace: bool + ) -> tuple[bool, Token, Stack]: + """ Returns (reachable?, closing '}', stack).""" + braces = 1 + tkn = next(tkn_iter) + reachable = True + if tkn.kind != "LBRACE": + raise analysis_error(f"Expected '{{' found {tkn.text}", tkn) + if emit_first_brace: + self.emit(tkn) for tkn in tkn_iter: - if tkn.kind == "IDENTIFIER" and tkn.text in self._replacers: - self._replacers[tkn.text](tkn, tkn_iter, uop, stack, inst) + if tkn.kind == "LBRACE": + self.out.emit(tkn) + braces += 1 + elif tkn.kind == "RBRACE": + braces -= 1 + if braces == 0: + return reachable, tkn, stack + self.out.emit(tkn) + elif tkn.kind == "GOTO": + reachable = False; + self.out.emit(tkn) + elif tkn.kind == "IDENTIFIER" and tkn.text in self._replacers: + if not self._replacers[tkn.text](tkn, tkn_iter, uop, stack, inst): + reachable = False + elif tkn.kind == "IF": + self.out.emit(tkn) + if_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, inst) + if reachable: + reachable = if_reachable + self.out.emit(rbrace) else: self.out.emit(tkn) + raise analysis_error("Expecting closing brace. Reached end of file", tkn) + + + def emit_tokens( + self, + uop: Uop, + stack: Stack, + inst: Instruction | None, + ) -> None: + tkn_iter = TokenIterator(uop.body) + self.out.start_line() + self._emit_block(tkn_iter, uop, stack, inst, False) def emit(self, txt: str | Token) -> None: self.out.emit(txt) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index b74f627235ad84..78074b8d262f03 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -18,6 +18,7 @@ ROOT, write_header, Emitter, + TokenIterator, ) from cwriter import CWriter from typing import TextIO, Iterator @@ -65,7 +66,7 @@ def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: def decref_inputs( out: CWriter, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index aebc60db0a9ded..50327d1d1623bc 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -60,6 +60,14 @@ def redefinition(var: StackItem, prev: "Local") -> "Local": assert var.is_array() == prev.is_array() return Local(var, prev.cached, prev.in_memory, True) + def copy(self) -> "Local": + return Local( + self.item, + self.cached, + self.in_memory, + self.defined + ) + @property def size(self) -> str: return self.item.size @@ -302,18 +310,6 @@ def _do_flush( out.emit("assert(WITHIN_STACK_BOUNDS());\n") out.start_line() - def flush_locally( - self, out: CWriter, cast_type: str = "uintptr_t", extract_bits: bool = False - ) -> None: - self._do_flush( - out, - self.variables[:], - self.base_offset.copy(), - self.top_offset.copy(), - cast_type, - extract_bits, - ) - def flush( self, out: CWriter, cast_type: str = "uintptr_t", extract_bits: bool = False ) -> None: @@ -361,7 +357,7 @@ def peek_offset(self) -> str: def as_comment(self) -> str: return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" - def copy(self) -> Stack: + def copy(self) -> "Stack": other = Stack() other.top_offset = self.top_offset.copy() other.base_offset = self.base_offset.copy() @@ -380,6 +376,11 @@ def __eq__(self, other: object) -> bool: ) + def merge(self, other:"Stack") -> "Stack": + if self != other: + raise StackError("unequal stacks") + return self + def get_stack_effect(inst: Instruction | PseudoInstruction) -> Stack: stack = Stack() @@ -405,3 +406,5 @@ def stacks(inst: Instruction | PseudoInstruction) -> Iterator[StackEffect]: local = Local.unused(var) stack.push(local) return stack + +UNREACHABLE = Stack() diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index c3456cd39ffc3b..130cf18df8eb05 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -22,6 +22,7 @@ write_header, type_and_null, Emitter, + TokenIterator, ) from cwriter import CWriter from typing import TextIO @@ -201,8 +202,6 @@ def generate_tier1( out.start_line() if not inst.parts[-1].properties.always_exits: stack.flush(out) - if inst.parts[-1].properties.ends_with_eval_breaker: - out.emit("CHECK_EVAL_BREAKER();\n") out.emit("DISPATCH();\n") out.start_line() out.emit("}") diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 7ed937636ee855..0b9e0d47f31b1e 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -20,6 +20,7 @@ write_header, type_and_null, Emitter, + TokenIterator, ) from cwriter import CWriter from typing import TextIO, Iterator @@ -69,85 +70,100 @@ def __init__(self, out: CWriter): def error_if( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit_at("if ", tkn) - self.emit(next(tkn_iter)) + lparen = next(tkn_iter) + self.emit(lparen) + assert(lparen.kind == "LPAREN") + first_tkn = next(tkn_iter) + self.out.emit(first_tkn) emit_to(self.out, tkn_iter, "COMMA") label = next(tkn_iter).text next(tkn_iter) # RPAREN next(tkn_iter) # Semi colon self.emit(") JUMP_TO_ERROR();\n") + return first_tkn.text != "true" and first_tkn.text != "1" + def error_no_pop( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: next(tkn_iter) # LPAREN next(tkn_iter) # RPAREN next(tkn_iter) # Semi colon self.out.emit_at("JUMP_TO_ERROR();", tkn) + return False def deopt_if( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, unused: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit_at("if ", tkn) - self.emit(next(tkn_iter)) + lparen = next(tkn_iter) + self.emit(lparen) + assert(lparen.kind == "LPAREN") + first_tkn = tkn_iter.peek() emit_to(self.out, tkn_iter, "RPAREN") next(tkn_iter) # Semi colon self.emit(") {\n") self.emit("UOP_STAT_INC(uopcode, miss);\n") self.emit("JUMP_TO_JUMP_TARGET();\n") self.emit("}\n") + return first_tkn.text != "true" and first_tkn.text != "1" def exit_if( # type: ignore[override] self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, unused: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: self.out.emit_at("if ", tkn) - self.emit(next(tkn_iter)) + lparen = next(tkn_iter) + self.emit(lparen) + first_tkn = tkn_iter.peek() emit_to(self.out, tkn_iter, "RPAREN") next(tkn_iter) # Semi colon self.emit(") {\n") self.emit("UOP_STAT_INC(uopcode, miss);\n") self.emit("JUMP_TO_JUMP_TARGET();\n") self.emit("}\n") + return first_tkn.text != "true" and first_tkn.text != "1" def oparg( self, tkn: Token, - tkn_iter: Iterator[Token], + tkn_iter: TokenIterator, uop: Uop, unused: Stack, inst: Instruction | None, - ) -> None: + ) -> bool: if not uop.name.endswith("_0") and not uop.name.endswith("_1"): self.emit(tkn) - return + return True amp = next(tkn_iter) if amp.text != "&": self.emit(tkn) self.emit(amp) - return + return True one = next(tkn_iter) assert one.text == "1" self.out.emit_at(uop.name[-1], tkn) + return True def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> None: @@ -230,8 +246,6 @@ def generate_tier2( out.start_line() if not uop.properties.always_exits: stack.flush(out) - if uop.properties.ends_with_eval_breaker: - out.emit("CHECK_EVAL_BREAKER();\n") out.emit("break;\n") out.start_line() out.emit("}") From 132df06a740ad37296d5396efd7c15dd5d65a3a7 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 9 Aug 2024 10:06:56 +0100 Subject: [PATCH 04/14] Fix type errors and rename ahead to look_ahead --- Tools/cases_generator/generators_common.py | 18 +++++++++--------- Tools/cases_generator/tier2_generator.py | 5 +++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 4a4648ef574b22..b7730b8072d42d 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -16,31 +16,31 @@ class TokenIterator: - ahead: Token | None + look_ahead: Token | None iterator: Iterator[Token] def __init__(self, tkns: Iterable[Token]): self.iterator = iter(tkns) - self.ahead = None + self.look_ahead = None def __iter__(self) -> "TokenIterator": return self def __next__(self) -> Token: - if self.ahead is None: + if self.look_ahead is None: return next(self.iterator) else: - res = self.ahead - self.ahead = None + res = self.look_ahead + self.look_ahead = None return res def peek(self) -> Token | None: - if self.ahead is None: + if self.look_ahead is None: try: - self.ahead = next(self.iterator) + self.look_ahead = next(self.iterator) except StopIteration: pass - return self.ahead + return self.look_ahead ROOT = Path(__file__).parent.parent.parent DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute().as_posix() @@ -176,7 +176,7 @@ def error_if( self.out.emit(label) self.out.emit(";\n") self.out.emit("}\n") - return first_tkn.text != "true" and first_tkn.text != "1" + return not always_true(first_tkn) def error_no_pop( self, diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 0b9e0d47f31b1e..cb7f5c38edbcd4 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -21,6 +21,7 @@ type_and_null, Emitter, TokenIterator, + always_true, ) from cwriter import CWriter from typing import TextIO, Iterator @@ -122,7 +123,7 @@ def deopt_if( self.emit("UOP_STAT_INC(uopcode, miss);\n") self.emit("JUMP_TO_JUMP_TARGET();\n") self.emit("}\n") - return first_tkn.text != "true" and first_tkn.text != "1" + return not always_true(first_tkn) def exit_if( # type: ignore[override] self, @@ -142,7 +143,7 @@ def exit_if( # type: ignore[override] self.emit("UOP_STAT_INC(uopcode, miss);\n") self.emit("JUMP_TO_JUMP_TARGET();\n") self.emit("}\n") - return first_tkn.text != "true" and first_tkn.text != "1" + return not always_true(first_tkn) def oparg( self, From b7f71d43c6d828d0173070bfe69a54d55e8ee8a7 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 9 Aug 2024 14:09:55 +0100 Subject: [PATCH 05/14] Track state of output variables --- Lib/test/test_generated_cases.py | 44 +++++++++++++- Python/generated_cases.c.h | 12 ++-- Tools/cases_generator/analyzer.py | 40 +++++++++---- Tools/cases_generator/generators_common.py | 62 ++++++++++++++++---- Tools/cases_generator/optimizer_generator.py | 4 +- Tools/cases_generator/stack.py | 4 +- Tools/cases_generator/tier1_generator.py | 4 +- Tools/cases_generator/tier2_generator.py | 9 ++- 8 files changed, 140 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index beafa544aaacb7..09b49246d59204 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -60,7 +60,7 @@ def test_effect_sizes(self): stack.pop(y) stack.pop(x) for out in outputs: - stack.push(Local.local(out)) + stack.push(Local.undefined(out)) self.assertEqual(stack.base_offset.to_c(), "-1 - oparg - oparg*2") self.assertEqual(stack.top_offset.to_c(), "1 - oparg - oparg*2 + oparg*4") @@ -247,14 +247,13 @@ def test_overlap(self): """ self.run_cases_test(input, output) - def test_predictions_and_eval_breaker(self): + def test_predictions(self): input = """ inst(OP1, (arg -- rest)) { } inst(OP3, (arg -- res)) { DEOPT_IF(xxx); res = Py_None; - CHECK_EVAL_BREAKER(); } family(OP1, INLINE_CACHE_ENTRIES_OP1) = { OP3 }; """ @@ -277,6 +276,45 @@ def test_predictions_and_eval_breaker(self): DEOPT_IF(xxx, OP1); res = Py_None; stack_pointer[-1] = res; + DISPATCH(); + } + """ + self.run_cases_test(input, output) + + def test_eval_breaker(self): + input = """ + inst(A, (arg -- res)) { + CHECK_EVAL_BREAKER(); + res = Py_None; + } + inst(B, (arg -- res)) { + res = Py_None; + CHECK_EVAL_BREAKER(); + } + """ + output = """ + TARGET(A) { + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(A); + _PyStackRef res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + CHECK_EVAL_BREAKER(); + res = Py_None; + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); + DISPATCH(); + } + + TARGET(B) { + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(B); + _PyStackRef res; + res = Py_None; + stack_pointer[-1] = res; CHECK_EVAL_BREAKER(); DISPATCH(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 929ecb8f6193e7..9a2e4846a55b7c 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1580,12 +1580,10 @@ assert(WITHIN_STACK_BOUNDS()); goto error; } - stack_pointer += -3 - (oparg & 1); + stack_pointer[-3 - (oparg & 1)] = result; + stack_pointer += -2 - (oparg & 1); assert(WITHIN_STACK_BOUNDS()); CHECK_EVAL_BREAKER(); - stack_pointer[0] = result; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); DISPATCH(); } @@ -1779,12 +1777,10 @@ goto error; } res = PyStackRef_FromPyObjectSteal(res_o); - stack_pointer += -3 - oparg; + stack_pointer[-3 - oparg] = res; + stack_pointer += -2 - oparg; assert(WITHIN_STACK_BOUNDS()); CHECK_EVAL_BREAKER(); - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); DISPATCH(); } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index fea4e45e049303..eb1f010ddb40fb 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -155,6 +155,7 @@ class Uop: stack: StackEffect caches: list[CacheEntry] deferred_refs: dict[lexer.Token, str | None] + output_stores: list[lexer.Token] body: list[lexer.Token] properties: Properties _size: int = -1 @@ -359,18 +360,34 @@ def analyze_caches(inputs: list[parser.InputEffect]) -> list[CacheEntry]: return [CacheEntry(i.name, int(i.size)) for i in caches] +def find_assignment_target(node: parser.InstDef, idx: int) -> list[lexer.Token]: + """Find the tokens that make up the left-hand side of an assignment""" + offset = 0 + for tkn in reversed(node.block.tokens[: idx]): + if tkn.kind == "SEMI" or tkn.kind == "LBRACE" or tkn.kind == "RBRACE": + return node.block.tokens[idx - offset : idx] + offset += 1 + return [] + + +def find_stores_outputs(node: parser.InstDef) -> list[lexer.Token]: + res: list[lexer.Token] = [] + outnames = [ out.name for out in node.outputs ] + for idx, tkn in enumerate(node.block.tokens): + if tkn.kind != "EQUALS": + continue + lhs = find_assignment_target(node, idx) + assert lhs + if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER": + continue + name = lhs[0] + if name.text in outnames: + res.append(name) + return res + def analyze_deferred_refs(node: parser.InstDef) -> dict[lexer.Token, str | None]: """Look for PyStackRef_FromPyObjectNew() calls""" - def find_assignment_target(idx: int) -> list[lexer.Token]: - """Find the tokens that make up the left-hand side of an assignment""" - offset = 1 - for tkn in reversed(node.block.tokens[: idx - 1]): - if tkn.kind == "SEMI" or tkn.kind == "LBRACE" or tkn.kind == "RBRACE": - return node.block.tokens[idx - offset : idx - 1] - offset += 1 - return [] - refs: dict[lexer.Token, str | None] = {} for idx, tkn in enumerate(node.block.tokens): if tkn.kind != "IDENTIFIER" or tkn.text != "PyStackRef_FromPyObjectNew": @@ -379,7 +396,7 @@ def find_assignment_target(idx: int) -> list[lexer.Token]: if idx == 0 or node.block.tokens[idx - 1].kind != "EQUALS": raise analysis_error("Expected '=' before PyStackRef_FromPyObjectNew", tkn) - lhs = find_assignment_target(idx) + lhs = find_assignment_target(node, idx - 1) if len(lhs) == 0: raise analysis_error( "PyStackRef_FromPyObjectNew() must be assigned to an output", tkn @@ -698,6 +715,7 @@ def make_uop( stack=analyze_stack(op), caches=analyze_caches(inputs), deferred_refs=analyze_deferred_refs(op), + output_stores=find_stores_outputs(op), body=op.block.tokens, properties=compute_properties(op), ) @@ -718,6 +736,7 @@ def make_uop( stack=analyze_stack(op, bit), caches=analyze_caches(inputs), deferred_refs=analyze_deferred_refs(op), + output_stores=find_stores_outputs(op), body=op.block.tokens, properties=properties, ) @@ -741,6 +760,7 @@ def make_uop( stack=analyze_stack(op), caches=analyze_caches(inputs), deferred_refs=analyze_deferred_refs(op), + output_stores=find_stores_outputs(op), body=op.block.tokens, properties=properties, ) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index b7730b8072d42d..16fcffa48651cf 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -11,7 +11,7 @@ from cwriter import CWriter from typing import Callable, Mapping, TextIO, Iterator, Iterable from lexer import Token -from stack import Stack +from stack import Stack, Local class TokenIterator: @@ -89,7 +89,7 @@ def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: ReplacementFunctionType = Callable[ - [Token, TokenIterator, Uop, Stack, Instruction | None], bool + [Token, TokenIterator, Uop, Stack, list[Local], Instruction | None], bool ] def always_true(tkn: Token | None) -> bool: @@ -97,6 +97,27 @@ def always_true(tkn: Token | None) -> bool: return False return tkn.text == "true" or tkn.text == "1" +def push_defined_locals(outputs: list[Local], stack:Stack, tkn: Token) -> None: + while outputs: + out = outputs[0] + if out.defined: + stack.push(out) + outputs.pop(0) + else: + break + undefined = "" + for out in outputs: + if out.defined: + raise analysis_error( + f"Locals not defined in stack order. " + f"Expected '{out.name}' is defined before '{undefined}'", + tkn + ) + else: + undefined = out.name + + + class Emitter: out: CWriter _replacers: dict[str, ReplacementFunctionType] @@ -120,6 +141,7 @@ def deopt_if( tkn_iter: TokenIterator, uop: Uop, unused: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit_at("DEOPT_IF", tkn) @@ -144,6 +166,7 @@ def error_if( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -184,6 +207,7 @@ def error_no_pop( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: next(tkn_iter) # LPAREN @@ -198,6 +222,7 @@ def decref_inputs( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: next(tkn_iter) @@ -226,6 +251,7 @@ def sync_sp( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: next(tkn_iter) @@ -240,11 +266,13 @@ def check_eval_breaker( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) + push_defined_locals(outputs, stack, tkn) stack.flush(self.out) self.out.emit_at("CHECK_EVAL_BREAKER();", tkn) return True @@ -255,6 +283,7 @@ def py_stack_ref_from_py_object_new( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit(tkn) @@ -277,6 +306,7 @@ def _emit_if( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> tuple[bool, Token, Stack]: """ Returns (reachable?, closing '}', stack).""" @@ -286,7 +316,7 @@ def _emit_if( rparen = emit_to(self.out, tkn_iter, "RPAREN") self.emit(rparen) if_stack = stack.copy() - reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_stack, inst, True) + reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_stack, outputs, inst, True) maybe_else = tkn_iter.peek() if maybe_else and maybe_else.kind == "ELSE": self.emit(rbrace) @@ -294,9 +324,9 @@ def _emit_if( maybe_if = tkn_iter.peek() if maybe_if and maybe_if.kind == "IF": self.emit(next(tkn_iter)) - else_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, inst) + else_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, outputs, inst) else: - else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, stack, inst, True) + else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, stack, outputs, inst, True) if not reachable: # Discard the if stack reachable = else_reachable @@ -318,11 +348,13 @@ def _emit_block( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, emit_first_brace: bool ) -> tuple[bool, Token, Stack]: """ Returns (reachable?, closing '}', stack).""" braces = 1 + out_stores = set(uop.output_stores) tkn = next(tkn_iter) reachable = True if tkn.kind != "LBRACE": @@ -341,12 +373,21 @@ def _emit_block( elif tkn.kind == "GOTO": reachable = False; self.out.emit(tkn) - elif tkn.kind == "IDENTIFIER" and tkn.text in self._replacers: - if not self._replacers[tkn.text](tkn, tkn_iter, uop, stack, inst): - reachable = False + elif tkn.kind == "IDENTIFIER": + if tkn.text in self._replacers: + if not self._replacers[tkn.text](tkn, tkn_iter, uop, stack, outputs, inst): + reachable = False + else: + if tkn in out_stores: + for out in outputs: + if out.name == tkn.text: + out.defined = True + out.in_memory = False + break + self.out.emit(tkn) elif tkn.kind == "IF": self.out.emit(tkn) - if_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, inst) + if_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, outputs, inst) if reachable: reachable = if_reachable self.out.emit(rbrace) @@ -359,11 +400,12 @@ def emit_tokens( self, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> None: tkn_iter = TokenIterator(uop.body) self.out.start_line() - self._emit_block(tkn_iter, uop, stack, inst, False) + self._emit_block(tkn_iter, uop, stack, outputs, inst, False) def emit(self, txt: str | Token) -> None: self.out.emit(txt) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 78074b8d262f03..2916382d829ba7 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -131,7 +131,7 @@ def write_uop( out.emit(f"{type}{cache.name} = ({cast})this_instr->operand;\n") if override: emitter = OptimizerEmitter(out) - emitter.emit_tokens(override, stack, None) + emitter.emit_tokens(override, stack, [], None) else: emit_default(out, uop) @@ -139,7 +139,7 @@ def write_uop( if var.name in locals: local = locals[var.name] else: - local = Local.local(var) + local = Local.undefined(var) stack.push(local) out.start_line() stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=True) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 50327d1d1623bc..ee7d55f5483b7e 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -51,9 +51,9 @@ def unused(defn: StackItem) -> "Local": return Local(defn, False, defn.is_array(), False) @staticmethod - def local(defn: StackItem) -> "Local": + def undefined(defn: StackItem) -> "Local": array = defn.is_array() - return Local(defn, not array, array, True) + return Local(defn, not array, array, False) @staticmethod def redefinition(var: StackItem, prev: "Local") -> "Local": diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 130cf18df8eb05..b94e4aeebe889e 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -109,7 +109,7 @@ def write_uop( elif var.name == "unused": local = Local.unused(var) else: - local = Local.local(var) + local = Local.undefined(var) outputs.append(local) for cache in uop.caches: @@ -126,7 +126,7 @@ def write_uop( if inst.family is None: emitter.emit(f"(void){cache.name};\n") offset += cache.size - emitter.emit_tokens(uop, stack, inst) + emitter.emit_tokens(uop, stack, outputs, inst) for output in outputs: if output.name in uop.deferred_refs.values(): # We've already spilled this when emitting tokens diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index cb7f5c38edbcd4..731155ee27d325 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -74,6 +74,7 @@ def error_if( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -96,6 +97,7 @@ def error_no_pop( tkn_iter: TokenIterator, uop: Uop, stack: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: next(tkn_iter) # LPAREN @@ -110,6 +112,7 @@ def deopt_if( tkn_iter: TokenIterator, uop: Uop, unused: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -131,6 +134,7 @@ def exit_if( # type: ignore[override] tkn_iter: TokenIterator, uop: Uop, unused: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -151,6 +155,7 @@ def oparg( tkn_iter: TokenIterator, uop: Uop, unused: Stack, + outputs: list[Local], inst: Instruction | None, ) -> bool: if not uop.name.endswith("_0") and not uop.name.endswith("_1"): @@ -188,7 +193,7 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> None: if var.name in locals: local = locals[var.name] else: - local = Local.local(var) + local = Local.undefined(var) outputs.append(local) for cache in uop.caches: if cache.name != "unused": @@ -198,7 +203,7 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> None: type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" emitter.emit(f"{type}{cache.name} = ({cast})CURRENT_OPERAND();\n") - emitter.emit_tokens(uop, stack, None) + emitter.emit_tokens(uop, stack, outputs, None) for output in outputs: if output.name in uop.deferred_refs.values(): # We've already spilled this when emitting tokens From b97dea982a9a117839fa9a5a469509ab3297e5f7 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 9 Aug 2024 16:13:58 +0100 Subject: [PATCH 06/14] Handle stack and output locals togather as Storage class --- Python/optimizer_cases.c.h | 139 +++++++++++-------- Tools/cases_generator/generators_common.py | 63 ++++----- Tools/cases_generator/optimizer_generator.py | 16 +-- Tools/cases_generator/stack.py | 42 +++++- Tools/cases_generator/tier1_generator.py | 20 +-- Tools/cases_generator/tier2_generator.py | 38 +++-- 6 files changed, 178 insertions(+), 140 deletions(-) diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 50aa9728cf2939..1e308d5a60da3e 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -52,7 +52,6 @@ int opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE; REPLACE_OP(this_instr, opcode, 0, (uintptr_t)val); value = sym_new_const(ctx, val); - stack_pointer[0] = value; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -210,14 +209,20 @@ } sym_set_type(left, &PyLong_Type); sym_set_type(right, &PyLong_Type); + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_NOS_INT: { + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_TOS_INT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -331,14 +336,20 @@ } sym_set_type(left, &PyFloat_Type); sym_set_type(right, &PyFloat_Type); + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_NOS_FLOAT: { + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_TOS_FLOAT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -446,6 +457,8 @@ } sym_set_type(left, &PyUnicode_Type); sym_set_type(left, &PyUnicode_Type); + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -540,6 +553,8 @@ } case _BINARY_SUBSCR_CHECK_FUNC: { + stack_pointer += -2; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -553,13 +568,13 @@ } case _LIST_APPEND: { - stack_pointer += -1; + stack_pointer += -2 - (oparg-1); assert(WITHIN_STACK_BOUNDS()); break; } case _SET_ADD: { - stack_pointer += -1; + stack_pointer += -2 - (oparg-1); assert(WITHIN_STACK_BOUNDS()); break; } @@ -642,9 +657,7 @@ case _GET_ANEXT: { _Py_UopsSymbol *awaitable; awaitable = sym_new_not_null(ctx); - stack_pointer[0] = awaitable; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = awaitable; break; } @@ -724,8 +737,6 @@ _Py_UopsSymbol *val0; val1 = sym_new_not_null(ctx); val0 = sym_new_not_null(ctx); - stack_pointer[-1] = val1; - stack_pointer[0] = val0; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -794,7 +805,6 @@ case _LOAD_LOCALS: { _Py_UopsSymbol *locals; locals = sym_new_not_null(ctx); - stack_pointer[0] = locals; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -921,13 +931,13 @@ } case _LIST_EXTEND: { - stack_pointer += -1; + stack_pointer += -2 - (oparg-1); assert(WITHIN_STACK_BOUNDS()); break; } case _SET_UPDATE: { - stack_pointer += -1; + stack_pointer += -2 - (oparg-1); assert(WITHIN_STACK_BOUNDS()); break; } @@ -955,19 +965,19 @@ } case _DICT_UPDATE: { - stack_pointer += -1; + stack_pointer += -2 - (oparg - 1); assert(WITHIN_STACK_BOUNDS()); break; } case _DICT_MERGE: { - stack_pointer += -1; + stack_pointer += -5 - (oparg - 1); assert(WITHIN_STACK_BOUNDS()); break; } case _MAP_ADD: { - stack_pointer += -2; + stack_pointer += -3 - (oparg - 1); assert(WITHIN_STACK_BOUNDS()); break; } @@ -1035,10 +1045,14 @@ } } } + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } case _CHECK_MANAGED_OBJECT_HAS_VALUES: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1077,6 +1091,8 @@ } } } + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1113,6 +1129,8 @@ } case _CHECK_ATTR_WITH_HINT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1143,7 +1161,6 @@ null = sym_new_null(ctx); (void)index; (void)owner; - stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); @@ -1151,6 +1168,8 @@ } case _CHECK_ATTR_CLASS: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1164,7 +1183,6 @@ null = sym_new_null(ctx); (void)descr; (void)owner; - stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); @@ -1181,6 +1199,8 @@ /* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 */ case _GUARD_DORV_NO_DICT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1328,7 +1348,9 @@ case _CHECK_EXC_MATCH: { _Py_UopsSymbol *b; b = sym_new_not_null(ctx); - stack_pointer[-1] = b; + stack_pointer[-2] = b; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1344,9 +1366,7 @@ case _IMPORT_FROM: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } @@ -1364,9 +1384,7 @@ case _GET_LEN: { _Py_UopsSymbol *len; len = sym_new_not_null(ctx); - stack_pointer[0] = len; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = len; break; } @@ -1382,26 +1400,22 @@ case _MATCH_MAPPING: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } case _MATCH_SEQUENCE: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } case _MATCH_KEYS: { _Py_UopsSymbol *values_or_none; values_or_none = sym_new_not_null(ctx); - stack_pointer[0] = values_or_none; - stack_pointer += 1; + stack_pointer[-2] = values_or_none; + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -1425,59 +1439,63 @@ case _FOR_ITER_TIER_TWO: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); - stack_pointer[0] = next; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = next; break; } /* _INSTRUMENTED_FOR_ITER is not a viable micro-op for tier 2 */ case _ITER_CHECK_LIST: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_LIST is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_LIST: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_NEXT_LIST: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); - stack_pointer[0] = next; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_CHECK_TUPLE: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_TUPLE is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_TUPLE: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_NEXT_TUPLE: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); - stack_pointer[0] = next; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_CHECK_RANGE: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_RANGE is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_RANGE: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1487,9 +1505,7 @@ iter = stack_pointer[-1]; next = sym_new_type(ctx, &PyLong_Type); (void)iter; - stack_pointer[0] = next; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = next; break; } @@ -1517,8 +1533,8 @@ case _WITH_EXCEPT_START: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[0] = res; - stack_pointer += 1; + stack_pointer[-5] = res; + stack_pointer += -4; assert(WITHIN_STACK_BOUNDS()); break; } @@ -1536,10 +1552,14 @@ } case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_KEYS_VERSION: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1552,7 +1572,6 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; - stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1568,7 +1587,6 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; - stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1578,18 +1596,18 @@ case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { _Py_UopsSymbol *attr; attr = sym_new_not_null(ctx); - stack_pointer[-1] = attr; break; } case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { _Py_UopsSymbol *attr; attr = sym_new_not_null(ctx); - stack_pointer[-1] = attr; break; } case _CHECK_ATTR_METHOD_LAZY_DICT: { + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1602,7 +1620,6 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; - stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1624,8 +1641,6 @@ (void)args; func = sym_new_not_null(ctx); maybe_self = sym_new_not_null(ctx); - stack_pointer[-2 - oparg] = func; - stack_pointer[-1 - oparg] = maybe_self; break; } @@ -1658,10 +1673,14 @@ } case _CHECK_FUNCTION_VERSION: { + stack_pointer += -2 - oparg; + assert(WITHIN_STACK_BOUNDS()); break; } case _CHECK_METHOD_VERSION: { + stack_pointer += -2 - oparg; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1670,12 +1689,12 @@ _Py_UopsSymbol *self; method = sym_new_not_null(ctx); self = sym_new_not_null(ctx); - stack_pointer[-2 - oparg] = method; - stack_pointer[-1 - oparg] = self; break; } case _CHECK_IS_NOT_PY_CALLABLE: { + stack_pointer += -2 - oparg; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1695,6 +1714,8 @@ callable = stack_pointer[-2 - oparg]; sym_set_null(null); sym_set_type(callable, &PyMethod_Type); + stack_pointer += -2 - oparg; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1706,8 +1727,6 @@ (void)callable; func = sym_new_not_null(ctx); self = sym_new_not_null(ctx); - stack_pointer[-2 - oparg] = func; - stack_pointer[-1 - oparg] = self; break; } @@ -1727,6 +1746,8 @@ callable = stack_pointer[-2 - oparg]; sym_set_type(callable, &PyFunction_Type); (void)self_or_null; + stack_pointer += -2 - oparg; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -2037,8 +2058,8 @@ bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = bottom; - stack_pointer[0] = top; - stack_pointer += 1; + stack_pointer[-1 - (oparg-1)] = top; + stack_pointer += -(oparg-1); assert(WITHIN_STACK_BOUNDS()); break; } @@ -2194,7 +2215,6 @@ _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; value = sym_new_const(ctx, ptr); - stack_pointer[0] = value; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -2223,7 +2243,6 @@ PyObject *ptr = (PyObject *)this_instr->operand; value = sym_new_const(ctx, ptr); null = sym_new_null(ctx); - stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; assert(WITHIN_STACK_BOUNDS()); diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 16fcffa48651cf..f02763ab81457e 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -11,7 +11,7 @@ from cwriter import CWriter from typing import Callable, Mapping, TextIO, Iterator, Iterable from lexer import Token -from stack import Stack, Local +from stack import Stack, Local, Storage class TokenIterator: @@ -89,7 +89,7 @@ def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: ReplacementFunctionType = Callable[ - [Token, TokenIterator, Uop, Stack, list[Local], Instruction | None], bool + [Token, TokenIterator, Uop, Storage, Instruction | None], bool ] def always_true(tkn: Token | None) -> bool: @@ -117,7 +117,6 @@ def push_defined_locals(outputs: list[Local], stack:Stack, tkn: Token) -> None: undefined = out.name - class Emitter: out: CWriter _replacers: dict[str, ReplacementFunctionType] @@ -140,8 +139,7 @@ def deopt_if( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - unused: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit_at("DEOPT_IF", tkn) @@ -165,8 +163,7 @@ def error_if( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -179,7 +176,7 @@ def error_if( next(tkn_iter) # RPAREN next(tkn_iter) # Semi colon self.out.emit(") ") - c_offset = stack.peek_offset() + c_offset = storage.stack.peek_offset() try: offset = -int(c_offset) except ValueError: @@ -194,7 +191,7 @@ def error_if( self.out.emit(";\n") else: self.out.emit("{\n") - stack.copy().flush(self.out) + storage.stack.copy().flush(self.out) self.out.emit("goto ") self.out.emit(label) self.out.emit(";\n") @@ -206,8 +203,7 @@ def error_no_pop( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: next(tkn_iter) # LPAREN @@ -221,8 +217,7 @@ def decref_inputs( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: next(tkn_iter) @@ -250,14 +245,13 @@ def sync_sp( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) - stack.flush(self.out) + storage.stack.flush(self.out) return True def check_eval_breaker( @@ -265,15 +259,13 @@ def check_eval_breaker( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: next(tkn_iter) next(tkn_iter) next(tkn_iter) - push_defined_locals(outputs, stack, tkn) - stack.flush(self.out) + storage.flush(self.out) self.out.emit_at("CHECK_EVAL_BREAKER();", tkn) return True @@ -282,8 +274,7 @@ def py_stack_ref_from_py_object_new( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit(tkn) @@ -298,15 +289,14 @@ def py_stack_ref_from_py_object_new( # Flush the assignment to the stack. Note that we don't flush the # stack pointer here, and instead are currently relying on initializing # unused portions of the stack to NULL. - stack.flush_single_var(self.out, target, uop.stack.outputs) + storage.stack.flush_single_var(self.out, target, uop.stack.outputs) return True def _emit_if( self, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> tuple[bool, Token, Stack]: """ Returns (reachable?, closing '}', stack).""" @@ -315,8 +305,10 @@ def _emit_if( self.out.emit(tkn) rparen = emit_to(self.out, tkn_iter, "RPAREN") self.emit(rparen) + stack = storage.stack if_stack = stack.copy() - reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_stack, outputs, inst, True) + if_storage = Storage(if_stack, storage.outputs) + reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_storage, inst, True) maybe_else = tkn_iter.peek() if maybe_else and maybe_else.kind == "ELSE": self.emit(rbrace) @@ -324,9 +316,9 @@ def _emit_if( maybe_if = tkn_iter.peek() if maybe_if and maybe_if.kind == "IF": self.emit(next(tkn_iter)) - else_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, outputs, inst) + else_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, storage, inst) else: - else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, stack, outputs, inst, True) + else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, storage, inst, True) if not reachable: # Discard the if stack reachable = else_reachable @@ -347,8 +339,7 @@ def _emit_block( self, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, emit_first_brace: bool ) -> tuple[bool, Token, Stack]: @@ -356,6 +347,7 @@ def _emit_block( braces = 1 out_stores = set(uop.output_stores) tkn = next(tkn_iter) + stack = storage.stack reachable = True if tkn.kind != "LBRACE": raise analysis_error(f"Expected '{{' found {tkn.text}", tkn) @@ -375,11 +367,11 @@ def _emit_block( self.out.emit(tkn) elif tkn.kind == "IDENTIFIER": if tkn.text in self._replacers: - if not self._replacers[tkn.text](tkn, tkn_iter, uop, stack, outputs, inst): + if not self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst): reachable = False else: if tkn in out_stores: - for out in outputs: + for out in storage.outputs: if out.name == tkn.text: out.defined = True out.in_memory = False @@ -387,7 +379,7 @@ def _emit_block( self.out.emit(tkn) elif tkn.kind == "IF": self.out.emit(tkn) - if_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, stack, outputs, inst) + if_reachable, rbrace, stack = self._emit_if(tkn_iter, uop, storage, inst) if reachable: reachable = if_reachable self.out.emit(rbrace) @@ -399,13 +391,12 @@ def _emit_block( def emit_tokens( self, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> None: tkn_iter = TokenIterator(uop.body) self.out.start_line() - self._emit_block(tkn_iter, uop, stack, outputs, inst, False) + self._emit_block(tkn_iter, uop, storage, inst, False) def emit(self, txt: str | Token) -> None: self.out.emit(txt) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 2916382d829ba7..a1378424248f2f 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -23,7 +23,7 @@ from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token -from stack import Local, Stack, StackError +from stack import Local, Stack, StackError, Storage DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_posix() @@ -114,6 +114,7 @@ def write_uop( if local.defined: locals[local.name] = local out.emit(stack.define_output_arrays(prototype.stack.outputs)) + storage = Storage.for_uop(stack, prototype, locals) if debug: args = [] for var in prototype.stack.inputs: @@ -131,16 +132,15 @@ def write_uop( out.emit(f"{type}{cache.name} = ({cast})this_instr->operand;\n") if override: emitter = OptimizerEmitter(out) - emitter.emit_tokens(override, stack, [], None) + emitter.emit_tokens(override, storage, None) else: emit_default(out, uop) - for var in prototype.stack.outputs: - if var.name in locals: - local = locals[var.name] - else: - local = Local.undefined(var) - stack.push(local) + for output in storage.outputs: + if output.name in uop.deferred_refs.values(): + # We've already spilled this when emitting tokens + output.cached = False + stack.push(output) out.start_line() stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=True) except StackError as ex: diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index ee7d55f5483b7e..227a320bbab3d3 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -407,4 +407,44 @@ def stacks(inst: Instruction | PseudoInstruction) -> Iterator[StackEffect]: stack.push(local) return stack -UNREACHABLE = Stack() +@dataclass +class Storage: + + stack: Stack + outputs: list[Local] + + def _push_defined_locals(self) -> None: + while self.outputs: + out = self.outputs[0] + if out.defined: + self.stack.push(out) + self.outputs.pop(0) + else: + break + undefined = "" + for out in self.outputs: + if out.defined: + raise StackError( + f"Locals not defined in stack order. " + f"Expected '{out.name}' is defined before '{undefined}'" + ) + else: + undefined = out.name + + def flush(self, out: CWriter) -> None: + self._push_defined_locals() + self.stack.flush(out) + + @staticmethod + def for_uop(stack: Stack, uop: Uop, locals: dict[str, Local]) -> "Storage": + outputs: list[Local] = [] + for var in uop.stack.outputs: + if not var.peek: + if var.name in locals: + local = locals[var.name] + elif var.name == "unused": + local = Local.unused(var) + else: + local = Local.undefined(var) + outputs.append(local) + return Storage(stack, outputs) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index b94e4aeebe889e..41a9373c64d749 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -26,7 +26,7 @@ ) from cwriter import CWriter from typing import TextIO -from stack import Local, Stack, StackError, get_stack_effect +from stack import Local, Stack, StackError, get_stack_effect, Storage DEFAULT_OUTPUT = ROOT / "Python/generated_cases.c.h" @@ -100,17 +100,8 @@ def write_uop( stack.push(peeks.pop()) if braces: emitter.emit("{\n") - emitter.out.emit(stack.define_output_arrays(uop.stack.outputs)) - outputs: list[Local] = [] - for var in uop.stack.outputs: - if not var.peek: - if var.name in locals: - local = locals[var.name] - elif var.name == "unused": - local = Local.unused(var) - else: - local = Local.undefined(var) - outputs.append(local) + emitter.emit(stack.define_output_arrays(uop.stack.outputs)) + storage = Storage.for_uop(stack, uop, locals) for cache in uop.caches: if cache.name != "unused": @@ -126,8 +117,9 @@ def write_uop( if inst.family is None: emitter.emit(f"(void){cache.name};\n") offset += cache.size - emitter.emit_tokens(uop, stack, outputs, inst) - for output in outputs: + + emitter.emit_tokens(uop, storage, inst) + for output in storage.outputs: if output.name in uop.deferred_refs.values(): # We've already spilled this when emitting tokens output.cached = False diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 731155ee27d325..1f2df958bf32c9 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -26,7 +26,7 @@ from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token -from stack import Local, Stack, StackError, get_stack_effect +from stack import Local, Stack, StackError, Storage DEFAULT_OUTPUT = ROOT / "Python/executor_cases.c.h" @@ -54,7 +54,7 @@ def declare_variables(uop: Uop, out: CWriter) -> None: for var in reversed(uop.stack.inputs): stack.pop(var) for var in uop.stack.outputs: - stack.push(Local.unused(var)) + stack.push(Local.undefined(var)) required = set(stack.defined) required.discard("unused") for var in reversed(uop.stack.inputs): @@ -73,8 +73,7 @@ def error_if( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -96,8 +95,7 @@ def error_no_pop( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - stack: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: next(tkn_iter) # LPAREN @@ -111,8 +109,7 @@ def deopt_if( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - unused: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -133,8 +130,7 @@ def exit_if( # type: ignore[override] tkn: Token, tkn_iter: TokenIterator, uop: Uop, - unused: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: self.out.emit_at("if ", tkn) @@ -154,8 +150,7 @@ def oparg( tkn: Token, tkn_iter: TokenIterator, uop: Uop, - unused: Stack, - outputs: list[Local], + storage: Storage, inst: Instruction | None, ) -> bool: if not uop.name.endswith("_0") and not uop.name.endswith("_1"): @@ -182,19 +177,20 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> None: elif uop.properties.const_oparg >= 0: emitter.emit(f"oparg = {uop.properties.const_oparg};\n") emitter.emit(f"assert(oparg == CURRENT_OPARG());\n") + peeks: list[Local] = [] for var in reversed(uop.stack.inputs): code, local = stack.pop(var) emitter.emit(code) + if var.peek: + peeks.append(local) if local.defined: locals[local.name] = local + # Push back the peeks, so that they remain on the logical + # stack, but their values are cached. + while peeks: + stack.push(peeks.pop()) emitter.emit(stack.define_output_arrays(uop.stack.outputs)) - outputs: list[Local] = [] - for var in uop.stack.outputs: - if var.name in locals: - local = locals[var.name] - else: - local = Local.undefined(var) - outputs.append(local) + storage = Storage.for_uop(stack, uop, locals) for cache in uop.caches: if cache.name != "unused": if cache.size == 4: @@ -203,8 +199,8 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> None: type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" emitter.emit(f"{type}{cache.name} = ({cast})CURRENT_OPERAND();\n") - emitter.emit_tokens(uop, stack, outputs, None) - for output in outputs: + emitter.emit_tokens(uop, storage, None) + for output in storage.outputs: if output.name in uop.deferred_refs.values(): # We've already spilled this when emitting tokens output.cached = False From 9838a05a681f1ce8700e5444a0dda93a8cc4acbf Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 9 Aug 2024 17:53:19 +0100 Subject: [PATCH 07/14] Track locals as well as stack on differing paths --- Python/bytecodes.c | 2 ++ Python/executor_cases.c.h | 2 ++ Python/generated_cases.c.h | 2 ++ Python/optimizer_bytecodes.c | 25 ++++++++++---- Python/optimizer_cases.c.h | 25 ++++++++++---- Tools/cases_generator/analyzer.py | 6 ++++ Tools/cases_generator/generators_common.py | 35 ++++++++++++-------- Tools/cases_generator/optimizer_generator.py | 6 ++-- Tools/cases_generator/stack.py | 14 ++++++++ 9 files changed, 87 insertions(+), 30 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fbaa894a30ac51..c0aadb6bac1962 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1991,6 +1991,8 @@ dummy_func( attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); DECREF_INPUTS(); ERROR_IF(attr_o == NULL, error); + /* We need to define self_or_null on all paths */ + self_or_null = PyStackRef_NULL; } attr = PyStackRef_FromPyObjectSteal(attr_o); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 893f86684ddd12..1914efd9c7eadb 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2221,6 +2221,8 @@ attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); PyStackRef_CLOSE(owner); if (attr_o == NULL) JUMP_TO_ERROR(); + /* We need to define self_or_null on all paths */ + self_or_null = PyStackRef_NULL; } attr = PyStackRef_FromPyObjectSteal(attr_o); stack_pointer[-1] = attr; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 9a2e4846a55b7c..ca7c7b1f563a47 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4428,6 +4428,8 @@ attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name); PyStackRef_CLOSE(owner); if (attr_o == NULL) goto pop_1_error; + /* We need to define self_or_null on all paths */ + self_or_null = PyStackRef_NULL; } attr = PyStackRef_FromPyObjectSteal(attr_o); } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index c982e37182157a..ef58c0d868cb45 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -182,7 +182,9 @@ dummy_func(void) { res = sym_new_type(ctx, &PyFloat_Type); } } - res = sym_new_unknown(ctx); + else { + res = sym_new_unknown(ctx); + } } op(_BINARY_OP_ADD_INT, (left, right -- res)) { @@ -330,41 +332,47 @@ dummy_func(void) { } op(_TO_BOOL, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { res = sym_new_type(ctx, &PyBool_Type); } } op(_TO_BOOL_BOOL, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyBool_Type); res = value; } } op(_TO_BOOL_INT, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyLong_Type); res = sym_new_type(ctx, &PyBool_Type); } } op(_TO_BOOL_LIST, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyList_Type); res = sym_new_type(ctx, &PyBool_Type); } } op(_TO_BOOL_NONE, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_const(value, Py_None); res = sym_new_const(ctx, Py_False); } } op(_TO_BOOL_STR, (value -- res)) { - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { res = sym_new_type(ctx, &PyBool_Type); sym_set_type(value, &PyUnicode_Type); } @@ -475,6 +483,9 @@ dummy_func(void) { if (oparg & 1) { self_or_null = sym_new_unknown(ctx); } + else { + self_or_null = NULL; + } } op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 1e308d5a60da3e..2a85ca56d89157 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -108,7 +108,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { res = sym_new_type(ctx, &PyBool_Type); } stack_pointer[-1] = res; @@ -119,7 +120,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyBool_Type); res = value; } @@ -131,7 +133,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyLong_Type); res = sym_new_type(ctx, &PyBool_Type); } @@ -143,7 +146,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_type(value, &PyList_Type); res = sym_new_type(ctx, &PyBool_Type); } @@ -155,7 +159,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { sym_set_const(value, Py_None); res = sym_new_const(ctx, Py_False); } @@ -167,7 +172,8 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (!optimize_to_bool(this_instr, ctx, value, &res)) { + int opt = optimize_to_bool(this_instr, ctx, value, &res); + if (!opt) { res = sym_new_type(ctx, &PyBool_Type); sym_set_type(value, &PyUnicode_Type); } @@ -1015,6 +1021,9 @@ if (oparg & 1) { self_or_null = sym_new_unknown(ctx); } + else { + self_or_null = NULL; + } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += (oparg & 1); @@ -2085,7 +2094,9 @@ res = sym_new_type(ctx, &PyFloat_Type); } } - res = sym_new_unknown(ctx); + else { + res = sym_new_unknown(ctx); + } stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index eb1f010ddb40fb..bce7a47b83f6e6 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -374,10 +374,16 @@ def find_stores_outputs(node: parser.InstDef) -> list[lexer.Token]: res: list[lexer.Token] = [] outnames = [ out.name for out in node.outputs ] for idx, tkn in enumerate(node.block.tokens): + if tkn.kind == "AND": + name = node.block.tokens[idx+1] + if name.text in outnames: + res.append(name) if tkn.kind != "EQUALS": continue lhs = find_assignment_target(node, idx) assert lhs + while lhs and lhs[0].kind == "COMMENT": + lhs = lhs[1:] if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER": continue name = lhs[0] diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index f02763ab81457e..67d2097f759010 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -131,9 +131,21 @@ def __init__(self, out: CWriter): "CHECK_EVAL_BREAKER": self.check_eval_breaker, "SYNC_SP": self.sync_sp, "PyStackRef_FromPyObjectNew": self.py_stack_ref_from_py_object_new, + "DISPATCH": self.dispatch } self.out = out + def dispatch( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: Uop, + storage: Storage, + inst: Instruction | None, + ) -> bool: + self.emit(tkn) + return False + def deopt_if( self, tkn: Token, @@ -298,16 +310,14 @@ def _emit_if( uop: Uop, storage: Storage, inst: Instruction | None, - ) -> tuple[bool, Token, Stack]: + ) -> tuple[bool, Token, Storage]: """ Returns (reachable?, closing '}', stack).""" tkn = next(tkn_iter) assert (tkn.kind == "LPAREN") self.out.emit(tkn) rparen = emit_to(self.out, tkn_iter, "RPAREN") self.emit(rparen) - stack = storage.stack - if_stack = stack.copy() - if_storage = Storage(if_stack, storage.outputs) + if_storage = storage.copy() reachable, rbrace, if_stack = self._emit_block(tkn_iter, uop, if_storage, inst, True) maybe_else = tkn_iter.peek() if maybe_else and maybe_else.kind == "ELSE": @@ -320,20 +330,20 @@ def _emit_if( else: else_reachable, rbrace, stack = self._emit_block(tkn_iter, uop, storage, inst, True) if not reachable: - # Discard the if stack + # Discard the if storage reachable = else_reachable elif not else_reachable: - # Discard the else stack - stack = if_stack + # Discard the else storage + storage = if_storage else: - stack = stack.merge(if_stack) + storage = storage.merge(if_storage, self.out) else: if reachable: - stack = stack.merge(if_stack) + storage = storage.merge(if_storage, self.out) else: # Discard the if stack reachable = True - return reachable, rbrace, stack + return reachable, rbrace, storage def _emit_block( self, @@ -342,12 +352,11 @@ def _emit_block( storage: Storage, inst: Instruction | None, emit_first_brace: bool - ) -> tuple[bool, Token, Stack]: + ) -> tuple[bool, Token, Storage]: """ Returns (reachable?, closing '}', stack).""" braces = 1 out_stores = set(uop.output_stores) tkn = next(tkn_iter) - stack = storage.stack reachable = True if tkn.kind != "LBRACE": raise analysis_error(f"Expected '{{' found {tkn.text}", tkn) @@ -360,7 +369,7 @@ def _emit_block( elif tkn.kind == "RBRACE": braces -= 1 if braces == 0: - return reachable, tkn, stack + return reachable, tkn, storage self.out.emit(tkn) elif tkn.kind == "GOTO": reachable = False; diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index a1378424248f2f..9388eb3bef65ec 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -103,9 +103,9 @@ def write_uop( skip_inputs: bool, ) -> None: locals: dict[str, Local] = {} + prototype = override if override else uop + is_override = override is not None try: - prototype = override if override else uop - is_override = override is not None out.start_line() for var in reversed(prototype.stack.inputs): code, local = stack.pop(var, extract_bits=True) @@ -144,7 +144,7 @@ def write_uop( out.start_line() stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=True) except StackError as ex: - raise analysis_error(ex.args[0], uop.body[0]) + raise analysis_error(ex.args[0], prototype.body[0]) SKIPS = ("_EXTENDED_ARG",) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 227a320bbab3d3..eac6804b52c466 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -448,3 +448,17 @@ def for_uop(stack: Stack, uop: Uop, locals: dict[str, Local]) -> "Storage": local = Local.undefined(var) outputs.append(local) return Storage(stack, outputs) + + def copy(self) -> "Storage": + return Storage(self.stack.copy(), [ l.copy() for l in self.outputs]) + + def merge(self, other: "Storage", out: CWriter) -> "Storage": + self.stack.merge(other.stack) + if self.outputs != other.outputs: + raise StackError("unequal locals") + return self + + def as_comment(self) -> str: + stack_comment = self.stack.as_comment() + return stack_comment[:-2] + str(self.outputs) + " */" + From 1f829be66afd7575533f92137bc909b9d8aa43d9 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 11:57:26 +0100 Subject: [PATCH 08/14] Use 'PEP 7' in syntax error, to make it clear that this is not a C syntax error, just a tool limitation. --- Tools/cases_generator/generators_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 67d2097f759010..224359d129c4b7 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -359,7 +359,7 @@ def _emit_block( tkn = next(tkn_iter) reachable = True if tkn.kind != "LBRACE": - raise analysis_error(f"Expected '{{' found {tkn.text}", tkn) + raise analysis_error(f"PEP 7: expected '{{' found {tkn.text}", tkn) if emit_first_brace: self.emit(tkn) for tkn in tkn_iter: From d2e5f129ab67e88f76e43ca642fcf0ad1ca070e5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 13:47:13 +0100 Subject: [PATCH 09/14] Push peeks back to stack in optimizer code gen --- Python/optimizer_cases.c.h | 139 ++++++++----------- Tools/cases_generator/optimizer_generator.py | 13 +- 2 files changed, 68 insertions(+), 84 deletions(-) diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 2a85ca56d89157..07c3b44bbe9acd 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -52,6 +52,7 @@ int opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE; REPLACE_OP(this_instr, opcode, 0, (uintptr_t)val); value = sym_new_const(ctx, val); + stack_pointer[0] = value; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -215,20 +216,14 @@ } sym_set_type(left, &PyLong_Type); sym_set_type(right, &PyLong_Type); - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_NOS_INT: { - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_TOS_INT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -342,20 +337,14 @@ } sym_set_type(left, &PyFloat_Type); sym_set_type(right, &PyFloat_Type); - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_NOS_FLOAT: { - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_TOS_FLOAT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -463,8 +452,6 @@ } sym_set_type(left, &PyUnicode_Type); sym_set_type(left, &PyUnicode_Type); - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -559,8 +546,6 @@ } case _BINARY_SUBSCR_CHECK_FUNC: { - stack_pointer += -2; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -574,13 +559,13 @@ } case _LIST_APPEND: { - stack_pointer += -2 - (oparg-1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } case _SET_ADD: { - stack_pointer += -2 - (oparg-1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -663,7 +648,9 @@ case _GET_ANEXT: { _Py_UopsSymbol *awaitable; awaitable = sym_new_not_null(ctx); - stack_pointer[-1] = awaitable; + stack_pointer[0] = awaitable; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -743,6 +730,8 @@ _Py_UopsSymbol *val0; val1 = sym_new_not_null(ctx); val0 = sym_new_not_null(ctx); + stack_pointer[-1] = val1; + stack_pointer[0] = val0; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -811,6 +800,7 @@ case _LOAD_LOCALS: { _Py_UopsSymbol *locals; locals = sym_new_not_null(ctx); + stack_pointer[0] = locals; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -937,13 +927,13 @@ } case _LIST_EXTEND: { - stack_pointer += -2 - (oparg-1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } case _SET_UPDATE: { - stack_pointer += -2 - (oparg-1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -971,19 +961,19 @@ } case _DICT_UPDATE: { - stack_pointer += -2 - (oparg - 1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } case _DICT_MERGE: { - stack_pointer += -5 - (oparg - 1); + stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; } case _MAP_ADD: { - stack_pointer += -3 - (oparg - 1); + stack_pointer += -2; assert(WITHIN_STACK_BOUNDS()); break; } @@ -1054,14 +1044,10 @@ } } } - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } case _CHECK_MANAGED_OBJECT_HAS_VALUES: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1100,8 +1086,6 @@ } } } - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1138,8 +1122,6 @@ } case _CHECK_ATTR_WITH_HINT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1170,6 +1152,7 @@ null = sym_new_null(ctx); (void)index; (void)owner; + stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); @@ -1177,8 +1160,6 @@ } case _CHECK_ATTR_CLASS: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1192,6 +1173,7 @@ null = sym_new_null(ctx); (void)descr; (void)owner; + stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); @@ -1208,8 +1190,6 @@ /* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 */ case _GUARD_DORV_NO_DICT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1357,9 +1337,7 @@ case _CHECK_EXC_MATCH: { _Py_UopsSymbol *b; b = sym_new_not_null(ctx); - stack_pointer[-2] = b; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = b; break; } @@ -1375,7 +1353,9 @@ case _IMPORT_FROM: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1393,7 +1373,9 @@ case _GET_LEN: { _Py_UopsSymbol *len; len = sym_new_not_null(ctx); - stack_pointer[-1] = len; + stack_pointer[0] = len; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1409,22 +1391,26 @@ case _MATCH_MAPPING: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } case _MATCH_SEQUENCE: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } case _MATCH_KEYS: { _Py_UopsSymbol *values_or_none; values_or_none = sym_new_not_null(ctx); - stack_pointer[-2] = values_or_none; - stack_pointer += -1; + stack_pointer[0] = values_or_none; + stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -1448,63 +1434,59 @@ case _FOR_ITER_TIER_TWO: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } /* _INSTRUMENTED_FOR_ITER is not a viable micro-op for tier 2 */ case _ITER_CHECK_LIST: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_LIST is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_LIST: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_NEXT_LIST: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); + stack_pointer[0] = next; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_CHECK_TUPLE: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_TUPLE is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_TUPLE: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_NEXT_TUPLE: { _Py_UopsSymbol *next; next = sym_new_not_null(ctx); + stack_pointer[0] = next; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } case _ITER_CHECK_RANGE: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } /* _ITER_JUMP_RANGE is not a viable micro-op for tier 2 */ case _GUARD_NOT_EXHAUSTED_RANGE: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1514,7 +1496,9 @@ iter = stack_pointer[-1]; next = sym_new_type(ctx, &PyLong_Type); (void)iter; - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1542,8 +1526,8 @@ case _WITH_EXCEPT_START: { _Py_UopsSymbol *res; res = sym_new_not_null(ctx); - stack_pointer[-5] = res; - stack_pointer += -4; + stack_pointer[0] = res; + stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -1561,14 +1545,10 @@ } case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } case _GUARD_KEYS_VERSION: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1581,6 +1561,7 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; + stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1596,6 +1577,7 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; + stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1605,18 +1587,18 @@ case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { _Py_UopsSymbol *attr; attr = sym_new_not_null(ctx); + stack_pointer[-1] = attr; break; } case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { _Py_UopsSymbol *attr; attr = sym_new_not_null(ctx); + stack_pointer[-1] = attr; break; } case _CHECK_ATTR_METHOD_LAZY_DICT: { - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1629,6 +1611,7 @@ (void)descr; attr = sym_new_not_null(ctx); self = owner; + stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); @@ -1650,6 +1633,8 @@ (void)args; func = sym_new_not_null(ctx); maybe_self = sym_new_not_null(ctx); + stack_pointer[-2 - oparg] = func; + stack_pointer[-1 - oparg] = maybe_self; break; } @@ -1682,14 +1667,10 @@ } case _CHECK_FUNCTION_VERSION: { - stack_pointer += -2 - oparg; - assert(WITHIN_STACK_BOUNDS()); break; } case _CHECK_METHOD_VERSION: { - stack_pointer += -2 - oparg; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1698,12 +1679,12 @@ _Py_UopsSymbol *self; method = sym_new_not_null(ctx); self = sym_new_not_null(ctx); + stack_pointer[-2 - oparg] = method; + stack_pointer[-1 - oparg] = self; break; } case _CHECK_IS_NOT_PY_CALLABLE: { - stack_pointer += -2 - oparg; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1723,8 +1704,6 @@ callable = stack_pointer[-2 - oparg]; sym_set_null(null); sym_set_type(callable, &PyMethod_Type); - stack_pointer += -2 - oparg; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -1736,6 +1715,8 @@ (void)callable; func = sym_new_not_null(ctx); self = sym_new_not_null(ctx); + stack_pointer[-2 - oparg] = func; + stack_pointer[-1 - oparg] = self; break; } @@ -1755,8 +1736,6 @@ callable = stack_pointer[-2 - oparg]; sym_set_type(callable, &PyFunction_Type); (void)self_or_null; - stack_pointer += -2 - oparg; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -2067,8 +2046,8 @@ bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = bottom; - stack_pointer[-1 - (oparg-1)] = top; - stack_pointer += -(oparg-1); + stack_pointer[0] = top; + stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; } @@ -2226,6 +2205,7 @@ _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; value = sym_new_const(ctx, ptr); + stack_pointer[0] = value; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); break; @@ -2254,6 +2234,7 @@ PyObject *ptr = (PyObject *)this_instr->operand; value = sym_new_const(ctx, ptr); null = sym_new_null(ctx); + stack_pointer[0] = value; stack_pointer[1] = null; stack_pointer += 2; assert(WITHIN_STACK_BOUNDS()); diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 9388eb3bef65ec..c5c52f0f8f1333 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -104,21 +104,27 @@ def write_uop( ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop - is_override = override is not None try: out.start_line() + peeks: list[Local] = [] for var in reversed(prototype.stack.inputs): code, local = stack.pop(var, extract_bits=True) if not skip_inputs: out.emit(code) + if var.peek: + peeks.append(local) if local.defined: locals[local.name] = local + # Push back the peeks, so that they remain on the logical + # stack, but their values are cached. + while peeks: + stack.push(peeks.pop()) out.emit(stack.define_output_arrays(prototype.stack.outputs)) storage = Storage.for_uop(stack, prototype, locals) if debug: args = [] for var in prototype.stack.inputs: - if not var.peek or is_override: + if not var.peek or override: args.append(var.name) out.emit(f'DEBUG_PRINTF({", ".join(args)});\n') if override: @@ -137,9 +143,6 @@ def write_uop( emit_default(out, uop) for output in storage.outputs: - if output.name in uop.deferred_refs.values(): - # We've already spilled this when emitting tokens - output.cached = False stack.push(output) out.start_line() stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=True) From 1754fc4c8116959de0fe43f69a7d4837941b0208 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 14:22:25 +0100 Subject: [PATCH 10/14] Update test --- Lib/test/test_generated_cases.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 12e9a395af7dbb..8f0012b40bc77d 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -281,17 +281,17 @@ def test_predictions(self): """ self.run_cases_test(input, output) - def test_eval_breaker(self): + def test_sync_sp(self): input = """ inst(A, (arg -- res)) { SYNC_SP(); - CHECK_EVAL_BREAKER(); + escaping_call(); res = Py_None; } inst(B, (arg -- res)) { res = Py_None; SYNC_SP(); - CHECK_EVAL_BREAKER(); + escaping_call(); } """ output = """ @@ -302,7 +302,7 @@ def test_eval_breaker(self): _PyStackRef res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); + escaping_call(); res = Py_None; stack_pointer[0] = res; stack_pointer += 1; @@ -318,7 +318,7 @@ def test_eval_breaker(self): res = Py_None; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); - CHECK_EVAL_BREAKER(); + escaping_call(); stack_pointer[0] = res; stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); From 98f9720959e99a3f4e210a312ea8717102e2a87f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 15:09:55 +0100 Subject: [PATCH 11/14] Cleanup whitespace --- Tools/cases_generator/stack.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index eac6804b52c466..4fc429b13fb3a6 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -461,4 +461,3 @@ def merge(self, other: "Storage", out: CWriter) -> "Storage": def as_comment(self) -> str: stack_comment = self.stack.as_comment() return stack_comment[:-2] + str(self.outputs) + " */" - From 0284b3fc9cf6eb3de38d548f67ec7c87dcbb15f8 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 15:52:24 +0100 Subject: [PATCH 12/14] Add tests for PEP 7 parsing and escaping call in condition --- Lib/test/test_generated_cases.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 8f0012b40bc77d..c2f60267d9d787 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -327,6 +327,38 @@ def test_sync_sp(self): """ self.run_cases_test(input, output) + + def test_pep7_condition(self): + input = """ + inst(OP, (arg1 -- out)) { + if (arg1) + out = 0; + else { + out = 1; + } + } + """ + output = "" + with self.assertRaises(Exception): + self.run_cases_test(input, output) + + + def test_escapes_in_condition(self): + input = """ + inst(OP, (arg1 -- out)) { + if (escaping_call(arg1)) { + out = 0; + } + else { + out = 1; + } + } + """ + output = "" + with self.assertRaises(Exception): + self.run_cases_test(input, output) + + def test_error_if_plain(self): input = """ inst(OP, (--)) { From 03bea71e0fbca5f3a7acc62b0ee54ceac9a78ac5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 16:50:58 +0100 Subject: [PATCH 13/14] Remove merge artifact --- Tools/cases_generator/stack.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 4fc429b13fb3a6..bd959c95ae6ce6 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -83,8 +83,6 @@ def condition(self) -> str | None: def is_array(self) -> bool: return self.item.is_array() - return Local(self.item, self.cached, self.in_memory, self.defined) - def __eq__(self, other: object) -> bool: if not isinstance(other, Local): return NotImplemented From ca2f457cfefbf57eac87aeb20df66905ed293196 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 27 Aug 2024 17:01:29 +0100 Subject: [PATCH 14/14] Remove test for escaping calls in conditions --- Lib/test/test_generated_cases.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index c2f60267d9d787..ce0d9c791eeb4c 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -339,26 +339,9 @@ def test_pep7_condition(self): } """ output = "" - with self.assertRaises(Exception): - self.run_cases_test(input, output) - - - def test_escapes_in_condition(self): - input = """ - inst(OP, (arg1 -- out)) { - if (escaping_call(arg1)) { - out = 0; - } - else { - out = 1; - } - } - """ - output = "" - with self.assertRaises(Exception): + with self.assertRaises(SyntaxError): self.run_cases_test(input, output) - def test_error_if_plain(self): input = """ inst(OP, (--)) { @@ -888,7 +871,7 @@ def test_deopt_and_exit(self): } """ output = "" - with self.assertRaises(Exception): + with self.assertRaises(SyntaxError): self.run_cases_test(input, output) def test_array_of_one(self):