1use std::fmt;
4
5use crate::Module;
6use crate::arena::{Handle, UniqueArena};
7use crate::expr::{
8 AtomicFunction, BinaryOp, Expression, Literal, MathFunction, SwizzleComponent, UnaryOp,
9};
10use crate::global::{AddressSpace, Binding, BuiltIn, ResourceBinding, StorageAccess};
11use crate::stmt::{Barrier, Statement};
12use crate::types::{ArraySize, Scalar, ScalarKind, Type, TypeInner, VectorSize};
13
14impl fmt::Display for ScalarKind {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 Self::Bool => write!(f, "bool"),
18 Self::Sint => write!(f, "sint"),
19 Self::Uint => write!(f, "uint"),
20 Self::Float => write!(f, "float"),
21 Self::BFloat => write!(f, "bfloat"),
22 }
23 }
24}
25
26impl fmt::Display for Scalar {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self.kind {
29 ScalarKind::Bool => write!(f, "bool"),
30 ScalarKind::Sint => write!(f, "i{}", self.width * 8),
31 ScalarKind::Uint => write!(f, "u{}", self.width * 8),
32 ScalarKind::Float => write!(f, "f{}", self.width * 8),
33 ScalarKind::BFloat => write!(f, "bf{}", self.width * 8),
34 }
35 }
36}
37
38impl fmt::Display for VectorSize {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", *self as u32)
41 }
42}
43
44impl fmt::Display for StorageAccess {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 let has_load = self.contains(StorageAccess::LOAD);
47 let has_store = self.contains(StorageAccess::STORE);
48 match (has_load, has_store) {
49 (true, true) => write!(f, "read_write"),
50 (true, false) => write!(f, "read"),
51 (false, true) => write!(f, "write"),
52 (false, false) => write!(f, "none"),
53 }
54 }
55}
56
57impl fmt::Display for AddressSpace {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Self::Function => write!(f, "function"),
61 Self::Private => write!(f, "private"),
62 Self::Workgroup => write!(f, "workgroup"),
63 Self::Uniform => write!(f, "uniform"),
64 Self::Storage { access } => write!(f, "storage, {access}"),
65 }
66 }
67}
68
69impl fmt::Display for BuiltIn {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::GlobalInvocationId => write!(f, "global_invocation_id"),
73 Self::LocalInvocationId => write!(f, "local_invocation_id"),
74 Self::LocalInvocationIndex => write!(f, "local_invocation_index"),
75 Self::WorkgroupId => write!(f, "workgroup_id"),
76 Self::NumWorkgroups => write!(f, "num_workgroups"),
77 }
78 }
79}
80
81impl fmt::Display for Binding {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::BuiltIn(b) => write!(f, "@builtin({b})"),
85 Self::Location { location } => write!(f, "@location({location})"),
86 }
87 }
88}
89
90impl fmt::Display for ResourceBinding {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "@group({}) @binding({})", self.group, self.binding)
93 }
94}
95
96impl fmt::Display for Literal {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 Self::Bool(v) => write!(f, "{v}"),
100 Self::I32(v) => write!(f, "{v}i"),
101 Self::U32(v) => write!(f, "{v}u"),
102 Self::F32(v) => write!(f, "{v}f"),
103 Self::F64(v) => write!(f, "{v}lf"),
104 Self::AbstractInt(v) => write!(f, "{v}"),
105 Self::AbstractFloat(v) => write!(f, "{v}"),
106 }
107 }
108}
109
110impl fmt::Display for UnaryOp {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::Negate => write!(f, "-"),
114 Self::LogicalNot => write!(f, "!"),
115 Self::BitwiseNot => write!(f, "~"),
116 }
117 }
118}
119
120impl fmt::Display for BinaryOp {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 match self {
123 Self::Add => write!(f, "+"),
124 Self::Subtract => write!(f, "-"),
125 Self::Multiply => write!(f, "*"),
126 Self::Divide => write!(f, "/"),
127 Self::Modulo => write!(f, "%"),
128 Self::Equal => write!(f, "=="),
129 Self::NotEqual => write!(f, "!="),
130 Self::Less => write!(f, "<"),
131 Self::LessEqual => write!(f, "<="),
132 Self::Greater => write!(f, ">"),
133 Self::GreaterEqual => write!(f, ">="),
134 Self::LogicalAnd => write!(f, "&&"),
135 Self::LogicalOr => write!(f, "||"),
136 Self::BitwiseAnd => write!(f, "&"),
137 Self::BitwiseOr => write!(f, "|"),
138 Self::BitwiseXor => write!(f, "^"),
139 Self::ShiftLeft => write!(f, "<<"),
140 Self::ShiftRight => write!(f, ">>"),
141 }
142 }
143}
144
145impl fmt::Display for MathFunction {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 let name = match self {
148 Self::Abs => "abs",
149 Self::Min => "min",
150 Self::Max => "max",
151 Self::Clamp => "clamp",
152 Self::Saturate => "saturate",
153 Self::Floor => "floor",
154 Self::Ceil => "ceil",
155 Self::Round => "round",
156 Self::Fract => "fract",
157 Self::Trunc => "trunc",
158 Self::Sin => "sin",
159 Self::Cos => "cos",
160 Self::Tan => "tan",
161 Self::Asin => "asin",
162 Self::Acos => "acos",
163 Self::Atan => "atan",
164 Self::Atan2 => "atan2",
165 Self::Sinh => "sinh",
166 Self::Cosh => "cosh",
167 Self::Tanh => "tanh",
168 Self::Sqrt => "sqrt",
169 Self::InverseSqrt => "inverseSqrt",
170 Self::Log => "log",
171 Self::Log2 => "log2",
172 Self::Exp => "exp",
173 Self::Exp2 => "exp2",
174 Self::Pow => "pow",
175 Self::Dot => "dot",
176 Self::Cross => "cross",
177 Self::Normalize => "normalize",
178 Self::Length => "length",
179 Self::Distance => "distance",
180 Self::Mix => "mix",
181 Self::Step => "step",
182 Self::SmoothStep => "smoothStep",
183 Self::Fma => "fma",
184 Self::ExtractBits => "extractBits",
185 Self::InsertBits => "insertBits",
186 };
187 write!(f, "{name}")
188 }
189}
190
191impl fmt::Display for Barrier {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let storage = self.contains(Barrier::STORAGE);
194 let workgroup = self.contains(Barrier::WORKGROUP);
195 match (storage, workgroup) {
196 (true, true) => write!(f, "storageBarrier | workgroupBarrier"),
197 (true, false) => write!(f, "storageBarrier"),
198 (false, true) => write!(f, "workgroupBarrier"),
199 (false, false) => write!(f, "<no barrier>"),
200 }
201 }
202}
203
204impl fmt::Display for SwizzleComponent {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 match self {
207 Self::X => write!(f, "x"),
208 Self::Y => write!(f, "y"),
209 Self::Z => write!(f, "z"),
210 Self::W => write!(f, "w"),
211 }
212 }
213}
214
215impl fmt::Display for AtomicFunction {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 match self {
218 Self::Add => write!(f, "atomicAdd"),
219 Self::Subtract => write!(f, "atomicSub"),
220 Self::And => write!(f, "atomicAnd"),
221 Self::ExclusiveOr => write!(f, "atomicXor"),
222 Self::InclusiveOr => write!(f, "atomicOr"),
223 Self::Min => write!(f, "atomicMin"),
224 Self::Max => write!(f, "atomicMax"),
225 Self::Exchange { compare: None } => write!(f, "atomicExchange"),
226 Self::Exchange { compare: Some(c) } => write!(f, "atomicCompareExchange({c:?})"),
227 }
228 }
229}
230
231pub fn format_type(ty: &Type, types: &UniqueArena<Type>) -> String {
233 if let Some(ref name) = ty.name {
234 return name.clone();
235 }
236 format_type_inner(&ty.inner, types)
237}
238
239pub fn format_type_inner(inner: &TypeInner, types: &UniqueArena<Type>) -> String {
241 match inner {
242 TypeInner::Scalar(s) => format!("{s}"),
243 TypeInner::Vector { size, scalar } => format!("vec{size}<{scalar}>"),
244 TypeInner::Matrix {
245 columns,
246 rows,
247 scalar,
248 } => format!("mat{columns}x{rows}<{scalar}>"),
249 TypeInner::Atomic(s) => format!("atomic<{s}>"),
250 TypeInner::Pointer { base, space } => {
251 let base_str = format_type(&types[*base], types);
252 format!("ptr<{space}, {base_str}>")
253 }
254 TypeInner::Array { base, size, stride } => {
255 let base_str = format_type(&types[*base], types);
256 match size {
257 ArraySize::Constant(n) => format!("array<{base_str}, {n}> /*stride {stride}*/"),
258 ArraySize::Dynamic => format!("array<{base_str}> /*stride {stride}*/"),
259 }
260 }
261 TypeInner::Struct { members, span } => {
262 format!("struct({} members, span {span})", members.len())
263 }
264 TypeInner::Tensor { scalar, shape } => {
265 let dims: Vec<String> = shape
266 .dims
267 .iter()
268 .map(|d| match d {
269 crate::Dimension::Fixed(n) => n.to_string(),
270 crate::Dimension::Symbolic(name) => name.clone(),
271 crate::Dimension::Dynamic(Some(name)) => name.clone(),
272 crate::Dimension::Dynamic(None) => "?".into(),
273 })
274 .collect();
275 format!("tensor<{scalar}>[{}]", dims.join(", "))
276 }
277 }
278}
279
280fn format_expr(handle: Handle<Expression>, exprs: &crate::Arena<Expression>) -> String {
281 match &exprs[handle] {
282 Expression::Literal(lit) => format!("{lit}"),
283 Expression::Compose { ty, components } => {
284 let args: Vec<_> = components.iter().map(|h| format!("{h:?}")).collect();
285 format!("Compose({ty:?}, [{}])", args.join(", "))
286 }
287 Expression::FunctionArgument(i) => format!("FunctionArgument({i})"),
288 Expression::GlobalVariable(h) => format!("GlobalVariable({h:?})"),
289 Expression::LocalVariable(h) => format!("LocalVariable({h:?})"),
290 Expression::Load { pointer } => format!("Load({pointer:?})"),
291 Expression::Access { base, index } => format!("Access({base:?}, {index:?})"),
292 Expression::AccessIndex { base, index } => format!("AccessIndex({base:?}, {index})"),
293 Expression::Swizzle {
294 size,
295 vector,
296 pattern,
297 } => {
298 let n = *size as usize;
299 let comps: Vec<_> = pattern[..n].iter().map(|c| format!("{c}")).collect();
300 format!("Swizzle({vector:?}).{}", comps.join(""))
301 }
302 Expression::Splat { size, value } => format!("Splat({value:?}, vec{size})"),
303 Expression::Unary { op, expr } => format!("{op}{expr:?}"),
304 Expression::Binary { op, left, right } => format!("{left:?} {op} {right:?}"),
305 Expression::Select {
306 condition,
307 accept,
308 reject,
309 } => format!("Select({condition:?}, {accept:?}, {reject:?})"),
310 Expression::Math {
311 fun,
312 arg,
313 arg1,
314 arg2,
315 arg3,
316 } => {
317 let mut args = format!("{arg:?}");
318 if let Some(a1) = arg1 {
319 args += &format!(", {a1:?}");
320 }
321 if let Some(a2) = arg2 {
322 args += &format!(", {a2:?}");
323 }
324 if let Some(a3) = arg3 {
325 args += &format!(", {a3:?}");
326 }
327 format!("{fun}({args})")
328 }
329 Expression::As {
330 expr,
331 kind,
332 convert,
333 } => match convert {
334 Some(w) => format!("As({expr:?} -> {kind}/{w})"),
335 None => format!("Bitcast({expr:?} -> {kind})"),
336 },
337 Expression::ArrayLength(expr) => format!("ArrayLength({expr:?})"),
338 Expression::CallResult(f) => format!("CallResult({f:?})"),
339 Expression::AtomicResult { ty, comparison } => {
340 format!("AtomicResult({ty:?}, cmp={comparison})")
341 }
342 Expression::ZeroValue(ty) => format!("ZeroValue({ty:?})"),
343 }
344}
345
346fn write_stmt(out: &mut String, stmt: &Statement, indent: usize) {
347 let pad = " ".repeat(indent);
348 match stmt {
349 Statement::Emit(range) => {
350 out.push_str(&format!("{pad}Emit({range:?})\n"));
351 }
352 Statement::Store { pointer, value } => {
353 out.push_str(&format!("{pad}Store {pointer:?} = {value:?}\n"));
354 }
355 Statement::If {
356 condition,
357 accept,
358 reject,
359 } => {
360 out.push_str(&format!("{pad}If ({condition:?}) {{\n"));
361 for s in accept {
362 write_stmt(out, s, indent + 4);
363 }
364 if !reject.is_empty() {
365 out.push_str(&format!("{pad}}} else {{\n"));
366 for s in reject {
367 write_stmt(out, s, indent + 4);
368 }
369 }
370 out.push_str(&format!("{pad}}}\n"));
371 }
372 Statement::Loop {
373 body,
374 continuing,
375 break_if,
376 } => {
377 out.push_str(&format!("{pad}Loop {{\n"));
378 for s in body {
379 write_stmt(out, s, indent + 4);
380 }
381 if !continuing.is_empty() {
382 out.push_str(&format!("{pad} Continuing {{\n"));
383 for s in continuing {
384 write_stmt(out, s, indent + 8);
385 }
386 if let Some(brk) = break_if {
387 out.push_str(&format!("{pad} BreakIf({brk:?})\n"));
388 }
389 out.push_str(&format!("{pad} }}\n"));
390 }
391 out.push_str(&format!("{pad}}}\n"));
392 }
393 Statement::Call {
394 function,
395 arguments,
396 result,
397 } => {
398 let args: Vec<_> = arguments.iter().map(|h| format!("{h:?}")).collect();
399 let res = match result {
400 Some(r) => format!(" -> {r:?}"),
401 None => String::new(),
402 };
403 out.push_str(&format!(
404 "{pad}Call {function:?}({}){res}\n",
405 args.join(", ")
406 ));
407 }
408 Statement::Atomic {
409 pointer,
410 fun,
411 value,
412 result,
413 } => {
414 let res = match result {
415 Some(r) => format!(" -> {r:?}"),
416 None => String::new(),
417 };
418 out.push_str(&format!("{pad}{fun}({pointer:?}, {value:?}){res}\n"));
419 }
420 Statement::Break => {
421 out.push_str(&format!("{pad}Break\n"));
422 }
423 Statement::Continue => {
424 out.push_str(&format!("{pad}Continue\n"));
425 }
426 Statement::Return { value } => match value {
427 Some(v) => out.push_str(&format!("{pad}Return {v:?}\n")),
428 None => out.push_str(&format!("{pad}Return\n")),
429 },
430 Statement::Barrier(b) => {
431 out.push_str(&format!("{pad}Barrier({b})\n"));
432 }
433 }
434}
435
436pub fn dump_module(module: &Module) -> String {
438 let mut out = String::new();
439
440 out.push_str("Types:\n");
442 for (handle, ty) in module.types.iter() {
443 let formatted = format_type(ty, &module.types);
444 out.push_str(&format!(" {handle:?} {formatted}\n"));
445 }
446
447 if !module.global_variables.is_empty() {
449 out.push_str("\nGlobal Variables:\n");
450 for (handle, var) in module.global_variables.iter() {
451 let name = var.name.as_deref().unwrap_or("_");
452 let ty_str = format_type(&module.types[var.ty], &module.types);
453 let binding_str = match &var.binding {
454 Some(b) => format!("{b} "),
455 None => String::new(),
456 };
457 out.push_str(&format!(
458 " {handle:?} {binding_str}var<{}> {name}: {ty_str}\n",
459 var.space
460 ));
461 }
462 }
463
464 if !module.global_expressions.is_empty() {
466 out.push_str("\nGlobal Expressions:\n");
467 for (handle, _) in module.global_expressions.iter() {
468 let formatted = format_expr(handle, &module.global_expressions);
469 out.push_str(&format!(" {handle:?} {formatted}\n"));
470 }
471 }
472
473 if !module.functions.is_empty() {
475 out.push_str("\nFunctions:\n");
476 for (handle, func) in module.functions.iter() {
477 dump_function(&mut out, &format!("{handle:?}"), func, &module.types);
478 }
479 }
480
481 if !module.entry_points.is_empty() {
483 out.push_str("\nEntry Points:\n");
484 for ep in &module.entry_points {
485 let [x, y, z] = ep.workgroup_size;
486 out.push_str(&format!(" @compute @workgroup_size({x}, {y}, {z})\n"));
487 dump_function(&mut out, &ep.name, &ep.function, &module.types);
488 }
489 }
490
491 out
492}
493
494fn dump_function(out: &mut String, label: &str, func: &crate::Function, types: &UniqueArena<Type>) {
495 let name = func.name.as_deref().unwrap_or("_");
496
497 let args: Vec<_> = func
499 .arguments
500 .iter()
501 .map(|arg| {
502 let arg_name = arg.name.as_deref().unwrap_or("_");
503 let ty_str = format_type(&types[arg.ty], types);
504 let binding = match &arg.binding {
505 Some(b) => format!("{b} "),
506 None => String::new(),
507 };
508 format!("{binding}{arg_name}: {ty_str}")
509 })
510 .collect();
511 let ret = match &func.result {
512 Some(r) => format!(" -> {}", format_type(&types[r.ty], types)),
513 None => String::new(),
514 };
515 out.push_str(&format!(
516 " fn {name}({}) [{label}]{ret} {{\n",
517 args.join(", ")
518 ));
519
520 for (handle, var) in func.local_variables.iter() {
522 let var_name = var.name.as_deref().unwrap_or("_");
523 let ty_str = format_type(&types[var.ty], types);
524 let init = match var.init {
525 Some(h) => format!(" = {}", format_expr(h, &func.expressions)),
526 None => String::new(),
527 };
528 out.push_str(&format!(" var {handle:?} {var_name}: {ty_str}{init}\n"));
529 }
530
531 if !func.expressions.is_empty() {
533 out.push_str(" Expressions:\n");
534 for (handle, _) in func.expressions.iter() {
535 let formatted = format_expr(handle, &func.expressions);
536 out.push_str(&format!(" {handle:?} {formatted}\n"));
537 }
538 }
539
540 if !func.body.is_empty() {
542 out.push_str(" Body:\n");
543 for stmt in &func.body {
544 write_stmt(out, stmt, 6);
545 }
546 }
547
548 out.push_str(" }\n");
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn display_scalar() {
557 assert_eq!(format!("{}", Scalar::F32), "f32");
558 assert_eq!(format!("{}", Scalar::I32), "i32");
559 assert_eq!(format!("{}", Scalar::U32), "u32");
560 assert_eq!(format!("{}", Scalar::F16), "f16");
561 assert_eq!(format!("{}", Scalar::BOOL), "bool");
562 }
563
564 #[test]
565 fn display_address_space() {
566 assert_eq!(format!("{}", AddressSpace::Uniform), "uniform");
567 assert_eq!(format!("{}", AddressSpace::Workgroup), "workgroup");
568 assert_eq!(
569 format!(
570 "{}",
571 AddressSpace::Storage {
572 access: StorageAccess::LOAD | StorageAccess::STORE
573 }
574 ),
575 "storage, read_write"
576 );
577 }
578
579 #[test]
580 fn display_literal() {
581 assert_eq!(format!("{}", Literal::F32(3.125)), "3.125f");
582 assert_eq!(format!("{}", Literal::U32(42)), "42u");
583 assert_eq!(format!("{}", Literal::Bool(true)), "true");
584 }
585
586 #[test]
587 fn display_binary_op() {
588 assert_eq!(format!("{}", BinaryOp::Add), "+");
589 assert_eq!(format!("{}", BinaryOp::Equal), "==");
590 assert_eq!(format!("{}", BinaryOp::ShiftLeft), "<<");
591 }
592
593 #[test]
594 fn display_math_function() {
595 assert_eq!(format!("{}", MathFunction::Dot), "dot");
596 assert_eq!(format!("{}", MathFunction::Normalize), "normalize");
597 }
598
599 #[test]
600 fn display_binding() {
601 let b = Binding::BuiltIn(BuiltIn::GlobalInvocationId);
602 assert_eq!(format!("{b}"), "@builtin(global_invocation_id)");
603 }
604
605 #[test]
606 fn display_resource_binding() {
607 let rb = ResourceBinding {
608 group: 0,
609 binding: 2,
610 };
611 assert_eq!(format!("{rb}"), "@group(0) @binding(2)");
612 }
613
614 #[test]
615 fn dump_empty_module() {
616 let module = Module::default();
617 let dump = dump_module(&module);
618 assert!(dump.contains("Types:"));
619 }
620
621 #[test]
622 fn display_scalar_kind_all_variants() {
623 assert_eq!(format!("{}", ScalarKind::Bool), "bool");
624 assert_eq!(format!("{}", ScalarKind::Sint), "sint");
625 assert_eq!(format!("{}", ScalarKind::Uint), "uint");
626 assert_eq!(format!("{}", ScalarKind::Float), "float");
627 assert_eq!(format!("{}", ScalarKind::BFloat), "bfloat");
628 }
629
630 #[test]
631 fn display_scalar_bfloat() {
632 assert_eq!(format!("{}", Scalar::BF16), "bf16");
633 }
634
635 #[test]
636 fn display_storage_access_all_variants() {
637 assert_eq!(format!("{}", StorageAccess::LOAD), "read");
638 assert_eq!(format!("{}", StorageAccess::STORE), "write");
639 assert_eq!(
640 format!("{}", StorageAccess::LOAD | StorageAccess::STORE),
641 "read_write"
642 );
643 assert_eq!(format!("{}", StorageAccess::EMPTY), "none");
644 }
645
646 #[test]
647 fn display_address_space_all_variants() {
648 assert_eq!(format!("{}", AddressSpace::Function), "function");
649 assert_eq!(format!("{}", AddressSpace::Private), "private");
650 assert_eq!(format!("{}", AddressSpace::Workgroup), "workgroup");
651 assert_eq!(format!("{}", AddressSpace::Uniform), "uniform");
652 assert_eq!(
653 format!(
654 "{}",
655 AddressSpace::Storage {
656 access: StorageAccess::LOAD
657 }
658 ),
659 "storage, read"
660 );
661 }
662
663 #[test]
664 fn display_builtin_all_variants() {
665 assert_eq!(
666 format!("{}", BuiltIn::GlobalInvocationId),
667 "global_invocation_id"
668 );
669 assert_eq!(
670 format!("{}", BuiltIn::LocalInvocationId),
671 "local_invocation_id"
672 );
673 assert_eq!(
674 format!("{}", BuiltIn::LocalInvocationIndex),
675 "local_invocation_index"
676 );
677 assert_eq!(format!("{}", BuiltIn::WorkgroupId), "workgroup_id");
678 assert_eq!(format!("{}", BuiltIn::NumWorkgroups), "num_workgroups");
679 }
680
681 #[test]
682 fn display_binding_location() {
683 let b = Binding::Location { location: 3 };
684 assert_eq!(format!("{b}"), "@location(3)");
685 }
686
687 #[test]
688 fn display_literal_all_variants() {
689 assert_eq!(format!("{}", Literal::Bool(false)), "false");
690 assert_eq!(format!("{}", Literal::I32(-7)), "-7i");
691 assert_eq!(format!("{}", Literal::U32(42)), "42u");
692 assert_eq!(format!("{}", Literal::F32(1.5)), "1.5f");
693 assert_eq!(format!("{}", Literal::F64(2.5)), "2.5lf");
694 assert_eq!(format!("{}", Literal::AbstractInt(99)), "99");
695 assert_eq!(format!("{}", Literal::AbstractFloat(1.23)), "1.23");
696 }
697
698 #[test]
699 fn display_unary_op_all_variants() {
700 assert_eq!(format!("{}", UnaryOp::Negate), "-");
701 assert_eq!(format!("{}", UnaryOp::LogicalNot), "!");
702 assert_eq!(format!("{}", UnaryOp::BitwiseNot), "~");
703 }
704
705 #[test]
706 fn display_binary_op_all_variants() {
707 assert_eq!(format!("{}", BinaryOp::Add), "+");
708 assert_eq!(format!("{}", BinaryOp::Subtract), "-");
709 assert_eq!(format!("{}", BinaryOp::Multiply), "*");
710 assert_eq!(format!("{}", BinaryOp::Divide), "/");
711 assert_eq!(format!("{}", BinaryOp::Modulo), "%");
712 assert_eq!(format!("{}", BinaryOp::Equal), "==");
713 assert_eq!(format!("{}", BinaryOp::NotEqual), "!=");
714 assert_eq!(format!("{}", BinaryOp::Less), "<");
715 assert_eq!(format!("{}", BinaryOp::LessEqual), "<=");
716 assert_eq!(format!("{}", BinaryOp::Greater), ">");
717 assert_eq!(format!("{}", BinaryOp::GreaterEqual), ">=");
718 assert_eq!(format!("{}", BinaryOp::LogicalAnd), "&&");
719 assert_eq!(format!("{}", BinaryOp::LogicalOr), "||");
720 assert_eq!(format!("{}", BinaryOp::BitwiseAnd), "&");
721 assert_eq!(format!("{}", BinaryOp::BitwiseOr), "|");
722 assert_eq!(format!("{}", BinaryOp::BitwiseXor), "^");
723 assert_eq!(format!("{}", BinaryOp::ShiftLeft), "<<");
724 assert_eq!(format!("{}", BinaryOp::ShiftRight), ">>");
725 }
726
727 #[test]
728 fn display_swizzle_component_all_variants() {
729 assert_eq!(format!("{}", SwizzleComponent::X), "x");
730 assert_eq!(format!("{}", SwizzleComponent::Y), "y");
731 assert_eq!(format!("{}", SwizzleComponent::Z), "z");
732 assert_eq!(format!("{}", SwizzleComponent::W), "w");
733 }
734
735 #[test]
736 fn display_atomic_function_all_variants() {
737 assert_eq!(format!("{}", AtomicFunction::Add), "atomicAdd");
738 assert_eq!(format!("{}", AtomicFunction::Subtract), "atomicSub");
739 assert_eq!(format!("{}", AtomicFunction::And), "atomicAnd");
740 assert_eq!(format!("{}", AtomicFunction::ExclusiveOr), "atomicXor");
741 assert_eq!(format!("{}", AtomicFunction::InclusiveOr), "atomicOr");
742 assert_eq!(format!("{}", AtomicFunction::Min), "atomicMin");
743 assert_eq!(format!("{}", AtomicFunction::Max), "atomicMax");
744 assert_eq!(
745 format!("{}", AtomicFunction::Exchange { compare: None }),
746 "atomicExchange"
747 );
748 let cmp_handle = {
749 let mut arena = crate::Arena::new();
750 arena.append(Expression::Literal(Literal::U32(0)))
751 };
752 assert!(
753 format!(
754 "{}",
755 AtomicFunction::Exchange {
756 compare: Some(cmp_handle)
757 }
758 )
759 .starts_with("atomicCompareExchange(")
760 );
761 }
762
763 #[test]
764 fn display_barrier_all_variants() {
765 assert_eq!(format!("{}", Barrier::STORAGE), "storageBarrier");
766 assert_eq!(format!("{}", Barrier::WORKGROUP), "workgroupBarrier");
767 assert_eq!(
768 format!("{}", Barrier::STORAGE | Barrier::WORKGROUP),
769 "storageBarrier | workgroupBarrier"
770 );
771 assert_eq!(format!("{}", Barrier::EMPTY), "<no barrier>");
772 }
773
774 #[test]
775 fn format_type_named() {
776 let mut types = UniqueArena::new();
777 let h = types.insert(Type {
778 name: Some("MyStruct".into()),
779 inner: TypeInner::Scalar(Scalar::F32),
780 });
781 assert_eq!(format_type(&types[h], &types), "MyStruct");
782 }
783
784 #[test]
785 fn format_type_inner_all_variants() {
786 let mut types = UniqueArena::new();
787 let f32_ty = types.insert(Type {
788 name: None,
789 inner: TypeInner::Scalar(Scalar::F32),
790 });
791
792 assert_eq!(
794 format_type_inner(&TypeInner::Scalar(Scalar::F32), &types),
795 "f32"
796 );
797
798 assert_eq!(
800 format_type_inner(
801 &TypeInner::Vector {
802 size: VectorSize::Tri,
803 scalar: Scalar::F32
804 },
805 &types
806 ),
807 "vec3<f32>"
808 );
809
810 assert_eq!(
812 format_type_inner(
813 &TypeInner::Matrix {
814 columns: VectorSize::Quad,
815 rows: VectorSize::Quad,
816 scalar: Scalar::F32
817 },
818 &types
819 ),
820 "mat4x4<f32>"
821 );
822
823 assert_eq!(
825 format_type_inner(&TypeInner::Atomic(Scalar::U32), &types),
826 "atomic<u32>"
827 );
828
829 assert_eq!(
831 format_type_inner(
832 &TypeInner::Pointer {
833 base: f32_ty,
834 space: AddressSpace::Function
835 },
836 &types
837 ),
838 "ptr<function, f32>"
839 );
840
841 assert_eq!(
843 format_type_inner(
844 &TypeInner::Array {
845 base: f32_ty,
846 size: ArraySize::Constant(16),
847 stride: 4
848 },
849 &types
850 ),
851 "array<f32, 16> /*stride 4*/"
852 );
853
854 assert_eq!(
856 format_type_inner(
857 &TypeInner::Array {
858 base: f32_ty,
859 size: ArraySize::Dynamic,
860 stride: 4
861 },
862 &types
863 ),
864 "array<f32> /*stride 4*/"
865 );
866
867 assert_eq!(
869 format_type_inner(
870 &TypeInner::Struct {
871 members: vec![],
872 span: 16
873 },
874 &types
875 ),
876 "struct(0 members, span 16)"
877 );
878
879 assert_eq!(
881 format_type_inner(
882 &TypeInner::Tensor {
883 scalar: Scalar::F32,
884 shape: crate::TensorShape {
885 dims: vec![
886 crate::Dimension::Fixed(224),
887 crate::Dimension::Dynamic(Some("batch".into())),
888 crate::Dimension::Dynamic(None),
889 ],
890 }
891 },
892 &types
893 ),
894 "tensor<f32>[224, batch, ?]"
895 );
896
897 assert_eq!(
899 format_type_inner(
900 &TypeInner::Tensor {
901 scalar: Scalar::F32,
902 shape: crate::TensorShape {
903 dims: vec![
904 crate::Dimension::Symbolic("batch".into()),
905 crate::Dimension::Fixed(224),
906 crate::Dimension::Fixed(224),
907 crate::Dimension::Fixed(3),
908 ],
909 }
910 },
911 &types
912 ),
913 "tensor<f32>[batch, 224, 224, 3]"
914 );
915 }
916
917 #[test]
918 fn dump_module_with_globals_and_entry_point() {
919 use crate::{EntryPoint, Expression, Function, GlobalVariable, ResourceBinding, Statement};
920
921 let mut module = Module::default();
922
923 let f32_ty = module.types.insert(Type {
924 name: None,
925 inner: TypeInner::Scalar(Scalar::F32),
926 });
927 let arr_ty = module.types.insert(Type {
928 name: None,
929 inner: TypeInner::Array {
930 base: f32_ty,
931 size: ArraySize::Dynamic,
932 stride: 4,
933 },
934 });
935
936 module.global_variables.append(GlobalVariable {
937 name: Some("a".into()),
938 space: AddressSpace::Storage {
939 access: StorageAccess::LOAD,
940 },
941 binding: Some(ResourceBinding {
942 group: 0,
943 binding: 0,
944 }),
945 ty: arr_ty,
946 init: None,
947 layout: None,
948 });
949
950 module.global_variables.append(GlobalVariable {
951 name: None,
952 space: AddressSpace::Private,
953 binding: None,
954 ty: f32_ty,
955 init: None,
956 layout: None,
957 });
958
959 let mut func = Function::new("main");
960 let lit = func
961 .expressions
962 .append(Expression::Literal(Literal::F32(1.0)));
963 let gv = func.expressions.append(Expression::GlobalVariable(
964 module.global_variables.next_handle(),
965 ));
966 func.body.push(Statement::Store {
967 pointer: gv,
968 value: lit,
969 });
970
971 module.entry_points.push(EntryPoint {
972 name: "main".into(),
973 workgroup_size: [256, 1, 1],
974 function: func,
975 });
976
977 let dump = dump_module(&module);
978 assert!(dump.contains("Global Variables:"));
979 assert!(dump.contains("@group(0) @binding(0)"));
980 assert!(dump.contains("a:"));
981 assert!(dump.contains("storage, read"));
982 assert!(dump.contains("Entry Points:"));
983 assert!(dump.contains("@compute @workgroup_size(256, 1, 1)"));
984 assert!(dump.contains("Expressions:"));
985 assert!(dump.contains("Body:"));
986 assert!(dump.contains("Store"));
987 }
988
989 #[test]
990 fn dump_module_with_if_and_loop() {
991 use crate::{EntryPoint, Expression, Function, Statement};
992
993 let mut module = Module::default();
994 let mut func = Function::new("main");
995
996 let cond = func
997 .expressions
998 .append(Expression::Literal(Literal::Bool(true)));
999 let val = func
1000 .expressions
1001 .append(Expression::Literal(Literal::F32(1.0)));
1002
1003 func.body.push(Statement::If {
1004 condition: cond,
1005 accept: vec![Statement::Return { value: Some(val) }],
1006 reject: vec![Statement::Return { value: None }],
1007 });
1008 func.body.push(Statement::Loop {
1009 body: vec![Statement::Break],
1010 continuing: vec![Statement::Continue],
1011 break_if: Some(cond),
1012 });
1013 func.body.push(Statement::Barrier(Barrier::STORAGE));
1014
1015 module.entry_points.push(EntryPoint {
1016 name: "main".into(),
1017 workgroup_size: [1, 1, 1],
1018 function: func,
1019 });
1020
1021 let dump = dump_module(&module);
1022 assert!(dump.contains("If ("));
1023 assert!(dump.contains("} else {"));
1024 assert!(dump.contains("Return"));
1025 assert!(dump.contains("Loop {"));
1026 assert!(dump.contains("Continuing {"));
1027 assert!(dump.contains("BreakIf("));
1028 assert!(dump.contains("Break"));
1029 assert!(dump.contains("Continue"));
1030 assert!(dump.contains("Barrier(storageBarrier)"));
1031 }
1032
1033 #[test]
1034 fn dump_module_with_helper_function() {
1035 use crate::{Function, FunctionArgument, FunctionResult};
1036
1037 let mut module = Module::default();
1038 let f32_ty = module.types.insert(Type {
1039 name: None,
1040 inner: TypeInner::Scalar(Scalar::F32),
1041 });
1042
1043 let mut func = Function::new("helper");
1044 func.arguments.push(FunctionArgument {
1045 name: Some("x".into()),
1046 ty: f32_ty,
1047 binding: None,
1048 });
1049 func.result = Some(FunctionResult {
1050 ty: f32_ty,
1051 binding: None,
1052 });
1053 module.functions.append(func);
1054
1055 let dump = dump_module(&module);
1056 assert!(dump.contains("Functions:"));
1057 assert!(dump.contains("fn helper(x: f32)"));
1058 assert!(dump.contains("-> f32"));
1059 }
1060
1061 #[test]
1062 fn display_vector_size() {
1063 assert_eq!(format!("{}", VectorSize::Bi), "2");
1064 assert_eq!(format!("{}", VectorSize::Tri), "3");
1065 assert_eq!(format!("{}", VectorSize::Quad), "4");
1066 }
1067
1068 #[test]
1069 fn display_math_function_all_variants() {
1070 assert_eq!(format!("{}", MathFunction::Abs), "abs");
1072 assert_eq!(format!("{}", MathFunction::Clamp), "clamp");
1073 assert_eq!(format!("{}", MathFunction::Fma), "fma");
1074 assert_eq!(format!("{}", MathFunction::Sin), "sin");
1075 assert_eq!(format!("{}", MathFunction::Tanh), "tanh");
1076 assert_eq!(format!("{}", MathFunction::Sqrt), "sqrt");
1077 assert_eq!(format!("{}", MathFunction::Pow), "pow");
1078 assert_eq!(format!("{}", MathFunction::Mix), "mix");
1079 assert_eq!(format!("{}", MathFunction::SmoothStep), "smoothStep");
1080 assert_eq!(format!("{}", MathFunction::InverseSqrt), "inverseSqrt");
1081 }
1082}
1083
1084#[cfg(test)]
1085mod math_function_display_tests {
1086 use super::*;
1087
1088 #[test]
1089 fn bit_manipulation_prints_as_wgsl_spells_it() {
1090 assert_eq!(MathFunction::ExtractBits.to_string(), "extractBits");
1094 assert_eq!(MathFunction::InsertBits.to_string(), "insertBits");
1095 }
1096}