| import contextlib |
| import re |
| import typing |
| from collections.abc import Iterator |
| |
| from parsing import StackEffect, Family |
| |
| UNUSED = "unused" |
| |
| |
| class Formatter: |
| """Wraps an output stream with the ability to indent etc.""" |
| |
| stream: typing.TextIO |
| prefix: str |
| emit_line_directives: bool = False |
| lineno: int # Next line number, 1-based |
| filename: str # Slightly improved stream.filename |
| nominal_lineno: int |
| nominal_filename: str |
| |
| def __init__( |
| self, |
| stream: typing.TextIO, |
| indent: int, |
| emit_line_directives: bool = False, |
| comment: str = "//", |
| ) -> None: |
| self.stream = stream |
| self.prefix = " " * indent |
| self.emit_line_directives = emit_line_directives |
| self.comment = comment |
| self.lineno = 1 |
| self.filename = prettify_filename(self.stream.name) |
| self.nominal_lineno = 1 |
| self.nominal_filename = self.filename |
| |
| def write_raw(self, s: str) -> None: |
| self.stream.write(s) |
| newlines = s.count("\n") |
| self.lineno += newlines |
| self.nominal_lineno += newlines |
| |
| def emit(self, arg: str) -> None: |
| if arg: |
| self.write_raw(f"{self.prefix}{arg}\n") |
| else: |
| self.write_raw("\n") |
| |
| def set_lineno(self, lineno: int, filename: str) -> None: |
| if self.emit_line_directives: |
| if lineno != self.nominal_lineno or filename != self.nominal_filename: |
| self.emit(f'#line {lineno} "{filename}"') |
| self.nominal_lineno = lineno |
| self.nominal_filename = filename |
| |
| def reset_lineno(self) -> None: |
| if self.lineno != self.nominal_lineno or self.filename != self.nominal_filename: |
| self.set_lineno(self.lineno + 1, self.filename) |
| |
| @contextlib.contextmanager |
| def indent(self) -> Iterator[None]: |
| self.prefix += " " |
| yield |
| self.prefix = self.prefix[:-4] |
| |
| @contextlib.contextmanager |
| def block(self, head: str, tail: str = "") -> Iterator[None]: |
| if head: |
| self.emit(head + " {") |
| else: |
| self.emit("{") |
| with self.indent(): |
| yield |
| self.emit("}" + tail) |
| |
| def stack_adjust( |
| self, |
| input_effects: list[StackEffect], |
| output_effects: list[StackEffect], |
| ) -> None: |
| shrink, isym = list_effect_size(input_effects) |
| grow, osym = list_effect_size(output_effects) |
| diff = grow - shrink |
| if isym and isym != osym: |
| self.emit(f"STACK_SHRINK({isym});") |
| if diff < 0: |
| self.emit(f"STACK_SHRINK({-diff});") |
| if diff > 0: |
| self.emit(f"STACK_GROW({diff});") |
| if osym and osym != isym: |
| self.emit(f"STACK_GROW({osym});") |
| |
| def declare(self, dst: StackEffect, src: StackEffect | None) -> None: |
| if dst.name == UNUSED or dst.cond == "0": |
| return |
| typ = f"{dst.type}" if dst.type else "PyObject *" |
| if src: |
| cast = self.cast(dst, src) |
| initexpr = f"{cast}{src.name}" |
| if src.cond and src.cond != "1": |
| initexpr = f"{parenthesize_cond(src.cond)} ? {initexpr} : NULL" |
| init = f" = {initexpr}" |
| elif dst.cond and dst.cond != "1": |
| init = " = NULL" |
| else: |
| init = "" |
| sepa = "" if typ.endswith("*") else " " |
| self.emit(f"{typ}{sepa}{dst.name}{init};") |
| |
| def assign(self, dst: StackEffect, src: StackEffect) -> None: |
| if src.name == UNUSED or dst.name == UNUSED: |
| return |
| cast = self.cast(dst, src) |
| if re.match(r"^REG\(oparg(\d+)\)$", dst.name): |
| self.emit(f"Py_XSETREF({dst.name}, {cast}{src.name});") |
| else: |
| stmt = f"{dst.name} = {cast}{src.name};" |
| if src.cond and src.cond != "1": |
| if src.cond == "0": |
| # It will not be executed |
| return |
| stmt = f"if ({src.cond}) {{ {stmt} }}" |
| self.emit(stmt) |
| |
| def cast(self, dst: StackEffect, src: StackEffect) -> str: |
| return f"({dst.type or 'PyObject *'})" if src.type != dst.type else "" |
| |
| def static_assert_family_size( |
| self, name: str, family: Family | None, cache_offset: int |
| ) -> None: |
| """Emit a static_assert for the size of a family, if known. |
| |
| This will fail at compile time if the cache size computed from |
| the instruction definition does not match the size of the struct |
| used by specialize.c. |
| """ |
| if family and name == family.name: |
| cache_size = family.size |
| if cache_size: |
| self.emit( |
| f"static_assert({cache_size} == {cache_offset}, " |
| f'"incorrect cache size");' |
| ) |
| |
| |
| def prettify_filename(filename: str) -> str: |
| # Make filename more user-friendly and less platform-specific, |
| # it is only used for error reporting at this point. |
| filename = filename.replace("\\", "/") |
| if filename.startswith("./"): |
| filename = filename[2:] |
| if filename.endswith(".new"): |
| filename = filename[:-4] |
| return filename |
| |
| |
| def list_effect_size(effects: list[StackEffect]) -> tuple[int, str]: |
| numeric = 0 |
| symbolic: list[str] = [] |
| for effect in effects: |
| diff, sym = effect_size(effect) |
| numeric += diff |
| if sym: |
| symbolic.append(maybe_parenthesize(sym)) |
| return numeric, " + ".join(symbolic) |
| |
| |
| def effect_size(effect: StackEffect) -> tuple[int, str]: |
| """Return the 'size' impact of a stack effect. |
| |
| Returns a tuple (numeric, symbolic) where: |
| |
| - numeric is an int giving the statically analyzable size of the effect |
| - symbolic is a string representing a variable effect (e.g. 'oparg*2') |
| |
| At most one of these will be non-zero / non-empty. |
| """ |
| if effect.size: |
| assert not effect.cond, "Array effects cannot have a condition" |
| return 0, effect.size |
| elif effect.cond: |
| if effect.cond in ("0", "1"): |
| return int(effect.cond), "" |
| return 0, f"{maybe_parenthesize(effect.cond)} ? 1 : 0" |
| else: |
| return 1, "" |
| |
| |
| def maybe_parenthesize(sym: str) -> str: |
| """Add parentheses around a string if it contains an operator. |
| |
| An exception is made for '*' which is common and harmless |
| in the context where the symbolic size is used. |
| """ |
| if re.match(r"^[\s\w*]+$", sym): |
| return sym |
| else: |
| return f"({sym})" |
| |
| |
| def parenthesize_cond(cond: str) -> str: |
| """Parenthesize a condition, but only if it contains ?: itself.""" |
| if "?" in cond: |
| cond = f"({cond})" |
| return cond |