Skip to content

Commit ab8fd57

Browse files
committed
Analyze liveness of vregs
1 parent 401dad6 commit ab8fd57

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};
@@ -139,6 +140,18 @@ impl BasicBlock {
139140
pub fn sort_key(&self) -> (usize, usize) {
140141
(self.rpo_index, self.id.0)
141142
}
143+
144+
pub fn successors(&self) -> Vec<BlockId> {
145+
let EdgePair(edge1, edge2) = self.edges();
146+
let mut succs = Vec::new();
147+
if let Some(edge) = edge1 {
148+
succs.push(edge.target);
149+
}
150+
if let Some(edge) = edge2 {
151+
succs.push(edge.target);
152+
}
153+
succs
154+
}
142155
}
143156

144157
pub use crate::backend::current::{
@@ -2363,6 +2376,107 @@ impl Assembler
23632376
.map(move |(idx, (insn, insn_id))| (block_id, insn_id, idx, insn))
23642377
})
23652378
}
2379+
2380+
/// Compute initial liveness sets (kill and gen) for the given blocks.
2381+
/// Returns (kill_sets, gen_sets) where each is indexed by block ID.
2382+
/// - kill: VRegs defined (written) in the block
2383+
/// - gen: VRegs used (read) in the block before being defined
2384+
pub fn compute_initial_liveness_sets(&self, block_ids: &[BlockId]) -> (Vec<BitSet<usize>>, Vec<BitSet<usize>>) {
2385+
let num_blocks = self.basic_blocks.len();
2386+
let num_vregs = self.live_ranges.len();
2387+
2388+
let mut kill_sets: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2389+
let mut gen_sets: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2390+
2391+
for &block_id in block_ids {
2392+
let block = &self.basic_blocks[block_id.0];
2393+
let kill_set = &mut kill_sets[block_id.0];
2394+
let gen_set = &mut gen_sets[block_id.0];
2395+
2396+
// Add block parameters to kill set FIRST (they're defined at block entry)
2397+
for param in &block.parameters {
2398+
if let Opnd::VReg { idx, .. } = param {
2399+
kill_set.insert(*idx);
2400+
}
2401+
}
2402+
2403+
// Iterate over instructions in reverse
2404+
for insn in block.insns.iter().rev() {
2405+
// If the instruction has an output that is a VReg, add to kill set
2406+
if let Some(out) = insn.out_opnd() {
2407+
if let Opnd::VReg { idx, .. } = out {
2408+
kill_set.insert(*idx);
2409+
}
2410+
}
2411+
2412+
// For all input operands that are VRegs, add to gen set
2413+
// (only if not already in kill set)
2414+
for opnd in insn.opnd_iter() {
2415+
if let Opnd::VReg { idx, .. } = opnd {
2416+
if !kill_set.get(*idx) {
2417+
gen_set.insert(*idx);
2418+
}
2419+
}
2420+
}
2421+
}
2422+
}
2423+
2424+
(kill_sets, gen_sets)
2425+
}
2426+
2427+
/// Analyze liveness for all blocks using a fixed-point algorithm.
2428+
/// Returns live_in sets for each block, indexed by block ID.
2429+
/// A VReg is live-in to a block if it may be used before being defined.
2430+
pub fn analyze_liveness(&self) -> Vec<BitSet<usize>> {
2431+
// Get blocks in postorder
2432+
let po_blocks = {
2433+
let entry_blocks: Vec<BlockId> = self.basic_blocks.iter()
2434+
.filter(|block| block.entry)
2435+
.map(|block| block.id)
2436+
.collect();
2437+
self.po_from(entry_blocks)
2438+
};
2439+
2440+
// Compute initial gen/kill sets
2441+
let (kill_sets, gen_sets) = self.compute_initial_liveness_sets(&po_blocks);
2442+
2443+
let num_blocks = self.basic_blocks.len();
2444+
let num_vregs = self.live_ranges.len();
2445+
2446+
// Initialize live_in sets
2447+
let mut live_in: Vec<BitSet<usize>> = vec![BitSet::with_capacity(num_vregs); num_blocks];
2448+
2449+
// Fixed-point iteration
2450+
let mut changed = true;
2451+
while changed {
2452+
changed = false;
2453+
2454+
// Iterate over blocks in postorder
2455+
for &block_id in &po_blocks {
2456+
let block = &self.basic_blocks[block_id.0];
2457+
2458+
// block_live = union of live_in[succ] for all successors
2459+
let mut block_live = BitSet::with_capacity(num_vregs);
2460+
for succ_id in block.successors() {
2461+
block_live.union_with(&live_in[succ_id.0]);
2462+
}
2463+
2464+
// block_live |= gen[block]
2465+
block_live.union_with(&gen_sets[block_id.0]);
2466+
2467+
// block_live &= ~kill[block]
2468+
block_live.difference_with(&kill_sets[block_id.0]);
2469+
2470+
// Update live_in if changed
2471+
if !live_in[block_id.0].equals(&block_live) {
2472+
live_in[block_id.0] = block_live;
2473+
changed = true;
2474+
}
2475+
}
2476+
}
2477+
2478+
live_in
2479+
}
23662480
}
23672481

23682482
/// Return a result of fmt::Display for Assembler without escape sequence
@@ -3222,4 +3336,103 @@ mod tests {
32223336
(Opnd::mem(64, C_ARG_OPNDS[0], 0), CFP),
32233337
], Some(scratch_reg()));
32243338
}
3339+
3340+
// Helper function to convert a BitSet to a list of vreg indices
3341+
fn bitset_to_vreg_indices(bitset: &BitSet<usize>, num_vregs: usize) -> Vec<usize> {
3342+
(0..num_vregs)
3343+
.filter(|&idx| bitset.get(idx))
3344+
.collect()
3345+
}
3346+
3347+
struct TestFunc {
3348+
asm: Assembler,
3349+
r10: Opnd,
3350+
r11: Opnd,
3351+
r12: Opnd,
3352+
r13: Opnd,
3353+
r14: Opnd,
3354+
r15: Opnd,
3355+
b1: BlockId,
3356+
b2: BlockId,
3357+
b3: BlockId,
3358+
b4: BlockId,
3359+
}
3360+
3361+
fn build_func() -> TestFunc {
3362+
let mut asm = Assembler::new();
3363+
3364+
// Create virtual registers - these will be parameters
3365+
let r10 = asm.new_vreg(64);
3366+
let r11 = asm.new_vreg(64);
3367+
let r12 = asm.new_vreg(64);
3368+
let r13 = asm.new_vreg(64);
3369+
3370+
// Create blocks
3371+
let b1 = asm.new_block(hir::BlockId(0), true, 0);
3372+
let b2 = asm.new_block(hir::BlockId(1), false, 1);
3373+
let b3 = asm.new_block(hir::BlockId(2), false, 2);
3374+
let b4 = asm.new_block(hir::BlockId(3), false, 3);
3375+
3376+
// Build b1: define(r10, r11) { jump(edge(b2, [imm(1), r11])) }
3377+
asm.set_current_block(b1);
3378+
asm.basic_blocks[b1.0].add_parameter(r10);
3379+
asm.basic_blocks[b1.0].add_parameter(r11);
3380+
asm.basic_blocks[b1.0].push_insn(Insn::Jmp(Target::Block(BranchEdge {
3381+
target: b2,
3382+
args: vec![Opnd::UImm(1), r11],
3383+
})));
3384+
3385+
// Build b2: define(r12, r13) { cmp(r13, imm(1)); blt(...) }
3386+
asm.set_current_block(b2);
3387+
asm.basic_blocks[b2.0].add_parameter(r12);
3388+
asm.basic_blocks[b2.0].add_parameter(r13);
3389+
asm.basic_blocks[b2.0].push_insn(Insn::Cmp { left: r13, right: Opnd::UImm(1) });
3390+
asm.basic_blocks[b2.0].push_insn(Insn::Jl(Target::Block(BranchEdge { target: b4, args: vec![] })));
3391+
asm.basic_blocks[b2.0].push_insn(Insn::Jmp(Target::Block(BranchEdge { target: b3, args: vec![] })));
3392+
3393+
// Build b3: r14 = mul(r12, r13); r15 = sub(r13, imm(1)); jump(edge(b2, [r14, r15]))
3394+
asm.set_current_block(b3);
3395+
let r14 = asm.new_vreg(64);
3396+
let r15 = asm.new_vreg(64);
3397+
asm.basic_blocks[b3.0].push_insn(Insn::Mul { left: r12, right: r13, out: r14 });
3398+
asm.basic_blocks[b3.0].push_insn(Insn::Sub { left: r13, right: Opnd::UImm(1), out: r15 });
3399+
asm.basic_blocks[b3.0].push_insn(Insn::Jmp(Target::Block(BranchEdge {
3400+
target: b2,
3401+
args: vec![r14, r15],
3402+
})));
3403+
3404+
// Build b4: out = add(r10, r12); ret out
3405+
asm.set_current_block(b4);
3406+
let out = asm.new_vreg(64);
3407+
asm.basic_blocks[b4.0].push_insn(Insn::Add { left: r10, right: r12, out });
3408+
asm.basic_blocks[b4.0].push_insn(Insn::CRet(out));
3409+
3410+
TestFunc { asm, r10, r11, r12, r13, r14, r15, b1, b2, b3, b4 }
3411+
}
3412+
3413+
#[test]
3414+
fn test_live_in() {
3415+
let TestFunc { asm, r10, r12, r13, b1, b2, b3, b4, .. } = build_func();
3416+
3417+
let num_vregs = asm.live_ranges.len();
3418+
let live_in = asm.analyze_liveness();
3419+
3420+
// b1: [] - entry block, no variables are live-in
3421+
assert_eq!(bitset_to_vreg_indices(&live_in[b1.0], num_vregs), vec![]);
3422+
3423+
// b2: [r10] - r10 is live-in (used in b4 which is reachable)
3424+
assert_eq!(bitset_to_vreg_indices(&live_in[b2.0], num_vregs), vec![r10.vreg_idx()]);
3425+
3426+
// b3: [r10, r12, r13] - all are live-in
3427+
assert_eq!(
3428+
bitset_to_vreg_indices(&live_in[b3.0], num_vregs),
3429+
vec![r10.vreg_idx(), r12.vreg_idx(), r13.vreg_idx()]
3430+
);
3431+
3432+
// b4: [r10, r12] - both are live-in
3433+
assert_eq!(
3434+
bitset_to_vreg_indices(&live_in[b4.0], num_vregs),
3435+
vec![r10.vreg_idx(), r12.vreg_idx()]
3436+
);
3437+
}
32253438
}

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)