@@ -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 } ;
@@ -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
144157pub 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}
0 commit comments