wesl/lib.rs
1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "eval")]
5pub mod eval;
6#[cfg(feature = "generics")]
7mod generics;
8#[cfg(feature = "package")]
9mod package;
10
11mod builtin;
12mod condcomp;
13mod error;
14mod import;
15mod lower;
16mod mangle;
17mod resolve;
18mod sourcemap;
19mod strip;
20mod syntax_util;
21mod validate;
22mod visit;
23
24#[cfg(feature = "eval")]
25pub use eval::{Eval, EvalError, Exec, Inputs, exec_entrypoint};
26
27#[cfg(feature = "generics")]
28pub use generics::GenericsError;
29
30#[cfg(feature = "package")]
31pub use package::PkgBuilder;
32
33pub use condcomp::{CondCompError, Feature, Features};
34pub use error::{Diagnostic, Error};
35pub use import::ImportError;
36pub use lower::lower;
37pub use mangle::{CacheMangler, EscapeMangler, HashMangler, Mangler, NoMangler, UnicodeMangler};
38pub use resolve::{
39 FileResolver, NoResolver, Pkg, PkgModule, PkgResolver, Preprocessor, ResolveError, Resolver,
40 Router, StandardResolver, VirtualResolver, emit_rerun_if_changed,
41};
42pub use sourcemap::{BasicSourceMap, NoSourceMap, SourceMap, SourceMapper};
43pub use syntax_util::SyntaxUtil;
44pub use validate::{ValidateError, validate_wesl, validate_wgsl};
45
46// re-exports
47pub use wesl_macros::*;
48pub use wgsl_parse::syntax;
49pub use wgsl_parse::syntax::ModulePath;
50
51#[cfg(feature = "eval")]
52use std::collections::HashMap;
53use std::{collections::HashSet, fmt::Display, path::Path};
54
55use import::{Module, Resolutions};
56use strip::strip_except;
57use wgsl_parse::syntax::{Ident, TranslationUnit};
58
59/// Compilation options. Used in [`compile`] and [`Wesl::set_options`].
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct CompileOptions {
62 /// Toggle [WESL Imports](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/Imports.md).
63 ///
64 /// If disabled:
65 /// * The compiler will silently remove the import statements and inline paths.
66 /// * Validation will not trigger an error if referencing an imported item.
67 pub imports: bool,
68 /// Toggle [WESL Conditional Translation](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md).
69 ///
70 /// See `features` to enable/disable each feature flag.
71 pub condcomp: bool,
72 /// Toggle generics. Generics are super experimental, don't expect anything from it.
73 ///
74 /// Requires the `generics` crate feature flag.
75 pub generics: bool,
76 /// Enable stripping (aka. Dead Code Elimination).
77 ///
78 /// By default, all declarations reachable by entrypoint functions, const_asserts and
79 /// pipeline-overridable constants are kept. See [`Self::keep`] and
80 /// [`Self::keep_root`] to control what gets stripped.
81 ///
82 /// Stripping can have side-effects in rare cases, refer to the WESL docs to learn
83 /// more.
84 pub strip: bool,
85 /// Enable lowering/polyfills. This transforms the output code in various ways.
86 ///
87 /// See [`lower`].
88 pub lower: bool,
89 /// Enable validation of individual WESL modules and the final output.
90 /// This will catch *some* errors, not all.
91 /// See [`validate_wesl`] and [`validate_wgsl`].
92 ///
93 /// Requires the `eval` crate feature flag.
94 pub validate: bool,
95 /// Make the import resolution lazy (This is the default mandated by WESL).
96 ///
97 /// The "lazy" import algorithm will only read a submodule is one of its item is used
98 /// by the entrypoints or `keep` declarations (recursively via static usage analysis).
99 ///
100 /// In contrast, the "eager" import algorithm will follow all import statements.
101 pub lazy: bool,
102 /// Enable mangling of declarations in the root module.
103 ///
104 /// By default, WESL does not mangle root module declarations.
105 pub mangle_root: bool,
106 /// If `Some`, specify a list of root module declarations to keep. If `None`, only the
107 /// entrypoint functions (and their dependencies) are kept.
108 ///
109 /// This option has no effect if [`Self::keep_root`] is enabled or [`Self::strip`] is
110 /// disabled.
111 pub keep: Option<Vec<String>>,
112 /// If `true`, all root module declarations are preserved when stripping is enabled.
113 ///
114 /// This option takes precedence over [`Self::keep`], and has no effect if
115 /// [`Self::strip`] is disabled.
116 pub keep_root: bool,
117 /// [WESL Conditional Translation](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
118 /// features to enable/disable.
119 ///
120 /// Conditional translation can be incremental. If not all feature flags are handled,
121 /// the output will contain unevaluated `@if` attributes and will therefore *not* be
122 /// valid WGSL.
123 ///
124 /// This option has no effect if [`Self::condcomp`] is disabled.
125 pub features: Features,
126}
127
128impl Default for CompileOptions {
129 fn default() -> Self {
130 Self {
131 imports: true,
132 condcomp: true,
133 generics: false,
134 strip: true,
135 lower: false,
136 validate: true,
137 lazy: true,
138 mangle_root: false,
139 keep: Default::default(),
140 keep_root: false,
141 features: Default::default(),
142 }
143 }
144}
145
146/// Mangling scheme. Used in [`Wesl::set_mangler`].
147#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
148pub enum ManglerKind {
149 /// Escaped path mangler.
150 /// `foo_bar::item -> _1foo_bar_item`
151 #[default]
152 Escape,
153 /// Hash mangler.
154 /// `foo::bar::item -> item_1985638328947`
155 Hash,
156 /// Make valid identifiers with unicode "confusables" characters.
157 /// `foo::bar<baz, moo> -> foo::barᐸbazˏmooᐳ`
158 Unicode,
159 /// Disable mangling. (warning: will break shaders if case of name conflicts!)
160 None,
161}
162
163/// Include a WGSL file compiled with [`Wesl::build_artifact`] as a string.
164///
165/// The argument corresponds to the `artifact_name` passed to [`Wesl::build_artifact`].
166///
167/// This is a very simple convenience macro. See the crate documentation for a usage
168/// example.
169#[macro_export]
170macro_rules! include_wesl {
171 ($root:literal) => {
172 include_str!(concat!(env!("OUT_DIR"), "/", $root, ".wgsl"))
173 };
174}
175
176/// Include a generated package.
177///
178/// See [`PkgBuilder`] for more information about building WESL packages.
179#[macro_export]
180macro_rules! wesl_pkg {
181 ($pkg_name:ident) => {
182 $crate::wesl_pkg!($pkg_name, concat!("/", stringify!($pkg_name), ".rs"));
183 };
184 ($pkg_name:ident, $source:expr) => {
185 pub mod $pkg_name {
186 use $crate::{Pkg, PkgModule};
187 include!(concat!(env!("OUT_DIR"), $source));
188 }
189 };
190}
191
192/// The WESL compiler high-level API.
193///
194/// # Basic Usage
195///
196/// ```rust
197/// # use wesl::{Wesl, VirtualResolver};
198/// #
199/// let compiler = Wesl::new("path/to/dir/containing/shaders");
200/// #
201/// # // just adding a virtual file here so the doctest runs without a filesystem
202/// # let mut resolver = VirtualResolver::new();
203/// # resolver.add_module("package::main".parse().unwrap(), "fn my_fn() {}".into());
204/// # let compiler = compiler.set_custom_resolver(resolver);
205/// #
206/// let wgsl_string = compiler
207/// .compile(&"package::main".parse().unwrap())
208/// .unwrap()
209/// .to_string();
210/// ```
211pub struct Wesl<R: Resolver> {
212 options: CompileOptions,
213 use_sourcemap: bool,
214 resolver: R,
215 mangler: Box<dyn Mangler + Send + Sync + 'static>,
216}
217
218impl Wesl<StandardResolver> {
219 /// Get a WESL compiler with all *mandatory* and *optional* WESL extensions enabled,
220 /// but not *experimental* and *non-standard* extensions.
221 ///
222 /// See also: [`Wesl::new_barebones`], [`Wesl::new_experimental`].
223 ///
224 /// # WESL Reference
225 /// This WESL compiler is spec-compliant.
226 ///
227 /// Mandatory extensions: imports, conditional translation.
228 /// Optional extrensions: stripping.
229 pub fn new(base: impl AsRef<Path>) -> Self {
230 Self {
231 options: CompileOptions::default(),
232 use_sourcemap: true,
233 resolver: StandardResolver::new(base),
234 mangler: Box::new(EscapeMangler),
235 }
236 }
237
238 /// Get a WESL compiler with all functionalities enabled, including *experimental* and
239 /// *non-standard* ones.
240 ///
241 /// See also: [`Wesl::new`] and [`Wesl::new_barebones`].
242 ///
243 /// # WESL Reference
244 /// This WESL compiler is *not* spec-compliant because it enables all extensions
245 /// including *experimental* and *non-standard* ones. See [`Wesl::new`].
246 ///
247 /// Experimental extensions: generics.
248 /// Non-standard extensions: `@const`.
249 pub fn new_experimental(base: impl AsRef<Path>) -> Self {
250 Self {
251 options: CompileOptions {
252 generics: true,
253 lower: true,
254 ..Default::default()
255 },
256 use_sourcemap: true,
257 resolver: StandardResolver::new(base),
258 mangler: Box::new(EscapeMangler),
259 }
260 }
261
262 /// Add a package dependency.
263 ///
264 /// Learn more about packages in [`PkgBuilder`].
265 pub fn add_package(&mut self, pkg: &'static Pkg) -> &mut Self {
266 self.resolver.add_package(pkg);
267 self
268 }
269
270 /// Add several package dependencies.
271 ///
272 /// Learn more about packages in [`PkgBuilder`].
273 pub fn add_packages(&mut self, pkgs: impl IntoIterator<Item = &'static Pkg>) -> &mut Self {
274 for pkg in pkgs {
275 self.resolver.add_package(pkg);
276 }
277 self
278 }
279
280 /// Add a const-declaration to the special `constants` module.
281 ///
282 /// See [`StandardResolver::add_constant`].
283 pub fn add_constant(&mut self, name: impl ToString, value: f64) -> &mut Self {
284 self.resolver.add_constant(name, value);
285 self
286 }
287
288 /// Add several const-declarations to the special `constants` module.
289 ///
290 /// See [`StandardResolver::add_constant`].
291 pub fn add_constants(
292 &mut self,
293 constants: impl IntoIterator<Item = (impl ToString, f64)>,
294 ) -> &mut Self {
295 for (name, value) in constants {
296 self.resolver.add_constant(name, value);
297 }
298 self
299 }
300}
301
302impl Wesl<NoResolver> {
303 /// Get a WESL compiler with no extensions, no mangler and no resolver.
304 ///
305 /// You must set a [`Mangler`] and a [`Resolver`] manually to use this compiler,
306 /// see [`Wesl::set_mangler`] and [`Wesl::set_custom_resolver`].
307 ///
308 /// # WESL Reference
309 /// This WESL compiler is *not* spec-compliant because it does not enable *mandatory*
310 /// WESL extensions. See [`Wesl::new`].
311 pub fn new_barebones() -> Self {
312 Self {
313 options: CompileOptions {
314 imports: false,
315 condcomp: false,
316 generics: false,
317 strip: false,
318 lower: false,
319 validate: false,
320 lazy: false,
321 mangle_root: false,
322 keep: None,
323 keep_root: false,
324 features: Default::default(),
325 },
326 use_sourcemap: false,
327 resolver: NoResolver,
328 mangler: Box::new(NoMangler),
329 }
330 }
331}
332
333impl<R: Resolver> Wesl<R> {
334 /// Set all compilation options.
335 pub fn set_options(&mut self, options: CompileOptions) -> &mut Self {
336 self.options = options;
337 self
338 }
339
340 /// Set the [`Mangler`].
341 ///
342 /// The default mangler is [`EscapeMangler`].
343 ///
344 /// # WESL Reference
345 /// Custom manglers *must* conform to the constraints described in [`Mangler`].
346 ///
347 /// Spec: not yet available.
348 pub fn set_mangler(&mut self, kind: ManglerKind) -> &mut Self {
349 self.mangler = match kind {
350 ManglerKind::Escape => Box::new(EscapeMangler),
351 ManglerKind::Hash => Box::new(HashMangler),
352 ManglerKind::Unicode => Box::new(UnicodeMangler),
353 ManglerKind::None => Box::new(NoMangler),
354 };
355 self
356 }
357
358 /// Set a custom [`Mangler`].
359 ///
360 /// The default mangler is [`EscapeMangler`].
361 pub fn set_custom_mangler(
362 &mut self,
363 mangler: impl Mangler + Send + Sync + 'static,
364 ) -> &mut Self {
365 self.mangler = Box::new(mangler);
366 self
367 }
368
369 /// Set a custom [`Resolver`] (customize how import paths are translated to WESL modules).
370 ///
371 ///```rust
372 /// # use wesl::{FileResolver, Router, VirtualResolver, Wesl};
373 /// // `import runtime::constants::PI` is in a custom module mounted at runtime.
374 /// let mut resolver = VirtualResolver::new();
375 /// resolver.add_module("constants".parse().unwrap(), "const PI = 3.1415; const TAU = PI * 2.0;".into());
376 /// let mut router = Router::new();
377 /// router.mount_fallback_resolver(FileResolver::new("src/shaders"));
378 /// router.mount_resolver("runtime".parse().unwrap(), resolver);
379 /// let compiler = Wesl::new("").set_custom_resolver(router);
380 /// ```
381 ///
382 /// # WESL Reference
383 /// Both [`FileResolver`] and [`VirtualResolver`] are spec-compliant.
384 /// Custom resolvers *must* conform to the constraints described in [`Resolver`].
385 pub fn set_custom_resolver<CustomResolver: Resolver>(
386 self,
387 resolver: CustomResolver,
388 ) -> Wesl<CustomResolver> {
389 Wesl {
390 options: self.options,
391 use_sourcemap: self.use_sourcemap,
392 mangler: self.mangler,
393 resolver,
394 }
395 }
396
397 /// Enable sourcemapping.
398 ///
399 /// Turning "on" this option improves the quality of error messages.
400 ///
401 /// # WESL Reference
402 /// Sourcemapping is not yet part of the WESL Specification and does not impact
403 /// compliance.
404 pub fn use_sourcemap(&mut self, val: bool) -> &mut Self {
405 self.use_sourcemap = val;
406 self
407 }
408
409 /// Enable imports.
410 ///
411 /// # WESL Reference
412 /// Imports is a *mandatory* WESL extension.
413 ///
414 /// Spec: [`Imports.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/Imports.md)
415 pub fn use_imports(&mut self, val: bool) -> &mut Self {
416 self.options.imports = val;
417 self
418 }
419
420 /// Enable conditional translation.
421 ///
422 /// # WESL Reference
423 /// Conditional Compilation is a *mandatory* WESL extension.
424 /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
425 pub fn use_condcomp(&mut self, val: bool) -> &mut Self {
426 self.options.condcomp = val;
427 self
428 }
429
430 /// Enable generics.
431 ///
432 /// # WESL Reference
433 /// Generics is an *experimental* WESL extension.
434 ///
435 /// Spec: not yet available.
436 #[cfg(feature = "generics")]
437 pub fn use_generics(&mut self, val: bool) -> &mut Self {
438 self.options.generics = val;
439 self
440 }
441 /// Set a conditional compilation feature flag.
442 ///
443 /// # WESL Reference
444 /// Conditional translation is a *mandatory* WESL extension.
445 ///
446 /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
447 pub fn set_feature(&mut self, feat: &str, val: impl Into<Feature>) -> &mut Self {
448 self.options
449 .features
450 .flags
451 .insert(feat.to_string(), val.into());
452 self
453 }
454 /// Set conditional compilation feature flags.
455 ///
456 /// # WESL Reference
457 /// Conditional translation is a *mandatory* WESL extension.
458 /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
459 pub fn set_features(
460 &mut self,
461 feats: impl IntoIterator<Item = (impl ToString, impl Into<Feature>)>,
462 ) -> &mut Self {
463 self.options
464 .features
465 .flags
466 .extend(feats.into_iter().map(|(k, v)| (k.to_string(), v.into())));
467 self
468 }
469 /// Unset a conditional compilation feature flag.
470 ///
471 /// # WESL Reference
472 /// Conditional translation is a *mandatory* WESL extension.
473 ///
474 /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
475 pub fn unset_feature(&mut self, feat: &str) -> &mut Self {
476 self.options.features.flags.remove(feat);
477 self
478 }
479 /// Set the behavior for unspecified conditional compilation feature flags.
480 ///
481 /// Controls what happens when a feature flag is used in shader code but not set with
482 /// [`Wesl::set_feature`]. By default it turns off the feature, but it can also be set to leave
483 /// it in the code ([`Feature::Keep`]) or to trigger compilation error ([`Feature::Error`]).
484 ///
485 /// # WESL Reference
486 /// Conditional translation is a *mandatory* WESL extension.
487 ///
488 /// Spec: [`ConditionalTranslation.md`](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/ConditionalTranslation.md)
489 pub fn set_missing_feature_behavior(&mut self, val: impl Into<Feature>) -> &mut Self {
490 self.options.features.default = val.into();
491 self
492 }
493 /// Remove unused declarations from the final WGSL output.
494 ///
495 /// Unused declarations are all declarations not used (directly or indirectly) by any
496 /// of the entrypoints (functions marked `@compute`, `@vertex` or `@fragment`) in the
497 /// root module.
498 ///
499 /// see also: [`Wesl::keep_declarations`]
500 ///
501 /// # WESL Reference
502 /// Code stripping is an *optional* WESL extension.
503 /// Customizing entrypoints returned by the compiler is explicitly allowed by the spec.
504 ///
505 /// Spec: not yet available.
506 pub fn use_stripping(&mut self, val: bool) -> &mut Self {
507 self.options.strip = val;
508 self
509 }
510 /// Transform an output into a simplified WGSL that is better supported by
511 /// implementors.
512 ///
513 /// See also: [`lower`].
514 ///
515 /// # WESL Reference
516 /// Lowering is an *experimental* WESL extension.
517 ///
518 /// Spec: not yet available.
519 pub fn use_lower(&mut self, val: bool) -> &mut Self {
520 self.options.lower = val;
521 self
522 }
523 /// If stripping is enabled, specify which root module declarations to keep in the
524 /// final WGSL. Function entrypoints are kept by default.
525 ///
526 /// # WESL Reference
527 /// Code stripping is an *optional* WESL extension.
528 /// Customizing entrypoints returned by the compiler is explicitly allowed by the
529 /// spec.
530 ///
531 /// Spec: not yet available.
532 pub fn keep_declarations(&mut self, keep: Vec<String>) -> &mut Self {
533 self.options.keep = Some(keep);
534 self
535 }
536 /// If stripping is enabled, keep all entrypoints in the root WESL module.
537 ///
538 /// Entrypoints are functions with `@compute`, `@vertex` or `@fragment`.
539 /// This is the default. See also: [`Wesl::keep_declarations`].
540 ///
541 /// # WESL Reference
542 /// Code stripping is an *optional* WESL extension.
543 /// Customizing entrypoints returned by the compiler is explicitly allowed by the
544 /// spec.
545 ///
546 /// Spec: not yet available.
547 pub fn keep_all_entrypoints(&mut self) -> &mut Self {
548 self.options.keep = None;
549 self
550 }
551}
552
553/// The result of [`Wesl::compile`].
554///
555/// This type contains both the resulting WGSL syntax tree and the sourcemap if
556/// [`Wesl`] was invoked with sourcemapping enabled.
557///
558/// This type implements `Display`, call `to_string()` to get the compiled WGSL.
559#[derive(Clone, Default)]
560pub struct CompileResult {
561 pub syntax: TranslationUnit,
562 pub sourcemap: Option<BasicSourceMap>,
563 /// A list of absolute paths or packages.
564 pub modules: Vec<ModulePath>,
565}
566
567impl CompileResult {
568 pub fn write_to_file(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
569 std::fs::write(path, self.to_string())
570 }
571
572 /// Write the result in rust's `OUT_DIR`.
573 ///
574 /// This function is meant to be used in a `build.rs` workflow. The output WGSL will
575 /// be accessed with the [`include_wesl`] macro. See the crate documentation for a
576 /// usage example.
577 ///
578 /// # Panics
579 /// Panics when the output file cannot be written.
580 pub fn write_artifact(&self, artifact_name: &str) {
581 let dirname = std::env::var("OUT_DIR").unwrap();
582 let out_name = Path::new(artifact_name);
583 if out_name.iter().count() != 1 || out_name.extension().is_some() {
584 eprintln!("`out_name` cannot contain path separators or file extension");
585 panic!()
586 }
587 let mut output = Path::new(&dirname).join(out_name);
588 output.set_extension("wgsl");
589 self.write_to_file(output)
590 .expect("failed to write output shader");
591 }
592}
593
594impl Display for CompileResult {
595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596 self.syntax.fmt(f)
597 }
598}
599
600/// The result of [`CompileResult::exec`].
601///
602/// This type contains both the return value of the function called (if any) and the
603/// evaluation context (including bindings).
604///
605/// This type implements `Display`, call `to_string()` to get the function return value.
606#[cfg(feature = "eval")]
607pub struct ExecResult<'a> {
608 /// The executed function return value
609 pub inst: Option<eval::Instance>,
610 /// Context after execution
611 pub ctx: eval::Context<'a>,
612}
613
614#[cfg(feature = "eval")]
615impl ExecResult<'_> {
616 /// Get the function return value.
617 pub fn return_value(&self) -> Option<&eval::Instance> {
618 self.inst.as_ref()
619 }
620
621 /// Get a [shader resource](https://www.w3.org/TR/WGSL/#resource).
622 ///
623 /// Shader resources (aka. bindings) with `write`
624 /// [access mode](https://www.w3.org/TR/WGSL/#memory-access-mode) can be modified
625 /// after executing an entry point.
626 pub fn resource(&self, group: u32, binding: u32) -> Option<&eval::RefInstance> {
627 self.ctx.resource(group, binding)
628 }
629}
630
631/// The result of [`CompileResult::eval`].
632///
633/// This type contains both the resulting WGSL instance and the evaluation context
634/// (including bindings).
635///
636/// This type implements `Display`, call `to_string()` to get the evaluation result.
637#[cfg(feature = "eval")]
638pub struct EvalResult<'a> {
639 /// The expression evaluation result
640 pub inst: eval::Instance,
641 /// Context after evaluation
642 pub ctx: eval::Context<'a>,
643}
644
645#[cfg(feature = "eval")]
646impl EvalResult<'_> {
647 // TODO: make context non-mut
648 /// Get the WGSL string representing the evaluated expression.
649 pub fn to_buffer(&mut self) -> Option<Vec<u8>> {
650 use eval::HostShareable;
651 self.inst.to_buffer(&mut self.ctx)
652 }
653}
654
655#[cfg(feature = "eval")]
656impl Display for EvalResult<'_> {
657 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658 self.inst.fmt(f)
659 }
660}
661
662#[cfg(feature = "eval")]
663impl CompileResult {
664 /// Evaluate a const-expression in the context of this compilation result.
665 ///
666 /// Highly experimental. Not all builtin WGSL functions are supported yet.
667 /// Contrary to [`eval_str`], the provided expression can reference declarations
668 /// in the compiled WGSL: global const-declarations and user-defined functions with
669 /// the `@const` attribute.
670 ///
671 /// # WESL Reference
672 /// The user-defined `@const` attribute is non-standard.
673 /// See issue [#46](https://github.com/wgsl-tooling-wg/wesl-spec/issues/46#issuecomment-2389531479).
674 pub fn eval<'a>(&'a self, source: &str) -> Result<EvalResult<'a>, Error> {
675 let expr = source
676 .parse::<syntax::Expression>()
677 .map_err(|e| Error::Error(Diagnostic::from(e).with_source(source.to_string())))?;
678 let (inst, ctx) = eval(&expr, &self.syntax);
679 let inst = inst.map_err(|e| {
680 Diagnostic::from(e)
681 .with_source(source.to_string())
682 .with_ctx(&ctx)
683 });
684
685 let inst = if let Some(sourcemap) = &self.sourcemap {
686 inst.map_err(|e| Error::Error(e.with_sourcemap(sourcemap)))
687 } else {
688 inst.map_err(Error::Error)
689 }?;
690
691 let res = EvalResult { inst, ctx };
692 Ok(res)
693 }
694
695 /// Execute an entrypoint in the same way that it would be executed on the GPU.
696 ///
697 /// Highly experimental.
698 ///
699 /// # WESL Reference
700 /// The `@const` attribute is non-standard.
701 pub fn exec<'a>(
702 &'a self,
703 entrypoint: &str,
704 inputs: Inputs,
705 bindings: HashMap<(u32, u32), eval::RefInstance>,
706 overrides: HashMap<String, eval::Instance>,
707 ) -> Result<ExecResult<'a>, Error> {
708 let mut ctx = eval::Context::new(&self.syntax);
709 ctx.add_bindings(bindings);
710 ctx.add_overrides(overrides);
711 ctx.set_stage(eval::EvalStage::Exec);
712
713 let entry_fn = eval::SyntaxUtil::decl_function(ctx.source, entrypoint)
714 .ok_or_else(|| EvalError::UnknownFunction(entrypoint.to_string()))?;
715
716 let _ = self.syntax.exec(&mut ctx)?;
717
718 let inst = exec_entrypoint(entry_fn, inputs, &mut ctx).map_err(|e| {
719 if let Some(sourcemap) = &self.sourcemap {
720 Diagnostic::from(e).with_ctx(&ctx).with_sourcemap(sourcemap)
721 } else {
722 Diagnostic::from(e).with_ctx(&ctx)
723 }
724 })?;
725
726 Ok(ExecResult { inst, ctx })
727 }
728}
729
730impl<R: Resolver> Wesl<R> {
731 /// Compile a WESL program from a root file.
732 ///
733 /// # WESL Reference
734 /// Spec: not available yet.
735 pub fn compile(&self, root: &ModulePath) -> Result<CompileResult, Error> {
736 // TODO
737 // root.origin = PathOrigin::Absolute; // we force absolute paths
738
739 if self.use_sourcemap {
740 compile_sourcemap(root, &self.resolver, &self.mangler, &self.options)
741 } else {
742 compile(root, &self.resolver, &self.mangler, &self.options)
743 }
744 }
745
746 /// Compile a WESL program from a root file and output the result in Rust's `OUT_DIR`.
747 ///
748 /// This function is meant to be used in a `build.rs` workflow. The output WGSL will
749 /// be accessed with the [`include_wesl`] macro. See the crate documentation for a
750 /// usage example.
751 ///
752 /// * The first argument is the path to the root module relative to the base
753 /// directory.
754 /// * The second argument is the name of the artifact, used in [`include_wesl`].
755 ///
756 /// Will emit `rerun-if-changed` instructions so the build script reruns only if the
757 /// shader files are modified.
758 ///
759 /// # Panics
760 /// Panics when compilation fails or if the output file cannot be written.
761 /// Pretty-prints the WESL error message to stderr.
762 pub fn build_artifact(&self, root: &ModulePath, artifact_name: &str) {
763 let compiled = self
764 .compile(root)
765 .inspect_err(|e| {
766 eprintln!("failed to build WESL shader `{root}`.\n{e}");
767 panic!();
768 })
769 .unwrap();
770 emit_rerun_if_changed(&compiled.modules, &self.resolver);
771 compiled.write_artifact(artifact_name);
772 }
773}
774
775/// What idents to keep from the root module. They should be either:
776/// * all named declarations, if `strip` is disabled or `keep_root` is enabled.
777/// * `keep` idents that exist, if it is set and `strip` is enabled,
778/// * all entrypoints, if `strip` is enabled and `keep` is not set
779fn keep_idents(
780 wesl: &TranslationUnit,
781 keep: &Option<Vec<String>>,
782 keep_root: bool,
783 strip: bool,
784) -> HashSet<Ident> {
785 if strip && !keep_root {
786 if let Some(keep) = keep {
787 wesl.global_declarations
788 .iter()
789 .filter_map(|decl| {
790 let ident = decl.ident()?;
791 keep.iter()
792 .any(|name| name == &*ident.name())
793 .then_some(ident.clone())
794 })
795 .collect()
796 } else {
797 // when stripping is enabled and keep is unset, we keep the entrypoints (default)
798 wesl.entry_points().cloned().collect()
799 }
800 } else {
801 // when stripping is disabled, we keep all declarations in the root module.
802 wesl.global_declarations
803 .iter()
804 .filter_map(|decl| decl.ident())
805 .cloned()
806 .collect()
807 }
808}
809
810fn compile_pre_assembly(
811 root: &ModulePath,
812 resolver: &impl Resolver,
813 opts: &CompileOptions,
814) -> Result<(Resolutions, HashSet<Ident>), Error> {
815 let resolver: Box<dyn Resolver> = if opts.condcomp {
816 Box::new(Preprocessor::new(resolver, |wesl| {
817 condcomp::run(wesl, &opts.features)?;
818 Ok(())
819 }))
820 } else {
821 Box::new(resolver)
822 };
823
824 let mut wesl = resolver.resolve_module(root)?;
825 wesl.retarget_idents();
826 let keep = keep_idents(&wesl, &opts.keep, opts.keep_root, opts.strip);
827
828 let mut resolutions = Resolutions::new();
829 let module = Module::new(wesl, root.clone())?;
830 resolutions.push_module(module);
831
832 if opts.imports {
833 if opts.lazy {
834 import::resolve_lazy(&keep, &mut resolutions, &resolver)?
835 } else {
836 import::resolve_eager(&mut resolutions, &resolver)?
837 }
838 }
839
840 if opts.validate {
841 for module in resolutions.modules() {
842 let module = module.borrow();
843 validate_wesl(&module.source).map_err(|d| {
844 d.with_module_path(module.path.clone(), resolver.display_name(&module.path))
845 })?;
846 }
847 }
848
849 Ok((resolutions, keep))
850}
851
852fn compile_post_assembly(
853 wesl: &mut TranslationUnit,
854 options: &CompileOptions,
855 keep: &HashSet<Ident>,
856) -> Result<(), Error> {
857 #[cfg(feature = "generics")]
858 if options.generics {
859 generics::generate_variants(wesl)?;
860 generics::replace_calls(wesl)?;
861 };
862 if options.validate {
863 validate_wgsl(wesl)?;
864 }
865 if options.lower {
866 lower(wesl)?;
867 }
868 if options.strip {
869 strip_except(wesl, keep);
870 }
871 Ok(())
872}
873
874/// Low-level version of [`Wesl::compile`].
875/// To get a source map, use [`compile_sourcemap`] instead.
876pub fn compile(
877 root: &ModulePath,
878 resolver: &impl Resolver,
879 mangler: &impl Mangler,
880 options: &CompileOptions,
881) -> Result<CompileResult, Error> {
882 let (mut resolutions, keep) = compile_pre_assembly(root, resolver, options)?;
883 resolutions.mangle(mangler, options.mangle_root);
884 let mut assembly = resolutions.assemble(options.strip && options.lazy);
885 // resolutions hold idents use-counts. We only need the list of modules now.
886 let modules = resolutions.into_module_order();
887 compile_post_assembly(&mut assembly, options, &keep)?;
888 Ok(CompileResult {
889 syntax: assembly,
890 sourcemap: None,
891 modules,
892 })
893}
894
895/// Like [`compile`], but provides better error diagnostics and returns the sourcemap.
896pub fn compile_sourcemap(
897 root: &ModulePath,
898 resolver: &impl Resolver,
899 mangler: &impl Mangler,
900 options: &CompileOptions,
901) -> Result<CompileResult, Error> {
902 let sourcemapper = SourceMapper::new(root, resolver, mangler);
903
904 match compile_pre_assembly(root, &sourcemapper, options) {
905 Ok((mut resolutions, keep)) => {
906 resolutions.mangle(&sourcemapper, options.mangle_root);
907 let sourcemap = sourcemapper.finish();
908 let mut assembly = resolutions.assemble(options.strip && options.lazy);
909 let modules = resolutions.into_module_order();
910 compile_post_assembly(&mut assembly, options, &keep)
911 .map_err(|e| {
912 Diagnostic::from(e)
913 .with_output(assembly.to_string())
914 .with_sourcemap(&sourcemap)
915 .unmangle(Some(&sourcemap), Some(&mangler))
916 .into()
917 })
918 .map(|()| CompileResult {
919 syntax: assembly,
920 sourcemap: Some(sourcemap),
921 modules,
922 })
923 }
924 Err(e) => {
925 let sourcemap = sourcemapper.finish();
926 Err(Diagnostic::from(e)
927 .with_sourcemap(&sourcemap)
928 .unmangle(Some(&sourcemap), Some(&mangler))
929 .into())
930 }
931 }
932}
933
934/// Evaluate a const-expression.
935///
936/// Only builtin function declarations marked `@const` can be called from
937/// const-expressions.
938///
939/// Highly experimental. Not all builtin `@const` WGSL functions are supported yet.
940#[cfg(feature = "eval")]
941pub fn eval_str(expr: &str) -> Result<eval::Instance, Error> {
942 let expr = expr
943 .parse::<syntax::Expression>()
944 .map_err(|e| Error::Error(Diagnostic::from(e).with_source(expr.to_string())))?;
945 let wgsl = TranslationUnit::default();
946 let (inst, ctx) = eval(&expr, &wgsl);
947 inst.map_err(|e| {
948 Error::Error(
949 Diagnostic::from(e)
950 .with_source(expr.to_string())
951 .with_ctx(&ctx),
952 )
953 })
954}
955
956/// Low-level version of [`eval_str`].
957#[cfg(feature = "eval")]
958pub fn eval<'s>(
959 expr: &syntax::Expression,
960 wgsl: &'s TranslationUnit,
961) -> (Result<eval::Instance, EvalError>, eval::Context<'s>) {
962 let mut ctx = eval::Context::new(wgsl);
963 let res = wgsl.exec(&mut ctx).and_then(|_| expr.eval(&mut ctx));
964 (res, ctx)
965}
966
967/// Low-level version of [`CompileResult::exec`].
968#[cfg(feature = "eval")]
969pub fn exec<'s>(
970 expr: &impl Eval,
971 wgsl: &'s TranslationUnit,
972 bindings: HashMap<(u32, u32), eval::RefInstance>,
973 overrides: HashMap<String, eval::Instance>,
974) -> (Result<Option<eval::Instance>, EvalError>, eval::Context<'s>) {
975 let mut ctx = eval::Context::new(wgsl);
976 ctx.add_bindings(bindings);
977 ctx.add_overrides(overrides);
978 ctx.set_stage(eval::EvalStage::Exec);
979
980 let res = wgsl.exec(&mut ctx).and_then(|_| match expr.eval(&mut ctx) {
981 Ok(ret) => Ok(Some(ret)),
982 Err(eval::EvalError::Void(_)) => Ok(None),
983 Err(e) => Err(e),
984 });
985 (res, ctx)
986}
987
988#[test]
989fn test_send_sync() {
990 fn assert_send_sync<T: Send + Sync>() {}
991 assert_send_sync::<Wesl<StandardResolver>>();
992}