///|
/// Constant folding pass: walks nodes in order, replacing foldable nodes
/// whose inputs are all constants with new Constant nodes.
/// Returns (folded_nodes, index_map, changed).
fn fold_constants(
nodes : Array[DspNode],
) -> (Array[DspNode], FixedArray[Int], Bool) {
let n = nodes.length()
// Bail out if nodes are not in topological order (forward references present)
for i = 0; i < n; i = i + 1 {
let node = nodes[i]
if (node.input0 >= 0 && node.input0 >= i) ||
(node.input1 >= 0 && node.input1 >= i) {
return (nodes, FixedArray::makei(n, fn(i) { i }), false)
}
}
let folded : Array[DspNode] = []
let index_map = FixedArray::make(n, -1)
let mut changed = false
for i = 0; i < n; i = i + 1 {
let node = nodes[i]
let is_constant = node.kind is DspNodeKind::Constant
if node.is_foldable() && !is_constant {
// Check if all inputs are Constant in the folded array
let has_input0 = node.input0 >= 0
let has_input1 = node.input1 >= 0
let input0_is_const = if has_input0 {
let mapped = index_map[node.input0]
mapped >= 0 && folded[mapped].kind is DspNodeKind::Constant
} else {
true
}
let input1_is_const = if has_input1 {
let mapped = index_map[node.input1]
mapped >= 0 && folded[mapped].kind is DspNodeKind::Constant
} else {
true
}
if input0_is_const && input1_is_const {
// Skip folding Clip with invalid threshold — let validation reject it
if node.kind is DspNodeKind::Clip && !(node.value0 > 0.0) {
// Invalid Clip threshold — fall through to keep as-is
} else {
// Get input values
let v0 = if has_input0 {
folded[index_map[node.input0]].value0
} else {
0.0
}
let v1 = if has_input1 {
folded[index_map[node.input1]].value0
} else {
0.0
}
let result = node.fold_value(v0, v1)
// Skip folding if result is non-finite
if !@dsp.is_finite(result) {
// Fall through to keep node as-is
} else {
let new_index = folded.length()
folded.push(DspNode::constant(result))
index_map[i] = new_index
changed = true
continue
}
}
}
}
// Keep node as-is, remapping inputs
let new_input0 = if node.input0 >= 0 { index_map[node.input0] } else { -1 }
let new_input1 = if node.input1 >= 0 { index_map[node.input1] } else { -1 }
let new_index = folded.length()
folded.push(
DspNode::new(
node.kind,
new_input0,
new_input1,
node.value0,
node.value1,
node.value2,
node.value3,
node.waveform,
node.filter_mode,
node.delay_max_samples,
node.delay_samples,
node.seed,
),
)
index_map[i] = new_index
}
(folded, index_map, changed)
}
///|
/// Dead-node elimination pass: removes nodes not reachable from the output.
/// Returns (live_nodes, index_map, changed).
fn eliminate_dead(
nodes : Array[DspNode],
) -> (Array[DspNode], FixedArray[Int], Bool) {
let n = nodes.length()
if n == 0 {
return ([], FixedArray::make(0, -1), false)
}
// Find output node(s) and mark reachable nodes
let reachable = FixedArray::make(n, false)
// Walk backward from all output nodes
for i = n - 1; i >= 0; i = i - 1 {
match nodes[i].kind {
Output | StereoOutput => mark_reachable(nodes, reachable, i)
_ => ()
}
}
// Check if anything was eliminated
let mut all_reachable = true
for i = 0; i < n; i = i + 1 {
if !reachable[i] {
all_reachable = false
break
}
}
if all_reachable {
return (nodes, FixedArray::makei(n, fn(i) { i }), false)
}
// Build compacted array
let index_map = FixedArray::make(n, -1)
let live : Array[DspNode] = []
for i = 0; i < n; i = i + 1 {
if reachable[i] {
index_map[i] = live.length()
live.push(nodes[i])
}
}
// Remap inputs in live nodes
let remap = index_map
let result : Array[DspNode] = []
for i = 0; i < live.length(); i = i + 1 {
let node = live[i]
let new_input0 = if node.input0 >= 0 && node.input0 < remap.length() {
remap[node.input0]
} else {
-1
}
let new_input1 = if node.input1 >= 0 && node.input1 < remap.length() {
remap[node.input1]
} else {
-1
}
result.push(
DspNode::new(
node.kind,
new_input0,
new_input1,
node.value0,
node.value1,
node.value2,
node.value3,
node.waveform,
node.filter_mode,
node.delay_max_samples,
node.delay_samples,
node.seed,
),
)
}
(result, index_map, true)
}
///|
/// Iteratively mark a node and its inputs as reachable using a worklist.
fn mark_reachable(
nodes : Array[DspNode],
reachable : FixedArray[Bool],
start : Int,
) -> Unit {
let stack : Array[Int] = [start]
while stack.length() > 0 {
let index = stack.pop().unwrap()
if index < 0 || index >= nodes.length() || reachable[index] {
continue
}
reachable[index] = true
let node = nodes[index]
if node.input0 >= 0 {
stack.push(node.input0)
}
if node.input1 >= 0 {
stack.push(node.input1)
}
}
}
///|
/// Compose two index maps: for each original index, follow through
/// map1 then map2 to get the final index. If either step yields -1,
/// the result is -1.
fn compose_index_maps(
map1 : FixedArray[Int],
map2 : FixedArray[Int],
) -> FixedArray[Int] {
FixedArray::makei(map1.length(), fn(i) {
let mid = map1[i]
if mid < 0 {
-1
} else {
map2[mid]
}
})
}
///|
/// Optimize a DSP graph by repeatedly applying constant folding and
/// dead-node elimination until a fixpoint is reached.
///
/// Returns (optimized_nodes, index_map) where index_map maps each
/// original node index to its final optimized index (-1 if eliminated).
fn optimize_graph(nodes : Array[DspNode]) -> (Array[DspNode], FixedArray[Int]) {
let n = nodes.length()
let mut current = nodes
let mut total_map = FixedArray::makei(n, fn(i) { i })
// Fixpoint loop
for round = 0; round < n; round = round + 1 {
let mut any_change = false
// Constant folding pass
let (folded, fold_map, fold_changed) = fold_constants(current)
if fold_changed {
total_map = compose_index_maps(total_map, fold_map)
current = folded
any_change = true
}
// Dead-node elimination pass
let (live, elim_map, elim_changed) = eliminate_dead(current)
if elim_changed {
total_map = compose_index_maps(total_map, elim_map)
current = live
any_change = true
}
if !any_change {
break
}
}
(current, total_map)
}