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}