Skip to content

Commit d877910

Browse files
committed
Analyze liveness of vregs
1 parent 171cec4 commit d877910

2 files changed

Lines changed: 248 additions & 0 deletions

File tree

zjit/src/backend/lir.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::mem::take;
44
use std::panic;
55
use std::rc::Rc;
66
use std::sync::{Arc, Mutex};
7+
use crate::bitset::BitSet;
78
use crate::codegen::local_size_and_idx_to_ep_offset;
89
use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary};
910
use crate::hir::{Invariant, SideExitReason};
@@ -127,6 +128,18 @@ impl BasicBlock {
127128
}
128129
}
129130
}
131+
132+
pub fn successors(&self) -> Vec<BlockId> {
133+
let EdgePair(edge1, edge2) = self.edges();
134+
let mut succs = Vec::new();
135+
if let Some(edge) = edge1 {
136+
succs.push(edge.target);
137+
}
138+
if let Some(edge) = edge2 {
139+
succs.push(edge.target);
140+
}
141+
succs
142+
}
130143
}
131144

132145
pub use crate::backend::current::{
@@ -2332,6 +2345,107 @@ impl Assembler
23322345
.map(move |(idx, (insn, insn_id))| (block_id, insn_id, idx, insn))
23332346
})
23342347
}
2348+
2349+
/// Compute initial liveness sets (kill and gen) for the given blocks.
2350+
/// Returns (kill_sets, gen_sets) where each is indexed by block ID.
2351+
/// - kill: VRegs defined (written) in the block
2352+
/// - gen: VRegs used (read) in the block before being defined
2353+
pub fn compute_initial_liveness_sets(&self, block_ids: &[BlockId]) -> (Vec<BitSet<usize>>, Vec<BitSet<usize>>) {
2354+
let num_blocks = self.basic_blocks.len();
2355+
let num_vregs = self.live_ranges.len();
2356+
2357+
let mut kill_sets: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2358+
let mut gen_sets: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2359+
2360+
for &block_id in block_ids {
2361+
let block = &self.basic_blocks[block_id.0];
2362+
let kill_set = &mut kill_sets[block_id.0];
2363+
let gen_set = &mut gen_sets[block_id.0];
2364+
2365+
// Add block parameters to kill set FIRST (they're defined at block entry)
2366+
for param in &block.parameters {
2367+
if let Opnd::VReg { idx, .. } = param {
2368+
kill_set.insert(*idx);
2369+
}
2370+
}
2371+
2372+
// Iterate over instructions in reverse
2373+
for insn in block.insns.iter().rev() {
2374+
// If the instruction has an output that is a VReg, add to kill set
2375+
if let Some(out) = insn.out_opnd() {
2376+
if let Opnd::VReg { idx, .. } = out {
2377+
kill_set.insert(*idx);
2378+
}
2379+
}
2380+
2381+
// For all input operands that are VRegs, add to gen set
2382+
// (only if not already in kill set)
2383+
for opnd in insn.opnd_iter() {
2384+
if let Opnd::VReg { idx, .. } = opnd {
2385+
if !kill_set.get(*idx) {
2386+
gen_set.insert(*idx);
2387+
}
2388+
}
2389+
}
2390+
}
2391+
}
2392+
2393+
(kill_sets, gen_sets)
2394+
}
2395+
2396+
/// Analyze liveness for all blocks using a fixed-point algorithm.
2397+
/// Returns live_in sets for each block, indexed by block ID.
2398+
/// A VReg is live-in to a block if it may be used before being defined.
2399+
pub fn analyze_liveness(&self) -> Vec<BitSet<usize>> {
2400+
// Get blocks in postorder
2401+
let po_blocks = {
2402+
let entry_blocks: Vec<BlockId> = self.basic_blocks.iter()
2403+
.filter(|block| block.entry)
2404+
.map(|block| block.id)
2405+
.collect();
2406+
self.po_from(entry_blocks)
2407+
};
2408+
2409+
// Compute initial gen/kill sets
2410+
let (kill_sets, gen_sets) = self.compute_initial_liveness_sets(&po_blocks);
2411+
2412+
let num_blocks = self.basic_blocks.len();
2413+
let num_vregs = self.live_ranges.len();
2414+
2415+
// Initialize live_in sets
2416+
let mut live_in: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2417+
2418+
// Fixed-point iteration
2419+
let mut changed = true;
2420+
while changed {
2421+
changed = false;
2422+
2423+
// Iterate over blocks in postorder
2424+
for &block_id in &po_blocks {
2425+
let block = &self.basic_blocks[block_id.0];
2426+
2427+
// block_live = union of live_in[succ] for all successors
2428+
let mut block_live = BitSet::with_capacity(num_vregs);
2429+
for succ_id in block.successors() {
2430+
block_live.union_with(&live_in[succ_id.0]);
2431+
}
2432+
2433+
// block_live |= gen[block]
2434+
block_live.union_with(&gen_sets[block_id.0]);
2435+
2436+
// block_live &= ~kill[block]
2437+
block_live.difference_with(&kill_sets[block_id.0]);
2438+
2439+
// Update live_in if changed
2440+
if !live_in[block_id.0].equals(&block_live) {
2441+
live_in[block_id.0] = block_live;
2442+
changed = true;
2443+
}
2444+
}
2445+
}
2446+
2447+
live_in
2448+
}
23352449
}
23362450

23372451
/// Return a result of fmt::Display for Assembler without escape sequence
@@ -3187,4 +3301,103 @@ mod tests {
31873301
(Opnd::mem(64, C_ARG_OPNDS[0], 0), CFP),
31883302
], Some(scratch_reg()));
31893303
}
3304+
3305+
// Helper function to convert a BitSet to a list of vreg indices
3306+
fn bitset_to_vreg_indices(bitset: &BitSet<usize>, num_vregs: usize) -> Vec<usize> {
3307+
(0..num_vregs)
3308+
.filter(|&idx| bitset.get(idx))
3309+
.collect()
3310+
}
3311+
3312+
struct TestFunc {
3313+
asm: Assembler,
3314+
r10: Opnd,
3315+
r11: Opnd,
3316+
r12: Opnd,
3317+
r13: Opnd,
3318+
r14: Opnd,
3319+
r15: Opnd,
3320+
b1: BlockId,
3321+
b2: BlockId,
3322+
b3: BlockId,
3323+
b4: BlockId,
3324+
}
3325+
3326+
fn build_func() -> TestFunc {
3327+
let mut asm = Assembler::new();
3328+
3329+
// Create virtual registers - these will be parameters
3330+
let r10 = asm.new_vreg(64);
3331+
let r11 = asm.new_vreg(64);
3332+
let r12 = asm.new_vreg(64);
3333+
let r13 = asm.new_vreg(64);
3334+
3335+
// Create blocks
3336+
let b1 = asm.new_block(hir::BlockId(0), true, 0);
3337+
let b2 = asm.new_block(hir::BlockId(1), false, 1);
3338+
let b3 = asm.new_block(hir::BlockId(2), false, 2);
3339+
let b4 = asm.new_block(hir::BlockId(3), false, 3);
3340+
3341+
// Build b1: define(r10, r11) { jump(edge(b2, [imm(1), r11])) }
3342+
asm.set_current_block(b1);
3343+
asm.basic_blocks[b1.0].add_parameter(r10);
3344+
asm.basic_blocks[b1.0].add_parameter(r11);
3345+
asm.basic_blocks[b1.0].push_insn(Insn::Jmp(Target::Block(BranchEdge {
3346+
target: b2,
3347+
args: vec![Opnd::UImm(1), r11],
3348+
})));
3349+
3350+
// Build b2: define(r12, r13) { cmp(r13, imm(1)); blt(...) }
3351+
asm.set_current_block(b2);
3352+
asm.basic_blocks[b2.0].add_parameter(r12);
3353+
asm.basic_blocks[b2.0].add_parameter(r13);
3354+
asm.basic_blocks[b2.0].push_insn(Insn::Cmp { left: r13, right: Opnd::UImm(1) });
3355+
asm.basic_blocks[b2.0].push_insn(Insn::Jl(Target::Block(BranchEdge { target: b4, args: vec![] })));
3356+
asm.basic_blocks[b2.0].push_insn(Insn::Jmp(Target::Block(BranchEdge { target: b3, args: vec![] })));
3357+
3358+
// Build b3: r14 = mul(r12, r13); r15 = sub(r13, imm(1)); jump(edge(b2, [r14, r15]))
3359+
asm.set_current_block(b3);
3360+
let r14 = asm.new_vreg(64);
3361+
let r15 = asm.new_vreg(64);
3362+
asm.basic_blocks[b3.0].push_insn(Insn::Mul { left: r12, right: r13, out: r14 });
3363+
asm.basic_blocks[b3.0].push_insn(Insn::Sub { left: r13, right: Opnd::UImm(1), out: r15 });
3364+
asm.basic_blocks[b3.0].push_insn(Insn::Jmp(Target::Block(BranchEdge {
3365+
target: b2,
3366+
args: vec![r14, r15],
3367+
})));
3368+
3369+
// Build b4: out = add(r10, r12); ret out
3370+
asm.set_current_block(b4);
3371+
let out = asm.new_vreg(64);
3372+
asm.basic_blocks[b4.0].push_insn(Insn::Add { left: r10, right: r12, out });
3373+
asm.basic_blocks[b4.0].push_insn(Insn::CRet(out));
3374+
3375+
TestFunc { asm, r10, r11, r12, r13, r14, r15, b1, b2, b3, b4 }
3376+
}
3377+
3378+
#[test]
3379+
fn test_live_in() {
3380+
let TestFunc { asm, r10, r12, r13, b1, b2, b3, b4, .. } = build_func();
3381+
3382+
let num_vregs = asm.live_ranges.len();
3383+
let live_in = asm.analyze_liveness();
3384+
3385+
// b1: [] - entry block, no variables are live-in
3386+
assert_eq!(bitset_to_vreg_indices(&live_in[b1.0], num_vregs), vec![]);
3387+
3388+
// b2: [r10] - r10 is live-in (used in b4 which is reachable)
3389+
assert_eq!(bitset_to_vreg_indices(&live_in[b2.0], num_vregs), vec![r10.vreg_idx()]);
3390+
3391+
// b3: [r10, r12, r13] - all are live-in
3392+
assert_eq!(
3393+
bitset_to_vreg_indices(&live_in[b3.0], num_vregs),
3394+
vec![r10.vreg_idx(), r12.vreg_idx(), r13.vreg_idx()]
3395+
);
3396+
3397+
// b4: [r10, r12] - both are live-in
3398+
assert_eq!(
3399+
bitset_to_vreg_indices(&live_in[b4.0], num_vregs),
3400+
vec![r10.vreg_idx(), r12.vreg_idx()]
3401+
);
3402+
}
31903403
}

zjit/src/bitset.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,41 @@ impl<T: Into<usize> + Copy> BitSet<T> {
5757
}
5858
changed
5959
}
60+
61+
/// Modify `self` to have bits set if they are set in either `self` or `other`. Returns true if `self`
62+
/// was modified, and false otherwise.
63+
/// `self` and `other` must have the same number of bits.
64+
pub fn union_with(&mut self, other: &Self) -> bool {
65+
assert_eq!(self.num_bits, other.num_bits);
66+
let mut changed = false;
67+
for i in 0..self.entries.len() {
68+
let before = self.entries[i];
69+
self.entries[i] |= other.entries[i];
70+
changed |= self.entries[i] != before;
71+
}
72+
changed
73+
}
74+
75+
/// Modify `self` to remove bits that are set in `other`. Returns true if `self`
76+
/// was modified, and false otherwise.
77+
/// `self` and `other` must have the same number of bits.
78+
pub fn difference_with(&mut self, other: &Self) -> bool {
79+
assert_eq!(self.num_bits, other.num_bits);
80+
let mut changed = false;
81+
for i in 0..self.entries.len() {
82+
let before = self.entries[i];
83+
self.entries[i] &= !other.entries[i];
84+
changed |= self.entries[i] != before;
85+
}
86+
changed
87+
}
88+
89+
/// Check if two BitSets are equal.
90+
/// `self` and `other` must have the same number of bits.
91+
pub fn equals(&self, other: &Self) -> bool {
92+
assert_eq!(self.num_bits, other.num_bits);
93+
self.entries == other.entries
94+
}
6095
}
6196

6297
#[cfg(test)]

0 commit comments

Comments
 (0)