1use alloc::{
11 borrow::Cow,
12 format,
13 string::{String, ToString},
14 vec::Vec,
15};
16use core::fmt::{Error as FmtError, Write as _};
17
18use crate::{
19 arena::Handle,
20 valid::{FunctionInfo, ModuleInfo},
21};
22
23#[derive(Clone, Debug, Default)]
25pub struct Options {
26 pub cfg_only: bool,
28}
29
30type NodeId = usize;
32
33#[derive(Default, Clone, Copy)]
35struct Targets {
36 continue_target: Option<usize>,
38 break_target: Option<usize>,
40}
41
42#[derive(Default)]
44struct StatementGraph {
45 nodes: Vec<&'static str>,
47 flow: Vec<(NodeId, NodeId, &'static str)>,
50 jumps: Vec<(NodeId, NodeId, &'static str, usize)>,
54 dependencies: Vec<(NodeId, Handle<crate::Expression>, &'static str)>,
57 emits: Vec<(NodeId, Handle<crate::Expression>)>,
59 calls: Vec<(NodeId, Handle<crate::Function>)>,
61}
62
63impl StatementGraph {
64 fn add(&mut self, block: &[crate::Statement], targets: Targets) -> (NodeId, NodeId) {
66 use crate::Statement as S;
67
68 let root = self.nodes.len();
70 self.nodes.push(if root == 0 { "Root" } else { "Node" });
71 let mut last_node = root;
74 for statement in block {
75 let id = self.nodes.len();
78 self.flow.push((last_node, id, ""));
79 self.nodes.push(""); let mut merge_id = id;
86
87 self.nodes[id] = match *statement {
88 S::Emit(ref range) => {
89 for handle in range.clone() {
90 self.emits.push((id, handle));
91 }
92 "Emit"
93 }
94 S::Kill => "Kill", S::Break => {
96 if let Some(target) = targets.break_target {
99 self.jumps.push((id, target, "Break", 5))
100 } else {
101 self.jumps.push((id, root, "Broken", 7))
102 }
103 "Break"
104 }
105 S::Continue => {
106 if let Some(target) = targets.continue_target {
109 self.jumps.push((id, target, "Continue", 5))
110 } else {
111 self.jumps.push((id, root, "Broken", 7))
112 }
113 "Continue"
114 }
115 S::ControlBarrier(_flags) => "ControlBarrier",
116 S::MemoryBarrier(_flags) => "MemoryBarrier",
117 S::Block(ref b) => {
118 let (other, last) = self.add(b, targets);
119 self.flow.push((id, other, ""));
120 merge_id = last;
123 "Block"
124 }
125 S::If {
126 condition,
127 ref accept,
128 ref reject,
129 } => {
130 self.dependencies.push((id, condition, "condition"));
131 let (accept_id, accept_last) = self.add(accept, targets);
132 self.flow.push((id, accept_id, "accept"));
133 let (reject_id, reject_last) = self.add(reject, targets);
134 self.flow.push((id, reject_id, "reject"));
135
136 merge_id = self.nodes.len();
139 self.nodes.push("Merge");
140 self.flow.push((accept_last, merge_id, ""));
141 self.flow.push((reject_last, merge_id, ""));
142
143 "If"
144 }
145 S::Switch {
146 selector,
147 ref cases,
148 } => {
149 self.dependencies.push((id, selector, "selector"));
150
151 merge_id = self.nodes.len();
154 self.nodes.push("Merge");
155
156 let mut targets = targets;
159 targets.break_target = Some(merge_id);
160
161 for case in cases {
162 let (case_id, case_last) = self.add(&case.body, targets);
163 let label = match case.value {
164 crate::SwitchValue::Default => "default",
165 _ => "case",
166 };
167 self.flow.push((id, case_id, label));
168 self.flow.push((case_last, merge_id, ""));
170 }
171 "Switch"
172 }
173 S::Loop {
174 ref body,
175 ref continuing,
176 break_if,
177 } => {
178 let mut targets = targets;
182 targets.break_target = Some(id);
183
184 let (continuing_id, continuing_last) = self.add(continuing, targets);
185
186 targets.continue_target = Some(continuing_id);
189
190 let (body_id, body_last) = self.add(body, targets);
191
192 self.flow.push((id, body_id, "body"));
193
194 self.flow.push((body_last, continuing_id, "continuing"));
196 self.flow.push((continuing_last, body_id, "continuing"));
199
200 if let Some(expr) = break_if {
201 self.dependencies.push((continuing_id, expr, "break if"));
202 }
203
204 "Loop"
205 }
206 S::Return { value } => {
207 if let Some(expr) = value {
208 self.dependencies.push((id, expr, "value"));
209 }
210 "Return"
211 }
212 S::Store { pointer, value } => {
213 self.dependencies.push((id, value, "value"));
214 self.emits.push((id, pointer));
215 "Store"
216 }
217 S::ImageStore {
218 image,
219 coordinate,
220 array_index,
221 value,
222 } => {
223 self.dependencies.push((id, image, "image"));
224 self.dependencies.push((id, coordinate, "coordinate"));
225 if let Some(expr) = array_index {
226 self.dependencies.push((id, expr, "array_index"));
227 }
228 self.dependencies.push((id, value, "value"));
229 "ImageStore"
230 }
231 S::Call {
232 function,
233 ref arguments,
234 result,
235 } => {
236 for &arg in arguments {
237 self.dependencies.push((id, arg, "arg"));
238 }
239 if let Some(expr) = result {
240 self.emits.push((id, expr));
241 }
242 self.calls.push((id, function));
243 "Call"
244 }
245 S::Atomic {
246 pointer,
247 ref fun,
248 value,
249 result,
250 } => {
251 if let Some(result) = result {
252 self.emits.push((id, result));
253 }
254 self.dependencies.push((id, pointer, "pointer"));
255 self.dependencies.push((id, value, "value"));
256 if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun {
257 self.dependencies.push((id, cmp, "cmp"));
258 }
259 "Atomic"
260 }
261 S::ImageAtomic {
262 image,
263 coordinate,
264 array_index,
265 fun: _,
266 value,
267 } => {
268 self.dependencies.push((id, image, "image"));
269 self.dependencies.push((id, coordinate, "coordinate"));
270 if let Some(expr) = array_index {
271 self.dependencies.push((id, expr, "array_index"));
272 }
273 self.dependencies.push((id, value, "value"));
274 "ImageAtomic"
275 }
276 S::WorkGroupUniformLoad { pointer, result } => {
277 self.emits.push((id, result));
278 self.dependencies.push((id, pointer, "pointer"));
279 "WorkGroupUniformLoad"
280 }
281 S::RayQuery { query, ref fun } => {
282 self.dependencies.push((id, query, "query"));
283 match *fun {
284 crate::RayQueryFunction::Initialize {
285 acceleration_structure,
286 descriptor,
287 } => {
288 self.dependencies.push((
289 id,
290 acceleration_structure,
291 "acceleration_structure",
292 ));
293 self.dependencies.push((id, descriptor, "descriptor"));
294 "RayQueryInitialize"
295 }
296 crate::RayQueryFunction::Proceed { result } => {
297 self.emits.push((id, result));
298 "RayQueryProceed"
299 }
300 crate::RayQueryFunction::GenerateIntersection { hit_t } => {
301 self.dependencies.push((id, hit_t, "hit_t"));
302 "RayQueryGenerateIntersection"
303 }
304 crate::RayQueryFunction::ConfirmIntersection => {
305 "RayQueryConfirmIntersection"
306 }
307 crate::RayQueryFunction::Terminate => "RayQueryTerminate",
308 crate::RayQueryFunction::Begin => "RayQueryVariableUsageBegins",
309 }
310 }
311 S::SubgroupBallot { result, predicate } => {
312 if let Some(predicate) = predicate {
313 self.dependencies.push((id, predicate, "predicate"));
314 }
315 self.emits.push((id, result));
316 "SubgroupBallot"
317 }
318 S::SubgroupCollectiveOperation {
319 op,
320 collective_op,
321 argument,
322 result,
323 } => {
324 self.dependencies.push((id, argument, "arg"));
325 self.emits.push((id, result));
326 match (collective_op, op) {
327 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => {
328 "SubgroupAll"
329 }
330 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => {
331 "SubgroupAny"
332 }
333 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => {
334 "SubgroupAdd"
335 }
336 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => {
337 "SubgroupMul"
338 }
339 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => {
340 "SubgroupMax"
341 }
342 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => {
343 "SubgroupMin"
344 }
345 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => {
346 "SubgroupAnd"
347 }
348 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => {
349 "SubgroupOr"
350 }
351 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => {
352 "SubgroupXor"
353 }
354 (
355 crate::CollectiveOperation::ExclusiveScan,
356 crate::SubgroupOperation::Add,
357 ) => "SubgroupExclusiveAdd",
358 (
359 crate::CollectiveOperation::ExclusiveScan,
360 crate::SubgroupOperation::Mul,
361 ) => "SubgroupExclusiveMul",
362 (
363 crate::CollectiveOperation::InclusiveScan,
364 crate::SubgroupOperation::Add,
365 ) => "SubgroupInclusiveAdd",
366 (
367 crate::CollectiveOperation::InclusiveScan,
368 crate::SubgroupOperation::Mul,
369 ) => "SubgroupInclusiveMul",
370 _ => unimplemented!(),
371 }
372 }
373 S::SubgroupGather {
374 mode,
375 argument,
376 result,
377 } => {
378 match mode {
379 crate::GatherMode::BroadcastFirst => {}
380 crate::GatherMode::Broadcast(index)
381 | crate::GatherMode::Shuffle(index)
382 | crate::GatherMode::ShuffleDown(index)
383 | crate::GatherMode::ShuffleUp(index)
384 | crate::GatherMode::ShuffleXor(index)
385 | crate::GatherMode::QuadBroadcast(index) => {
386 self.dependencies.push((id, index, "index"))
387 }
388 crate::GatherMode::QuadSwap(_) => {}
389 }
390 self.dependencies.push((id, argument, "arg"));
391 self.emits.push((id, result));
392 match mode {
393 crate::GatherMode::BroadcastFirst => "SubgroupBroadcastFirst",
394 crate::GatherMode::Broadcast(_) => "SubgroupBroadcast",
395 crate::GatherMode::Shuffle(_) => "SubgroupShuffle",
396 crate::GatherMode::ShuffleDown(_) => "SubgroupShuffleDown",
397 crate::GatherMode::ShuffleUp(_) => "SubgroupShuffleUp",
398 crate::GatherMode::ShuffleXor(_) => "SubgroupShuffleXor",
399 crate::GatherMode::QuadBroadcast(_) => "SubgroupQuadBroadcast",
400 crate::GatherMode::QuadSwap(direction) => match direction {
401 crate::Direction::X => "SubgroupQuadSwapX",
402 crate::Direction::Y => "SubgroupQuadSwapY",
403 crate::Direction::Diagonal => "SubgroupQuadSwapDiagonal",
404 },
405 }
406 }
407 S::CooperativeStore { target, data } => {
408 self.dependencies.push((id, target, "target"));
409 self.dependencies.push((id, data.pointer, "pointer"));
410 self.dependencies.push((id, data.stride, "stride"));
411 if data.row_major {
412 "CoopStoreT"
413 } else {
414 "CoopStore"
415 }
416 }
417 S::RayPipelineFunction(func) => match func {
418 crate::RayPipelineFunction::TraceRay {
419 acceleration_structure,
420 descriptor,
421 payload,
422 } => {
423 self.dependencies.push((
424 id,
425 acceleration_structure,
426 "acceleration_structure",
427 ));
428 self.dependencies.push((id, descriptor, "descriptor"));
429 self.dependencies.push((id, payload, "payload"));
430 "TraceRay"
431 }
432 },
433 S::DebugPrintf {
434 format: _,
435 ref arguments,
436 } => {
437 for &expr in arguments {
438 self.dependencies.push((id, expr, "arg"));
439 }
440 "DebugPrintf"
441 }
442 };
443 last_node = merge_id;
445 }
446 (root, last_node)
447 }
448}
449
450fn name(option: &Option<String>) -> &str {
451 option.as_deref().unwrap_or_default()
452}
453
454const COLORS: &[&str] = &[
456 "white", "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
458 "#d9d9d9",
459];
460
461struct Prefixed<T>(Handle<T>);
462
463impl core::fmt::Display for Prefixed<crate::Expression> {
464 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
465 self.0.write_prefixed(f, "e")
466 }
467}
468
469impl core::fmt::Display for Prefixed<crate::LocalVariable> {
470 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
471 self.0.write_prefixed(f, "l")
472 }
473}
474
475impl core::fmt::Display for Prefixed<crate::GlobalVariable> {
476 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477 self.0.write_prefixed(f, "g")
478 }
479}
480
481impl core::fmt::Display for Prefixed<crate::Function> {
482 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
483 self.0.write_prefixed(f, "f")
484 }
485}
486
487fn write_fun(
488 output: &mut String,
489 prefix: String,
490 fun: &crate::Function,
491 info: Option<&FunctionInfo>,
492 options: &Options,
493) -> Result<(), FmtError> {
494 writeln!(output, "\t\tnode [ style=filled ]")?;
495
496 if !options.cfg_only {
497 for (handle, var) in fun.local_variables.iter() {
498 writeln!(
499 output,
500 "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]",
501 prefix,
502 Prefixed(handle),
503 handle,
504 name(&var.name),
505 )?;
506 }
507
508 write_function_expressions(output, &prefix, fun, info)?;
509 }
510
511 let mut sg = StatementGraph::default();
512 sg.add(&fun.body, Targets::default());
513 for (index, label) in sg.nodes.into_iter().enumerate() {
514 writeln!(
515 output,
516 "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]",
517 )?;
518 }
519 for (from, to, label) in sg.flow {
520 writeln!(
521 output,
522 "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]",
523 )?;
524 }
525 for (from, to, label, color_id) in sg.jumps {
526 writeln!(
527 output,
528 "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]",
529 prefix, from, prefix, to, COLORS[color_id], label,
530 )?;
531 }
532
533 if !options.cfg_only {
534 for (to, expr, label) in sg.dependencies {
535 writeln!(
536 output,
537 "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]",
538 prefix,
539 Prefixed(expr),
540 prefix,
541 to,
542 label,
543 )?;
544 }
545 for (from, to) in sg.emits {
546 writeln!(
547 output,
548 "\t\t{}_s{} -> {}_{} [ style=dotted ]",
549 prefix,
550 from,
551 prefix,
552 Prefixed(to),
553 )?;
554 }
555 }
556
557 assert!(sg.calls.is_empty());
558 for (from, function) in sg.calls {
559 writeln!(
560 output,
561 "\t\t{}_s{} -> {}_s0",
562 prefix,
563 from,
564 Prefixed(function),
565 )?;
566 }
567
568 Ok(())
569}
570
571fn write_function_expressions(
572 output: &mut String,
573 prefix: &str,
574 fun: &crate::Function,
575 info: Option<&FunctionInfo>,
576) -> Result<(), FmtError> {
577 enum Payload<'a> {
578 Arguments(&'a [Handle<crate::Expression>]),
579 Local(Handle<crate::LocalVariable>),
580 Global(Handle<crate::GlobalVariable>),
581 }
582
583 let mut edges = crate::FastHashMap::<&str, _>::default();
584 let mut payload = None;
585 for (handle, expression) in fun.expressions.iter() {
586 use crate::Expression as E;
587 let (label, color_id) = match *expression {
588 E::Literal(_) => ("Literal".into(), 2),
589 E::Constant(_) => ("Constant".into(), 2),
590 E::Override(_) => ("Override".into(), 2),
591 E::ZeroValue(_) => ("ZeroValue".into(), 2),
592 E::Compose { ref components, .. } => {
593 payload = Some(Payload::Arguments(components));
594 ("Compose".into(), 3)
595 }
596 E::Access { base, index } => {
597 edges.insert("base", base);
598 edges.insert("index", index);
599 ("Access".into(), 1)
600 }
601 E::AccessIndex { base, index } => {
602 edges.insert("base", base);
603 (format!("AccessIndex[{index}]").into(), 1)
604 }
605 E::Splat { size, value } => {
606 edges.insert("value", value);
607 (format!("Splat{size:?}").into(), 3)
608 }
609 E::Swizzle {
610 size,
611 vector,
612 pattern,
613 } => {
614 edges.insert("vector", vector);
615 (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3)
616 }
617 E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1),
618 E::GlobalVariable(h) => {
619 payload = Some(Payload::Global(h));
620 ("Global".into(), 2)
621 }
622 E::LocalVariable(h) => {
623 payload = Some(Payload::Local(h));
624 ("Local".into(), 1)
625 }
626 E::Load { pointer } => {
627 edges.insert("pointer", pointer);
628 ("Load".into(), 4)
629 }
630 E::ImageSample {
631 image,
632 sampler,
633 gather,
634 coordinate,
635 array_index,
636 offset: _,
637 level,
638 depth_ref,
639 clamp_to_edge: _,
640 } => {
641 edges.insert("image", image);
642 edges.insert("sampler", sampler);
643 edges.insert("coordinate", coordinate);
644 if let Some(expr) = array_index {
645 edges.insert("array_index", expr);
646 }
647 match level {
648 crate::SampleLevel::Auto => {}
649 crate::SampleLevel::Zero => {}
650 crate::SampleLevel::Exact(expr) => {
651 edges.insert("level", expr);
652 }
653 crate::SampleLevel::Bias(expr) => {
654 edges.insert("bias", expr);
655 }
656 crate::SampleLevel::Gradient { x, y } => {
657 edges.insert("grad_x", x);
658 edges.insert("grad_y", y);
659 }
660 }
661 if let Some(expr) = depth_ref {
662 edges.insert("depth_ref", expr);
663 }
664 let string = match gather {
665 Some(component) => Cow::Owned(format!("ImageGather{component:?}")),
666 _ => Cow::Borrowed("ImageSample"),
667 };
668 (string, 5)
669 }
670 E::ImageLoad {
671 image,
672 coordinate,
673 array_index,
674 sample,
675 level,
676 } => {
677 edges.insert("image", image);
678 edges.insert("coordinate", coordinate);
679 if let Some(expr) = array_index {
680 edges.insert("array_index", expr);
681 }
682 if let Some(sample) = sample {
683 edges.insert("sample", sample);
684 }
685 if let Some(level) = level {
686 edges.insert("level", level);
687 }
688 ("ImageLoad".into(), 5)
689 }
690 E::ImageQuery { image, query } => {
691 edges.insert("image", image);
692 let args = match query {
693 crate::ImageQuery::Size { level } => {
694 if let Some(expr) = level {
695 edges.insert("level", expr);
696 }
697 Cow::from("ImageSize")
698 }
699 _ => Cow::Owned(format!("{query:?}")),
700 };
701 (args, 7)
702 }
703 E::Unary { op, expr } => {
704 edges.insert("expr", expr);
705 (format!("{op:?}").into(), 6)
706 }
707 E::Binary { op, left, right } => {
708 edges.insert("left", left);
709 edges.insert("right", right);
710 (format!("{op:?}").into(), 6)
711 }
712 E::Select {
713 condition,
714 accept,
715 reject,
716 } => {
717 edges.insert("condition", condition);
718 edges.insert("accept", accept);
719 edges.insert("reject", reject);
720 ("Select".into(), 3)
721 }
722 E::Derivative { axis, ctrl, expr } => {
723 edges.insert("", expr);
724 (format!("d{axis:?}{ctrl:?}").into(), 8)
725 }
726 E::Relational { fun, argument } => {
727 edges.insert("arg", argument);
728 (format!("{fun:?}").into(), 6)
729 }
730 E::Math {
731 fun,
732 arg,
733 arg1,
734 arg2,
735 arg3,
736 } => {
737 edges.insert("arg", arg);
738 if let Some(expr) = arg1 {
739 edges.insert("arg1", expr);
740 }
741 if let Some(expr) = arg2 {
742 edges.insert("arg2", expr);
743 }
744 if let Some(expr) = arg3 {
745 edges.insert("arg3", expr);
746 }
747 (format!("{fun:?}").into(), 7)
748 }
749 E::As {
750 kind,
751 expr,
752 convert,
753 } => {
754 edges.insert("", expr);
755 let string = match convert {
756 Some(width) => format!("Convert<{kind:?},{width}>"),
757 None => format!("Bitcast<{kind:?}>"),
758 };
759 (string.into(), 3)
760 }
761 E::CallResult(_function) => ("CallResult".into(), 4),
762 E::AtomicResult { .. } => ("AtomicResult".into(), 4),
763 E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4),
764 E::ArrayLength(expr) => {
765 edges.insert("", expr);
766 ("ArrayLength".into(), 7)
767 }
768 E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4),
769 E::RayQueryGetIntersection { query, committed } => {
770 edges.insert("", query);
771 let ty = if committed { "Committed" } else { "Candidate" };
772 (format!("rayQueryGet{ty}Intersection").into(), 4)
773 }
774 E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4),
775 E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4),
776 E::RayQueryVertexPositions { query, committed } => {
777 edges.insert("", query);
778 let ty = if committed { "Committed" } else { "Candidate" };
779 (format!("get{ty}HitVertexPositions").into(), 4)
780 }
781 E::CooperativeLoad { ref data, .. } => {
782 edges.insert("pointer", data.pointer);
783 edges.insert("stride", data.stride);
784 let suffix = if data.row_major { "T " } else { "" };
785 (format!("coopLoad{suffix}").into(), 4)
786 }
787 E::CooperativeMultiplyAdd { a, b, c } => {
788 edges.insert("a", a);
789 edges.insert("b", b);
790 edges.insert("c", c);
791 ("cooperativeMultiplyAdd".into(), 4)
792 }
793 };
794
795 let color_attr = match info {
797 Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
798 _ => "color",
799 };
800 writeln!(
801 output,
802 "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]",
803 prefix,
804 Prefixed(handle),
805 color_attr,
806 COLORS[color_id],
807 handle,
808 label,
809 )?;
810
811 for (key, edge) in edges.drain() {
812 writeln!(
813 output,
814 "\t\t{}_{} -> {}_{} [ label=\"{}\" ]",
815 prefix,
816 Prefixed(edge),
817 prefix,
818 Prefixed(handle),
819 key,
820 )?;
821 }
822 match payload.take() {
823 Some(Payload::Arguments(list)) => {
824 write!(output, "\t\t{{")?;
825 for &comp in list {
826 write!(output, " {}_{}", prefix, Prefixed(comp))?;
827 }
828 writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?;
829 }
830 Some(Payload::Local(h)) => {
831 writeln!(
832 output,
833 "\t\t{}_{} -> {}_{}",
834 prefix,
835 Prefixed(h),
836 prefix,
837 Prefixed(handle),
838 )?;
839 }
840 Some(Payload::Global(h)) => {
841 writeln!(
842 output,
843 "\t\t{} -> {}_{} [fillcolor=gray]",
844 Prefixed(h),
845 prefix,
846 Prefixed(handle),
847 )?;
848 }
849 None => {}
850 }
851 }
852
853 Ok(())
854}
855
856pub fn write(
858 module: &crate::Module,
859 mod_info: Option<&ModuleInfo>,
860 options: Options,
861) -> Result<String, FmtError> {
862 use core::fmt::Write as _;
863
864 let mut output = String::new();
865 output += "digraph Module {\n";
866
867 if !options.cfg_only {
868 writeln!(output, "\tsubgraph cluster_globals {{")?;
869 writeln!(output, "\t\tlabel=\"Globals\"")?;
870 for (handle, var) in module.global_variables.iter() {
871 writeln!(
872 output,
873 "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
874 Prefixed(handle),
875 handle,
876 var.space,
877 name(&var.name),
878 )?;
879 }
880 writeln!(output, "\t}}")?;
881 }
882
883 for (handle, fun) in module.functions.iter() {
884 let prefix = Prefixed(handle).to_string();
885 writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
886 writeln!(
887 output,
888 "\t\tlabel=\"Function{:?}/'{}'\"",
889 handle,
890 name(&fun.name)
891 )?;
892 let info = mod_info.map(|a| &a[handle]);
893 write_fun(&mut output, prefix, fun, info, &options)?;
894 writeln!(output, "\t}}")?;
895 }
896 for (ep_index, ep) in module.entry_points.iter().enumerate() {
897 let prefix = format!("ep{ep_index}");
898 writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
899 writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
900 let info = mod_info.map(|a| a.get_entry_point(ep_index));
901 write_fun(&mut output, prefix, &ep.function, info, &options)?;
902 writeln!(output, "\t}}")?;
903 }
904
905 output += "}\n";
906 Ok(output)
907}