Skip to content

Commit 42b24cb

Browse files
committed
Add duplicate_exits_without_lineno (disabled) and Block: Clone
Prepare infrastructure for exit block duplication optimization. Currently disabled pending stackdepth integration.
1 parent f8927e1 commit 42b24cb

File tree

4 files changed

+237
-104
lines changed

4 files changed

+237
-104
lines changed

crates/codegen/src/compile.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7575,7 +7575,8 @@ impl Compiler {
75757575
lower, upper, step, ..
75767576
}) => {
75777577
// Try constant slice folding first
7578-
if self.try_fold_constant_slice(lower.as_deref(), upper.as_deref(), step.as_deref()) {
7578+
if self.try_fold_constant_slice(lower.as_deref(), upper.as_deref(), step.as_deref())
7579+
{
75797580
return Ok(());
75807581
}
75817582
let mut compile_bound = |bound: Option<&ast::Expr>| match bound {
@@ -8998,17 +8999,16 @@ impl Compiler {
89988999
fn to_const(expr: Option<&ast::Expr>) -> Option<ConstantData> {
89999000
match expr {
90009001
None | Some(ast::Expr::NoneLiteral(_)) => Some(ConstantData::None),
9001-
Some(ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. })) => {
9002-
match value {
9003-
ast::Number::Int(i) => Some(ConstantData::Integer {
9004-
value: i.as_i64()?.into(),
9005-
}),
9006-
ast::Number::Float(f) => Some(ConstantData::Float { value: *f }),
9007-
ast::Number::Complex { real, imag } => Some(ConstantData::Complex {
9008-
value: num_complex::Complex64::new(*real, *imag),
9009-
}),
9010-
}
9011-
}
9002+
Some(ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. })) => match value
9003+
{
9004+
ast::Number::Int(i) => Some(ConstantData::Integer {
9005+
value: i.as_i64()?.into(),
9006+
}),
9007+
ast::Number::Float(f) => Some(ConstantData::Float { value: *f }),
9008+
ast::Number::Complex { real, imag } => Some(ConstantData::Complex {
9009+
value: num_complex::Complex64::new(*real, *imag),
9010+
}),
9011+
},
90129012
Some(ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. })) => {
90139013
Some(ConstantData::Boolean { value: *value })
90149014
}

crates/codegen/src/ir.rs

Lines changed: 185 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub struct ExceptHandlerInfo {
125125
// spell-checker:ignore petgraph
126126
// TODO: look into using petgraph for handling blocks and stuff? it's heavier than this, but it
127127
// might enable more analysis/optimizations
128-
#[derive(Debug)]
128+
#[derive(Debug, Clone)]
129129
pub struct Block {
130130
pub instructions: Vec<InstructionInfo>,
131131
pub next: BlockIdx,
@@ -225,6 +225,8 @@ impl CodeInfo {
225225
normalize_jumps(&mut self.blocks);
226226
self.dce(); // re-run within-block DCE after normalize_jumps creates new instructions
227227
self.eliminate_unreachable_blocks();
228+
// TODO: duplicate_exits_without_lineno disabled pending stackdepth fix
229+
// duplicate_exits_without_lineno(&mut self.blocks);
228230
duplicate_end_returns(&mut self.blocks);
229231
self.dce(); // truncate after terminal in blocks that got return duplicated
230232
self.eliminate_unreachable_blocks(); // remove now-unreachable last block
@@ -2239,15 +2241,14 @@ fn jump_threading(blocks: &mut [Block]) {
22392241
}
22402242
// Check if target block's first instruction is an unconditional jump
22412243
let target_block = &blocks[target.idx()];
2242-
if let Some(target_ins) = target_block.instructions.first() {
2243-
if target_ins.instr.is_unconditional_jump()
2244-
&& target_ins.target != BlockIdx::NULL
2245-
&& target_ins.target != target
2246-
{
2247-
let final_target = target_ins.target;
2248-
blocks[bi].instructions[last_idx].target = final_target;
2249-
changed = true;
2250-
}
2244+
if let Some(target_ins) = target_block.instructions.first()
2245+
&& target_ins.instr.is_unconditional_jump()
2246+
&& target_ins.target != BlockIdx::NULL
2247+
&& target_ins.target != target
2248+
{
2249+
let final_target = target_ins.target;
2250+
blocks[bi].instructions[last_idx].target = final_target;
2251+
changed = true;
22512252
}
22522253
}
22532254
}
@@ -2301,8 +2302,7 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
23012302

23022303
visited.fill(false);
23032304

2304-
for vi in 0..visit_order.len() {
2305-
let block_idx = visit_order[vi];
2305+
for &block_idx in &visit_order {
23062306
let idx = block_idx.idx();
23072307
visited[idx] = true;
23082308

@@ -2323,76 +2323,80 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
23232323

23242324
// Normalize conditional jumps: forward gets NOT_TAKEN, backward gets inverted
23252325
let last = blocks[idx].instructions.last();
2326-
if let Some(last_ins) = last {
2327-
if is_conditional_jump(&last_ins.instr) && last_ins.target != BlockIdx::NULL {
2328-
let target = last_ins.target;
2329-
let is_forward = !visited[target.idx()];
2330-
2331-
if is_forward {
2332-
// Insert NOT_TAKEN after forward conditional jump
2333-
let not_taken = InstructionInfo {
2326+
if let Some(last_ins) = last
2327+
&& is_conditional_jump(&last_ins.instr)
2328+
&& last_ins.target != BlockIdx::NULL
2329+
{
2330+
let target = last_ins.target;
2331+
let is_forward = !visited[target.idx()];
2332+
2333+
if is_forward {
2334+
// Insert NOT_TAKEN after forward conditional jump
2335+
let not_taken = InstructionInfo {
2336+
instr: Instruction::NotTaken.into(),
2337+
arg: OpArg::new(0),
2338+
target: BlockIdx::NULL,
2339+
location: last_ins.location,
2340+
end_location: last_ins.end_location,
2341+
except_handler: last_ins.except_handler,
2342+
lineno_override: None,
2343+
cache_entries: 0,
2344+
};
2345+
blocks[idx].instructions.push(not_taken);
2346+
} else {
2347+
// Backward conditional jump: invert and create new block
2348+
// Transform: `cond_jump T` (backward)
2349+
// Into: `reversed_cond_jump b_next` + new block [NOT_TAKEN, JUMP T]
2350+
let loc = last_ins.location;
2351+
let end_loc = last_ins.end_location;
2352+
let exc_handler = last_ins.except_handler;
2353+
2354+
if let Some(reversed) = reversed_conditional(&last_ins.instr) {
2355+
let old_next = blocks[idx].next;
2356+
let is_cold = blocks[idx].cold;
2357+
2358+
// Create new block with NOT_TAKEN + JUMP to original backward target
2359+
let new_block_idx = BlockIdx(blocks.len() as u32);
2360+
let mut new_block = Block {
2361+
cold: is_cold,
2362+
..Block::default()
2363+
};
2364+
new_block.instructions.push(InstructionInfo {
23342365
instr: Instruction::NotTaken.into(),
23352366
arg: OpArg::new(0),
23362367
target: BlockIdx::NULL,
2337-
location: last_ins.location,
2338-
end_location: last_ins.end_location,
2339-
except_handler: last_ins.except_handler,
2368+
location: loc,
2369+
end_location: end_loc,
2370+
except_handler: exc_handler,
23402371
lineno_override: None,
23412372
cache_entries: 0,
2342-
};
2343-
blocks[idx].instructions.push(not_taken);
2344-
} else {
2345-
// Backward conditional jump: invert and create new block
2346-
// Transform: `cond_jump T` (backward)
2347-
// Into: `reversed_cond_jump b_next` + new block [NOT_TAKEN, JUMP T]
2348-
let loc = last_ins.location;
2349-
let end_loc = last_ins.end_location;
2350-
let exc_handler = last_ins.except_handler;
2351-
2352-
if let Some(reversed) = reversed_conditional(&last_ins.instr) {
2353-
let old_next = blocks[idx].next;
2354-
let is_cold = blocks[idx].cold;
2355-
2356-
// Create new block with NOT_TAKEN + JUMP to original backward target
2357-
let new_block_idx = BlockIdx(blocks.len() as u32);
2358-
let mut new_block = Block {
2359-
cold: is_cold,
2360-
..Block::default()
2361-
};
2362-
new_block.instructions.push(InstructionInfo {
2363-
instr: Instruction::NotTaken.into(),
2364-
arg: OpArg::new(0),
2365-
target: BlockIdx::NULL,
2366-
location: loc,
2367-
end_location: end_loc,
2368-
except_handler: exc_handler,
2369-
lineno_override: None,
2370-
cache_entries: 0,
2371-
});
2372-
new_block.instructions.push(InstructionInfo {
2373-
instr: PseudoInstruction::Jump { delta: Arg::marker() }.into(),
2374-
arg: OpArg::new(0),
2375-
target,
2376-
location: loc,
2377-
end_location: end_loc,
2378-
except_handler: exc_handler,
2379-
lineno_override: None,
2380-
cache_entries: 0,
2381-
});
2382-
new_block.next = old_next;
2383-
2384-
// Update the conditional jump: invert opcode, target = old next block
2385-
let last_mut = blocks[idx].instructions.last_mut().unwrap();
2386-
last_mut.instr = reversed;
2387-
last_mut.target = old_next;
2388-
2389-
// Splice new block between current and old next
2390-
blocks[idx].next = new_block_idx;
2391-
blocks.push(new_block);
2392-
2393-
// Extend visited array and update visit order
2394-
visited.push(true);
2395-
}
2373+
});
2374+
new_block.instructions.push(InstructionInfo {
2375+
instr: PseudoInstruction::Jump {
2376+
delta: Arg::marker(),
2377+
}
2378+
.into(),
2379+
arg: OpArg::new(0),
2380+
target,
2381+
location: loc,
2382+
end_location: end_loc,
2383+
except_handler: exc_handler,
2384+
lineno_override: None,
2385+
cache_entries: 0,
2386+
});
2387+
new_block.next = old_next;
2388+
2389+
// Update the conditional jump: invert opcode, target = old next block
2390+
let last_mut = blocks[idx].instructions.last_mut().unwrap();
2391+
last_mut.instr = reversed;
2392+
last_mut.target = old_next;
2393+
2394+
// Splice new block between current and old next
2395+
blocks[idx].next = new_block_idx;
2396+
blocks.push(new_block);
2397+
2398+
// Extend visited array and update visit order
2399+
visited.push(true);
23962400
}
23972401
}
23982402
}
@@ -2496,10 +2500,111 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
24962500
}
24972501
}
24982502

2503+
#[allow(dead_code)]
2504+
fn is_simple_return_block(block: &Block) -> bool {
2505+
block.instructions.len() == 1
2506+
&& matches!(
2507+
block.instructions[0].instr.real(),
2508+
Some(Instruction::ReturnValue)
2509+
)
2510+
}
2511+
2512+
/// Follow chain of empty blocks to find first non-empty block.
2513+
fn next_nonempty_block(blocks: &[Block], mut idx: BlockIdx) -> BlockIdx {
2514+
while idx != BlockIdx::NULL
2515+
&& blocks[idx.idx()].instructions.is_empty()
2516+
&& blocks[idx.idx()].next != BlockIdx::NULL
2517+
{
2518+
idx = blocks[idx.idx()].next;
2519+
}
2520+
idx
2521+
}
2522+
2523+
#[allow(dead_code)]
2524+
fn duplicate_exits_without_lineno(blocks: &mut Vec<Block>) {
2525+
// Count predecessors for each block
2526+
let mut predecessors = vec![0u32; blocks.len()];
2527+
let mut current = BlockIdx(0);
2528+
while current != BlockIdx::NULL {
2529+
let block = &blocks[current.idx()];
2530+
// Fall-through predecessor
2531+
let next = next_nonempty_block(blocks, block.next);
2532+
if next != BlockIdx::NULL {
2533+
let has_fallthrough = block
2534+
.instructions
2535+
.last()
2536+
.is_none_or(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump());
2537+
if has_fallthrough {
2538+
predecessors[next.idx()] += 1;
2539+
}
2540+
}
2541+
// Jump target predecessor
2542+
for ins in &block.instructions {
2543+
if ins.target != BlockIdx::NULL {
2544+
let target = next_nonempty_block(blocks, ins.target);
2545+
if target != BlockIdx::NULL {
2546+
predecessors[target.idx()] += 1;
2547+
}
2548+
}
2549+
}
2550+
current = block.next;
2551+
}
2552+
2553+
// For each block, if its last instruction jumps to an exit block without lineno
2554+
// that has >1 predecessor, copy that exit block.
2555+
current = BlockIdx(0);
2556+
while current != BlockIdx::NULL {
2557+
let block = &blocks[current.idx()];
2558+
let last = match block.instructions.last() {
2559+
Some(ins) if ins.target != BlockIdx::NULL => ins,
2560+
_ => {
2561+
current = blocks[current.idx()].next;
2562+
continue;
2563+
}
2564+
};
2565+
if !last.instr.is_unconditional_jump() && !is_conditional_jump(&last.instr) {
2566+
current = blocks[current.idx()].next;
2567+
continue;
2568+
}
2569+
2570+
let target = next_nonempty_block(blocks, last.target);
2571+
if target == BlockIdx::NULL || !is_simple_return_block(&blocks[target.idx()]) {
2572+
current = blocks[current.idx()].next;
2573+
continue;
2574+
}
2575+
if predecessors[target.idx()] <= 1 {
2576+
current = blocks[current.idx()].next;
2577+
continue;
2578+
}
2579+
2580+
// Copy the exit block
2581+
let new_idx = BlockIdx(blocks.len() as u32);
2582+
let mut new_block = blocks[target.idx()].clone();
2583+
// Set location from the jump instruction
2584+
let jump_loc = blocks[current.idx()].instructions.last().unwrap().location;
2585+
let jump_end_loc = blocks[current.idx()]
2586+
.instructions
2587+
.last()
2588+
.unwrap()
2589+
.end_location;
2590+
if let Some(first) = new_block.instructions.first_mut() {
2591+
first.location = jump_loc;
2592+
first.end_location = jump_end_loc;
2593+
}
2594+
new_block.next = blocks[target.idx()].next;
2595+
blocks.push(new_block);
2596+
2597+
// Update the jump target
2598+
let last_mut = blocks[current.idx()].instructions.last_mut().unwrap();
2599+
last_mut.target = new_idx;
2600+
predecessors[target.idx()] -= 1;
2601+
2602+
current = blocks[current.idx()].next;
2603+
}
2604+
}
2605+
24992606
/// Duplicate `LOAD_CONST None + RETURN_VALUE` for blocks that fall through
2500-
/// to the final return block. Matches CPython's behavior of ensuring every
2501-
/// code path that reaches the end of a function/module has its own explicit
2502-
/// return instruction.
2607+
/// to the final return block.
25032608
fn duplicate_end_returns(blocks: &mut [Block]) {
25042609
// Walk the block chain to find the last block
25052610
let mut last_block = BlockIdx(0);

crates/compiler-core/src/bytecode.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -851,17 +851,37 @@ impl CodeUnits {
851851
/// ```
852852
#[derive(Debug, Clone)]
853853
pub enum ConstantData {
854-
Tuple { elements: Vec<ConstantData> },
855-
Integer { value: BigInt },
856-
Float { value: f64 },
857-
Complex { value: Complex64 },
858-
Boolean { value: bool },
859-
Str { value: Wtf8Buf },
860-
Bytes { value: Vec<u8> },
861-
Code { code: Box<CodeObject> },
854+
Tuple {
855+
elements: Vec<ConstantData>,
856+
},
857+
Integer {
858+
value: BigInt,
859+
},
860+
Float {
861+
value: f64,
862+
},
863+
Complex {
864+
value: Complex64,
865+
},
866+
Boolean {
867+
value: bool,
868+
},
869+
Str {
870+
value: Wtf8Buf,
871+
},
872+
Bytes {
873+
value: Vec<u8>,
874+
},
875+
Code {
876+
code: Box<CodeObject>,
877+
},
862878
/// Constant slice(start, stop, step)
863-
Slice { elements: Box<[ConstantData; 3]> },
864-
Frozenset { elements: Vec<ConstantData> },
879+
Slice {
880+
elements: Box<[ConstantData; 3]>,
881+
},
882+
Frozenset {
883+
elements: Vec<ConstantData>,
884+
},
865885
None,
866886
Ellipsis,
867887
}

0 commit comments

Comments
 (0)