Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Align CPython 3.14 LOAD_GLOBAL null-bit and RERAISE semantics
  • Loading branch information
youknowone committed Mar 1, 2026
commit ccc95f6a45e53f1519a771b3a9ad19a0a188aee2
7 changes: 0 additions & 7 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2020,7 +2020,6 @@ def function():
_global_ref = object()
class TestGetClosureVars(unittest.TestCase):

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': <built-in function print>}, unbound=set()) != Closu[132 chars]={'print': <built-in function print>}, unbound={'unbound_ref'})
def test_name_resolution(self):
# Basic test of the 4 different resolution mechanisms
def f(nonlocal_ref):
Expand All @@ -2036,7 +2035,6 @@ def g(local_ref):
builtin_vars, unbound_names)
self.assertEqual(inspect.getclosurevars(f(_arg)), expected)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': <built-in function print>}, unbound=set()) != Closu[132 chars]={'print': <built-in function print>}, unbound={'unbound_ref'})
def test_generator_closure(self):
def f(nonlocal_ref):
def g(local_ref):
Expand All @@ -2052,7 +2050,6 @@ def g(local_ref):
builtin_vars, unbound_names)
self.assertEqual(inspect.getclosurevars(f(_arg)), expected)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[132 chars]={'print': <built-in function print>}, unbound=set()) != Closu[132 chars]={'print': <built-in function print>}, unbound={'unbound_ref'})
def test_method_closure(self):
class C:
def f(self, nonlocal_ref):
Expand All @@ -2068,7 +2065,6 @@ def g(local_ref):
builtin_vars, unbound_names)
self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[139 chars]als={}, builtins={'print': <built-in function [18 chars]et()) != Closu[139 chars]als={'_global_ref': <object object at 0xa4ffee[73 chars]ef'})
def test_attribute_same_name_as_global_var(self):
class C:
_global_ref = object()
Expand Down Expand Up @@ -2138,21 +2134,18 @@ def _private_globals(self):
exec(code, ns)
return ns["f"], ns

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[34 chars]uiltins={'print': <built-in function print>}, unbound=set()) != Closu[34 chars]uiltins={'print': <built-in function print>}, unbound={'path'})
def test_builtins_fallback(self):
f, ns = self._private_globals()
ns.pop("__builtins__", None)
expected = inspect.ClosureVars({}, {}, {"print":print}, {"path"})
self.assertEqual(inspect.getclosurevars(f), expected)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ClosureVars(nonlocals={}, globals={}, builtins={}, unbound={'print'}) != ClosureVars(nonlocals={}, globals={}, builtins={'path': 1}, unbound={'print'})
def test_builtins_as_dict(self):
f, ns = self._private_globals()
ns["__builtins__"] = {"path":1}
expected = inspect.ClosureVars({}, {}, {"path":1}, {"print"})
self.assertEqual(inspect.getclosurevars(f), expected)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Closu[38 chars]ins={}, unbound={'print'}) != Closu[38 chars]ins={'path': <module 'posixpath' from '/Users/[79 chars]nt'})
def test_builtins_as_module(self):
f, ns = self._private_globals()
ns["__builtins__"] = os
Expand Down
86 changes: 23 additions & 63 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2010,7 +2010,10 @@ impl Compiler {
NameOp::Global => {
let idx = self.get_global_name_index(&name);
let op = match usage {
NameUsage::Load => Instruction::LoadGlobal,
NameUsage::Load => {
self.emit_load_global(idx, false);
return Ok(());
}
NameUsage::Store => Instruction::StoreGlobal,
NameUsage::Delete => Instruction::DeleteGlobal,
};
Expand Down Expand Up @@ -2973,24 +2976,14 @@ impl Compiler {
emit!(self, Instruction::PopExcept);

// RERAISE 0: re-raise the original exception to outer handler
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack
}
);
emit!(self, Instruction::Reraise { depth: 0 });
}

if let Some(cleanup) = finally_cleanup_block {
self.switch_to_block(cleanup);
emit!(self, Instruction::Copy { index: 3_u32 });
emit!(self, Instruction::PopExcept);
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack
}
);
emit!(self, Instruction::Reraise { depth: 1 });
}

self.switch_to_block(end_block);
Expand Down Expand Up @@ -3128,12 +3121,7 @@ impl Compiler {
// Stack at entry: [prev_exc (at handler_depth), lasti, exc]
// This RERAISE is within ExceptionHandler scope, so it routes to cleanup_block
// which does COPY 3; POP_EXCEPT; RERAISE
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack,
}
);
emit!(self, Instruction::Reraise { depth: 1 });
}

// Switch to normal exit block - this is where handler body success continues
Expand Down Expand Up @@ -3181,12 +3169,7 @@ impl Compiler {
// RERAISE 0
// Stack: [prev_exc, exc] - exception is on stack from PUSH_EXC_INFO
// NOTE: We emit RERAISE 0 BEFORE popping fblock so it is within cleanup handler scope
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack,
}
);
emit!(self, Instruction::Reraise { depth: 0 });

// Pop EXCEPTION_HANDLER fblock
// Pop after RERAISE so the instruction has the correct exception handler
Expand All @@ -3200,12 +3183,7 @@ impl Compiler {
self.switch_to_block(cleanup_block);
emit!(self, Instruction::Copy { index: 3_u32 });
emit!(self, Instruction::PopExcept);
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack,
}
);
emit!(self, Instruction::Reraise { depth: 1 });

// We successfully ran the try block:
// else:
Expand Down Expand Up @@ -3277,12 +3255,7 @@ impl Compiler {

// RERAISE 0: re-raise the original exception to outer handler
// Stack: [lasti, prev_exc, exc] - exception is on top
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack,
}
);
emit!(self, Instruction::Reraise { depth: 0 });
}

// finally cleanup block
Expand All @@ -3295,12 +3268,7 @@ impl Compiler {
// POP_EXCEPT: restore prev_exc as current exception
emit!(self, Instruction::PopExcept);
// RERAISE 1: reraise with lasti from stack
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack,
}
);
emit!(self, Instruction::Reraise { depth: 1 });
}

// End block - continuation point after try-finally
Expand Down Expand Up @@ -3673,23 +3641,13 @@ impl Compiler {

emit!(self, Instruction::Copy { index: 2_u32 });
emit!(self, Instruction::PopExcept);
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack
}
);
emit!(self, Instruction::Reraise { depth: 0 });

if let Some(cleanup) = finally_cleanup_block {
self.switch_to_block(cleanup);
emit!(self, Instruction::Copy { index: 3_u32 });
emit!(self, Instruction::PopExcept);
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack
}
);
emit!(self, Instruction::Reraise { depth: 1 });
}
}

Expand Down Expand Up @@ -4036,7 +3994,7 @@ impl Compiler {
emit!(self, Instruction::LoadDeref(idx));
} else {
let cond_annotations_name = self.name("__conditional_annotations__");
emit!(self, Instruction::LoadGlobal(cond_annotations_name));
self.emit_load_global(cond_annotations_name, false);
}
// CONTAINS_OP (in)
emit!(self, Instruction::ContainsOp(bytecode::Invert::No));
Expand Down Expand Up @@ -4528,7 +4486,7 @@ impl Compiler {

// Load (global) __name__ and store as __module__
let dunder_name = self.name("__name__");
emit!(self, Instruction::LoadGlobal(dunder_name));
self.emit_load_global(dunder_name, false);
let dunder_module = self.name("__module__");
emit!(self, Instruction::StoreName(dunder_module));

Expand Down Expand Up @@ -7994,12 +7952,7 @@ impl Compiler {
emit!(self, Instruction::StoreFast(idx));
}
// Re-raise the exception
emit!(
self,
Instruction::RaiseVarargs {
kind: bytecode::RaiseKind::ReraiseFromStack
}
);
emit!(self, Instruction::Reraise { depth: 0 });

// Normal end path
self.switch_to_block(end_block);
Expand Down Expand Up @@ -8118,6 +8071,13 @@ impl Compiler {
self.emit_arg(encoded, |arg| Instruction::LoadAttr { idx: arg })
}

/// Emit LOAD_GLOBAL.
/// Encodes: (name_idx << 1) | push_null_bit
fn emit_load_global(&mut self, name_idx: u32, push_null: bool) {
let encoded = (name_idx << 1) | u32::from(push_null);
self.emit_arg(encoded, Instruction::LoadGlobal);
}

/// Emit LOAD_SUPER_ATTR for 2-arg super().attr access.
/// Encodes: (name_idx << 2) | 0b10 (method=0, class=1)
fn emit_load_super_attr(&mut self, name_idx: u32) {
Expand Down
45 changes: 37 additions & 8 deletions crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ impl CodeInfo {
label_exception_targets(&mut self.blocks);
push_cold_blocks_to_end(&mut self.blocks);
normalize_jumps(&mut self.blocks);
self.optimize_load_global_push_null();

let max_stackdepth = self.max_stackdepth()?;
let cell2arg = self.cell2arg();
Expand Down Expand Up @@ -264,12 +265,12 @@ impl CodeInfo {
}
for block_idx in block_order {
let bi = block_idx.idx();
let mut src_instrs = core::mem::take(&mut blocks[bi].instructions);
let mut kept = Vec::with_capacity(src_instrs.len());
let mut src_instructions = core::mem::take(&mut blocks[bi].instructions);
let mut kept = Vec::with_capacity(src_instructions.len());
let mut prev_lineno = -1i32;

for src in 0..src_instrs.len() {
let instr = src_instrs[src];
for src in 0..src_instructions.len() {
let instr = src_instructions[src];
let lineno = instr
.lineno_override
.unwrap_or_else(|| instr.location.line.get() as i32);
Expand All @@ -281,14 +282,14 @@ impl CodeInfo {
remove = true;
}
// Remove if the next instruction has same line or no line.
else if src < src_instrs.len() - 1 {
let next_lineno = src_instrs[src + 1]
else if src < src_instructions.len() - 1 {
let next_lineno = src_instructions[src + 1]
.lineno_override
.unwrap_or_else(|| src_instrs[src + 1].location.line.get() as i32);
.unwrap_or_else(|| src_instructions[src + 1].location.line.get() as i32);
if next_lineno == lineno {
remove = true;
} else if next_lineno < 0 {
src_instrs[src + 1].lineno_override = Some(lineno);
src_instructions[src + 1].lineno_override = Some(lineno);
remove = true;
}
}
Expand Down Expand Up @@ -701,6 +702,34 @@ impl CodeInfo {
}
}

/// CPython flowgraph.c:
/// LOAD_GLOBAL <even> + PUSH_NULL -> LOAD_GLOBAL <odd>, NOP
fn optimize_load_global_push_null(&mut self) {
for block in &mut self.blocks {
let mut i = 0;
while i + 1 < block.instructions.len() {
let curr = &block.instructions[i];
let next = &block.instructions[i + 1];

let (Some(Instruction::LoadGlobal(_)), Some(Instruction::PushNull)) =
(curr.instr.real(), next.instr.real())
else {
i += 1;
continue;
};

let oparg = u32::from(block.instructions[i].arg);
if (oparg & 1) != 0 {
i += 1;
continue;
}

block.instructions[i].arg = OpArg::new(oparg | 1);
block.instructions.remove(i + 1);
}
}
}

/// Convert LOAD_CONST for small integers to LOAD_SMALL_INT
/// maybe_instr_make_load_smallint
fn convert_to_load_small_int(&mut self) {
Expand Down
Loading