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