From 65ee7bae8bff7e0c6a169965eadedc9f421ec558 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 24 Oct 2026 23:11:10 -0700 Subject: [PATCH] Align Python 3.15 bytecode tooling (#9344) Extract independent bytecode tooling from the Python 3.15 integration. Decode packed import-name arguments without changing older-interpreter fallback behavior, validate canonical opcode numbering, bound deoptimization families to the enum, and reuse parsed CPython analysis. Validated generators reproduce tracked metadata exactly; normal, lazy and eager import fallback matches canonical disassembly. No interpreter, generated metadata or test changes. Assisted-by: OpenAI Codex: model version unavailable --- scripts/dis_dump.py | 12 +++++++++++- tools/opcode_metadata/cpython.py | 2 ++ tools/opcode_metadata/opcodes.py | 16 ++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/scripts/dis_dump.py b/scripts/dis_dump.py index 813de22e6..a149f724a 100755 --- a/scripts/dis_dump.py +++ b/scripts/dis_dump.py @@ -170,12 +170,22 @@ def _resolve_arg_fallback(code, opname, arg): "LOAD_ATTR", "STORE_ATTR", "DELETE_ATTR", - "IMPORT_NAME", "IMPORT_FROM", "LOAD_FROM_DICT_OR_GLOBALS", ): if 0 <= arg < len(code.co_names): return code.co_names[arg] + elif opname == "IMPORT_NAME": + name_idx, policy = arg, 0 + if sys.version_info >= (3, 15): + name_idx, policy = arg >> 2, arg & 3 + if 0 <= name_idx < len(code.co_names): + name = code.co_names[name_idx] + if policy & 1 and name: + return f"{name} + lazy" + if policy & 2 and name: + return f"{name} + eager" + return name elif opname == "LOAD_SUPER_ATTR": name_idx = arg >> 2 if 0 <= name_idx < len(code.co_names): diff --git a/tools/opcode_metadata/cpython.py b/tools/opcode_metadata/cpython.py index 56743a888..7e32430a2 100644 --- a/tools/opcode_metadata/cpython.py +++ b/tools/opcode_metadata/cpython.py @@ -1,3 +1,4 @@ +import functools import os import pathlib import sys @@ -18,6 +19,7 @@ from analyzer import SKIP_PROPERTIES, Analysis, Family, Properties, analyze_file from stack import get_stack_effect +@functools.cache def get_analysis() -> Analysis: from generators_common import DEFAULT_INPUT diff --git a/tools/opcode_metadata/opcodes.py b/tools/opcode_metadata/opcodes.py index 9178921b2..7550af1e2 100644 --- a/tools/opcode_metadata/opcodes.py +++ b/tools/opcode_metadata/opcodes.py @@ -33,10 +33,11 @@ class OpcodeInfo: for member in family.members: member_name = to_pascal_case(member.name) - if member.name == family_name: + if member_name == family_name or member_name not in names: continue - res[family_name].append(member_name) + if member_name not in res[family_name]: + res[family_name].append(member_name) return dict(res) @@ -76,6 +77,17 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]: opcode = Opcode.from_str(entry) rust_name = opcode.rust_name + # Shared opcodes use CPython's wire-format IDs; RustPython-only + # instructions must occupy an otherwise unused slot. + expected_id = analysis.opmap.get(opcode.cpython_name) + if expected_id is not None and opcode.id != expected_id: + raise ValueError( + f"{rust_name} has opcode ID {opcode.id}; CPython requires {expected_id}" + ) + if expected_id is None and opcode.id in analysis.opmap.values(): + raise ValueError( + f"RustPython-only opcode {rust_name} collides with CPython ID {opcode.id}" + ) override = override_confs.get(rust_name, SKIP_OVERRIDE) cpython_name = opcode.cpython_name