1 /*! GraphViz (DOT) backend
2 *
3 * This backend writes a graph in the DOT format, for the ease
4 * of IR inspection and debugging.
5 !*/
6
7 use crate::{
8 arena::Handle,
9 valid::{FunctionInfo, ModuleInfo},
10 };
11
12 use std::{
13 borrow::Cow,
14 fmt::{Error as FmtError, Write as _},
15 };
16
17 #[derive(Default)]
18 struct StatementGraph {
19 nodes: Vec<&'static str>,
20 flow: Vec<(usize, usize, &'static str)>,
21 dependencies: Vec<(usize, Handle<crate::Expression>, &'static str)>,
22 emits: Vec<(usize, Handle<crate::Expression>)>,
23 calls: Vec<(usize, Handle<crate::Function>)>,
24 }
25
26 impl StatementGraph {
add(&mut self, block: &[crate::Statement]) -> usize27 fn add(&mut self, block: &[crate::Statement]) -> usize {
28 use crate::Statement as S;
29 let root = self.nodes.len();
30 self.nodes.push(if root == 0 { "Root" } else { "Node" });
31 for statement in block {
32 let id = self.nodes.len();
33 self.flow.push((id - 1, id, ""));
34 self.nodes.push(""); // reserve space
35 self.nodes[id] = match *statement {
36 S::Emit(ref range) => {
37 for handle in range.clone() {
38 self.emits.push((id, handle));
39 }
40 "Emit"
41 }
42 S::Break => "Break", //TODO: loop context
43 S::Continue => "Continue", //TODO: loop context
44 S::Kill => "Kill", //TODO: link to the beginning
45 S::Barrier(_flags) => "Barrier",
46 S::Block(ref b) => {
47 let other = self.add(b);
48 self.flow.push((id, other, ""));
49 "Block"
50 }
51 S::If {
52 condition,
53 ref accept,
54 ref reject,
55 } => {
56 self.dependencies.push((id, condition, "condition"));
57 let accept_id = self.add(accept);
58 self.flow.push((id, accept_id, "accept"));
59 let reject_id = self.add(reject);
60 self.flow.push((id, reject_id, "reject"));
61 "If"
62 }
63 S::Switch {
64 selector,
65 ref cases,
66 ref default,
67 } => {
68 self.dependencies.push((id, selector, "selector"));
69 for case in cases {
70 let case_id = self.add(&case.body);
71 self.flow.push((id, case_id, "case"));
72 }
73 let default_id = self.add(default);
74 self.flow.push((id, default_id, "default"));
75 "Switch"
76 }
77 S::Loop {
78 ref body,
79 ref continuing,
80 } => {
81 let body_id = self.add(body);
82 self.flow.push((id, body_id, "body"));
83 let continuing_id = self.add(continuing);
84 self.flow.push((body_id, continuing_id, "continuing"));
85 "Loop"
86 }
87 S::Return { value } => {
88 if let Some(expr) = value {
89 self.dependencies.push((id, expr, "value"));
90 }
91 "Return"
92 }
93 S::Store { pointer, value } => {
94 self.dependencies.push((id, value, "value"));
95 self.emits.push((id, pointer));
96 "Store"
97 }
98 S::ImageStore {
99 image,
100 coordinate,
101 array_index,
102 value,
103 } => {
104 self.dependencies.push((id, image, "image"));
105 self.dependencies.push((id, coordinate, "coordinate"));
106 if let Some(expr) = array_index {
107 self.dependencies.push((id, expr, "array_index"));
108 }
109 self.dependencies.push((id, value, "value"));
110 "ImageStore"
111 }
112 S::Call {
113 function,
114 ref arguments,
115 result,
116 } => {
117 for &arg in arguments {
118 self.dependencies.push((id, arg, "arg"));
119 }
120 if let Some(expr) = result {
121 self.emits.push((id, expr));
122 }
123 self.calls.push((id, function));
124 "Call"
125 }
126 };
127 }
128 root
129 }
130 }
131
132 #[allow(clippy::manual_unwrap_or)]
name(option: &Option<String>) -> &str133 fn name(option: &Option<String>) -> &str {
134 match *option {
135 Some(ref name) => name,
136 None => "",
137 }
138 }
139
140 /// set39 color scheme from https://graphviz.org/doc/info/colors.html
141 const COLORS: &[&str] = &[
142 "white", // pattern starts at 1
143 "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
144 "#d9d9d9",
145 ];
146
write_fun( output: &mut String, prefix: String, fun: &crate::Function, info: Option<&FunctionInfo>, ) -> Result<(), FmtError>147 fn write_fun(
148 output: &mut String,
149 prefix: String,
150 fun: &crate::Function,
151 info: Option<&FunctionInfo>,
152 ) -> Result<(), FmtError> {
153 enum Payload<'a> {
154 Arguments(&'a [Handle<crate::Expression>]),
155 Local(Handle<crate::LocalVariable>),
156 Global(Handle<crate::GlobalVariable>),
157 }
158
159 writeln!(output, "\t\tnode [ style=filled ]")?;
160
161 for (handle, var) in fun.local_variables.iter() {
162 writeln!(
163 output,
164 "\t\t{}_l{} [ shape=hexagon label=\"{:?} '{}'\" ]",
165 prefix,
166 handle.index(),
167 handle,
168 name(&var.name),
169 )?;
170 }
171
172 let mut edges = crate::FastHashMap::<&str, _>::default();
173 let mut payload = None;
174 for (handle, expression) in fun.expressions.iter() {
175 use crate::Expression as E;
176 let (label, color_id) = match *expression {
177 E::Access { base, index } => {
178 edges.insert("base", base);
179 edges.insert("index", index);
180 (Cow::Borrowed("Access"), 1)
181 }
182 E::AccessIndex { base, index } => {
183 edges.insert("base", base);
184 (Cow::Owned(format!("AccessIndex[{}]", index)), 1)
185 }
186 E::Constant(_) => (Cow::Borrowed("Constant"), 2),
187 E::Splat { size, value } => {
188 edges.insert("value", value);
189 (Cow::Owned(format!("Splat{:?}", size)), 3)
190 }
191 E::Swizzle {
192 size,
193 vector,
194 pattern,
195 } => {
196 edges.insert("vector", vector);
197 (
198 Cow::Owned(format!("Swizzle{:?}", &pattern[..size as usize])),
199 3,
200 )
201 }
202 E::Compose { ref components, .. } => {
203 payload = Some(Payload::Arguments(components));
204 (Cow::Borrowed("Compose"), 3)
205 }
206 E::FunctionArgument(index) => (Cow::Owned(format!("Argument[{}]", index)), 1),
207 E::GlobalVariable(h) => {
208 payload = Some(Payload::Global(h));
209 (Cow::Borrowed("Global"), 2)
210 }
211 E::LocalVariable(h) => {
212 payload = Some(Payload::Local(h));
213 (Cow::Borrowed("Local"), 1)
214 }
215 E::Load { pointer } => {
216 edges.insert("pointer", pointer);
217 (Cow::Borrowed("Load"), 4)
218 }
219 E::ImageSample {
220 image,
221 sampler,
222 coordinate,
223 array_index,
224 offset: _,
225 level,
226 depth_ref,
227 } => {
228 edges.insert("image", image);
229 edges.insert("sampler", sampler);
230 edges.insert("coordinate", coordinate);
231 if let Some(expr) = array_index {
232 edges.insert("array_index", expr);
233 }
234 match level {
235 crate::SampleLevel::Auto => {}
236 crate::SampleLevel::Zero => {}
237 crate::SampleLevel::Exact(expr) => {
238 edges.insert("level", expr);
239 }
240 crate::SampleLevel::Bias(expr) => {
241 edges.insert("bias", expr);
242 }
243 crate::SampleLevel::Gradient { x, y } => {
244 edges.insert("grad_x", x);
245 edges.insert("grad_y", y);
246 }
247 }
248 if let Some(expr) = depth_ref {
249 edges.insert("depth_ref", expr);
250 }
251 (Cow::Borrowed("ImageSample"), 5)
252 }
253 E::ImageLoad {
254 image,
255 coordinate,
256 array_index,
257 index,
258 } => {
259 edges.insert("image", image);
260 edges.insert("coordinate", coordinate);
261 if let Some(expr) = array_index {
262 edges.insert("array_index", expr);
263 }
264 if let Some(expr) = index {
265 edges.insert("index", expr);
266 }
267 (Cow::Borrowed("ImageLoad"), 5)
268 }
269 E::ImageQuery { image, query } => {
270 edges.insert("image", image);
271 let args = match query {
272 crate::ImageQuery::Size { level } => {
273 if let Some(expr) = level {
274 edges.insert("level", expr);
275 }
276 Cow::Borrowed("ImageSize")
277 }
278 _ => Cow::Owned(format!("{:?}", query)),
279 };
280 (args, 7)
281 }
282 E::Unary { op, expr } => {
283 edges.insert("expr", expr);
284 (Cow::Owned(format!("{:?}", op)), 6)
285 }
286 E::Binary { op, left, right } => {
287 edges.insert("left", left);
288 edges.insert("right", right);
289 (Cow::Owned(format!("{:?}", op)), 6)
290 }
291 E::Select {
292 condition,
293 accept,
294 reject,
295 } => {
296 edges.insert("condition", condition);
297 edges.insert("accept", accept);
298 edges.insert("reject", reject);
299 (Cow::Borrowed("Select"), 3)
300 }
301 E::Derivative { axis, expr } => {
302 edges.insert("", expr);
303 (Cow::Owned(format!("d{:?}", axis)), 8)
304 }
305 E::Relational { fun, argument } => {
306 edges.insert("arg", argument);
307 (Cow::Owned(format!("{:?}", fun)), 6)
308 }
309 E::Math {
310 fun,
311 arg,
312 arg1,
313 arg2,
314 } => {
315 edges.insert("arg", arg);
316 if let Some(expr) = arg1 {
317 edges.insert("arg1", expr);
318 }
319 if let Some(expr) = arg2 {
320 edges.insert("arg2", expr);
321 }
322 (Cow::Owned(format!("{:?}", fun)), 7)
323 }
324 E::As {
325 kind,
326 expr,
327 convert,
328 } => {
329 edges.insert("", expr);
330 let string = match convert {
331 Some(width) => format!("Convert<{:?},{}>", kind, width),
332 None => format!("Bitcast<{:?}>", kind),
333 };
334 (Cow::Owned(string), 3)
335 }
336 E::Call(_function) => (Cow::Borrowed("Call"), 4),
337 E::ArrayLength(expr) => {
338 edges.insert("", expr);
339 (Cow::Borrowed("ArrayLength"), 7)
340 }
341 };
342
343 // give uniform expressions an outline
344 let color_attr = match info {
345 Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
346 _ => "color",
347 };
348 writeln!(
349 output,
350 "\t\t{}_e{} [ {}=\"{}\" label=\"{:?} {}\" ]",
351 prefix,
352 handle.index(),
353 color_attr,
354 COLORS[color_id],
355 handle,
356 label,
357 )?;
358
359 for (key, edge) in edges.drain() {
360 writeln!(
361 output,
362 "\t\t{}_e{} -> {}_e{} [ label=\"{}\" ]",
363 prefix,
364 edge.index(),
365 prefix,
366 handle.index(),
367 key,
368 )?;
369 }
370 match payload.take() {
371 Some(Payload::Arguments(list)) => {
372 write!(output, "\t\t{{")?;
373 for &comp in list {
374 write!(output, " {}_e{}", prefix, comp.index())?;
375 }
376 writeln!(output, " }} -> {}_e{}", prefix, handle.index())?;
377 }
378 Some(Payload::Local(h)) => {
379 writeln!(
380 output,
381 "\t\t{}_l{} -> {}_e{}",
382 prefix,
383 h.index(),
384 prefix,
385 handle.index(),
386 )?;
387 }
388 Some(Payload::Global(h)) => {
389 writeln!(
390 output,
391 "\t\tg{} -> {}_e{} [fillcolor=gray]",
392 h.index(),
393 prefix,
394 handle.index(),
395 )?;
396 }
397 None => {}
398 }
399 }
400
401 let mut sg = StatementGraph::default();
402 sg.add(&fun.body);
403 for (index, label) in sg.nodes.into_iter().enumerate() {
404 writeln!(
405 output,
406 "\t\t{}_s{} [ shape=square label=\"{}\" ]",
407 prefix, index, label,
408 )?;
409 }
410 for (from, to, label) in sg.flow {
411 writeln!(
412 output,
413 "\t\t{}_s{} -> {}_s{} [ arrowhead=tee label=\"{}\" ]",
414 prefix, from, prefix, to, label,
415 )?;
416 }
417 for (to, expr, label) in sg.dependencies {
418 writeln!(
419 output,
420 "\t\t{}_e{} -> {}_s{} [ label=\"{}\" ]",
421 prefix,
422 expr.index(),
423 prefix,
424 to,
425 label,
426 )?;
427 }
428 for (from, to) in sg.emits {
429 writeln!(
430 output,
431 "\t\t{}_s{} -> {}_e{} [ style=dotted ]",
432 prefix,
433 from,
434 prefix,
435 to.index(),
436 )?;
437 }
438 for (from, function) in sg.calls {
439 writeln!(
440 output,
441 "\t\t{}_s{} -> f{}_s0",
442 prefix,
443 from,
444 function.index(),
445 )?;
446 }
447
448 Ok(())
449 }
450
write(module: &crate::Module, mod_info: Option<&ModuleInfo>) -> Result<String, FmtError>451 pub fn write(module: &crate::Module, mod_info: Option<&ModuleInfo>) -> Result<String, FmtError> {
452 use std::fmt::Write as _;
453
454 let mut output = String::new();
455 output += "digraph Module {\n";
456
457 writeln!(output, "\tsubgraph cluster_globals {{")?;
458 writeln!(output, "\t\tlabel=\"Globals\"")?;
459 for (handle, var) in module.global_variables.iter() {
460 writeln!(
461 output,
462 "\t\tg{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
463 handle.index(),
464 handle,
465 var.class,
466 name(&var.name),
467 )?;
468 }
469 writeln!(output, "\t}}")?;
470
471 for (handle, fun) in module.functions.iter() {
472 let prefix = format!("f{}", handle.index());
473 writeln!(output, "\tsubgraph cluster_{} {{", prefix)?;
474 writeln!(
475 output,
476 "\t\tlabel=\"Function{:?}/'{}'\"",
477 handle,
478 name(&fun.name)
479 )?;
480 let info = mod_info.map(|a| &a[handle]);
481 write_fun(&mut output, prefix, fun, info)?;
482 writeln!(output, "\t}}")?;
483 }
484 for (ep_index, ep) in module.entry_points.iter().enumerate() {
485 let prefix = format!("ep{}", ep_index);
486 writeln!(output, "\tsubgraph cluster_{} {{", prefix)?;
487 writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
488 let info = mod_info.map(|a| a.get_entry_point(ep_index));
489 write_fun(&mut output, prefix, &ep.function, info)?;
490 writeln!(output, "\t}}")?;
491 }
492
493 output += "}\n";
494 Ok(output)
495 }
496