@@ -4,6 +4,7 @@ use std::mem::take;
44use std:: panic;
55use std:: rc:: Rc ;
66use std:: sync:: { Arc , Mutex } ;
7+ use crate :: bitset:: BitSet ;
78use crate :: codegen:: local_size_and_idx_to_ep_offset;
89use crate :: cruby:: { Qundef , RUBY_OFFSET_CFP_PC , RUBY_OFFSET_CFP_SP , SIZEOF_VALUE_I32 , vm_stack_canary} ;
910use 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
132145pub 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}
0 commit comments