wesl/pass/lower.rs
1use crate::{error::Error, pass::Visit};
2
3use wgsl_parse::syntax::*;
4
5/// Performs conversions on the final syntax tree to make it more compatible with WGSL
6/// implementations like Naga, catch errors early and perform optimizations.
7///
8/// Currently, `lower` performs the following transforms:
9/// * remove aliases (inlined)
10/// * remove consts (inlined)
11/// * remove deprecated, non-standard attributes
12/// * remove import declarations
13///
14/// with the `eval` feature flag enabled, it performs additional transforms:
15/// * evaluate const-expressions (including calls to const functions)
16/// * remove unreachable code paths after const-evaluation
17/// * remove function call statements to const functions (no side-effects)
18/// * make implicit conversions from abstract types explicit (using conversion rank)
19///
20/// Customizing this behavior is not possible currently. The following transforms may
21/// be available in the future:
22/// * make variable types explicit
23/// * remove unused variables / code with no side-effects
24pub fn lower(module: &mut TranslationUnit) -> Result<(), Error> {
25 module.imports.clear();
26
27 for attrs in Visit::<Attributes>::visit_mut(module) {
28 attrs.retain(|attr| {
29 !matches!(attr.node(),
30 Attribute::Custom(CustomAttribute { name, .. }) if name == "generic")
31 })
32 }
33
34 #[cfg(not(feature = "eval"))]
35 {
36 // these are redundant with eval::lower.
37 inline_type_aliases(module);
38 inline_global_consts(module);
39 }
40 #[cfg(feature = "eval")]
41 {
42 use crate::error::Diagnostic;
43 use crate::eval::{Context, Exec, Lower, mark_functions_const};
44 use wgsl_parse::SyntaxNode;
45 mark_functions_const(module);
46
47 // we want to drop wesl2 at the end of the block for idents use_count
48 {
49 let module2 = module.clone();
50 let mut ctx = Context::new(&module2);
51 module
52 .exec(&mut ctx) // populate the ctx with module-scope declarations
53 .map_err(|e| Diagnostic::from(e).with_ctx(&ctx))?;
54 module
55 .lower(&mut ctx)
56 .map_err(|e| Diagnostic::from(e).with_ctx(&ctx))?;
57 }
58
59 // remove `@const` attributes.
60 for decl in &mut module.global_declarations {
61 if let GlobalDeclaration::Function(decl) = decl.node_mut() {
62 decl.retain_attributes_mut(|attr| *attr != Attribute::Const);
63 }
64 }
65 }
66 Ok(())
67}
68
69/// Eliminate all type aliases.
70#[allow(unused)]
71fn inline_type_aliases(wesl: &mut TranslationUnit) {
72 let take_next_alias = |wesl: &mut TranslationUnit| {
73 let index = wesl
74 .global_declarations
75 .iter()
76 .position(|decl| matches!(decl.node(), GlobalDeclaration::TypeAlias(_)));
77 index.map(|index| {
78 let decl = wesl.global_declarations.swap_remove(index);
79 match decl.into_inner() {
80 GlobalDeclaration::TypeAlias(alias) => alias,
81 _ => unreachable!(),
82 }
83 })
84 };
85
86 while let Some(mut alias) = take_next_alias(wesl) {
87 // we rename the alias and all references to its type expression,
88 // and drop the alias declaration.
89 alias.ident.rename(format!("{}", alias.ty));
90 }
91}
92
93/// Eliminate all const-declarations.
94///
95/// Replace usages of the const-declaration with its expression.
96///
97/// # Panics
98///
99/// panics if the const-declaration is ill-formed, i.e. has no initializer.
100#[allow(unused)]
101fn inline_global_consts(wesl: &mut TranslationUnit) {
102 let take_next_const = |wesl: &mut TranslationUnit| {
103 let index = wesl.global_declarations.iter().position(|decl| {
104 matches!(
105 decl.node(),
106 GlobalDeclaration::Declaration(Declaration {
107 kind: DeclarationKind::Const,
108 ..
109 })
110 )
111 });
112 index.map(|index| {
113 let decl = wesl.global_declarations.swap_remove(index);
114 match decl.into_inner() {
115 GlobalDeclaration::Declaration(d) => d,
116 _ => unreachable!(),
117 }
118 })
119 };
120
121 while let Some(mut decl) = take_next_const(wesl) {
122 // we rename the const and all references to its expression in parentheses,
123 // and drop the const declaration.
124 decl.ident
125 .rename(format!("({})", decl.initializer.unwrap()));
126 }
127}