Bytecode parity: align borrow optimization with CFG traversal (#7773)

* Align CFG cleanup bytecode with CPython

* Bytecode parity: fblock unwind, fstring join, folding, scope

- compile.rs: unwind_fblock_stack returns whether a finally ran so
  return-statement emission can adjust location handling; restructure
  try/except/finally cleanup to preserve or drop boundary NOPs based on
  whether the body falls through; rework f-string lowering with
  count/join helpers; remove the per-collection-type heuristic for
  AST-level folding and defer to flowgraph passes; add several folding
  helpers and a ComprehensionLoopControl enum.
- ir.rs: re-run unary/binop folding around tuple folding, add
  reorder_conditional_scope_exit_and_jump_back_blocks and several block
  classification helpers, add MAX_STR_SIZE, change is_exit_without_lineno
  to take the block list.
- symboltable.rs: in analyze_cells, remove names owned as cells in
  function-like scopes from the parent's free set; mark lambda scope
  type explicitly.

* Refine CFG scope-exit backedge ordering
This commit is contained in:
Jeong, YunWon
2026-05-05 14:04:12 +09:00
committed by GitHub
parent e4d35b08ea
commit ad5a55c1e3
7 changed files with 12096 additions and 851 deletions

View File

@@ -60,9 +60,11 @@
"dedentations",
"dedents",
"deduped",
"deoptimized",
"deoptimize",
"emscripten",
"excs",
"fnfe",
"interps",
"jitted",
"jitting",
@@ -72,6 +74,9 @@
"oparg",
"opargs",
"pyc",
"reborrow",
"reraises",
"reraising",
"significand",
"summands",
"unraisable",

View File

@@ -2065,7 +2065,6 @@ class CoroutineTest(unittest.TestCase):
run_async(f()),
([], {1: 1, 2: 2, 3: 3}))
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: __aiter__
def test_nested_comp(self):
async def run_list_inside_list():
return [[i + j async for i in asynciter([1, 2])] for j in [10, 20]]

View File

@@ -243,7 +243,6 @@ class TestTranforms(BytecodeTestCase):
self.assertTrue(g(4))
self.check_lnotab(g)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_constant_folding_small_int(self):
tests = [
('(0, )[0]', 0),
@@ -278,7 +277,6 @@ class TestTranforms(BytecodeTestCase):
self.assertNotInBytecode(code, 'LOAD_SMALL_INT')
self.check_lnotab(code)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'UNARY_NEGATIVE' starts with 'UNARY_'
def test_constant_folding_unaryop(self):
intrinsic_positive = 5
tests = [
@@ -324,7 +322,6 @@ class TestTranforms(BytecodeTestCase):
self.assertNotStartsWith(instr.opname, 'UNARY_')
self.check_lnotab(negzero)
@unittest.expectedFailure # TODO: RUSTPYTHON; BINARY_OP 26 ([])
def test_constant_folding_binop(self):
tests = [
('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
@@ -672,7 +669,6 @@ class TestTranforms(BytecodeTestCase):
return 6
self.check_lnotab(f)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 2 != 1
def test_assignment_idiom_in_comprehensions(self):
def listcomp():
return [y for x in a for y in [f(x)]]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -591,6 +591,14 @@ impl SymbolTableAnalyzer {
}
// Analyze symbols in current scope
let remove_owned_cells_from_free = matches!(
symbol_table.typ,
CompilerScope::Function
| CompilerScope::AsyncFunction
| CompilerScope::Lambda
| CompilerScope::Comprehension
| CompilerScope::Annotation
);
for symbol in symbol_table.symbols.values_mut() {
self.analyze_symbol(
symbol,
@@ -600,6 +608,13 @@ impl SymbolTableAnalyzer {
class_entry,
)?;
// CPython analyze_cells(): once a function-like scope owns a
// child-requested name as a cell, that name is no longer free in
// the enclosing scope.
if remove_owned_cells_from_free && symbol.scope == SymbolScope::Cell {
newfree.shift_remove(symbol.name.as_str());
}
// Collect free variables from this scope
if symbol.scope == SymbolScope::Free || symbol.flags.contains(SymbolFlags::FREE_CLASS) {
newfree.insert(symbol.name.clone());
@@ -2128,6 +2143,7 @@ impl SymbolTableBuilder {
false, // lambdas are never async
false, // don't skip defaults
)?;
self.tables.last_mut().unwrap().typ = CompilerScope::Lambda;
} else {
self.enter_scope(
"lambda",

View File

@@ -74,10 +74,16 @@ def _start_one(interpreter, targets, base_dir):
output_file = None
try:
files_file = tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", delete=False, dir=PROJECT_ROOT
mode="w",
encoding="utf-8",
delete=False,
prefix="compare-bytecode-files-",
)
output_file = tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", delete=False, dir=PROJECT_ROOT
mode="w",
encoding="utf-8",
delete=False,
prefix="compare-bytecode-output-",
)
for _, path in targets:
files_file.write(path)