Skip to main content

wesl/pass/
driver.rs

1use std::collections::HashSet;
2
3use wgsl_parse::syntax::{Ident, ModulePath, TranslationUnit, Visibility};
4
5use crate::{
6    error::Error,
7    pass::{self, Module, UsedItems},
8};
9
10pub struct CompileResult {
11    pub syntax: TranslationUnit,
12    pub modules: Vec<Module>,
13    pub used_items: UsedItems,
14}
15
16/// Re-implementing this trait gives full control over the steps of the compilation pipeline.
17/// It is implemented by [`crate::Compiler`].
18///
19/// The steps, default-implemented in [`Self::compile`], are:
20///
21/// 1. Load the main module.
22/// 2. Get the list declarations in the main module serving as entry points;
23///    they are the basis of static usage analysis.
24/// 3. Run static usage analysis: for each entry point, collect the list of declarations it depends on, transitively.
25///    Usage analysis returns the set of imported identifiers (defined in other modules).
26/// 4. Load missing imported modules found via usage analysis.
27/// 5. Run the previous two steps with imported modules/identifiers, until all identifiers have been usage-analyzed.
28/// 6. Assemble loaded modules into a final module.
29///
30/// WESL features are implemented at different steps of this pipeline:
31///
32/// * Conditional translation runs after module loading.
33/// * Import resolution runs at usage analysis.
34/// * Name mangling runs before assembly.
35pub trait CompilerDriver: Sized {
36    /// Get the path of the main module.
37    fn main_path(&self) -> &ModulePath;
38
39    /// Get the canonical path to a module.
40    ///
41    /// Currently, this only affects package dependency unification,
42    /// e.g. if package A imports from X and package B also imports from X,
43    /// and both X are semver-compatible, then there should be a unique path to X.
44    fn canonical_path(&self, path: &ModulePath) -> ModulePath {
45        path.clone()
46    }
47
48    /// List identifiers of declarations serving as starting point for static usage analysis.
49    ///
50    /// Typically they are the entry points functions (`@vertex`, `@fragment` and `@compute`),
51    /// but some users might want to keep different declarations.
52    fn main_entry_points(&self, main_module: &TranslationUnit) -> Result<HashSet<Ident>, Error> {
53        Ok(pass::main_entry_points(main_module))
54    }
55
56    /// Find declarations used in external modules which this module depends on, no matter what.
57    ///
58    /// Currently, only items referenced by module-scope `const_assert`s are always included.
59    ///
60    /// See [`Self::usage_analysis`].
61    fn module_usage_analysis(
62        &self,
63        module: &Module,
64        already_used: &mut UsedItems,
65        to_analyze: &mut UsedItems,
66    ) -> Result<(), Error> {
67        pass::module_usage_analysis(module, already_used, to_analyze, false)?;
68        Ok(())
69    }
70
71    /// Find declaration names which a local declaration depends on.
72    ///
73    /// Adds *external* referenced idents to `to_analyze` with their minimum required visibility.
74    /// Perform usage analysis recursively with *local* referenced idents, and adds them to `already_used`
75    /// with their declaration visibility.
76    /// So at the end of the call, `to_analyze` contains incomplete usage analysis which needs to continue
77    /// in a separate module. `already_used` contains finished analysis.
78    ///
79    /// `min_vis` is the minimum visibility requirement for the declaration.
80    fn usage_analysis(
81        &self,
82        module: &Module,
83        decl_name: &str,
84        min_vis: Visibility,
85        already_used: &mut UsedItems,
86        to_analyze: &mut UsedItems,
87    ) -> Result<(), Error> {
88        pass::usage_analysis(module, decl_name, min_vis, already_used, to_analyze, false)?;
89        Ok(())
90    }
91
92    /// Get the [`TranslationUnit`] for a module at a given path.
93    ///
94    /// This function is called only once per module path.
95    fn load_module(&mut self, path: &ModulePath) -> Result<TranslationUnit, Error>;
96
97    /// Assemble the list of loaded module into a final output.
98    ///
99    /// `used_items` contains the result of static usage analysis, i.e the list identifiers
100    /// which the main module entrypoints depend on, transitively.
101    fn link(
102        &self,
103        modules: &mut Vec<Module>,
104        used_items: &UsedItems,
105    ) -> Result<TranslationUnit, Error>;
106
107    /// Run the compilation pipeline.
108    ///
109    /// See standalone default implementation in [`pass::compile`].
110    fn compile(&mut self) -> Result<CompileResult, Error> {
111        pass::compile(self)
112    }
113
114    /// Async version of [`Self::load_module`].
115    fn load_module_async(
116        &mut self,
117        path: &ModulePath,
118    ) -> impl Future<Output = Result<TranslationUnit, Error>> {
119        async { self.load_module(path) }
120    }
121
122    /// Async version of [`Self::compile`].
123    fn compile_async(&mut self) -> impl Future<Output = Result<CompileResult, Error>> {
124        pass::compile_async(self)
125    }
126}