Skip to content

Commit ae34d89

Browse files
committed
analyzer/sim: fast and compact handling of large arrays
Two large-memory testbench arrays (e.g. `var dram: logic<32>[8M]`) previously dominated both analyze_pass2 CPU time and RSS in the IR. pass2 speedup: - AssignDestination: skip full-range iteration when the index is non-const and total_array > evaluate_array_limit. - Factor::eval_assign: avoid cloning the full Variable for large-array references (insert_reference bails anyway). Compact storage: - `var` / port / let initializers (all-same x-state) store a single template Value instead of N copies; `const` / `param` with `'{...}` literals keep per-element values. - Variable::new derives `assigned` length from r#type.total_array() and skips allocation for arrays past evaluate_array_limit. - fmt::Display replicates the template across total_array(). - Simulator mirrors via `v.value.len() < total_array`. heliodor linux_boot: pass2 75 s -> 27 s, Max RSS 3.5 GB -> 2.2 GB.
1 parent 03dffdf commit ae34d89

9 files changed

Lines changed: 144 additions & 55 deletions

File tree

crates/analyzer/src/conv/declaration.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ impl Conv<&GenerateForDeclaration> for ir::DeclarationBlock {
319319

320320
let block = context.block(|c| {
321321
let id = c.insert_var_path(path.clone(), comptime.clone());
322+
let array_limit = c.config.evaluate_array_limit;
322323
let variable = Variable::new(
323324
id,
324325
path,
@@ -327,6 +328,7 @@ impl Conv<&GenerateForDeclaration> for ir::DeclarationBlock {
327328
vec![comptime.get_value().unwrap().clone()],
328329
c.get_affiliation(),
329330
&token,
331+
array_limit,
330332
);
331333
c.insert_variable(id, variable);
332334

@@ -561,6 +563,7 @@ impl Conv<&PortDeclarationItem> for () {
561563

562564
// TODO for array
563565
let id = context.insert_var_path(path.clone(), comptime);
566+
let array_limit = context.config.evaluate_array_limit;
564567
let variable = Variable::new(
565568
id,
566569
path,
@@ -569,6 +572,7 @@ impl Conv<&PortDeclarationItem> for () {
569572
vec![value.clone()],
570573
context.get_affiliation(),
571574
&variable_token,
575+
array_limit,
572576
);
573577
context.insert_variable(id, variable);
574578
} else {
@@ -981,17 +985,17 @@ impl Conv<(&FunctionDeclaration, Option<&FuncPath>)> for () {
981985
let kind = VarKind::Variable;
982986
let r#type = ret_type.r#type.clone();
983987

984-
if let Some(total_array) = r#type.total_array()
988+
if let Some(_total_array) = r#type.total_array()
985989
&& let Some(total_width) = r#type.total_width()
986990
{
987991
// type.expand is not necessary
988992
// because member access is not allowed for return value
989-
let mut values = vec![];
990-
for _ in 0..total_array {
991-
values.push(Value::new_x(total_width, false));
992-
}
993+
// All elements are initialized to the same x-state
994+
// value, so a single template suffices.
995+
let values = vec![Value::new_x(total_width, false)];
993996

994997
let ret_id = c.insert_var_path(path.clone(), ret_type.clone());
998+
let array_limit = c.config.evaluate_array_limit;
995999
let variable = Variable::new(
9961000
ret_id,
9971001
path,
@@ -1000,6 +1004,7 @@ impl Conv<(&FunctionDeclaration, Option<&FuncPath>)> for () {
10001004
values,
10011005
c.get_affiliation(),
10021006
&token,
1007+
array_limit,
10031008
);
10041009
c.insert_variable(ret_id, variable);
10051010
Some(ret_id)

crates/analyzer/src/conv/statement.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ fn unroll_for(
704704
}
705705

706706
let id = c.insert_var_path(path.clone(), comptime.clone());
707+
let array_limit = c.config.evaluate_array_limit;
707708
let variable = ir::Variable::new(
708709
id,
709710
path,
@@ -712,6 +713,7 @@ fn unroll_for(
712713
vec![comptime.get_value().unwrap().clone()],
713714
c.get_affiliation(),
714715
&token,
716+
array_limit,
715717
);
716718
c.insert_variable(id, variable);
717719

crates/analyzer/src/conv/utils.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ pub fn eval_const_assign(
630630
if let Some(exprs) = exprs {
631631
let values = eval_array_literal_expressions(context, r#type, exprs, token)?;
632632
let id = context.insert_var_path(path.clone(), comptime);
633+
let array_limit = context.config.evaluate_array_limit;
633634
let variable = Variable::new(
634635
id,
635636
path.clone(),
@@ -638,6 +639,7 @@ pub fn eval_const_assign(
638639
values,
639640
context.get_affiliation(),
640641
&dst.token,
642+
array_limit,
641643
);
642644
context.insert_variable(id, variable);
643645
} else {
@@ -667,6 +669,7 @@ pub fn eval_const_assign(
667669
value.trunc(total_width);
668670
}
669671

672+
let array_limit = context.config.evaluate_array_limit;
670673
let variable = Variable::new(
671674
id,
672675
path.clone(),
@@ -675,6 +678,7 @@ pub fn eval_const_assign(
675678
vec![value],
676679
context.get_affiliation(),
677680
&dst.token,
681+
array_limit,
678682
);
679683
context.insert_variable(id, variable);
680684
}
@@ -709,14 +713,12 @@ pub fn eval_variable(
709713
let signed = comptime.r#type.signed;
710714
let id = context.insert_var_path(path.clone(), comptime);
711715

712-
let values = if let Some(total_array) = r#type.total_array()
713-
&& let Some(total_width) = r#type.total_width()
714-
{
715-
let mut values = vec![];
716-
for _ in 0..total_array {
717-
values.push(Value::new_x(total_width, signed));
718-
}
719-
values
716+
// Every element starts as the same x-state template, so store it once;
717+
// the simulator replicates it across the full `r#type.total_array()` at
718+
// fill time. Const/param arrays with per-element literals go through
719+
// `eval_array_literal` instead and keep their Vec<Value> intact.
720+
let values = if let Some(total_width) = r#type.total_width() {
721+
vec![Value::new_x(total_width, signed)]
720722
} else {
721723
vec![]
722724
};
@@ -729,6 +731,7 @@ pub fn eval_variable(
729731
context.insert_var_path_with_id(path, id, comptime);
730732
}
731733

734+
let array_limit = context.config.evaluate_array_limit;
732735
let variable = Variable::new(
733736
id,
734737
path.clone(),
@@ -737,6 +740,7 @@ pub fn eval_variable(
737740
values,
738741
context.get_affiliation(),
739742
&token,
743+
array_limit,
740744
);
741745
context.insert_variable(id, variable);
742746
}
@@ -1438,6 +1442,7 @@ pub fn build_for_statement(
14381442
} else {
14391443
vec![]
14401444
};
1445+
let array_limit = context.config.evaluate_array_limit;
14411446
let variable = Variable::new(
14421447
loop_var_id,
14431448
path,
@@ -1446,6 +1451,7 @@ pub fn build_for_statement(
14461451
values,
14471452
context.get_affiliation(),
14481453
&token,
1454+
array_limit,
14491455
);
14501456
context.insert_variable(loop_var_id, variable);
14511457

crates/analyzer/src/ir/assign_table.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ mod tests {
444444
vec![],
445445
Affiliation::Module,
446446
&TokenRange::default(),
447+
context.config.evaluate_array_limit,
447448
);
448449
let variable = VariableInfo::new(&variable);
449450

crates/analyzer/src/ir/expression.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,17 @@ impl Factor {
733733
) {
734734
match self {
735735
Factor::Variable(id, index, select, _) => {
736+
// `insert_reference` bails out on arrays over `array_limit`;
737+
// short-circuit to avoid cloning the full Variable (value
738+
// vec scales with array size).
739+
let total_array = context
740+
.variables
741+
.get(id)
742+
.map(|v| v.r#type.total_array().unwrap_or(0))
743+
.unwrap_or(0);
744+
if total_array > assign_table.array_limit {
745+
return;
746+
}
736747
if let Some(index) = index.eval_value(context)
737748
&& let Some(variable) = context.variables.get(id).cloned()
738749
&& let Some((beg, end)) = select.eval_value(context, &variable.r#type, false)

crates/analyzer/src/ir/statement.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -465,28 +465,38 @@ impl AssignDestination {
465465

466466
let mut errors = vec![];
467467
if let Some((beg, end)) = range {
468-
for i in beg..=end {
469-
let index = VarIndex::from_index(i, &variable.r#type.array);
470-
if let Some(index) = index.eval_value(context) {
471-
if assign_context.is_comb()
472-
&& assign_table.check_refered(&variable.id, &index, &mask)
473-
{
474-
let mut text = variable.path.to_string();
475-
for i in &index {
476-
text.push_str(&format!("[{i}]"));
468+
// `insert_assign` / `check_refered` both bail out on arrays
469+
// over `array_limit`; iterating beg..=end just to hit that
470+
// guard is pure waste when the index is non-const.
471+
let array_size = end.saturating_sub(beg).saturating_add(1);
472+
let skip_large_array = !is_index_const
473+
&& variable.r#type.total_array().unwrap_or(0) > assign_table.array_limit
474+
&& array_size > assign_table.array_limit;
475+
476+
if !skip_large_array {
477+
for i in beg..=end {
478+
let index = VarIndex::from_index(i, &variable.r#type.array);
479+
if let Some(index) = index.eval_value(context) {
480+
if assign_context.is_comb()
481+
&& assign_table.check_refered(&variable.id, &index, &mask)
482+
{
483+
let mut text = variable.path.to_string();
484+
for i in &index {
485+
text.push_str(&format!("[{i}]"));
486+
}
487+
// ignore `#[allow(unassign_variable)]` attribute
488+
errors.push(AnalyzerError::unassign_variable(&text, &self.token));
477489
}
478-
// ignore `#[allow(unassign_variable)]` attribute
479-
errors.push(AnalyzerError::unassign_variable(&text, &self.token));
480-
}
481490

482-
let maybe = !is_const | assign_context.is_system_verilog();
483-
let _ = assign_table.insert_assign(
484-
&variable,
485-
index,
486-
mask.clone(),
487-
maybe,
488-
self.token,
489-
);
491+
let maybe = !is_const | assign_context.is_system_verilog();
492+
let _ = assign_table.insert_assign(
493+
&variable,
494+
index,
495+
mask.clone(),
496+
maybe,
497+
self.token,
498+
);
499+
}
490500
}
491501
}
492502
}

crates/analyzer/src/ir/variable.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@ pub struct Variable {
741741
}
742742

743743
impl Variable {
744+
#[allow(clippy::too_many_arguments)]
744745
pub fn new(
745746
id: VarId,
746747
path: VarPath,
@@ -749,11 +750,19 @@ impl Variable {
749750
value: Vec<Value>,
750751
affiliation: Affiliation,
751752
token: &TokenRange,
753+
array_limit: usize,
752754
) -> Self {
753-
let mut assigned = vec![];
754-
for _ in 0..value.len() {
755-
assigned.push(0u32.into());
756-
}
755+
// `assigned` tracks per-element coverage and must match the full
756+
// array length (not `value.len()`, since `value` may hold a single
757+
// template entry for all-same init). `set_assigned` / `unassigned()`
758+
// callers gate on `> array_limit` before touching `assigned`, so
759+
// arrays past the limit can leave the vec empty.
760+
let total_array = r#type.total_array().unwrap_or(value.len()).max(value.len());
761+
let assigned: Vec<BigUint> = if total_array > array_limit {
762+
Vec::new()
763+
} else {
764+
vec![0u32.into(); total_array]
765+
};
757766

758767
Self {
759768
id,
@@ -866,8 +875,23 @@ impl fmt::Display for Variable {
866875
}
867876
r#type.array.clear();
868877

869-
let is_array = self.value.len() != 1;
870-
for (i, value) in self.value.iter().enumerate() {
878+
// Template form (`value.len() == 1 && total_array > 1`) means every
879+
// element shares value[0]; otherwise treat value.len() as the
880+
// effective element count.
881+
let type_len = self.r#type.total_array();
882+
let is_template = self.value.len() == 1 && matches!(type_len, Some(n) if n > 1);
883+
let display_len = if is_template {
884+
type_len.unwrap()
885+
} else {
886+
self.value.len()
887+
};
888+
let is_array = display_len != 1;
889+
for i in 0..display_len {
890+
let value = if is_template {
891+
&self.value[0]
892+
} else {
893+
&self.value[i]
894+
};
871895
if is_array {
872896
ret.push_str(&format!(
873897
"{} {}[{}]({}): ",

crates/simulator/src/ir/module.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,18 @@ fn fill_buffers_recursive(
8484
sorted.sort_by_key(|(k, _)| **k);
8585

8686
for (_, meta) in &sorted {
87-
for (element, initial) in meta.elements.iter().zip(meta.initial_values.iter()) {
87+
// Single-entry initial_values on a multi-element variable is the
88+
// compact template form used for large arrays.
89+
let template_mode = meta.initial_values.len() == 1 && meta.elements.len() > 1;
90+
for (i, element) in meta.elements.iter().enumerate() {
91+
let initial = if template_mode {
92+
&meta.initial_values[0]
93+
} else {
94+
match meta.initial_values.get(i) {
95+
Some(v) => v,
96+
None => continue,
97+
}
98+
};
8899
let nb = element.native_bytes;
89100
let _vs = value_size(nb, use_4state);
90101
if element.is_ff() {

crates/simulator/src/ir/variable.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -317,25 +317,30 @@ pub fn create_variable_meta(
317317
let nb = native_bytes(width);
318318
let vs = value_size(nb, use_4state);
319319

320+
// Analyzer collapses all-same initial arrays down to a single
321+
// template value (eval_variable), so derive the real array length
322+
// from the type.
323+
let total_array = v.r#type.total_array().unwrap_or(v.value.len());
324+
let total_array = total_array.max(v.value.len()).max(1);
325+
320326
// For multi-element variables (arrays), all elements must have the
321327
// same FF/comb classification. DynamicVariable expressions assume
322328
// uniform stride in a single buffer; mixed placement is invalid.
323-
let any_ff = v
324-
.value
325-
.iter()
326-
.enumerate()
327-
.any(|(i, _)| ff_table.is_ff(v.id, i));
328-
let force_ff = any_ff && v.value.len() > 1;
329-
330-
let mut elements = vec![];
331-
let mut initial_values = vec![];
332-
333-
for (i, val) in v.value.iter().enumerate() {
334-
let mut val = val.clone();
335-
if !use_4state {
336-
val.clear_xz();
337-
}
329+
let any_ff = (0..total_array).any(|i| ff_table.is_ff(v.id, i));
330+
let force_ff = any_ff && total_array > 1;
331+
332+
// `v.value.len() < total_array` means the analyzer supplied only
333+
// the template entry — replicate it across all elements.
334+
let template_mode = v.value.len() < total_array;
335+
336+
let mut elements = Vec::with_capacity(total_array);
337+
let mut initial_values = if template_mode {
338+
Vec::with_capacity(1)
339+
} else {
340+
Vec::with_capacity(total_array)
341+
};
338342

343+
for i in 0..total_array {
339344
if force_ff || ff_table.is_ff(v.id, i) {
340345
let current_offset = ff_pos;
341346
let next_offset = ff_pos + vs as isize;
@@ -355,6 +360,20 @@ pub fn create_variable_meta(
355360
comb_pos += vs as isize;
356361
}
357362

363+
if template_mode && i > 0 {
364+
continue;
365+
}
366+
let raw = if let Some(val) = v.value.get(i) {
367+
val.clone()
368+
} else if let Some(template) = v.value.first() {
369+
template.clone()
370+
} else {
371+
Value::new_x(width, v.r#type.signed)
372+
};
373+
let mut val = raw;
374+
if !use_4state {
375+
val.clear_xz();
376+
}
358377
initial_values.push(val);
359378
}
360379

0 commit comments

Comments
 (0)