mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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
This commit is contained in:
parent
ba11f45dd9
commit
c535a132e4
11 changed files with 206 additions and 212 deletions
|
@ -68,13 +68,15 @@ class TestEffects(unittest.TestCase):
|
||||||
StackItem("b", None, "oparg*4"),
|
StackItem("b", None, "oparg*4"),
|
||||||
StackItem("c", None, "1"),
|
StackItem("c", None, "1"),
|
||||||
]
|
]
|
||||||
stack.pop(z)
|
null = CWriter.null()
|
||||||
stack.pop(y)
|
stack.pop(z, null)
|
||||||
stack.pop(x)
|
stack.pop(y, null)
|
||||||
|
stack.pop(x, null)
|
||||||
for out in outputs:
|
for out in outputs:
|
||||||
stack.push(Local.undefined(out))
|
stack.push(Local.undefined(out))
|
||||||
self.assertEqual(stack.base_offset.to_c(), "-1 - oparg - oparg*2")
|
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):
|
class TestGeneratedCases(unittest.TestCase):
|
||||||
|
|
|
@ -1484,7 +1484,7 @@ dummy_func(
|
||||||
(void)counter;
|
(void)counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
op(_UNPACK_SEQUENCE, (seq -- output[oparg], top[0])) {
|
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0])) {
|
||||||
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
|
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
|
||||||
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, top);
|
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, top);
|
||||||
Py_DECREF(seq_o);
|
Py_DECREF(seq_o);
|
||||||
|
@ -1533,7 +1533,7 @@ dummy_func(
|
||||||
DECREF_INPUTS();
|
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);
|
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
|
||||||
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg & 0xFF, oparg >> 8, top);
|
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg & 0xFF, oparg >> 8, top);
|
||||||
Py_DECREF(seq_o);
|
Py_DECREF(seq_o);
|
||||||
|
|
10
Python/generated_cases.c.h
generated
10
Python/generated_cases.c.h
generated
|
@ -2608,9 +2608,9 @@
|
||||||
_PyStackRef *callable;
|
_PyStackRef *callable;
|
||||||
_PyStackRef *self_or_null;
|
_PyStackRef *self_or_null;
|
||||||
_PyStackRef *args;
|
_PyStackRef *args;
|
||||||
_PyStackRef kwnames;
|
|
||||||
_PyStackRef kwnames_in;
|
_PyStackRef kwnames_in;
|
||||||
_PyStackRef kwnames_out;
|
_PyStackRef kwnames_out;
|
||||||
|
_PyStackRef kwnames;
|
||||||
_PyStackRef res;
|
_PyStackRef res;
|
||||||
// _SPECIALIZE_CALL_KW
|
// _SPECIALIZE_CALL_KW
|
||||||
{
|
{
|
||||||
|
@ -2791,9 +2791,9 @@
|
||||||
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
||||||
_PyStackRef *callable;
|
_PyStackRef *callable;
|
||||||
_PyStackRef *null;
|
_PyStackRef *null;
|
||||||
_PyStackRef kwnames;
|
|
||||||
_PyStackRef *self_or_null;
|
_PyStackRef *self_or_null;
|
||||||
_PyStackRef *args;
|
_PyStackRef *args;
|
||||||
|
_PyStackRef kwnames;
|
||||||
_PyInterpreterFrame *new_frame;
|
_PyInterpreterFrame *new_frame;
|
||||||
/* Skip 1 cache entry */
|
/* Skip 1 cache entry */
|
||||||
// _CHECK_PEP_523
|
// _CHECK_PEP_523
|
||||||
|
@ -2924,9 +2924,9 @@
|
||||||
opcode = CALL_KW_NON_PY;
|
opcode = CALL_KW_NON_PY;
|
||||||
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
||||||
_PyStackRef *callable;
|
_PyStackRef *callable;
|
||||||
_PyStackRef kwnames;
|
|
||||||
_PyStackRef *self_or_null;
|
_PyStackRef *self_or_null;
|
||||||
_PyStackRef *args;
|
_PyStackRef *args;
|
||||||
|
_PyStackRef kwnames;
|
||||||
_PyStackRef res;
|
_PyStackRef res;
|
||||||
/* Skip 1 cache entry */
|
/* Skip 1 cache entry */
|
||||||
/* Skip 2 cache entries */
|
/* Skip 2 cache entries */
|
||||||
|
@ -3057,8 +3057,8 @@
|
||||||
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
static_assert(INLINE_CACHE_ENTRIES_CALL_KW == 3, "incorrect cache size");
|
||||||
_PyStackRef *callable;
|
_PyStackRef *callable;
|
||||||
_PyStackRef *self_or_null;
|
_PyStackRef *self_or_null;
|
||||||
_PyStackRef kwnames;
|
|
||||||
_PyStackRef *args;
|
_PyStackRef *args;
|
||||||
|
_PyStackRef kwnames;
|
||||||
_PyInterpreterFrame *new_frame;
|
_PyInterpreterFrame *new_frame;
|
||||||
/* Skip 1 cache entry */
|
/* Skip 1 cache entry */
|
||||||
// _CHECK_PEP_523
|
// _CHECK_PEP_523
|
||||||
|
@ -4682,8 +4682,8 @@
|
||||||
PREDICTED_CONTAINS_OP:;
|
PREDICTED_CONTAINS_OP:;
|
||||||
_Py_CODEUNIT* const this_instr = next_instr - 2;
|
_Py_CODEUNIT* const this_instr = next_instr - 2;
|
||||||
(void)this_instr;
|
(void)this_instr;
|
||||||
_PyStackRef left;
|
|
||||||
_PyStackRef right;
|
_PyStackRef right;
|
||||||
|
_PyStackRef left;
|
||||||
_PyStackRef b;
|
_PyStackRef b;
|
||||||
// _SPECIALIZE_CONTAINS_OP
|
// _SPECIALIZE_CONTAINS_OP
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
from lexer import Token
|
from lexer import Token
|
||||||
from typing import TextIO, Iterator
|
from typing import TextIO, Iterator
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
class CWriter:
|
class CWriter:
|
||||||
"A writer that understands tokens and how to format C code"
|
"A writer that understands tokens and how to format C code"
|
||||||
|
@ -18,6 +18,10 @@ class CWriter:
|
||||||
self.pending_spill = False
|
self.pending_spill = False
|
||||||
self.pending_reload = False
|
self.pending_reload = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def null() -> "CWriter":
|
||||||
|
return CWriter(StringIO(), 0, False)
|
||||||
|
|
||||||
def set_position(self, tkn: Token) -> None:
|
def set_position(self, tkn: Token) -> None:
|
||||||
if self.last_token is not None:
|
if self.last_token is not None:
|
||||||
if self.last_token.end_line < tkn.line:
|
if self.last_token.end_line < tkn.line:
|
||||||
|
|
|
@ -205,9 +205,9 @@ class Emitter:
|
||||||
next(tkn_iter) # Semi colon
|
next(tkn_iter) # Semi colon
|
||||||
storage.clear_inputs("at ERROR_IF")
|
storage.clear_inputs("at ERROR_IF")
|
||||||
|
|
||||||
c_offset = storage.stack.peek_offset()
|
c_offset = storage.stack.sp_offset()
|
||||||
try:
|
try:
|
||||||
offset = -int(c_offset)
|
offset = int(c_offset)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
offset = -1
|
offset = -1
|
||||||
self.out.emit(self.goto_error(offset, label, storage))
|
self.out.emit(self.goto_error(offset, label, storage))
|
||||||
|
|
|
@ -100,7 +100,7 @@ def generate_stack_effect_functions(analysis: Analysis, out: CWriter) -> None:
|
||||||
def add(inst: Instruction | PseudoInstruction) -> None:
|
def add(inst: Instruction | PseudoInstruction) -> None:
|
||||||
stack = get_stack_effect(inst)
|
stack = get_stack_effect(inst)
|
||||||
popped = (-stack.base_offset).to_c()
|
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))
|
popped_data.append((inst.name, popped))
|
||||||
pushed_data.append((inst.name, pushed))
|
pushed_data.append((inst.name, pushed))
|
||||||
|
|
||||||
|
|
|
@ -72,14 +72,15 @@ def decref_inputs(
|
||||||
|
|
||||||
|
|
||||||
def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None:
|
def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None:
|
||||||
|
null = CWriter.null()
|
||||||
for var in reversed(uop.stack.inputs):
|
for var in reversed(uop.stack.inputs):
|
||||||
stack.pop(var)
|
stack.pop(var, null)
|
||||||
top_offset = stack.top_offset.copy()
|
offset = stack.base_offset - stack.physical_sp
|
||||||
for var in uop.stack.outputs:
|
for var in uop.stack.outputs:
|
||||||
if var.is_array() and not var.peek and not var.name == "unused":
|
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")
|
out.emit(f"{var.name} = &stack_pointer[{c_offset}];\n")
|
||||||
top_offset.push(var)
|
offset = offset.push(var)
|
||||||
for var in uop.stack.outputs:
|
for var in uop.stack.outputs:
|
||||||
local = Local.undefined(var)
|
local = Local.undefined(var)
|
||||||
stack.push(local)
|
stack.push(local)
|
||||||
|
@ -123,9 +124,7 @@ def write_uop(
|
||||||
try:
|
try:
|
||||||
out.start_line()
|
out.start_line()
|
||||||
if override:
|
if override:
|
||||||
code_list, storage = Storage.for_uop(stack, prototype, check_liveness=False)
|
storage = Storage.for_uop(stack, prototype, out, check_liveness=False)
|
||||||
for code in code_list:
|
|
||||||
out.emit(code)
|
|
||||||
if debug:
|
if debug:
|
||||||
args = []
|
args = []
|
||||||
for input in prototype.stack.inputs:
|
for input in prototype.stack.inputs:
|
||||||
|
|
|
@ -30,79 +30,96 @@ def var_size(var: StackItem) -> str:
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class StackOffset:
|
class PointerOffset:
|
||||||
"The stack offset of the virtual base of the stack from the physical stack pointer"
|
"""The offset of a pointer from the reference pointer
|
||||||
|
The 'reference pointer' is the address of the physical stack pointer
|
||||||
popped: list[str]
|
at the start of the code section, as if each code section started with
|
||||||
pushed: list[str]
|
`const PyStackRef *reference = stack_pointer`
|
||||||
|
"""
|
||||||
|
numeric: int
|
||||||
|
positive: tuple[str, ...]
|
||||||
|
negative: tuple[str, ...]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def empty() -> "StackOffset":
|
def zero() -> "PointerOffset":
|
||||||
return StackOffset([], [])
|
return PointerOffset(0, (), ())
|
||||||
|
|
||||||
def copy(self) -> "StackOffset":
|
def pop(self, item: StackItem) -> "PointerOffset":
|
||||||
return StackOffset(self.popped[:], self.pushed[:])
|
return self - PointerOffset.from_item(item)
|
||||||
|
|
||||||
def pop(self, item: StackItem) -> None:
|
def push(self, item: StackItem) -> "PointerOffset":
|
||||||
self.popped.append(var_size(item))
|
return self + PointerOffset.from_item(item)
|
||||||
|
|
||||||
def push(self, item: StackItem) -> None:
|
@staticmethod
|
||||||
self.pushed.append(var_size(item))
|
def from_item(item: StackItem) -> "PointerOffset":
|
||||||
|
if not item.size:
|
||||||
def __sub__(self, other: "StackOffset") -> "StackOffset":
|
return PointerOffset(1, (), ())
|
||||||
return StackOffset(self.popped + other.pushed, self.pushed + other.popped)
|
txt = item.size.strip()
|
||||||
|
n: tuple[str, ...] = ()
|
||||||
def __neg__(self) -> "StackOffset":
|
p: tuple[str, ...] = ()
|
||||||
return StackOffset(self.pushed, self.popped)
|
try:
|
||||||
|
i = int(txt)
|
||||||
def simplify(self) -> None:
|
except ValueError:
|
||||||
"Remove matching values from both the popped and pushed list"
|
i = 0
|
||||||
if not self.popped:
|
if txt[0] == "+":
|
||||||
self.pushed.sort()
|
txt = txt[1:]
|
||||||
return
|
if txt[0] == "-":
|
||||||
if not self.pushed:
|
n = (txt[1:],)
|
||||||
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:
|
else:
|
||||||
self.pushed.append(push)
|
p = (txt,)
|
||||||
popped.append(pop)
|
return PointerOffset(i, p, n)
|
||||||
self.popped.extend(popped)
|
|
||||||
self.pushed.extend(pushed)
|
@staticmethod
|
||||||
self.pushed.sort()
|
def create(numeric: int, positive: tuple[str, ...], negative: tuple[str, ...]) -> "PointerOffset":
|
||||||
self.popped.sort()
|
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 __add__(self, other: "PointerOffset") -> "PointerOffset":
|
||||||
|
return PointerOffset.create(
|
||||||
|
self.numeric + other.numeric,
|
||||||
|
self.positive + other.positive,
|
||||||
|
self.negative + other.negative
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
def to_c(self) -> str:
|
||||||
self.simplify()
|
|
||||||
int_offset = 0
|
|
||||||
symbol_offset = ""
|
symbol_offset = ""
|
||||||
for item in self.popped:
|
for item in self.negative:
|
||||||
try:
|
|
||||||
int_offset -= int(item)
|
|
||||||
except ValueError:
|
|
||||||
symbol_offset += f" - {maybe_parenthesize(item)}"
|
symbol_offset += f" - {maybe_parenthesize(item)}"
|
||||||
for item in self.pushed:
|
for item in self.positive:
|
||||||
try:
|
|
||||||
int_offset += int(item)
|
|
||||||
except ValueError:
|
|
||||||
symbol_offset += f" + {maybe_parenthesize(item)}"
|
symbol_offset += f" + {maybe_parenthesize(item)}"
|
||||||
if symbol_offset and not int_offset:
|
if symbol_offset and self.numeric == 0:
|
||||||
res = symbol_offset
|
res = symbol_offset
|
||||||
else:
|
else:
|
||||||
res = f"{int_offset}{symbol_offset}"
|
res = f"{self.numeric}{symbol_offset}"
|
||||||
if res.startswith(" + "):
|
if res.startswith(" + "):
|
||||||
res = res[3:]
|
res = res[3:]
|
||||||
if res.startswith(" - "):
|
if res.startswith(" - "):
|
||||||
|
@ -110,53 +127,36 @@ class StackOffset:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def as_int(self) -> int | None:
|
def as_int(self) -> int | None:
|
||||||
self.simplify()
|
if self.positive or self.negative:
|
||||||
int_offset = 0
|
|
||||||
for item in self.popped:
|
|
||||||
try:
|
|
||||||
int_offset -= int(item)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
return None
|
||||||
for item in self.pushed:
|
return self.numeric
|
||||||
try:
|
|
||||||
int_offset += int(item)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
return int_offset
|
|
||||||
|
|
||||||
def clear(self) -> None:
|
|
||||||
self.popped = []
|
|
||||||
self.pushed = []
|
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
self.simplify()
|
return self.numeric != 0 or bool(self.positive) or bool(self.negative)
|
||||||
return bool(self.popped) or bool(self.pushed)
|
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __str__(self) -> str:
|
||||||
if not isinstance(other, StackOffset):
|
return self.to_c()
|
||||||
return NotImplemented
|
|
||||||
return self.to_c() == other.to_c()
|
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"PointerOffset({self.to_c()})"
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Local:
|
class Local:
|
||||||
item: StackItem
|
item: StackItem
|
||||||
memory_offset: StackOffset | None
|
memory_offset: PointerOffset | None
|
||||||
in_local: bool
|
in_local: bool
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Local('{self.item.name}', mem={self.memory_offset}, local={self.in_local}, array={self.is_array()})"
|
return f"Local('{self.item.name}', mem={self.memory_offset}, local={self.in_local}, array={self.is_array()})"
|
||||||
|
|
||||||
#def compact_str(self) -> str:
|
def compact_str(self) -> str:
|
||||||
#mtag = "M" if self.memory_offset else ""
|
mtag = "M" if self.memory_offset else ""
|
||||||
#dtag = "D" if self.in_local else ""
|
dtag = "D" if self.in_local else ""
|
||||||
#atag = "A" if self.is_array() else ""
|
atag = "A" if self.is_array() else ""
|
||||||
#return f"'{self.item.name}'{mtag}{dtag}{atag}"
|
return f"'{self.item.name}'{mtag}{dtag}{atag}"
|
||||||
|
|
||||||
compact_str = __repr__
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unused(defn: StackItem, offset: StackOffset) -> "Local":
|
def unused(defn: StackItem, offset: PointerOffset | None) -> "Local":
|
||||||
return Local(defn, offset, False)
|
return Local(defn, offset, False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -164,7 +164,7 @@ class Local:
|
||||||
return Local(defn, None, False)
|
return Local(defn, None, False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_memory(defn: StackItem, offset: StackOffset) -> "Local":
|
def from_memory(defn: StackItem, offset: PointerOffset) -> "Local":
|
||||||
return Local(defn, offset, True)
|
return Local(defn, offset, True)
|
||||||
|
|
||||||
def kill(self) -> None:
|
def kill(self) -> None:
|
||||||
|
@ -213,15 +213,15 @@ def array_or_scalar(var: StackItem | Local) -> str:
|
||||||
|
|
||||||
class Stack:
|
class Stack:
|
||||||
def __init__(self, extract_bits: bool=True, cast_type: str = "uintptr_t") -> None:
|
def __init__(self, extract_bits: bool=True, cast_type: str = "uintptr_t") -> None:
|
||||||
self.top_offset = StackOffset.empty()
|
self.base_offset = PointerOffset.zero()
|
||||||
self.base_offset = StackOffset.empty()
|
self.physical_sp = PointerOffset.zero()
|
||||||
|
self.logical_sp = PointerOffset.zero()
|
||||||
self.variables: list[Local] = []
|
self.variables: list[Local] = []
|
||||||
self.defined: set[str] = set()
|
|
||||||
self.extract_bits = extract_bits
|
self.extract_bits = extract_bits
|
||||||
self.cast_type = cast_type
|
self.cast_type = cast_type
|
||||||
|
|
||||||
def drop(self, var: StackItem, check_liveness: bool) -> None:
|
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:
|
if self.variables:
|
||||||
popped = self.variables.pop()
|
popped = self.variables.pop()
|
||||||
if popped.is_dead() or not var.used:
|
if popped.is_dead() or not var.used:
|
||||||
|
@ -229,8 +229,8 @@ class Stack:
|
||||||
if check_liveness:
|
if check_liveness:
|
||||||
raise StackError(f"Dropping live value '{var.name}'")
|
raise StackError(f"Dropping live value '{var.name}'")
|
||||||
|
|
||||||
def pop(self, var: StackItem) -> tuple[str, Local]:
|
def pop(self, var: StackItem, out: CWriter) -> Local:
|
||||||
self.top_offset.pop(var)
|
self.logical_sp = self.logical_sp.pop(var)
|
||||||
indirect = "&" if var.is_array() else ""
|
indirect = "&" if var.is_array() else ""
|
||||||
if self.variables:
|
if self.variables:
|
||||||
popped = self.variables.pop()
|
popped = self.variables.pop()
|
||||||
|
@ -244,125 +244,122 @@ class Stack:
|
||||||
f"Size mismatch when popping '{popped.name}' from stack to assign to '{var.name}'. "
|
f"Size mismatch when popping '{popped.name}' from stack to assign to '{var.name}'. "
|
||||||
f"Expected {var_size(var)} got {var_size(popped.item)}"
|
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:
|
if not var.used:
|
||||||
return "", popped
|
return popped
|
||||||
self.defined.add(var.name)
|
|
||||||
if popped.name != var.name:
|
if popped.name != var.name:
|
||||||
rename = f"{var.name} = {popped.name};\n"
|
rename = f"{var.name} = {popped.name};\n"
|
||||||
popped.item = var
|
popped.item = var
|
||||||
else:
|
else:
|
||||||
rename = ""
|
rename = ""
|
||||||
if not popped.in_local:
|
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():
|
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:
|
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
|
popped.in_local = True
|
||||||
else:
|
else:
|
||||||
defn = rename
|
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:
|
if var.name in UNUSED or not var.used:
|
||||||
return "", Local.unused(var, self.base_offset)
|
return Local.unused(var, self.base_offset)
|
||||||
self.defined.add(var.name)
|
|
||||||
cast = f"({var.type})" if (not indirect and var.type) else ""
|
cast = f"({var.type})" if (not indirect and var.type) else ""
|
||||||
bits = ".bits" if cast and self.extract_bits else ""
|
bits = ".bits" if cast and self.extract_bits else ""
|
||||||
assign = f"{var.name} = {cast}{indirect}stack_pointer[{self.base_offset.to_c()}]{bits};"
|
offset = (self.base_offset - self.physical_sp).to_c()
|
||||||
assign = f"{assign}\n"
|
assign = f"{var.name} = {cast}{indirect}stack_pointer[{offset}]{bits};\n"
|
||||||
return assign, Local.from_memory(var, self.base_offset.copy())
|
out.emit(assign)
|
||||||
|
return Local.from_memory(var, self.base_offset)
|
||||||
|
|
||||||
def push(self, var: Local) -> None:
|
def push(self, var: Local) -> None:
|
||||||
assert(var not in self.variables)
|
assert(var not in self.variables)
|
||||||
self.variables.append(var)
|
self.variables.append(var)
|
||||||
self.top_offset.push(var.item)
|
self.logical_sp = self.logical_sp.push(var.item)
|
||||||
if var.item.used:
|
|
||||||
self.defined.add(var.name)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _do_emit(
|
def _do_emit(
|
||||||
out: CWriter,
|
out: CWriter,
|
||||||
var: StackItem,
|
var: StackItem,
|
||||||
base_offset: StackOffset,
|
stack_offset: PointerOffset,
|
||||||
cast_type: str,
|
cast_type: str,
|
||||||
extract_bits: bool,
|
extract_bits: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
cast = f"({cast_type})" if var.type else ""
|
cast = f"({cast_type})" if var.type else ""
|
||||||
bits = ".bits" if cast and extract_bits 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:
|
def _save_physical_sp(self, out: CWriter) -> None:
|
||||||
if number != "0":
|
if self.physical_sp != self.logical_sp:
|
||||||
|
diff = self.logical_sp - self.physical_sp
|
||||||
out.start_line()
|
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")
|
out.emit("assert(WITHIN_STACK_BOUNDS());\n")
|
||||||
|
self.physical_sp = self.logical_sp
|
||||||
|
|
||||||
def flush(self, out: CWriter) -> None:
|
def flush(self, out: CWriter) -> None:
|
||||||
out.start_line()
|
out.start_line()
|
||||||
var_offset = self.base_offset.copy()
|
var_offset = self.base_offset
|
||||||
for var in self.variables:
|
for var in self.variables:
|
||||||
if (
|
if (
|
||||||
var.in_local and
|
var.in_local and
|
||||||
not var.memory_offset and
|
not var.memory_offset and
|
||||||
not var.is_array()
|
not var.is_array()
|
||||||
):
|
):
|
||||||
Stack._do_emit(out, var.item, var_offset, self.cast_type, self.extract_bits)
|
var.memory_offset = var_offset
|
||||||
var.memory_offset = var_offset.copy()
|
stack_offset = var_offset - self.physical_sp
|
||||||
var_offset.push(var.item)
|
Stack._do_emit(out, var.item, stack_offset, self.cast_type, self.extract_bits)
|
||||||
number = self.top_offset.to_c()
|
var_offset = var_offset.push(var.item)
|
||||||
self._adjust_stack_pointer(out, number)
|
self._save_physical_sp(out)
|
||||||
self.base_offset -= self.top_offset
|
|
||||||
self.top_offset.clear()
|
|
||||||
out.start_line()
|
out.start_line()
|
||||||
|
|
||||||
def is_flushed(self) -> bool:
|
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:
|
def sp_offset(self) -> str:
|
||||||
return self.top_offset.to_c()
|
return (self.physical_sp - self.logical_sp).to_c()
|
||||||
|
|
||||||
def as_comment(self) -> str:
|
def as_comment(self) -> str:
|
||||||
variables = ", ".join([v.compact_str() for v in self.variables])
|
variables = ", ".join([v.compact_str() for v in self.variables])
|
||||||
return (
|
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":
|
def copy(self) -> "Stack":
|
||||||
other = Stack(self.extract_bits, self.cast_type)
|
other = Stack(self.extract_bits, self.cast_type)
|
||||||
other.top_offset = self.top_offset.copy()
|
other.base_offset = self.base_offset
|
||||||
other.base_offset = self.base_offset.copy()
|
other.physical_sp = self.physical_sp
|
||||||
|
other.logical_sp = self.logical_sp
|
||||||
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
|
return other
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, Stack):
|
if not isinstance(other, Stack):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return (
|
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.base_offset == other.base_offset
|
||||||
and self.variables == other.variables
|
and self.variables == other.variables
|
||||||
)
|
)
|
||||||
|
|
||||||
def align(self, other: "Stack", out: CWriter) -> None:
|
def align(self, other: "Stack", out: CWriter) -> None:
|
||||||
if len(self.variables) != len(other.variables):
|
if self.logical_sp != other.logical_sp:
|
||||||
raise StackError("Cannot align stacks: differing variables")
|
raise StackError("Cannot align stacks: differing logical top")
|
||||||
if self.top_offset == other.top_offset:
|
if self.physical_sp == other.physical_sp:
|
||||||
return
|
return
|
||||||
diff = self.top_offset - other.top_offset
|
diff = other.physical_sp - self.physical_sp
|
||||||
try:
|
out.start_line()
|
||||||
self.top_offset -= diff
|
out.emit(f"stack_pointer += {diff.to_c()};\n")
|
||||||
self.base_offset -= diff
|
out.emit("assert(WITHIN_STACK_BOUNDS());\n")
|
||||||
self._adjust_stack_pointer(out, diff.to_c())
|
self.physical_sp = other.physical_sp
|
||||||
except ValueError:
|
|
||||||
raise StackError("Cannot align stacks: cannot adjust stack pointer")
|
|
||||||
|
|
||||||
def merge(self, other: "Stack", out: CWriter) -> None:
|
def merge(self, other: "Stack", out: CWriter) -> None:
|
||||||
if len(self.variables) != len(other.variables):
|
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:
|
def apply_stack_effect(stack: Stack, effect: StackEffect) -> None:
|
||||||
locals: dict[str, Local] = {}
|
locals: dict[str, Local] = {}
|
||||||
|
null = CWriter.null()
|
||||||
for var in reversed(effect.inputs):
|
for var in reversed(effect.inputs):
|
||||||
_, local = stack.pop(var)
|
local = stack.pop(var, null)
|
||||||
if var.name != "unused":
|
if var.name != "unused":
|
||||||
locals[local.name] = local
|
locals[local.name] = local
|
||||||
for var in effect.outputs:
|
for var in effect.outputs:
|
||||||
if var.name in locals:
|
if var.name in locals:
|
||||||
local = locals[var.name]
|
local = locals[var.name]
|
||||||
else:
|
else:
|
||||||
local = Local.unused(var, stack.base_offset)
|
local = Local.unused(var, None)
|
||||||
stack.push(local)
|
stack.push(local)
|
||||||
|
|
||||||
|
|
||||||
|
@ -521,13 +519,11 @@ class Storage:
|
||||||
out.emit_reload()
|
out.emit_reload()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def for_uop(stack: Stack, uop: Uop, check_liveness: bool = True) -> tuple[list[str], "Storage"]:
|
def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = True) -> "Storage":
|
||||||
code_list: list[str] = []
|
|
||||||
inputs: list[Local] = []
|
inputs: list[Local] = []
|
||||||
peeks: list[Local] = []
|
peeks: list[Local] = []
|
||||||
for input in reversed(uop.stack.inputs):
|
for input in reversed(uop.stack.inputs):
|
||||||
code, local = stack.pop(input)
|
local = stack.pop(input, out)
|
||||||
code_list.append(code)
|
|
||||||
if input.peek:
|
if input.peek:
|
||||||
peeks.append(local)
|
peeks.append(local)
|
||||||
else:
|
else:
|
||||||
|
@ -536,18 +532,16 @@ class Storage:
|
||||||
peeks.reverse()
|
peeks.reverse()
|
||||||
for peek in peeks:
|
for peek in peeks:
|
||||||
stack.push(peek)
|
stack.push(peek)
|
||||||
top_offset = stack.top_offset.copy()
|
offset = stack.logical_sp - stack.physical_sp
|
||||||
for ouput in uop.stack.outputs:
|
for ouput in uop.stack.outputs:
|
||||||
if ouput.is_array() and ouput.used and not ouput.peek:
|
if ouput.is_array() and ouput.used and not ouput.peek:
|
||||||
c_offset = top_offset.to_c()
|
c_offset = offset.to_c()
|
||||||
top_offset.push(ouput)
|
out.emit(f"{ouput.name} = &stack_pointer[{c_offset}];\n")
|
||||||
code_list.append(f"{ouput.name} = &stack_pointer[{c_offset}];\n")
|
offset = offset.push(ouput)
|
||||||
else:
|
|
||||||
top_offset.push(ouput)
|
|
||||||
for var in inputs:
|
for var in inputs:
|
||||||
stack.push(var)
|
stack.push(var)
|
||||||
outputs = [ Local.undefined(var) for var in uop.stack.outputs if not var.peek ]
|
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
|
@staticmethod
|
||||||
def copy_list(arg: list[Local]) -> list[Local]:
|
def copy_list(arg: list[Local]) -> list[Local]:
|
||||||
|
@ -712,7 +706,7 @@ class Storage:
|
||||||
self.stack.drop(self.inputs[0].item, False)
|
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
|
output_in_place = self.outputs and output is self.outputs[0] and lowest.memory_offset is not None
|
||||||
if output_in_place:
|
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:
|
else:
|
||||||
self.stack.flush(out)
|
self.stack.flush(out)
|
||||||
if output is not None:
|
if output is not None:
|
||||||
|
@ -721,5 +715,4 @@ class Storage:
|
||||||
if output_in_place:
|
if output_in_place:
|
||||||
self.stack.flush(out)
|
self.stack.flush(out)
|
||||||
if output is not None:
|
if output is not None:
|
||||||
code, output = self.stack.pop(output.item)
|
output = self.stack.pop(output.item, out)
|
||||||
out.emit(code)
|
|
||||||
|
|
|
@ -48,18 +48,17 @@ def declare_variables(inst: Instruction, out: CWriter) -> None:
|
||||||
stack = get_stack_effect(inst)
|
stack = get_stack_effect(inst)
|
||||||
except StackError as ex:
|
except StackError as ex:
|
||||||
raise analysis_error(ex.args[0], inst.where) from None
|
raise analysis_error(ex.args[0], inst.where) from None
|
||||||
required = set(stack.defined)
|
seen = {"unused"}
|
||||||
required.discard("unused")
|
|
||||||
for part in inst.parts:
|
for part in inst.parts:
|
||||||
if not isinstance(part, Uop):
|
if not isinstance(part, Uop):
|
||||||
continue
|
continue
|
||||||
for var in part.stack.inputs:
|
for var in part.stack.inputs:
|
||||||
if var.name in required:
|
if var.used and var.name not in seen:
|
||||||
required.remove(var.name)
|
seen.add(var.name)
|
||||||
declare_variable(var, out)
|
declare_variable(var, out)
|
||||||
for var in part.stack.outputs:
|
for var in part.stack.outputs:
|
||||||
if var.name in required:
|
if var.used and var.name not in seen:
|
||||||
required.remove(var.name)
|
seen.add(var.name)
|
||||||
declare_variable(var, out)
|
declare_variable(var, out)
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,10 +85,8 @@ def write_uop(
|
||||||
if braces:
|
if braces:
|
||||||
emitter.out.emit(f"// {uop.name}\n")
|
emitter.out.emit(f"// {uop.name}\n")
|
||||||
emitter.emit("{\n")
|
emitter.emit("{\n")
|
||||||
code_list, storage = Storage.for_uop(stack, uop)
|
storage = Storage.for_uop(stack, uop, emitter.out)
|
||||||
emitter._print_storage(storage)
|
emitter._print_storage(storage)
|
||||||
for code in code_list:
|
|
||||||
emitter.emit(code)
|
|
||||||
|
|
||||||
for cache in uop.caches:
|
for cache in uop.caches:
|
||||||
if cache.name != "unused":
|
if cache.name != "unused":
|
||||||
|
|
|
@ -34,11 +34,11 @@ DEFAULT_OUTPUT = ROOT / "Python/executor_cases.c.h"
|
||||||
|
|
||||||
|
|
||||||
def declare_variable(
|
def declare_variable(
|
||||||
var: StackItem, uop: Uop, required: set[str], out: CWriter
|
var: StackItem, uop: Uop, seen: set[str], out: CWriter
|
||||||
) -> None:
|
) -> None:
|
||||||
if not var.used or var.name not in required:
|
if not var.used or var.name in seen:
|
||||||
return
|
return
|
||||||
required.remove(var.name)
|
seen.add(var.name)
|
||||||
type, null = type_and_null(var)
|
type, null = type_and_null(var)
|
||||||
space = " " if type[-1].isalnum() else ""
|
space = " " if type[-1].isalnum() else ""
|
||||||
out.emit(f"{type}{space}{var.name};\n")
|
out.emit(f"{type}{space}{var.name};\n")
|
||||||
|
@ -46,16 +46,16 @@ def declare_variable(
|
||||||
|
|
||||||
def declare_variables(uop: Uop, out: CWriter) -> None:
|
def declare_variables(uop: Uop, out: CWriter) -> None:
|
||||||
stack = Stack()
|
stack = Stack()
|
||||||
|
null = CWriter.null()
|
||||||
for var in reversed(uop.stack.inputs):
|
for var in reversed(uop.stack.inputs):
|
||||||
stack.pop(var)
|
stack.pop(var, null)
|
||||||
for var in uop.stack.outputs:
|
for var in uop.stack.outputs:
|
||||||
stack.push(Local.undefined(var))
|
stack.push(Local.undefined(var))
|
||||||
required = set(stack.defined)
|
seen = {"unused"}
|
||||||
required.discard("unused")
|
|
||||||
for var in reversed(uop.stack.inputs):
|
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:
|
for var in uop.stack.outputs:
|
||||||
declare_variable(var, uop, required, out)
|
declare_variable(var, uop, seen, out)
|
||||||
|
|
||||||
|
|
||||||
class Tier2Emitter(Emitter):
|
class Tier2Emitter(Emitter):
|
||||||
|
@ -143,9 +143,7 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> Stack:
|
||||||
elif uop.properties.const_oparg >= 0:
|
elif uop.properties.const_oparg >= 0:
|
||||||
emitter.emit(f"oparg = {uop.properties.const_oparg};\n")
|
emitter.emit(f"oparg = {uop.properties.const_oparg};\n")
|
||||||
emitter.emit(f"assert(oparg == CURRENT_OPARG());\n")
|
emitter.emit(f"assert(oparg == CURRENT_OPARG());\n")
|
||||||
code_list, storage = Storage.for_uop(stack, uop)
|
storage = Storage.for_uop(stack, uop, emitter.out)
|
||||||
for code in code_list:
|
|
||||||
emitter.emit(code)
|
|
||||||
idx = 0
|
idx = 0
|
||||||
for cache in uop.caches:
|
for cache in uop.caches:
|
||||||
if cache.name != "unused":
|
if cache.name != "unused":
|
||||||
|
|
|
@ -47,13 +47,14 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None:
|
||||||
out.emit("};\n")
|
out.emit("};\n")
|
||||||
out.emit("int _PyUop_num_popped(int opcode, int oparg)\n{\n")
|
out.emit("int _PyUop_num_popped(int opcode, int oparg)\n{\n")
|
||||||
out.emit("switch(opcode) {\n")
|
out.emit("switch(opcode) {\n")
|
||||||
|
null = CWriter.null()
|
||||||
for uop in analysis.uops.values():
|
for uop in analysis.uops.values():
|
||||||
if uop.is_viable() and uop.properties.tier != 1:
|
if uop.is_viable() and uop.properties.tier != 1:
|
||||||
stack = Stack()
|
stack = Stack()
|
||||||
for var in reversed(uop.stack.inputs):
|
for var in reversed(uop.stack.inputs):
|
||||||
if var.peek:
|
if var.peek:
|
||||||
break
|
break
|
||||||
stack.pop(var)
|
stack.pop(var, null)
|
||||||
popped = (-stack.base_offset).to_c()
|
popped = (-stack.base_offset).to_c()
|
||||||
out.emit(f"case {uop.name}:\n")
|
out.emit(f"case {uop.name}:\n")
|
||||||
out.emit(f" return {popped};\n")
|
out.emit(f" return {popped};\n")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue