Files
RustPython/tools/opcode_metadata/opcodes.py
T
dot a974ec938d Align compiler and VM with pinned CPython 3.15 ABI
Couple opcode/cache/stack metadata with iterator pairs, generator preambles, ordinary import packing, common constants, compiler lowering and VM/JIT consumers. Pin official source/tool hashes and retain strict raw code and marshal comparisons.

Use private development magic 0xDA16 and cache tag devabi2 until serialization and Python integration are coherent. Final clean corpus matches recursive code fields in 111/111 requests; full marshal/header exactness remains 0/111. Eight existing disassembly tests remain blocked by unchanged Lib metadata. Monitoring export and frozendict/update_lib integration remain explicit follow-on gates.

Validation: compiler-core 23, codegen 807, CAPI 121 (4 existing ignored), focused certified-controller pytest, release/frozen witnesses, workspace and CAPI all-target Clippy. Independent source/artifact review covers manifest ec1013591ae97b4c519f4a2c78a8200664f3479f0c5452991b5fea4d1be669ea.

Assisted-by: OpenAI Codex:model identifier unavailable
2026-10-12 00:20:11 +09:00

193 lines
6.0 KiB
Python

from __future__ import annotations
import collections
import dataclasses
import re
import typing
import warnings
import utils
from cpython import SKIP_PROPERTIES, Family, Properties, get_analysis, get_stack_effect
from utils import SKIP_OVERRIDE, Override, OverrideConfs, StackEffect, to_pascal_case
if typing.TYPE_CHECKING:
from collections.abc import Iterable
@dataclasses.dataclass(frozen=True, slots=True)
class OpcodeInfo:
enum_name: str
size: str
opcodes: tuple[Opcode, ...]
@property
def deopts(self) -> dict[str, list[str]]:
analysis = get_analysis()
names = {opcode.rust_name for opcode in self}
res = collections.defaultdict(list)
for family in analysis.families.values():
family_name = to_pascal_case(family.name)
if family_name not in names:
continue
for member in family.members:
member_name = to_pascal_case(member.name)
if member.name == family_name:
continue
res[family_name].append(member_name)
if "LoadConstImmortal" in names:
res["LoadConst"].extend(["LoadConstImmortal", "LoadConstMortal"])
return dict(res)
def __iter__(self):
yield from self.opcodes
@classmethod
def iter_infos(
cls, text: str, override_confs: OverrideConfs
) -> Iterable[typing.Self]:
for block_match in re.finditer(
r"define_opcodes!\s*\((.+?)\);", text, re.DOTALL
):
block = block_match.group(1).strip()
size = re.search(r"#\[repr\((\w+)\)\]", block).group(1)
enum_name = re.search(
r"#\[repr\(\w+\)\]\s*pub\s+enum\s+(\w+)\s*;", block
).group(1)
second_enum_match = re.search(r"pub\s+enum\s+(\w+)\s*\{", block, re.DOTALL)
entries = utils.extract_enum_body(block, second_enum_match.end() - 1)
opcodes = tuple(sorted(iter_opcodes(entries, override_confs)))
yield cls(enum_name, size, opcodes)
def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]:
analysis = get_analysis()
# Split on commas that are followed by a newline + an uppercase letter (new entry)
entries = map(str.strip, re.split(r",\s*\n\s*(?=[A-Z])", text))
for entry in entries:
if not entry:
continue
opcode = Opcode.from_str(entry)
rust_name = opcode.rust_name
override = override_confs.get(rust_name, SKIP_OVERRIDE)
cpython_name = opcode.cpython_name
kwargs = {}
if instr := analysis.instructions.get(
"LOAD_CONST"
if rust_name in ("LoadConstImmortal", "LoadConstMortal")
else cpython_name
):
kwargs["properties"] = properties_with_pseudo_flags(instr)
kwargs["family"] = getattr(instr, "family", None)
kwargs["cache_entry"] = getattr(instr, "size", -1)
stack = get_stack_effect(instr)
popped = (-stack.base_offset).to_c()
pushed = (stack.logical_sp - stack.base_offset).to_c()
kwargs["stack_effect"] = StackEffect(popped=popped, pushed=pushed)
elif override == SKIP_OVERRIDE:
warnings.warn(
f"Could not get instruction metadata for {rust_name}"
" from CPython or override conf"
)
yield dataclasses.replace(opcode, override=override, **kwargs)
# opcode_metadata_generator.py generate_metadata_table: cflags(pseudo.properties)
# plus each explicit flag from the pseudo() definition.
_PSEUDO_FLAG_ATTRS = {
"HAS_ARG": "oparg",
"HAS_CONST": "uses_co_consts",
"HAS_NAME": "uses_co_names",
"HAS_JUMP": "jumps",
"HAS_FREE": "has_free",
"HAS_LOCAL": "uses_locals",
"HAS_EVAL_BREAK": "eval_breaker",
"HAS_DEOPT": "deopts",
"HAS_EXIT": "side_exit",
"HAS_PURE": "pure",
}
def properties_with_pseudo_flags(instr):
props = instr.properties
flags = getattr(instr, "flags", None)
if not flags:
return props
updates = {attr: True for flag in flags if (attr := _PSEUDO_FLAG_ATTRS.get(flag))}
if not updates:
return props
return dataclasses.replace(props, **updates)
@dataclasses.dataclass(frozen=True, slots=True)
class Opcode:
rust_name: str
id: int
have_argument: bool = False
cache_entry: int = 0
stack_effect: StackEffect | None = None
properties: Properties = dataclasses.field(default_factory=lambda: SKIP_PROPERTIES)
family: Family | None = None
override: Override = dataclasses.field(default_factory=Override)
@property
def is_instrumented(self) -> bool:
if (res := self.override.is_instrumented) is not None:
return res
return self.cpython_name.startswith("INSTRUMENTED_")
@property
def cpython_name(self):
return utils.to_upper_snake_case(self.rust_name)
@property
def cpy_popped(self) -> str | None:
return getattr(self.stack_effect, "popped", None)
@property
def cpy_pushed(self) -> str | None:
return getattr(self.stack_effect, "pushed", None)
@property
def stack_effect_popped(self) -> str:
ove_popped = self.override.stack_effect.popped
if (ove_popped is None) and (self.cpy_popped is None):
raise ValueError(f"{self.rust_name} is missing popped stack_effect")
return ove_popped or self.cpy_popped
@property
def stack_effect_pushed(self) -> str:
ove_pushed = self.override.stack_effect.pushed
if (ove_pushed is None) and (self.cpy_pushed is None):
raise ValueError(f"{self.rust_name} is missing pushed stack_effect")
return ove_pushed or self.cpy_pushed
@classmethod
def from_str(cls, entry: str) -> typing.Self:
rust_name = re.match(r"(\w+)", entry).group(1)
id_num = re.findall(r"= (\d+)", entry)[0]
have_argument = "Arg<" in entry
return cls(rust_name, int(id_num), have_argument=have_argument)
def __lt__(self, other: typing.Self) -> bool:
return self.id < other.id