Skip to content

Commit 31d6f44

Browse files
committed
perf(analyzer): split unpacked array accesses sparsely
1 parent 8e6217b commit 31d6f44

2 files changed

Lines changed: 51 additions & 49 deletions

File tree

crates/analyzer/src/comb_loop_detect.rs

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ type IdxKey = (VarId, usize);
3535
/// `BitPartition`, so bit-disjoint reads/writes form disjoint nodes.
3636
type NodeKey = (VarId, usize, usize);
3737

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;
41+
3842
/// Per `IdxKey`, atomic bit-range masks. Two bits are in the same range
3943
/// iff they appear in the same set of per-decl masks.
4044
#[derive(Default)]
@@ -57,6 +61,33 @@ impl BitPartition {
5761
.map(|(i, _)| i)
5862
.collect()
5963
}
64+
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+
74+
let zero = BigUint::default();
75+
let mut keys = self
76+
.ranges
77+
.iter()
78+
.filter(|((object, _), _)| *object == id)
79+
.flat_map(|((_, split_index), ranges)| {
80+
ranges
81+
.iter()
82+
.enumerate()
83+
.filter(|(_, range)| (*range & mask) != zero)
84+
.map(|(range, _)| (id, *split_index, range))
85+
})
86+
.collect::<Vec<_>>();
87+
keys.sort_unstable();
88+
keys.dedup();
89+
keys
90+
}
6091
}
6192

6293
/// `feedthrough[child_in_id] = { child_out_ids reachable purely combinationally }`.
@@ -189,11 +220,6 @@ fn atomic_ranges(masks: &[BigUint], _width: usize) -> Vec<BigUint> {
189220
ret
190221
}
191222

192-
/// The minimal SSA uses existing per-element coordinates. Whole or dynamic
193-
/// accesses above this limit are left for the sparse-array follow-up instead
194-
/// of expanding work with the declared element count.
195-
const OVERSIZED_ARRAY: usize = 1 << 16;
196-
197223
fn build_bit_partition(module: &Module, ctx: &mut Context) -> BitPartition {
198224
let mut masks: HashMap<(VarId, usize), Vec<BigUint>> = HashMap::default();
199225

@@ -660,9 +686,7 @@ fn collect_factor_node_keys(
660686
match factor {
661687
Factor::Variable(id, index, select, _) => {
662688
for (idx, mask) in var_reads(*id, index, select, ctx) {
663-
for r in bit_part.overlapping((*id, idx), &mask) {
664-
out.push((*id, idx, r));
665-
}
689+
out.extend(bit_part.overlapping_access(*id, idx, &mask));
666690
}
667691
}
668692
Factor::FunctionCall(_) | Factor::SystemFunctionCall(_) => {
@@ -951,12 +975,7 @@ impl<'a> SsaProcedure<'a> {
951975
fn read_keys(&mut self, id: VarId, index: &VarIndex, select: &VarSelect) -> Vec<NodeKey> {
952976
let mut keys = Vec::new();
953977
for (idx, mask) in var_reads(id, index, select, &mut self.ctx) {
954-
keys.extend(
955-
self.bit_part
956-
.overlapping((id, idx), &mask)
957-
.into_iter()
958-
.map(|range| (id, idx, range)),
959-
);
978+
keys.extend(self.bit_part.overlapping_access(id, idx, &mask));
960979
}
961980
keys.sort_unstable();
962981
keys.dedup();
@@ -966,12 +985,7 @@ impl<'a> SsaProcedure<'a> {
966985
fn write_keys(&mut self, destination: &AssignDestination) -> Vec<NodeKey> {
967986
let mut keys = Vec::new();
968987
for (idx, mask) in dst_writes(destination, &mut self.ctx) {
969-
keys.extend(
970-
self.bit_part
971-
.overlapping((destination.id, idx), &mask)
972-
.into_iter()
973-
.map(|range| (destination.id, idx, range)),
974-
);
988+
keys.extend(self.bit_part.overlapping_access(destination.id, idx, &mask));
975989
}
976990
keys.sort_unstable();
977991
keys.dedup();
@@ -1313,8 +1327,8 @@ impl<'a> SsaProcedure<'a> {
13131327
let mut reads = Vec::new();
13141328
for (idx, mask) in var_reads(*id, index, select, &mut self.ctx) {
13151329
let source_mask = &source_mask & mask;
1316-
for range in self.bit_part.overlapping((*id, idx), &source_mask) {
1317-
reads.push(self.current_version((*id, idx, range)));
1330+
for key in self.bit_part.overlapping_access(*id, idx, &source_mask) {
1331+
reads.push(self.current_version(key));
13181332
}
13191333
}
13201334
reads
@@ -1725,13 +1739,18 @@ fn dst_writes(dst: &AssignDestination, ctx: &mut Context) -> Vec<(usize, BigUint
17251739
let is_index_const = dst.index.is_const();
17261740
let is_select_const = dst.select.is_const();
17271741

1728-
if (!is_index_const || dst.index.0.is_empty())
1729-
&& variable
1730-
.r#type
1731-
.total_array()
1732-
.is_some_and(|total| total > OVERSIZED_ARRAY)
1742+
let mask = if !is_select_const {
1743+
conservative_select_mask(&dst.select, &variable.r#type, ctx)
1744+
} else {
1745+
let Some((beg, end)) = dst.select.eval_value(ctx, &variable.r#type, false) else {
1746+
return Vec::new();
1747+
};
1748+
ValueBigUint::gen_mask_range(beg, end)
1749+
};
1750+
1751+
if variable.r#type.total_array().unwrap_or(2) > 1 && (!is_index_const || dst.index.0.is_empty())
17331752
{
1734-
return Vec::new();
1753+
return vec![(SPLIT_REMAINDER_INDEX, mask)];
17351754
}
17361755

17371756
let range = if !is_index_const {
@@ -1743,15 +1762,6 @@ fn dst_writes(dst: &AssignDestination, ctx: &mut Context) -> Vec<(usize, BigUint
17431762
variable.r#type.array.calc_range(&index)
17441763
};
17451764

1746-
let mask = if !is_select_const {
1747-
conservative_select_mask(&dst.select, &variable.r#type, ctx)
1748-
} else {
1749-
let Some((beg, end)) = dst.select.eval_value(ctx, &variable.r#type, false) else {
1750-
return Vec::new();
1751-
};
1752-
ValueBigUint::gen_mask_range(beg, end)
1753-
};
1754-
17551765
let mut out = Vec::new();
17561766
if let Some((beg, end)) = range {
17571767
for i in beg..=end {
@@ -1770,30 +1780,23 @@ fn var_reads(
17701780
let Some(variable) = ctx.variables.get(&id).cloned() else {
17711781
return Vec::new();
17721782
};
1773-
if (!index.is_const() || index.0.is_empty())
1774-
&& variable
1775-
.r#type
1776-
.total_array()
1777-
.is_some_and(|total| total > OVERSIZED_ARRAY)
1778-
{
1779-
return Vec::new();
1780-
}
17811783
let mask = if select.is_const_with_range()
17821784
&& let Some((beg, end)) = select.eval_value(ctx, &variable.r#type, false)
17831785
{
17841786
ValueBigUint::gen_mask_range(beg, end)
17851787
} else {
17861788
conservative_select_mask(select, &variable.r#type, ctx)
17871789
};
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+
}
17881793
if index.is_const()
17891794
&& let Some(idx_path) = index.eval_value(ctx)
17901795
&& let Some(flat) = variable.r#type.array.calc_index(&idx_path)
17911796
{
17921797
return vec![(flat, mask)];
17931798
}
1794-
// Dynamic index: conservatively treat every element as read.
1795-
let total = variable.r#type.total_array().unwrap_or(1);
1796-
(0..total).map(|i| (i, mask.clone())).collect()
1799+
vec![(SPLIT_REMAINDER_INDEX, mask)]
17971800
}
17981801

17991802
fn conservative_select_mask(

crates/analyzer/src/tests/comb_loop_sparse_tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ fn comb_loop_oversized_array_is_sparse_and_complete_why_this_case_exists_a_seque
104104
}
105105

106106
#[test]
107-
#[ignore = "comb-loop migration: false negative; oversized sparse array feedback is missed"]
108107
fn comb_loop_oversized_array_is_sparse_and_complete_why_this_case_exists_the_former_size_guard_silently_discarded_every()
109108
{
110109
// Why this case exists: the former size guard silently discarded every

0 commit comments

Comments
 (0)