https://github.com/python/cpython/commit/c535a132e40a516a7cca219b2659e85bccaa0529
commit: c535a132e40a516a7cca219b2659e85bccaa0529
branch: main
author: Mark Shannon <m...@hotpy.org>
committer: markshannon <m...@hotpy.org>
date: 2025-03-31T13:52:48+01:00
summary:

GH-131498: Another refactoring of the code generator (GH-131827)

* Rename 'defined' attribute to 'in_local' to more accurately reflect how it is 
used

* Make death of variables explicit even for array variables.

* Convert in_memory from boolean to stack offset

* Don't apply liveness analyis to optimizer generated code

* Add 'out' parameter to stack.pop

files:
M Lib/test/test_generated_cases.py
M Python/bytecodes.c
M Python/generated_cases.c.h
M Tools/cases_generator/cwriter.py
M Tools/cases_generator/generators_common.py
M Tools/cases_generator/opcode_metadata_generator.py
M Tools/cases_generator/optimizer_generator.py
M Tools/cases_generator/stack.py
M Tools/cases_generator/tier1_generator.py
M Tools/cases_generator/tier2_generator.py
M Tools/cases_generator/uop_metadata_generator.py

diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py
index 856da584ca7e9e..3f396e4da3ef7d 100644
--- a/Lib/test/test_generated_cases.py
+++ b/Lib/test/test_generated_cases.py
@@ -68,13 +68,15 @@ def test_effect_sizes(self):
             StackItem("b", None, "oparg*4"),
             StackItem("c", None, "1"),
         ]
-        stack.pop(z)
-        stack.pop(y)
-        stack.pop(x)
+        null = CWriter.null()
+        stack.pop(z, null)
+        stack.pop(y, null)
+        stack.pop(x, null)
         for out in outputs:
             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")
+        self.assertEqual(stack.physical_sp.to_c(), "0")
+        self.assertEqual(stack.logical_sp.to_c(), "1 - oparg - oparg*2 + 
oparg*4")
 
 
 class TestGeneratedCases(unittest.TestCase):
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 2d18f658702fe7..43424447bb068d 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1484,7 +1484,7 @@ dummy_func(
             (void)counter;
         }
 
-        op(_UNPACK_SEQUENCE, (seq -- output[oparg], top[0])) {
+        op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0])) {
             PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
             int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, 
top);
             Py_DECREF(seq_o);
@@ -1533,7 +1533,7 @@ dummy_func(
             DECREF_INPUTS();
         }
 
-        inst(UNPACK_EX, (seq -- left[oparg & 0xFF], unused, right[oparg >> 8], 
top[0])) {
+        inst(UNPACK_EX, (seq -- unused[oparg & 0xFF], unused, unused[oparg >> 
8], top[0])) {
             PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
             int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg & 
0xFF, oparg >> 8, top);
             Py_DECREF(seq_o);
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 416035ae29d71c..50db2c867c89e4 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -2608,9 +2608,9 @@
             _PyStackRef *callable;
             _PyStackRef *self_or_null;
             _PyStackRef *args;
-            _PyStackRef kwnames;
             _PyStackRef kwnames_in;
             _PyStackRef kwnames_out;
+            _PyStackRef kwnames;
             _PyStackRef res;
             // _SPECIALIZE_CALL_KW
             {
@@ -2791,9 +2791,9 @@
             static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache 
size");
             _PyStackRef *callable;
             _PyStackRef *null;
-            _PyStackRef kwnames;
             _PyStackRef *self_or_null;
             _PyStackRef *args;
+            _PyStackRef kwnames;
             _PyInterpreterFrame *new_frame;
             /* Skip 1 cache entry */
             // _CHECK_PEP_523
@@ -2924,9 +2924,9 @@
             opcode = CALL_KW_NON_PY;
             static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache 
size");
             _PyStackRef *callable;
-            _PyStackRef kwnames;
             _PyStackRef *self_or_null;
             _PyStackRef *args;
+            _PyStackRef kwnames;
             _PyStackRef res;
             /* Skip 1 cache entry */
             /* Skip 2 cache entries */
@@ -3057,8 +3057,8 @@
             static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache 
size");
             _PyStackRef *callable;
             _PyStackRef *self_or_null;
-            _PyStackRef kwnames;
             _PyStackRef *args;
+            _PyStackRef kwnames;
             _PyInterpreterFrame *new_frame;
             /* Skip 1 cache entry */
             // _CHECK_PEP_523
@@ -4682,8 +4682,8 @@
             PREDICTED_CONTAINS_OP:;
             _Py_CODEUNIT* const this_instr = next_instr - 2;
             (void)this_instr;
-            _PyStackRef left;
             _PyStackRef right;
+            _PyStackRef left;
             _PyStackRef b;
             // _SPECIALIZE_CONTAINS_OP
             {
diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py
index 07a785e4312fa6..2bda9fe73df83e 100644
--- a/Tools/cases_generator/cwriter.py
+++ b/Tools/cases_generator/cwriter.py
@@ -1,7 +1,7 @@
 import contextlib
 from lexer import Token
 from typing import TextIO, Iterator
-
+from io import StringIO
 
 class CWriter:
     "A writer that understands tokens and how to format C code"
@@ -18,6 +18,10 @@ def __init__(self, out: TextIO, indent: int, 
line_directives: bool):
         self.pending_spill = False
         self.pending_reload = False
 
+    @staticmethod
+    def null() -> "CWriter":
+        return CWriter(StringIO(), 0, False)
+
     def set_position(self, tkn: Token) -> None:
         if self.last_token is not None:
             if self.last_token.end_line < tkn.line:
diff --git a/Tools/cases_generator/generators_common.py 
b/Tools/cases_generator/generators_common.py
index da22cb3181470c..b192738849d18d 100644
--- a/Tools/cases_generator/generators_common.py
+++ b/Tools/cases_generator/generators_common.py
@@ -205,9 +205,9 @@ def error_if(
         next(tkn_iter)  # Semi colon
         storage.clear_inputs("at ERROR_IF")
 
-        c_offset = storage.stack.peek_offset()
+        c_offset = storage.stack.sp_offset()
         try:
-            offset = -int(c_offset)
+            offset = int(c_offset)
         except ValueError:
             offset = -1
         self.out.emit(self.goto_error(offset, label, storage))
diff --git a/Tools/cases_generator/opcode_metadata_generator.py 
b/Tools/cases_generator/opcode_metadata_generator.py
index 9cfa30bd3b2afe..02283a0b647e84 100644
--- a/Tools/cases_generator/opcode_metadata_generator.py
+++ b/Tools/cases_generator/opcode_metadata_generator.py
@@ -100,7 +100,7 @@ def generate_stack_effect_functions(analysis: Analysis, 
out: CWriter) -> None:
     def add(inst: Instruction | PseudoInstruction) -> None:
         stack = get_stack_effect(inst)
         popped = (-stack.base_offset).to_c()
-        pushed = (stack.top_offset - stack.base_offset).to_c()
+        pushed = (stack.logical_sp - stack.base_offset).to_c()
         popped_data.append((inst.name, popped))
         pushed_data.append((inst.name, pushed))
 
diff --git a/Tools/cases_generator/optimizer_generator.py 
b/Tools/cases_generator/optimizer_generator.py
index 3d89448f1fd0f8..182933e9fc4af4 100644
--- a/Tools/cases_generator/optimizer_generator.py
+++ b/Tools/cases_generator/optimizer_generator.py
@@ -72,14 +72,15 @@ def decref_inputs(
 
 
 def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None:
+    null = CWriter.null()
     for var in reversed(uop.stack.inputs):
-        stack.pop(var)
-    top_offset = stack.top_offset.copy()
+        stack.pop(var, null)
+    offset = stack.base_offset - stack.physical_sp
     for var in uop.stack.outputs:
         if var.is_array() and not var.peek and not var.name == "unused":
-            c_offset = top_offset.to_c()
+            c_offset = offset.to_c()
             out.emit(f"{var.name} = &stack_pointer[{c_offset}];\n")
-        top_offset.push(var)
+        offset = offset.push(var)
     for var in uop.stack.outputs:
         local = Local.undefined(var)
         stack.push(local)
@@ -123,9 +124,7 @@ def write_uop(
     try:
         out.start_line()
         if override:
-            code_list, storage = Storage.for_uop(stack, prototype, 
check_liveness=False)
-            for code in code_list:
-                out.emit(code)
+            storage = Storage.for_uop(stack, prototype, out, 
check_liveness=False)
         if debug:
             args = []
             for input in prototype.stack.inputs:
diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py
index 94a5d395064191..76dfc48a6b594e 100644
--- a/Tools/cases_generator/stack.py
+++ b/Tools/cases_generator/stack.py
@@ -30,79 +30,96 @@ def var_size(var: StackItem) -> str:
 
 
 @dataclass
-class StackOffset:
-    "The stack offset of the virtual base of the stack from the physical stack 
pointer"
-
-    popped: list[str]
-    pushed: list[str]
+class PointerOffset:
+    """The offset of a pointer from the reference pointer
+        The 'reference pointer' is the address of the physical stack pointer
+        at the start of the code section, as if each code section started with
+        `const PyStackRef *reference = stack_pointer`
+    """
+    numeric: int
+    positive: tuple[str, ...]
+    negative: tuple[str, ...]
 
     @staticmethod
-    def empty() -> "StackOffset":
-        return StackOffset([], [])
+    def zero() -> "PointerOffset":
+        return PointerOffset(0, (), ())
 
-    def copy(self) -> "StackOffset":
-        return StackOffset(self.popped[:], self.pushed[:])
+    def pop(self, item: StackItem) -> "PointerOffset":
+        return self - PointerOffset.from_item(item)
 
-    def pop(self, item: StackItem) -> None:
-        self.popped.append(var_size(item))
+    def push(self, item: StackItem) -> "PointerOffset":
+        return self + PointerOffset.from_item(item)
 
-    def push(self, item: StackItem) -> None:
-        self.pushed.append(var_size(item))
+    @staticmethod
+    def from_item(item: StackItem) -> "PointerOffset":
+        if not item.size:
+            return PointerOffset(1, (), ())
+        txt = item.size.strip()
+        n: tuple[str, ...] = ()
+        p: tuple[str, ...] = ()
+        try:
+            i = int(txt)
+        except ValueError:
+            i = 0
+            if txt[0] == "+":
+                txt = txt[1:]
+            if txt[0] == "-":
+                n = (txt[1:],)
+            else:
+                p = (txt,)
+        return PointerOffset(i, p, n)
 
-    def __sub__(self, other: "StackOffset") -> "StackOffset":
-        return StackOffset(self.popped + other.pushed, self.pushed + 
other.popped)
+    @staticmethod
+    def create(numeric: int, positive: tuple[str, ...], negative: tuple[str, 
...]) -> "PointerOffset":
+        positive, negative = PointerOffset._simplify(positive, negative)
+        return PointerOffset(numeric, positive, negative)
+
+    def __sub__(self, other: "PointerOffset") -> "PointerOffset":
+        return PointerOffset.create(
+            self.numeric - other.numeric,
+            self.positive + other.negative,
+            self.negative + other.positive
+        )
 
-    def __neg__(self) -> "StackOffset":
-        return StackOffset(self.pushed, self.popped)
+    def __add__(self, other: "PointerOffset") -> "PointerOffset":
+        return PointerOffset.create(
+            self.numeric + other.numeric,
+            self.positive + other.positive,
+            self.negative + other.negative
+        )
 
-    def simplify(self) -> None:
-        "Remove matching values from both the popped and pushed list"
-        if not self.popped:
-            self.pushed.sort()
-            return
-        if not self.pushed:
-            self.popped.sort()
-            return
-        # Sort the list so the lexically largest element is last.
-        popped = sorted(self.popped)
-        pushed = sorted(self.pushed)
-        self.popped = []
-        self.pushed = []
-        while popped and pushed:
-            pop = popped.pop()
-            push = pushed.pop()
-            if pop == push:
-                pass
-            elif pop > push:
-                # if pop > push, there can be no element in pushed matching 
pop.
-                self.popped.append(pop)
-                pushed.append(push)
-            else:
-                self.pushed.append(push)
-                popped.append(pop)
-        self.popped.extend(popped)
-        self.pushed.extend(pushed)
-        self.pushed.sort()
-        self.popped.sort()
+    def __neg__(self) -> "PointerOffset":
+        return PointerOffset(-self.numeric, self.negative, self.positive)
+
+    @staticmethod
+    def _simplify(positive: tuple[str, ...], negative: tuple[str, ...]) -> 
tuple[tuple[str, ...], tuple[str, ...]]:
+        p_orig: list[str] = sorted(positive)
+        n_orig: list[str] = sorted(negative)
+        p_uniq: list[str] = []
+        n_uniq: list[str] = []
+        while p_orig and n_orig:
+            p_item = p_orig.pop()
+            n_item = n_orig.pop()
+            if p_item > n_item:
+                # if p_item > n_item, there can be no element in n matching 
p_item.
+                p_uniq.append(p_item)
+                n_orig.append(n_item)
+            elif p_item < n_item:
+                n_uniq.append(n_item)
+                p_orig.append(p_item)
+            # Otherwise they are the same and cancel each other out
+        return tuple(p_orig + p_uniq), tuple(n_orig + n_uniq)
 
     def to_c(self) -> str:
-        self.simplify()
-        int_offset = 0
         symbol_offset = ""
-        for item in self.popped:
-            try:
-                int_offset -= int(item)
-            except ValueError:
-                symbol_offset += f" - {maybe_parenthesize(item)}"
-        for item in self.pushed:
-            try:
-                int_offset += int(item)
-            except ValueError:
-                symbol_offset += f" + {maybe_parenthesize(item)}"
-        if symbol_offset and not int_offset:
+        for item in self.negative:
+            symbol_offset += f" - {maybe_parenthesize(item)}"
+        for item in self.positive:
+            symbol_offset += f" + {maybe_parenthesize(item)}"
+        if symbol_offset and self.numeric == 0:
             res = symbol_offset
         else:
-            res = f"{int_offset}{symbol_offset}"
+            res = f"{self.numeric}{symbol_offset}"
         if res.startswith(" + "):
             res = res[3:]
         if res.startswith(" - "):
@@ -110,53 +127,36 @@ def to_c(self) -> str:
         return res
 
     def as_int(self) -> int | None:
-        self.simplify()
-        int_offset = 0
-        for item in self.popped:
-            try:
-                int_offset -= int(item)
-            except ValueError:
-                return None
-        for item in self.pushed:
-            try:
-                int_offset += int(item)
-            except ValueError:
-                return None
-        return int_offset
-
-    def clear(self) -> None:
-        self.popped = []
-        self.pushed = []
+        if self.positive or self.negative:
+            return None
+        return self.numeric
 
     def __bool__(self) -> bool:
-        self.simplify()
-        return bool(self.popped) or bool(self.pushed)
+        return self.numeric != 0 or bool(self.positive) or bool(self.negative)
 
-    def __eq__(self, other: object) -> bool:
-        if not isinstance(other, StackOffset):
-            return NotImplemented
-        return self.to_c() == other.to_c()
+    def __str__(self) -> str:
+        return self.to_c()
 
+    def __repr__(self) -> str:
+        return f"PointerOffset({self.to_c()})"
 
 @dataclass
 class Local:
     item: StackItem
-    memory_offset: StackOffset | None
+    memory_offset: PointerOffset | None
     in_local: bool
 
     def __repr__(self) -> str:
         return f"Local('{self.item.name}', mem={self.memory_offset}, 
local={self.in_local}, array={self.is_array()})"
 
-    #def compact_str(self) -> str:
-        #mtag = "M" if self.memory_offset else ""
-        #dtag = "D" if self.in_local else ""
-        #atag = "A" if self.is_array() else ""
-        #return f"'{self.item.name}'{mtag}{dtag}{atag}"
-
-    compact_str = __repr__
+    def compact_str(self) -> str:
+        mtag = "M" if self.memory_offset else ""
+        dtag = "D" if self.in_local else ""
+        atag = "A" if self.is_array() else ""
+        return f"'{self.item.name}'{mtag}{dtag}{atag}"
 
     @staticmethod
-    def unused(defn: StackItem, offset: StackOffset) -> "Local":
+    def unused(defn: StackItem, offset: PointerOffset | None) -> "Local":
         return Local(defn, offset, False)
 
     @staticmethod
@@ -164,7 +164,7 @@ def undefined(defn: StackItem) -> "Local":
         return Local(defn, None, False)
 
     @staticmethod
-    def from_memory(defn: StackItem, offset: StackOffset) -> "Local":
+    def from_memory(defn: StackItem, offset: PointerOffset) -> "Local":
         return Local(defn, offset, True)
 
     def kill(self) -> None:
@@ -213,15 +213,15 @@ def array_or_scalar(var: StackItem | Local) -> str:
 
 class Stack:
     def __init__(self, extract_bits: bool=True, cast_type: str = "uintptr_t") 
-> None:
-        self.top_offset = StackOffset.empty()
-        self.base_offset = StackOffset.empty()
+        self.base_offset = PointerOffset.zero()
+        self.physical_sp = PointerOffset.zero()
+        self.logical_sp = PointerOffset.zero()
         self.variables: list[Local] = []
-        self.defined: set[str] = set()
         self.extract_bits = extract_bits
         self.cast_type = cast_type
 
     def drop(self, var: StackItem, check_liveness: bool) -> None:
-        self.top_offset.pop(var)
+        self.logical_sp = self.logical_sp.pop(var)
         if self.variables:
             popped = self.variables.pop()
             if popped.is_dead() or not var.used:
@@ -229,8 +229,8 @@ def drop(self, var: StackItem, check_liveness: bool) -> 
None:
         if check_liveness:
             raise StackError(f"Dropping live value '{var.name}'")
 
-    def pop(self, var: StackItem) -> tuple[str, Local]:
-        self.top_offset.pop(var)
+    def pop(self, var: StackItem, out: CWriter) -> Local:
+        self.logical_sp = self.logical_sp.pop(var)
         indirect = "&" if var.is_array() else ""
         if self.variables:
             popped = self.variables.pop()
@@ -244,125 +244,122 @@ def pop(self, var: StackItem) -> tuple[str, Local]:
                     f"Size mismatch when popping '{popped.name}' from stack to 
assign to '{var.name}'. "
                     f"Expected {var_size(var)} got {var_size(popped.item)}"
                 )
-            if var.name in UNUSED:
-                if popped.name not in UNUSED and popped.name in self.defined:
-                    raise StackError(
-                        f"Value is declared unused, but is already cached by 
prior operation as '{popped.name}'"
-                    )
-                return "", popped
             if not var.used:
-                return "", popped
-            self.defined.add(var.name)
+                return popped
             if popped.name != var.name:
                 rename = f"{var.name} = {popped.name};\n"
                 popped.item = var
             else:
                 rename = ""
             if not popped.in_local:
-                assert popped.memory_offset is not None
+                if popped.memory_offset is None:
+                    popped.memory_offset = self.logical_sp
+                assert popped.memory_offset == self.logical_sp, (popped, 
self.as_comment())
+                offset = popped.memory_offset.to_c()
                 if var.is_array():
-                    defn = f"{var.name} = 
&stack_pointer[{self.top_offset.to_c()}];\n"
+                    defn = f"{var.name} = &stack_pointer[{offset}];\n"
                 else:
-                    defn = f"{var.name} = 
stack_pointer[{self.top_offset.to_c()}];\n"
+                    defn = f"{var.name} = stack_pointer[{offset}];\n"
                     popped.in_local = True
             else:
                 defn = rename
-            return defn, popped
+            out.emit(defn)
+            return popped
 
-        self.base_offset.pop(var)
+        self.base_offset = self.logical_sp
         if var.name in UNUSED or not var.used:
-            return "", Local.unused(var, self.base_offset)
-        self.defined.add(var.name)
+            return Local.unused(var, self.base_offset)
         cast = f"({var.type})" if (not indirect and var.type) else ""
         bits = ".bits" if cast and self.extract_bits else ""
-        assign = f"{var.name} = 
{cast}{indirect}stack_pointer[{self.base_offset.to_c()}]{bits};"
-        assign = f"{assign}\n"
-        return assign, Local.from_memory(var, self.base_offset.copy())
+        offset = (self.base_offset - self.physical_sp).to_c()
+        assign = f"{var.name} = 
{cast}{indirect}stack_pointer[{offset}]{bits};\n"
+        out.emit(assign)
+        return Local.from_memory(var, self.base_offset)
 
     def push(self, var: Local) -> None:
         assert(var not in self.variables)
         self.variables.append(var)
-        self.top_offset.push(var.item)
-        if var.item.used:
-            self.defined.add(var.name)
+        self.logical_sp = self.logical_sp.push(var.item)
 
     @staticmethod
     def _do_emit(
         out: CWriter,
         var: StackItem,
-        base_offset: StackOffset,
+        stack_offset: PointerOffset,
         cast_type: str,
         extract_bits: bool,
     ) -> None:
         cast = f"({cast_type})" if var.type else ""
         bits = ".bits" if cast and extract_bits else ""
-        out.emit(f"stack_pointer[{base_offset.to_c()}]{bits} = 
{cast}{var.name};\n")
+        out.emit(f"stack_pointer[{stack_offset.to_c()}]{bits} = 
{cast}{var.name};\n")
 
-    def _adjust_stack_pointer(self, out: CWriter, number: str) -> None:
-        if number != "0":
+    def _save_physical_sp(self, out: CWriter) -> None:
+        if self.physical_sp != self.logical_sp:
+            diff = self.logical_sp - self.physical_sp
             out.start_line()
-            out.emit(f"stack_pointer += {number};\n")
+            out.emit(f"stack_pointer += {diff.to_c()};\n")
             out.emit("assert(WITHIN_STACK_BOUNDS());\n")
+            self.physical_sp = self.logical_sp
 
     def flush(self, out: CWriter) -> None:
         out.start_line()
-        var_offset = self.base_offset.copy()
+        var_offset = self.base_offset
         for var in self.variables:
             if (
                 var.in_local and
                 not var.memory_offset and
                 not var.is_array()
             ):
-                Stack._do_emit(out, var.item, var_offset, self.cast_type, 
self.extract_bits)
-                var.memory_offset = var_offset.copy()
-            var_offset.push(var.item)
-        number = self.top_offset.to_c()
-        self._adjust_stack_pointer(out, number)
-        self.base_offset -= self.top_offset
-        self.top_offset.clear()
+                var.memory_offset = var_offset
+                stack_offset = var_offset - self.physical_sp
+                Stack._do_emit(out, var.item, stack_offset, self.cast_type, 
self.extract_bits)
+            var_offset = var_offset.push(var.item)
+        self._save_physical_sp(out)
         out.start_line()
 
     def is_flushed(self) -> bool:
-        return not self.variables and not self.base_offset and not 
self.top_offset
+        for var in self.variables:
+            if not var.in_memory():
+                return False
+        return self.physical_sp == self.logical_sp
 
-    def peek_offset(self) -> str:
-        return self.top_offset.to_c()
+    def sp_offset(self) -> str:
+        return (self.physical_sp - self.logical_sp).to_c()
 
     def as_comment(self) -> str:
         variables = ", ".join([v.compact_str() for v in self.variables])
         return (
-            f"/* Variables: {variables}. base: {self.base_offset.to_c()}. top: 
{self.top_offset.to_c()} */"
+            f"/* Variables: {variables}. base: {self.base_offset.to_c()}. sp: 
{self.physical_sp.to_c()}. logical_sp: {self.logical_sp.to_c()} */"
         )
 
     def copy(self) -> "Stack":
         other = Stack(self.extract_bits, self.cast_type)
-        other.top_offset = self.top_offset.copy()
-        other.base_offset = self.base_offset.copy()
+        other.base_offset = self.base_offset
+        other.physical_sp = self.physical_sp
+        other.logical_sp = self.logical_sp
         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
+            self.physical_sp == other.physical_sp
+            and self.logical_sp == other.logical_sp
             and self.base_offset == other.base_offset
             and self.variables == other.variables
         )
 
     def align(self, other: "Stack", out: CWriter) -> None:
-        if len(self.variables) != len(other.variables):
-            raise StackError("Cannot align stacks: differing variables")
-        if self.top_offset == other.top_offset:
+        if self.logical_sp != other.logical_sp:
+            raise StackError("Cannot align stacks: differing logical top")
+        if self.physical_sp == other.physical_sp:
             return
-        diff = self.top_offset - other.top_offset
-        try:
-            self.top_offset -= diff
-            self.base_offset -= diff
-            self._adjust_stack_pointer(out, diff.to_c())
-        except ValueError:
-            raise StackError("Cannot align stacks: cannot adjust stack 
pointer")
+        diff = other.physical_sp - self.physical_sp
+        out.start_line()
+        out.emit(f"stack_pointer += {diff.to_c()};\n")
+        out.emit("assert(WITHIN_STACK_BOUNDS());\n")
+        self.physical_sp = other.physical_sp
 
     def merge(self, other: "Stack", out: CWriter) -> None:
         if len(self.variables) != len(other.variables):
@@ -394,15 +391,16 @@ def stacks(inst: Instruction | PseudoInstruction) -> 
Iterator[StackEffect]:
 
 def apply_stack_effect(stack: Stack, effect: StackEffect) -> None:
     locals: dict[str, Local] = {}
+    null = CWriter.null()
     for var in reversed(effect.inputs):
-        _, local = stack.pop(var)
+        local = stack.pop(var, null)
         if var.name != "unused":
             locals[local.name] = local
     for var in effect.outputs:
         if var.name in locals:
             local = locals[var.name]
         else:
-            local = Local.unused(var, stack.base_offset)
+            local = Local.unused(var, None)
         stack.push(local)
 
 
@@ -521,13 +519,11 @@ def reload(self, out: CWriter) -> None:
             out.emit_reload()
 
     @staticmethod
-    def for_uop(stack: Stack, uop: Uop, check_liveness: bool = True) -> 
tuple[list[str], "Storage"]:
-        code_list: list[str] = []
+    def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = 
True) -> "Storage":
         inputs: list[Local] = []
         peeks: list[Local] = []
         for input in reversed(uop.stack.inputs):
-            code, local = stack.pop(input)
-            code_list.append(code)
+            local = stack.pop(input, out)
             if input.peek:
                 peeks.append(local)
             else:
@@ -536,18 +532,16 @@ def for_uop(stack: Stack, uop: Uop, check_liveness: bool 
= True) -> tuple[list[s
         peeks.reverse()
         for peek in peeks:
             stack.push(peek)
-        top_offset = stack.top_offset.copy()
+        offset = stack.logical_sp - stack.physical_sp
         for ouput in uop.stack.outputs:
             if ouput.is_array() and ouput.used and not ouput.peek:
-                c_offset = top_offset.to_c()
-                top_offset.push(ouput)
-                code_list.append(f"{ouput.name} = 
&stack_pointer[{c_offset}];\n")
-            else:
-                top_offset.push(ouput)
+                c_offset = offset.to_c()
+                out.emit(f"{ouput.name} = &stack_pointer[{c_offset}];\n")
+            offset = offset.push(ouput)
         for var in inputs:
             stack.push(var)
         outputs = [ Local.undefined(var) for var in uop.stack.outputs if not 
var.peek ]
-        return code_list, Storage(stack, inputs, outputs, check_liveness)
+        return Storage(stack, inputs, outputs, check_liveness)
 
     @staticmethod
     def copy_list(arg: list[Local]) -> list[Local]:
@@ -712,7 +706,7 @@ def close_variable(var: Local, overwrite: str) -> None:
         self.stack.drop(self.inputs[0].item, False)
         output_in_place = self.outputs and output is self.outputs[0] and 
lowest.memory_offset is not None
         if output_in_place:
-            output.memory_offset = lowest.memory_offset.copy()  # type: 
ignore[union-attr]
+            output.memory_offset = lowest.memory_offset  # type: 
ignore[union-attr]
         else:
             self.stack.flush(out)
         if output is not None:
@@ -721,5 +715,4 @@ def close_variable(var: Local, overwrite: str) -> None:
         if output_in_place:
             self.stack.flush(out)
         if output is not None:
-            code, output = self.stack.pop(output.item)
-            out.emit(code)
+            output = self.stack.pop(output.item, out)
diff --git a/Tools/cases_generator/tier1_generator.py 
b/Tools/cases_generator/tier1_generator.py
index 20cad326a8a2c0..f784b6b3371908 100644
--- a/Tools/cases_generator/tier1_generator.py
+++ b/Tools/cases_generator/tier1_generator.py
@@ -48,18 +48,17 @@ def declare_variables(inst: Instruction, out: CWriter) -> 
None:
         stack = get_stack_effect(inst)
     except StackError as ex:
         raise analysis_error(ex.args[0], inst.where) from None
-    required = set(stack.defined)
-    required.discard("unused")
+    seen = {"unused"}
     for part in inst.parts:
         if not isinstance(part, Uop):
             continue
         for var in part.stack.inputs:
-            if var.name in required:
-                required.remove(var.name)
+            if var.used and var.name not in seen:
+                seen.add(var.name)
                 declare_variable(var, out)
         for var in part.stack.outputs:
-            if var.name in required:
-                required.remove(var.name)
+            if var.used and var.name not in seen:
+                seen.add(var.name)
                 declare_variable(var, out)
 
 
@@ -86,10 +85,8 @@ def write_uop(
         if braces:
             emitter.out.emit(f"// {uop.name}\n")
             emitter.emit("{\n")
-        code_list, storage = Storage.for_uop(stack, uop)
+        storage = Storage.for_uop(stack, uop, emitter.out)
         emitter._print_storage(storage)
-        for code in code_list:
-            emitter.emit(code)
 
         for cache in uop.caches:
             if cache.name != "unused":
diff --git a/Tools/cases_generator/tier2_generator.py 
b/Tools/cases_generator/tier2_generator.py
index 8a684c8328ef7b..8c78388ba53481 100644
--- a/Tools/cases_generator/tier2_generator.py
+++ b/Tools/cases_generator/tier2_generator.py
@@ -34,11 +34,11 @@
 
 
 def declare_variable(
-    var: StackItem, uop: Uop, required: set[str], out: CWriter
+    var: StackItem, uop: Uop, seen: set[str], out: CWriter
 ) -> None:
-    if not var.used or var.name not in required:
+    if not var.used or var.name in seen:
         return
-    required.remove(var.name)
+    seen.add(var.name)
     type, null = type_and_null(var)
     space = " " if type[-1].isalnum() else ""
     out.emit(f"{type}{space}{var.name};\n")
@@ -46,16 +46,16 @@ def declare_variable(
 
 def declare_variables(uop: Uop, out: CWriter) -> None:
     stack = Stack()
+    null = CWriter.null()
     for var in reversed(uop.stack.inputs):
-        stack.pop(var)
+        stack.pop(var, null)
     for var in uop.stack.outputs:
         stack.push(Local.undefined(var))
-    required = set(stack.defined)
-    required.discard("unused")
+    seen = {"unused"}
     for var in reversed(uop.stack.inputs):
-        declare_variable(var, uop, required, out)
+        declare_variable(var, uop, seen, out)
     for var in uop.stack.outputs:
-        declare_variable(var, uop, required, out)
+        declare_variable(var, uop, seen, out)
 
 
 class Tier2Emitter(Emitter):
@@ -143,9 +143,7 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> 
Stack:
         elif uop.properties.const_oparg >= 0:
             emitter.emit(f"oparg = {uop.properties.const_oparg};\n")
             emitter.emit(f"assert(oparg == CURRENT_OPARG());\n")
-        code_list, storage = Storage.for_uop(stack, uop)
-        for code in code_list:
-            emitter.emit(code)
+        storage = Storage.for_uop(stack, uop, emitter.out)
         idx = 0
         for cache in uop.caches:
             if cache.name != "unused":
diff --git a/Tools/cases_generator/uop_metadata_generator.py 
b/Tools/cases_generator/uop_metadata_generator.py
index 6eb022899d6cae..6f995e5c46bfcf 100644
--- a/Tools/cases_generator/uop_metadata_generator.py
+++ b/Tools/cases_generator/uop_metadata_generator.py
@@ -47,13 +47,14 @@ def generate_names_and_flags(analysis: Analysis, out: 
CWriter) -> None:
     out.emit("};\n")
     out.emit("int _PyUop_num_popped(int opcode, int oparg)\n{\n")
     out.emit("switch(opcode) {\n")
+    null = CWriter.null()
     for uop in analysis.uops.values():
         if uop.is_viable() and uop.properties.tier != 1:
             stack = Stack()
             for var in reversed(uop.stack.inputs):
                 if var.peek:
                     break
-                stack.pop(var)
+                stack.pop(var, null)
             popped = (-stack.base_offset).to_c()
             out.emit(f"case {uop.name}:\n")
             out.emit(f"    return {popped};\n")

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to