1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Copyright 2024-2026 Jonathan Shook
// SPDX-License-Identifier: Apache-2.0
//! Wiring / value-provenance analysis output for diagnostic modes.
//!
//! Called by the runner when `dryrun=wiring` is active. Renders
//! the variable connections between named wires at each kernel
//! scope: how each output was computed, what inputs it depends
//! on, and where those inputs came from. The polydat runtime
//! kernel is the source of this data, but the user-facing
//! concept is "wiring" between named values, not the kernel
//! representation.
use polydat::kernel::PolydatProgram;
use std::sync::Arc;
/// Print wiring analysis for a phase/iteration scope.
///
/// Called by the runner at the point where it would normally
/// dispatch cycles. The kernel has already been compiled through
/// the exact same pipeline as execution.
pub fn print_wiring_analysis(phase_name: &str, iter_note: &str, program: &Arc<PolydatProgram>) {
let input_names = program.input_names();
let coord_count = program.coord_count();
println!(
" Phase '{phase_name}'{iter_note} ({} nodes, {} outputs):",
program.node_count(),
program.output_count()
);
for (i, name) in input_names.iter().enumerate() {
let kind = if i < coord_count {
"coordinate"
} else {
"extern"
};
println!(" input {name}: {kind}");
}
for i in 0..program.output_count() {
let name = program.output_name(i);
let (node_idx, port_idx) = program.resolve_output_by_index(i);
let meta = program.node_meta(node_idx);
let provenance = program.input_provenance_for(node_idx);
let modifier = program.output_modifier(name);
let is_const = program.node_wiring(node_idx).is_empty();
let mut deps: Vec<String> = Vec::new();
for (j, inp_name) in input_names.iter().enumerate() {
if provenance.is_some_and(|p| p.contains(j)) {
deps.push(inp_name.clone());
}
}
// Build the [final shared volatile] tag suffix from the
// modifier's flag set. Order is fixed so the output is
// stable.
let mod_str = {
let mut tags: Vec<&str> = Vec::new();
if modifier.is_const() {
tags.push("final");
}
if modifier.is_shared() {
tags.push("shared");
}
if modifier.is_volatile() {
tags.push("volatile");
}
if tags.is_empty() {
String::new()
} else {
format!(" [{}]", tags.join(" "))
}
};
let mod_str = mod_str.as_str();
let out_type = if port_idx < meta.outs.len() {
format!("{:?}", meta.outs[port_idx].typ)
} else {
"?".into()
};
print!(" {name}{mod_str}: {out_type}");
if is_const {
println!(" (const-folded at compile time)");
} else if deps.is_empty() {
println!(" (no input deps)");
} else {
println!(" (per-cycle, depends on: {})", deps.join(", "));
}
let wiring = program.node_wiring(node_idx);
if !wiring.is_empty() {
let descs: Vec<String> = wiring
.iter()
.map(|w| match w {
polydat::kernel::WireSource::Input(idx) => {
if *idx < input_names.len() {
format!("input:{}", input_names[*idx])
} else {
format!("input:{idx}")
}
}
polydat::kernel::WireSource::NodeOutput(ni, pi) => {
let u = program.node_meta(*ni);
if *pi == 0 {
u.name.clone()
} else {
format!("{}[{pi}]", u.name)
}
}
})
.collect();
println!(" node: {}({})", meta.name, descs.join(", "));
}
}
println!();
}