11use std:: cell:: RefCell ;
2- use std:: collections:: { BTreeSet , HashMap , HashSet , VecDeque } ;
2+ use std:: collections:: { HashMap , HashSet , VecDeque } ;
33use std:: fmt;
44use std:: mem:: take;
55use std:: rc:: Rc ;
@@ -1372,7 +1372,7 @@ impl LiveRange {
13721372 }
13731373}
13741374
1375- #[ derive( Clone ) ]
1375+ #[ derive( Clone , PartialEq ) ]
13761376pub enum State {
13771377 Unhandled ,
13781378 Active ,
@@ -1410,13 +1410,40 @@ impl Interval {
14101410 self . ranges . last ( ) . unwrap ( ) . to
14111411 }
14121412
1413+ pub fn next_intersection ( & self , other : & Interval ) -> Option < usize > {
1414+ let ( mut i, mut j) = ( 0 , 0 ) ;
1415+
1416+ // Both range lists are sorted and disjoint, so advancing whichever
1417+ // range ends first can never skip past an overlap. That makes the
1418+ // first overlap found the earliest one.
1419+ while let ( Some ( a) , Some ( b) ) = ( self . ranges . get ( i) , other. ranges . get ( j) ) {
1420+ let lo = a. from . max ( b. from ) ;
1421+ if lo < a. to . min ( b. to ) {
1422+ return Some ( lo) ;
1423+ }
1424+ if a. to < b. to { i += 1 ; } else { j += 1 ; }
1425+ }
1426+
1427+ None
1428+ }
1429+
1430+ pub fn ends_before ( & self , pos : usize ) -> bool {
1431+ self . end ( ) <= pos
1432+ }
1433+
14131434 /// Check if the interval is alive at position
14141435 /// Panics if the range is not set
14151436 pub fn survives ( & self , position : usize ) -> bool {
14161437 assert ! ( self . ranges. len( ) > 0 , "survives called on interval with no range" ) ;
14171438 self . ranges . iter ( ) . any ( |range| range. from < position && position < range. to )
14181439 }
14191440
1441+ /// Returns true if position falls inside one of the ranges in this
1442+ /// interval.
1443+ pub fn covers ( & self , position : usize ) -> bool {
1444+ self . ranges . iter ( ) . any ( |range| range. from <= position && position < range. to )
1445+ }
1446+
14201447 pub fn born_at ( & self , x : usize ) -> bool {
14211448 self . start ( ) == x
14221449 }
@@ -2126,42 +2153,26 @@ impl Assembler
21262153 }
21272154 }
21282155
2129- fn release_assignment ( it : & Interval , num_registers : usize , regs : & [ Reg ] , free_registers : & mut BTreeSet < usize > ) {
2130- if let Some ( allocation) = it. assigned {
2131- if let Some ( reg) = allocation. alloc_pool_index ( num_registers) {
2132- let was_not_there_before = free_registers. insert ( reg) ;
2133- assert ! (
2134- was_not_there_before,
2135- "attempted to return allocator register {:?} to the free pool more than once" ,
2136- allocation. assigned_reg( regs) . unwrap( ) ,
2137- ) ;
2138- } else {
2139- assert ! (
2140- allocation. assigned_reg( regs) . is_none_or( |reg| {
2141- regs. iter( )
2142- . take( num_registers)
2143- . all( |candidate| candidate. reg_no != reg. reg_no)
2144- } ) ,
2145- "attempted to return non-allocatable register {:?} to the allocator pool" ,
2146- allocation. assigned_reg( regs) . unwrap( ) ,
2147- ) ;
2148- }
2149- }
2150- }
2151-
21522156 // TODO: We want to make the following refactoring so that we DON'T have
21532157 // to parcopy in to entry blocks
21542158 //
21552159 // * Pre-allocate pinned regs
21562160 // * Update linear scan to handle pinned LRs
21572161 //
2162+ // num_registers is the number of allocatable registers. `regs` is a
2163+ // list of _all_ registers partitioned by allocatable and non-allocatable.
2164+ // The registers in `regs` from 0 to num_registers is allocatable,
2165+ // num_registers to the end are only allocatable via "preferred" register
2166+ // support.
21582167 pub fn linear_scan (
21592168 & self ,
21602169 intervals : Vec < Interval > ,
2161- num_registers : usize ,
2162- regs : & [ Reg ] ,
2170+ num_registers : usize , // number of allocatable regs
2171+ regs : & [ Reg ] , // list of all registers used, partitioned by allocatability
21632172 ) -> ( Vec < Option < Allocation > > , usize ) {
2164- let mut free_registers: BTreeSet < usize > = ( 0 ..num_registers) . collect ( ) ;
2173+ debug_assert ! ( num_registers <= regs. len( ) ) ;
2174+
2175+ let mut free_registers: Vec < usize > = vec ! [ 0 ; regs. len( ) ] ;
21652176 let mut active: Vec < Interval > = Vec :: new ( ) ; // sorted by increasing end point
21662177 let mut inactive: Vec < Interval > = Vec :: new ( ) ; // intervals with lifetime holes
21672178 let mut num_stack_slots: usize = 0 ;
@@ -2176,46 +2187,100 @@ impl Assembler
21762187 let mut unhandled: VecDeque < Interval > = sorted_intervals. into ( ) ;
21772188
21782189 while let Some ( mut interval) = unhandled. pop_front ( ) {
2190+ let position = interval. start ( ) ;
2191+
21792192 // Expire old intervals.
21802193 for mut it in std:: mem:: take ( & mut active) {
2181- if it. end ( ) > interval . start ( ) {
2182- active . push ( it ) ;
2183- } else {
2184- Self :: release_assignment ( & it , num_registers , regs , & mut free_registers ) ;
2194+ assert ! ( it. state == State :: Active ) ;
2195+ // If the interval ends before the current position, then we're
2196+ // done with it and can mark it handled.
2197+ if it . ends_before ( position ) {
21852198 it. state = State :: Handled ;
21862199 handled. push ( it) ;
2200+ } else if !it. covers ( position) {
2201+ // If it doesn't cover the current position (there's a hole
2202+ // in the ranges), then move it to inactive
2203+ it. state = State :: Inactive ;
2204+ inactive. push ( it) ;
2205+ } else {
2206+ // Otherwise it's still live here and stays active.
2207+ active. push ( it) ;
21872208 }
21882209 }
21892210
2190- let preferred_alloc = interval. preferred ;
2191- let preferred_taken = preferred_alloc
2192- . is_some_and ( |alloc|
2193- active. iter ( ) . any ( |active_interval| active_interval. assigned == Some ( alloc) )
2194- ) ;
2211+ // Check for intervals in inactive that are handled or active
2212+ for mut it in std:: mem:: take ( & mut inactive) {
2213+ assert ! ( it. state == State :: Inactive ) ;
2214+ // If the inactive interval ends before the current position
2215+ if it. ends_before ( position) {
2216+ // Move it to handled
2217+ it. state = State :: Handled ;
2218+ handled. push ( it) ;
21952219
2196- if let Some ( preferred_alloc) = preferred_alloc. filter ( |_| !preferred_taken) {
2197- if let Some ( reg_idx) = preferred_alloc. alloc_pool_index ( num_registers) {
2198- if free_registers. remove ( & reg_idx) {
2199- interval. assigned = Some ( preferred_alloc) ;
2200- let insert_idx = active. partition_point ( |i| i. end ( ) < interval. end ( ) ) ;
2201- active. insert ( insert_idx, interval) ;
2202- continue ;
2203- }
2220+ } else if it. covers ( position) {
2221+ // If the current position falls inside one of the
2222+ // interval's ranges, then make it active
2223+ it. state = State :: Active ;
2224+ active. push ( it) ;
22042225 } else {
2205- interval. assigned = Some ( preferred_alloc) ;
2226+ // It's still inactive
2227+ inactive. push ( it) ;
2228+ }
2229+ }
2230+
2231+ // Mark all registers as "free". In other words, they aren't
2232+ // in use until instruction number usize::MAX.
2233+ free_registers. fill ( usize:: MAX ) ;
2234+
2235+ // Mark all active intervals with assignments as "unavailable". They are available
2236+ // again at instruction 0 which effectively means they can't be used (as all
2237+ // intervals will end after 0)
2238+ for it in & active {
2239+ debug_assert ! ( it. state == State :: Active ) ;
2240+ match it. assigned . expect ( "should have assignment" ) {
2241+ Allocation :: Reg ( idx) => free_registers[ idx] = 0 ,
2242+ _ => { } ,
2243+ }
2244+ }
2245+
2246+ // Inactive intervals are intervals that have gaps in them, and
2247+ // we're currently inside one of those gaps. We'll mark in the
2248+ // "free_registers" list when each interval will want to use its
2249+ // assigned reg again.
2250+ for it in & inactive {
2251+ debug_assert ! ( it. state == State :: Inactive ) ;
2252+ let Some ( pos) = it. next_intersection ( & interval) else { continue } ;
2253+ match it. assigned . expect ( "should have assignment" ) {
2254+ Allocation :: Reg ( idx) => free_registers[ idx] = free_registers[ idx] . min ( pos) ,
2255+ _ => { } ,
2256+ }
2257+ }
2258+
2259+ // If the current interval has a preferred allocation, use it if
2260+ // that register is available until the interval end.
2261+ if let Some ( Allocation :: Reg ( idx) ) = interval. preferred {
2262+ if free_registers[ idx] >= interval. end ( ) {
2263+ interval. assigned = Some ( Allocation :: Reg ( idx) ) ;
2264+ interval. state = State :: Active ;
22062265 let insert_idx = active. partition_point ( |i| i. end ( ) < interval. end ( ) ) ;
22072266 active. insert ( insert_idx, interval) ;
22082267 continue ;
22092268 }
22102269 }
22112270
2212- if free_registers. is_empty ( ) {
2271+ // Find a reg with the highest "free until use" point, then check if that "free until
2272+ // use" point is greater than or eq to than the current interval's end. If it is, then
2273+ // we know the current interval can fit in the window this register is available.
2274+ let best_reg = ( 0 ..num_registers) . rev ( )
2275+ . max_by_key ( |& reg| free_registers[ reg] )
2276+ . filter ( |& reg| free_registers[ reg] >= interval. end ( ) ) ;
2277+
2278+ // We couldn't find a best register, so we need to spill
2279+ if best_reg. is_none ( ) {
22132280 // Spill: pick the longest-lived active interval (last in sorted active)
22142281 // but only from the allocatable partition of the pool. An index
22152282 // at or past `num_registers` is a pinned physical register (for
22162283 // example SP), which is not ours to hand to someone else.
2217- // Take the id and end point rather than a reference, so that `active`
2218- // can be mutated below.
22192284 let spill = active. iter ( ) . rev ( )
22202285 . find ( |active_interval| {
22212286 active_interval. assigned
@@ -2231,28 +2296,38 @@ impl Assembler
22312296 let mut spilled = active. remove ( spill_idx) ;
22322297 interval. assigned = spilled. assigned ;
22332298 spilled. assigned = Some ( slot) ;
2299+ spilled. state = State :: Handled ;
22342300 handled. push ( spilled) ;
22352301 // Insert current into sorted active
22362302 let insert_idx = active. partition_point ( |i| i. end ( ) < interval. end ( ) ) ;
2303+ interval. state = State :: Active ;
22372304 active. insert ( insert_idx, interval) ;
22382305 } else {
22392306 // Spill the current interval
22402307 interval. assigned = Some ( slot) ;
2308+ interval. state = State :: Handled ;
22412309 handled. push ( interval) ;
22422310 }
22432311 } else {
2244- // Allocate lowest free register
2245- let reg = * free_registers. iter ( ) . min ( ) . unwrap ( ) ;
2246- free_registers. remove ( & reg) ;
2312+ let reg = best_reg. unwrap ( ) ;
22472313 interval. assigned = Some ( Allocation :: Reg ( reg) ) ;
2314+ interval. state = State :: Active ;
22482315 // Insert into sorted active
22492316 let insert_idx = active. partition_point ( |i| i. end ( ) < interval. end ( ) ) ;
22502317 active. insert ( insert_idx, interval) ;
22512318 }
22522319 }
22532320
2321+ // Drain active and inactive and mark everything as handled.
2322+ for mut it in active. drain ( ..) . chain ( inactive. drain ( ..) ) {
2323+ it. state = State :: Handled ;
2324+ handled. push ( it) ;
2325+ }
2326+ debug_assert ! ( active. is_empty( ) && inactive. is_empty( ) ) ;
2327+
22542328 let mut assignment: Vec < Option < Allocation > > = vec ! [ None ; num_intervals] ;
2255- for it in active. into_iter ( ) . chain ( handled) {
2329+ for it in handled {
2330+ debug_assert ! ( it. state == State :: Handled ) ;
22562331 assignment[ it. id ] = it. assigned ;
22572332 }
22582333
@@ -3343,7 +3418,7 @@ pub fn lir_intervals_string(asm: &Assembler, intervals: &[Interval]) -> String {
33433418 }
33443419 output. push_str ( & format ! ( "{param}" ) ) ;
33453420 }
3346- output. push_str ( "):\n " ) ;
3421+ output. push_str ( "):\n " ) ; // :)
33473422 }
33483423
33493424 for ( insn, insn_id) in block. insns . iter ( ) . zip ( & block. insn_ids ) {
@@ -3362,7 +3437,7 @@ pub fn lir_intervals_string(asm: &Assembler, intervals: &[Interval]) -> String {
33623437 output. push_str ( " v " ) ;
33633438 } else if interval. dies_at ( insn_id. 0 ) {
33643439 output. push_str ( " ^ " ) ;
3365- } else if interval. survives ( insn_id. 0 ) {
3440+ } else if interval. covers ( insn_id. 0 ) {
33663441 output. push_str ( " █ " ) ;
33673442 } else {
33683443 output. push_str ( " . " ) ;
@@ -4685,7 +4760,7 @@ mod tests {
46854760 assert_eq ! ( assignments[ r11_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
46864761 assert_eq ! ( assignments[ r12_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
46874762 assert_eq ! ( assignments[ r13_idx] , Some ( Allocation :: Reg ( 2 ) ) ) ;
4688- assert_eq ! ( assignments[ r14_idx] , Some ( Allocation :: Reg ( 3 ) ) ) ;
4763+ assert_eq ! ( assignments[ r14_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
46894764 assert_eq ! ( assignments[ r15_idx] , Some ( Allocation :: Reg ( 2 ) ) ) ;
46904765 }
46914766
@@ -4697,7 +4772,7 @@ mod tests {
46974772 asm. number_instructions ( 16 ) ;
46984773 let mut intervals = asm. build_intervals ( live_in) ;
46994774
4700- // 3 registers -- only r10 needs to spill
4775+ // 3 registers -- enough for every interval once holes are reused
47014776 let mut regs = crate :: backend:: current:: ALLOC_REGS . to_vec ( ) ;
47024777 let allocatable_regs = regs. len ( ) ;
47034778 asm. preferred_register_assignments ( & mut intervals, & mut regs) ;
@@ -4710,12 +4785,14 @@ mod tests {
47104785 let r14_idx = if let Opnd :: VReg { idx, .. } = r14 { idx } else { panic ! ( ) } ;
47114786 let r15_idx = if let Opnd :: VReg { idx, .. } = r15 { idx } else { panic ! ( ) } ;
47124787
4713- assert_eq ! ( num_stack_slots, 1 ) ;
4714- assert_eq ! ( assignments[ r10_idx] , Some ( Allocation :: Stack ( 0 ) ) ) ;
4788+ // Lifetime holes make three registers enough: nothing spills, and the
4789+ // assignment is the same one five registers produce.
4790+ assert_eq ! ( num_stack_slots, 0 ) ;
4791+ assert_eq ! ( assignments[ r10_idx] , Some ( Allocation :: Reg ( 0 ) ) ) ;
47154792 assert_eq ! ( assignments[ r11_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
47164793 assert_eq ! ( assignments[ r12_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
47174794 assert_eq ! ( assignments[ r13_idx] , Some ( Allocation :: Reg ( 2 ) ) ) ;
4718- assert_eq ! ( assignments[ r14_idx] , Some ( Allocation :: Reg ( 0 ) ) ) ;
4795+ assert_eq ! ( assignments[ r14_idx] , Some ( Allocation :: Reg ( 1 ) ) ) ;
47194796 assert_eq ! ( assignments[ r15_idx] , Some ( Allocation :: Reg ( 2 ) ) ) ;
47204797 }
47214798
@@ -4832,20 +4909,18 @@ mod tests {
48324909 if * dest == Opnd :: Reg ( regs[ 1 ] ) && * src == Opnd :: UImm ( 1 ) ) ) ;
48334910
48344911 // Edge b3->b2 (single succ): args=[v4, v5], params=[v2, v3]
4835- // v4->Reg(3), v5->Reg(2), v2->Reg(1), v3->Reg(2)
4836- // Reg copy: Reg(3)->Reg(1) -> Mov(regs[1], regs[3])
4837- // Reg(2)->Reg(2) is self-move, filtered
4838- // Inserted in b3 before Jmp: [Label, Mul, Sub, Mov, Jmp]
4912+ // v4->Reg(1), v5->Reg(2), v2->Reg(1), v3->Reg(2)
4913+ // Reg(1)->Reg(1) and Reg(2)->Reg(2) are both self-moves, filtered, so
4914+ // this edge needs no moves at all: [Label, Mul, Sub, Jmp]
48394915 let b3_insns = & asm. basic_blocks [ b3. 0 ] . insns ;
4840- assert_eq ! ( b3_insns. len( ) , 5 ) ;
4841- assert ! ( matches!( & b3_insns[ 3 ] , Insn :: Mov { dest, src }
4842- if * dest == Opnd :: Reg ( regs[ 1 ] ) && * src == Opnd :: Reg ( regs[ 3 ] ) ) ) ;
4916+ assert_eq ! ( b3_insns. len( ) , 4 ) ;
4917+ assert ! ( matches!( & b3_insns[ 3 ] , Insn :: Jmp ( ..) ) ) ;
48434918
48444919 // Verify original instructions in b3 are rewritten to physical registers.
48454920 // b3: Mul { left: r12, right: r13, out: r14 }, Sub { left: r13, right: UImm(1), out: r15 }
4846- // r12->Reg(1), r13->Reg(2), r14->Reg(3 ), r15->Reg(2)
4921+ // r12->Reg(1), r13->Reg(2), r14->Reg(1 ), r15->Reg(2)
48474922 assert ! ( matches!( & b3_insns[ 1 ] , Insn :: Mul { left, right, out }
4848- if * left == Opnd :: Reg ( regs[ 1 ] ) && * right == Opnd :: Reg ( regs[ 2 ] ) && * out == Opnd :: Reg ( regs[ 3 ] ) ) ) ;
4923+ if * left == Opnd :: Reg ( regs[ 1 ] ) && * right == Opnd :: Reg ( regs[ 2 ] ) && * out == Opnd :: Reg ( regs[ 1 ] ) ) ) ;
48494924 assert ! ( matches!( & b3_insns[ 2 ] , Insn :: Sub { left, right, out }
48504925 if * left == Opnd :: Reg ( regs[ 2 ] ) && * right == Opnd :: UImm ( 1 ) && * out == Opnd :: Reg ( regs[ 2 ] ) ) ) ;
48514926 }
0 commit comments