mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Align bytecode codegen structure with CPython 3.14 (#7588)
* Align bytecode codegen structure with CPython 3.14 * Bytecode parity - constant folding, annotation ordering, superinstruction alignment - Add BoolOp constant folding with short-circuit semantics in compile_expression - Add constant truthiness evaluation for assert statement optimization - Disable const collection/boolop folding in starred unpack and assignment contexts - Move annotation block generation after body with AnnotationsPlaceholder splicing - Reorder insert_superinstructions to run before push_cold_blocks (matching flowgraph.c) - Lower LOAD_CLOSURE after superinstructions to avoid false LOAD_FAST_LOAD_FAST - Add ToBool before PopJumpIf in comparisons and chained compare cleanup blocks - Unify annotation dict building to always use incremental BuildMap + StoreSubscr - Add TrueDivide constant folding for integer operands - Fold constant sets to Frozenset (not Tuple) in try_fold_constant_collection - Add PyVmBag for frozenset constant materialization in code objects - Add remove_redundant_const_pop_top_pairs pass and peephole const+branch folding - Emit Nop for skipped constant expressions and constant-true asserts - Preserve comprehension local ordering by source-order bound name collection - Simplify annotation scanning in symboltable (remove simple-name gate) * Fix CI regressions in marshal and fast-local ops * impl more * Align bytecode codegen with CPython structure * Bytecode parity - comprehension/except scope ordering, load_fast_borrow fixes - Reorder comprehension symbol-table walk so the outermost iterator registers its sub_tables in the enclosing scope before the comp scope, and rescan elt/ifs in CPython's order. Codegen peeks past the outermost iterator's nested scopes to find the comprehension table. - For plain try/except, emit handler sub_tables before the else block so codegen's linear sub_table cursor stays aligned. - Rename `collect_simple_annotations` to `collect_annotations` and evaluate non-simple annotations during __annotate__ compilation to preserve source-order side effects while keeping the simple-name index stable. - Dedupe equivalent code constants in `arg_constant` and add a structural equality check on `CodeObject`. - Disable LOAD_FAST_BORROW for the tail end block when a try has a bare `except:` clause, and have `new_block` inherit the flag from the current block. - Remove `cfg!(debug_assertions)` guard around the `optimize_load_fast_borrow` start-depth check so mismatches are handled (return instead of assert) in release builds. - Collapse nop-only blocks that precede a return epilogue and hoist the prior line number into the next real instruction so the line table matches. - Unmark now-passing `test_consts_in_conditionals`, `test_load_fast_unknown_simple`, `test_load_fast_known_because_already_loaded`, and PEP 646 f3/f4 annotation checks. * Bytecode parity - try/except line tracking, assert 0 shape - In `compile_try_except`, drop the leading Nop and set the end block's source range from the last orelse/body statement so line events after the try fall on the right line. - Recognise constant-false asserts as the direct-raise shape (no ToBool/PopJumpIfFalse) and flip the test assertion accordingly. - Extend `remove_redundant_nops_in_blocks` to also look through a trailing nop before a return-epilogue pair (LoadConst/ReturnValue or LoadSmallInt/ReturnValue) so the epilogue keeps the correct line number. - Rename `preds` to `predecessor_blocks` in the LOAD_FAST_BORROW disable pass and add a test-only `debug_late_cfg_trace` helper. - Regenerate the `nested_double_async_with` snapshot: the tail reference to `stop_exc` now emits LOAD_FAST instead of LOAD_FAST_BORROW. * Bytecode parity - iter folding, break/continue line, cold inlining - Fold a constant list iterable into a constant tuple in for-loop iterable position, matching the CPython optimizer, and strip a redundant LIST_TO_TUPLE immediately before GET_ITER in the IR peephole pass. - Emit a Nop at the break/continue source range before unwinding so line events land on the break/continue statement instead of the following instruction. - Drop `propagate_disable_load_fast_borrow`; the forward propagation was over-zealous and the per-block inheritance in `new_block` plus the bare-except marker are enough. - Relax `inline_small_or_no_lineno_blocks` so small exit blocks at the tail of a cold block are always inlined, not just return epilogues. - Add codegen tests covering the LIST_TO_TUPLE/GET_ITER peephole and the late-CFG trace helper for a for-loop list-literal iterable.
This commit is contained in:
1 parent
a61d2e81c0
commit
a04189fa34
26 files changed
+4651
-1097
No files matched your search
+37
-15
@@ -11,6 +11,8 @@ Usage:
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import builtins
|
||||
import dis
|
||||
import json
|
||||
import os
|
||||
@@ -38,6 +40,7 @@ _JUMP_OPNAMES = frozenset(
|
||||
"JUMP_IF_TRUE_OR_POP",
|
||||
"JUMP_IF_FALSE_OR_POP",
|
||||
"FOR_ITER",
|
||||
"END_ASYNC_FOR",
|
||||
"SEND",
|
||||
}
|
||||
)
|
||||
@@ -93,6 +96,14 @@ def _normalize_argrepr(argrepr):
|
||||
|
||||
argrepr = re.sub(r"\\u([0-9a-fA-F]{4})", _unescape, argrepr)
|
||||
argrepr = re.sub(r"\\U([0-9a-fA-F]{8})", _unescape, argrepr)
|
||||
if argrepr.startswith("frozenset({") and argrepr.endswith("})"):
|
||||
try:
|
||||
values = ast.literal_eval(argrepr[len("frozenset(") : -1])
|
||||
except Exception:
|
||||
return argrepr
|
||||
if isinstance(values, set):
|
||||
parts = sorted(_normalize_argrepr(repr(value)) for value in values)
|
||||
return f"frozenset({{{', '.join(parts)}}})"
|
||||
return argrepr
|
||||
|
||||
|
||||
@@ -100,10 +111,33 @@ _IS_RUSTPYTHON = (
|
||||
hasattr(sys, "implementation") and sys.implementation.name == "rustpython"
|
||||
)
|
||||
|
||||
if _IS_RUSTPYTHON and hasattr(dis, "_common_constants"):
|
||||
common_constants = list(dis._common_constants)
|
||||
while len(common_constants) < 7:
|
||||
common_constants.append(
|
||||
(builtins.list, builtins.set)[len(common_constants) - 5]
|
||||
)
|
||||
dis._common_constants = common_constants
|
||||
|
||||
# RustPython's ComparisonOperator enum values → operator strings
|
||||
_RP_CMP_OPS = {0: "<", 1: "<=", 2: "==", 3: "!=", 4: ">", 5: ">="}
|
||||
|
||||
|
||||
def _resolve_localsplus_name(code, arg):
|
||||
if not isinstance(arg, int) or arg < 0:
|
||||
return arg
|
||||
nlocals = len(code.co_varnames)
|
||||
if arg < nlocals:
|
||||
return code.co_varnames[arg]
|
||||
varnames_set = set(code.co_varnames)
|
||||
nonparam_cells = [v for v in code.co_cellvars if v not in varnames_set]
|
||||
extra = nonparam_cells + list(code.co_freevars)
|
||||
idx = arg - nlocals
|
||||
if 0 <= idx < len(extra):
|
||||
return extra[idx]
|
||||
return arg
|
||||
|
||||
|
||||
def _resolve_arg_fallback(code, opname, arg):
|
||||
"""Resolve a raw argument to its human-readable form.
|
||||
|
||||
@@ -113,8 +147,7 @@ def _resolve_arg_fallback(code, opname, arg):
|
||||
return arg
|
||||
try:
|
||||
if "FAST" in opname:
|
||||
if 0 <= arg < len(code.co_varnames):
|
||||
return code.co_varnames[arg]
|
||||
return _resolve_localsplus_name(code, arg)
|
||||
elif opname == "LOAD_CONST":
|
||||
if 0 <= arg < len(code.co_consts):
|
||||
return _normalize_argrepr(repr(code.co_consts[arg]))
|
||||
@@ -125,18 +158,7 @@ def _resolve_arg_fallback(code, opname, arg):
|
||||
"LOAD_CLOSURE",
|
||||
"MAKE_CELL",
|
||||
):
|
||||
# arg is localsplus index:
|
||||
# 0..nlocals-1 = varnames (parameter cells reuse these slots)
|
||||
# nlocals.. = non-parameter cells + freevars
|
||||
nlocals = len(code.co_varnames)
|
||||
if arg < nlocals:
|
||||
return code.co_varnames[arg]
|
||||
varnames_set = set(code.co_varnames)
|
||||
nonparam_cells = [v for v in code.co_cellvars if v not in varnames_set]
|
||||
extra = nonparam_cells + list(code.co_freevars)
|
||||
idx = arg - nlocals
|
||||
if 0 <= idx < len(extra):
|
||||
return extra[idx]
|
||||
return _resolve_localsplus_name(code, arg)
|
||||
elif opname in (
|
||||
"LOAD_NAME",
|
||||
"STORE_NAME",
|
||||
@@ -237,7 +259,7 @@ def _extract_instructions(code):
|
||||
# 1. argval not in offset_to_idx (not a valid byte offset)
|
||||
# 2. argval == arg (raw arg returned as-is, not resolved to offset)
|
||||
# 3. For backward jumps: argval should be < current offset
|
||||
is_backward = "BACKWARD" in inst.opname
|
||||
is_backward = "BACKWARD" in inst.opname or inst.opname == "END_ASYNC_FOR"
|
||||
argval_is_raw = inst.argval == inst.arg and inst.arg is not None
|
||||
if target_idx is None or argval_is_raw:
|
||||
target_idx = None # force recalculation
|
||||
|
||||
Reference in new issue
Block a user