@@ -28,16 +28,34 @@ use daggy::petgraph::visit::EdgeRef;
2828use std:: collections:: VecDeque ;
2929use veryl_parser:: resource_table:: StrId ;
3030
31- /// One array element. Bit precision lives in the sparse partition masks.
32- type IdxKey = ( VarId , usize ) ;
31+ #[ derive( Clone , Copy , Debug , Default , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
32+ struct ArraySpan {
33+ start : usize ,
34+ length : usize ,
35+ }
36+
37+ impl ArraySpan {
38+ fn end ( self ) -> Option < usize > {
39+ self . start . checked_add ( self . length )
40+ }
41+
42+ fn overlaps ( self , other : Self ) -> bool {
43+ let Some ( left_end) = self . end ( ) else {
44+ return false ;
45+ } ;
46+ let Some ( right_end) = other. end ( ) else {
47+ return false ;
48+ } ;
49+ self . start < right_end && other. start < left_end
50+ }
51+ }
52+
53+ /// One split unpacked-array interval. Bit precision lives in the masks.
54+ type IdxKey = ( VarId , ArraySpan ) ;
3355
3456/// `(VarId, array_idx, range_idx)`. `range_idx` indexes the variable's
3557/// `BitPartition`, so bit-disjoint reads/writes form disjoint nodes.
36- type NodeKey = ( VarId , usize , usize ) ;
37-
38- /// Unpacked elements not separated by a constant-index access are represented
39- /// by one split piece, regardless of the declared array length.
40- const SPLIT_REMAINDER_INDEX : usize = usize:: MAX ;
58+ type NodeKey = ( VarId , ArraySpan , usize ) ;
4159
4260/// Per `IdxKey`, atomic bit-range masks. Two bits are in the same range
4361/// iff they appear in the same set of per-decl masks.
@@ -62,26 +80,19 @@ impl BitPartition {
6280 . collect ( )
6381 }
6482
65- fn overlapping_access ( & self , id : VarId , index : usize , mask : & BigUint ) -> Vec < NodeKey > {
66- if index != SPLIT_REMAINDER_INDEX {
67- return self
68- . overlapping ( ( id, index) , mask)
69- . into_iter ( )
70- . map ( |range| ( id, index, range) )
71- . collect ( ) ;
72- }
73-
83+ fn overlapping_access ( & self , id : VarId , access : ArraySpan , mask : & BigUint ) -> Vec < NodeKey > {
7484 let zero = BigUint :: default ( ) ;
7585 let mut keys = self
7686 . ranges
7787 . iter ( )
7888 . filter ( |( ( object, _) , _) | * object == id)
79- . flat_map ( |( ( _, split_index) , ranges) | {
89+ . filter ( |( ( _, split) , _) | split. overlaps ( access) )
90+ . flat_map ( |( ( _, split) , ranges) | {
8091 ranges
8192 . iter ( )
8293 . enumerate ( )
8394 . filter ( |( _, range) | ( * range & mask) != zero)
84- . map ( |( range, _) | ( id, * split_index , range) )
95+ . map ( |( range, _) | ( id, * split , range) )
8596 } )
8697 . collect :: < Vec < _ > > ( ) ;
8798 keys. sort_unstable ( ) ;
@@ -221,7 +232,7 @@ fn atomic_ranges(masks: &[BigUint], _width: usize) -> Vec<BigUint> {
221232}
222233
223234fn build_bit_partition ( module : & Module , ctx : & mut Context ) -> BitPartition {
224- let mut masks: HashMap < ( VarId , usize ) , Vec < BigUint > > = HashMap :: default ( ) ;
235+ let mut masks: HashMap < IdxKey , Vec < BigUint > > = HashMap :: default ( ) ;
225236
226237 for declaration in & module. declarations {
227238 if let Declaration :: Comb ( comb) = declaration {
@@ -237,7 +248,16 @@ fn build_bit_partition(module: &Module, ctx: &mut Context) -> BitPartition {
237248 for out in & inst. outputs {
238249 for dst in & out. dst {
239250 if let Some ( ( idx, mask) ) = eval_dst_mask ( dst, & module. variables , ctx) {
240- masks. entry ( ( dst. id , idx) ) . or_default ( ) . push ( mask) ;
251+ masks
252+ . entry ( (
253+ dst. id ,
254+ ArraySpan {
255+ start : idx,
256+ length : 1 ,
257+ } ,
258+ ) )
259+ . or_default ( )
260+ . push ( mask) ;
241261 }
242262 }
243263 }
@@ -252,32 +272,70 @@ fn build_bit_partition(module: &Module, ctx: &mut Context) -> BitPartition {
252272 }
253273 }
254274
255- let mut ranges: HashMap < ( VarId , usize ) , Vec < BigUint > > = HashMap :: default ( ) ;
256- for ( key, mut ms) in masks {
275+ let ranges = split_array_masks ( module, masks) ;
276+
277+ BitPartition { ranges }
278+ }
279+
280+ fn split_array_masks (
281+ module : & Module ,
282+ masks : HashMap < IdxKey , Vec < BigUint > > ,
283+ ) -> HashMap < IdxKey , Vec < BigUint > > {
284+ let mut accesses: HashMap < VarId , Vec < ( ArraySpan , BigUint ) > > = HashMap :: default ( ) ;
285+ for ( ( id, span) , masks) in masks {
286+ for mask in masks {
287+ accesses. entry ( id) . or_default ( ) . push ( ( span, mask) ) ;
288+ }
289+ }
290+
291+ let mut ranges = HashMap :: default ( ) ;
292+ for ( id, accesses) in accesses {
293+ let mut boundaries = Vec :: with_capacity ( accesses. len ( ) * 2 ) ;
294+ for ( span, _) in & accesses {
295+ if span. length == 0 {
296+ continue ;
297+ }
298+ boundaries. push ( span. start ) ;
299+ if let Some ( end) = span. end ( ) {
300+ boundaries. push ( end) ;
301+ }
302+ }
303+ boundaries. sort_unstable ( ) ;
304+ boundaries. dedup ( ) ;
305+
257306 let width = module
258307 . variables
259- . get ( & key . 0 )
260- . and_then ( |v| v . total_width ( ) )
308+ . get ( & id )
309+ . and_then ( Variable :: total_width)
261310 . unwrap_or ( 1 ) ;
262- // Small values are cheap to scalarize and doing so lets an SSA value
263- // cross a function boundary without collapsing independently used
264- // bits into one version. Wide values remain split only at observed
265- // access boundaries.
266- if width <= 64 {
267- ms. extend ( ( 0 ..width) . map ( |bit| BigUint :: from ( 1u32 ) << bit) ) ;
268- }
269- let parts = atomic_ranges ( & ms, width) ;
270- if !parts. is_empty ( ) {
271- ranges. insert ( key, parts) ;
311+ for boundary in boundaries. windows ( 2 ) {
312+ let split = ArraySpan {
313+ start : boundary[ 0 ] ,
314+ length : boundary[ 1 ] - boundary[ 0 ] ,
315+ } ;
316+ let mut split_masks = accesses
317+ . iter ( )
318+ . filter ( |( access, _) | access. overlaps ( split) )
319+ . map ( |( _, mask) | mask. clone ( ) )
320+ . collect :: < Vec < _ > > ( ) ;
321+ if split_masks. is_empty ( ) {
322+ continue ;
323+ }
324+ if width <= 64 {
325+ split_masks. extend ( ( 0 ..width) . map ( |bit| BigUint :: from ( 1u32 ) << bit) ) ;
326+ }
327+ let parts = atomic_ranges ( & split_masks, width) ;
328+ if !parts. is_empty ( ) {
329+ ranges. insert ( ( id, split) , parts) ;
330+ }
272331 }
273332 }
274-
275- BitPartition { ranges }
333+ ranges
276334}
277335
278336fn collect_expr_masks (
279337 expr : & Expression ,
280- out : & mut HashMap < ( VarId , usize ) , Vec < BigUint > > ,
338+ out : & mut HashMap < IdxKey , Vec < BigUint > > ,
281339 ctx : & mut Context ,
282340) {
283341 match expr {
@@ -311,7 +369,7 @@ fn collect_expr_masks(
311369
312370fn collect_factor_masks (
313371 factor : & Factor ,
314- out : & mut HashMap < ( VarId , usize ) , Vec < BigUint > > ,
372+ out : & mut HashMap < IdxKey , Vec < BigUint > > ,
315373 ctx : & mut Context ,
316374) {
317375 match factor {
@@ -331,7 +389,7 @@ fn collect_factor_masks(
331389
332390fn collect_statement_masks (
333391 statements : & [ Statement ] ,
334- out : & mut HashMap < ( VarId , usize ) , Vec < BigUint > > ,
392+ out : & mut HashMap < IdxKey , Vec < BigUint > > ,
335393 ctx : & mut Context ,
336394) {
337395 for statement in statements {
@@ -706,8 +764,12 @@ fn collect_dst_node_keys(
706764 let Some ( ( idx, mask) ) = eval_dst_mask ( dst, parent_vars, ctx) else {
707765 return ;
708766 } ;
709- for r in bit_part. overlapping ( ( dst. id , idx) , & mask) {
710- out. push ( ( dst. id , idx, r) ) ;
767+ let span = ArraySpan {
768+ start : idx,
769+ length : 1 ,
770+ } ;
771+ for r in bit_part. overlapping ( ( dst. id , span) , & mask) {
772+ out. push ( ( dst. id , span, r) ) ;
711773 }
712774}
713775
@@ -1633,7 +1695,7 @@ impl<'a> SsaProcedure<'a> {
16331695 let requested =
16341696 ( ( mask >> low) << formal_offset) & ValueBigUint :: gen_mask ( formal_width) ;
16351697 for formal_key in self . keys_for_id ( formal) {
1636- if formal_key. 1 != 0 {
1698+ if formal_key. 1 . start != 0 {
16371699 continue ;
16381700 }
16391701 let Some ( formal_mask) = self . key_mask ( formal_key) else {
@@ -1732,11 +1794,10 @@ fn expression_has_unknown(expression: &Expression) -> bool {
17321794}
17331795
17341796/// Mirrors the masking logic of `AssignDestination::eval_assign`.
1735- fn dst_writes ( dst : & AssignDestination , ctx : & mut Context ) -> Vec < ( usize , BigUint ) > {
1797+ fn dst_writes ( dst : & AssignDestination , ctx : & mut Context ) -> Vec < ( ArraySpan , BigUint ) > {
17361798 let Some ( variable) = ctx. get_variable_info ( dst. id ) else {
17371799 return Vec :: new ( ) ;
17381800 } ;
1739- let is_index_const = dst. index . is_const ( ) ;
17401801 let is_select_const = dst. select . is_const ( ) ;
17411802
17421803 let mask = if !is_select_const {
@@ -1748,35 +1809,17 @@ fn dst_writes(dst: &AssignDestination, ctx: &mut Context) -> Vec<(usize, BigUint
17481809 ValueBigUint :: gen_mask_range ( beg, end)
17491810 } ;
17501811
1751- if variable. r#type . total_array ( ) . unwrap_or ( 2 ) > 1 && ( !is_index_const || dst. index . 0 . is_empty ( ) )
1752- {
1753- return vec ! [ ( SPLIT_REMAINDER_INDEX , mask) ] ;
1754- }
1755-
1756- let range = if !is_index_const {
1757- variable. r#type . array . calc_range ( & [ ] )
1758- } else {
1759- let Some ( index) = dst. index . eval_value ( ctx) else {
1760- return Vec :: new ( ) ;
1761- } ;
1762- variable. r#type . array . calc_range ( & index)
1763- } ;
1764-
1765- let mut out = Vec :: new ( ) ;
1766- if let Some ( ( beg, end) ) = range {
1767- for i in beg..=end {
1768- out. push ( ( i, mask. clone ( ) ) ) ;
1769- }
1770- }
1771- out
1812+ array_access_span ( & dst. index , & variable. r#type , ctx)
1813+ . map ( |span| vec ! [ ( span, mask) ] )
1814+ . unwrap_or_default ( )
17721815}
17731816
17741817fn var_reads (
17751818 id : VarId ,
17761819 index : & VarIndex ,
17771820 select : & VarSelect ,
17781821 ctx : & mut Context ,
1779- ) -> Vec < ( usize , BigUint ) > {
1822+ ) -> Vec < ( ArraySpan , BigUint ) > {
17801823 let Some ( variable) = ctx. variables . get ( & id) . cloned ( ) else {
17811824 return Vec :: new ( ) ;
17821825 } ;
@@ -1787,16 +1830,28 @@ fn var_reads(
17871830 } else {
17881831 conservative_select_mask ( select, & variable. r#type , ctx)
17891832 } ;
1790- if variable. r#type . total_array ( ) . unwrap_or ( 2 ) > 1 && ( !index. is_const ( ) || index. 0 . is_empty ( ) ) {
1791- return vec ! [ ( SPLIT_REMAINDER_INDEX , mask) ] ;
1792- }
1793- if index. is_const ( )
1794- && let Some ( idx_path) = index. eval_value ( ctx)
1795- && let Some ( flat) = variable. r#type . array . calc_index ( & idx_path)
1796- {
1797- return vec ! [ ( flat, mask) ] ;
1798- }
1799- vec ! [ ( SPLIT_REMAINDER_INDEX , mask) ]
1833+ array_access_span ( index, & variable. r#type , ctx)
1834+ . map ( |span| vec ! [ ( span, mask) ] )
1835+ . unwrap_or_default ( )
1836+ }
1837+
1838+ fn array_access_span (
1839+ index : & VarIndex ,
1840+ r#type : & crate :: ir:: Type ,
1841+ ctx : & mut Context ,
1842+ ) -> Option < ArraySpan > {
1843+ let prefix_len = index
1844+ . 0
1845+ . iter ( )
1846+ . take_while ( |expression| expression. comptime ( ) . is_const )
1847+ . count ( ) ;
1848+ let prefix = VarIndex ( index. 0 [ ..prefix_len] . to_vec ( ) ) ;
1849+ let values = prefix. eval_value ( ctx) ?;
1850+ let ( start, inclusive_end) = r#type. array . calc_range ( & values) ?;
1851+ Some ( ArraySpan {
1852+ start,
1853+ length : inclusive_end. checked_sub ( start) ?. checked_add ( 1 ) ?,
1854+ } )
18001855}
18011856
18021857fn conservative_select_mask (
0 commit comments