mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
* Add inline values flags and special-method lookup lookup_maybe_method clears AttributeError from descr_get. slot_tp_richcompare treats a miss as NotImplemented. hackcheck rejects object.__setattr__ on types. Heap types with a managed dict and itemsize 0 get INLINE_VALUES; _testinternalcapi.has_inline_values reports the valid bit. super is a base type; subclass __get__ binds the instance. __classcell__/__qualname__ may share a name with a slot. Unset member access uses the type's fully qualified name. Assisted-by: Grok:grok-4.6 * Call custom mro and descr_get for type.__new__ type.mro() computes C3 from the current bases. A metaclass other than type supplies the MRO via mro(). super() compares obj.__class__ to the instance type. slot_tp_new looks up __new__ through descr_get. is_subtype walks the stored MRO without an identity shortcut. __slots__ names are sorted so __class__ assignment matches. Assisted-by: Grok:grok-4.6 * Expose _testinternalcapi recursion and rare events get_recursion_depth feeds test.support.infinite_recursion. SPECIALIZATION_* and SHARED_KEYS_MAX_SIZE match the adaptive counters. Rare-event hooks increment on __class__/__bases__/function/builtins writes. Assisted-by: Grok:grok-4.6 * Add remaining _testinternalcapi code and locale APIs Expose get_co_localskinds, get_code_var_counts, verify_stateless_code, code_returns_only_none, normalize_path, EncodeLocaleEx, DecodeLocaleEx, and run_in_subinterp_with_config. Mark unimplemented compiler IR and specialization monitoring tests as expectedFailure. Assisted-by: Grok:grok-4.6 * Fix lint for _testinternalcapi identifiers Add CPython identifier spellings to the cspell dictionary and apply the rustfmt signature that CI rewrote. Assisted-by: Grok:grok-4.6 * Expand _testinternalcapi with remaining test APIs Add constants, compiler_cleandoc, dict_getitem_knownhash, time helpers, interpreter/frame accessors, configs, static types, a persistent hamt map, and GC/size counters used by the test suite. Assisted-by: Grok:grok-4.6 * Expose compiler IR APIs in _testinternalcapi Add InstructionSequence and compiler_codegen, optimize_cfg, and assemble_code_object. Drop expectedFailure marks on tests that pass. Assisted-by: Grok:grok-4.6 * Match optimize_cfg consts and addop locations Write optimized consts back into the caller's list, mark SETUP_* pseudo-ops as having an argument, and store addop locations in instruction-sequence field order. Drop expectedFailure marks on tests that pass. Assisted-by: Grok:grok-4.6 * Fix hamt API, SETUP_* HAS_ARG, and FOR_ITER events Honor explicit pseudo flags when generating opcode metadata so SETUP_FINALLY/CLEANUP/WITH keep HAS_ARG after regeneration. Complete the _testinternalcapi hamt mapping: identity on set/delete, richcompare, weakrefs, subscript, contains, and length-bearing iterators. Fire RAISE/STOP_ITERATION for FOR_ITER StopIteration, and skip both END_FOR and POP_ITER for the exhausted-branch destination. Assisted-by: Grok:grok-4.6 * Skip FOR_ITER RAISE for sequence iterators List/range/tuple exhaustion matches FOR_ITER_LIST/RANGE/TUPLE and must not emit a monitoring RAISE. That event stopped pdb's monitoring backend on for-over-list with Internal StopIteration. Assisted-by: Grok:grok-4.6 * Fix generator FOR_ITER events and review issues Restore settrace StopIteration for generator for-loops so test_bdb next/return cases pass, while sequence iterators still skip RAISE. Honor review notes: addop oparg bounds, linetable locations, AST instance errors, MRO subclass lock, builtin-dict notify, direct-base layout flags, and leftover _testinternalcapi args. Assisted-by: Grok:grok-4.6
187 lines
5.8 KiB
Python
187 lines
5.8 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)
|
|
|
|
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(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
|