Skip to main content

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}