Skip to main content

wesl/eval/
exec.rs

1use std::{collections::HashMap, fmt::Display, iter::zip};
2use wgsl_types::{
3    ShaderStage,
4    builtin::{call_builtin_fn, is_ctor, struct_ctor},
5    conv::Convert,
6    inst::{Instance, LiteralInstance, RefInstance, VecInstance},
7    syntax::{AccessMode, AddressSpace},
8    tplt::TpltParam,
9    ty::{Ty, Type},
10};
11
12use crate::eval::PRELUDE;
13
14use super::{
15    ATTR_INTRINSIC, Context, Eval, EvalError, EvalTy, ScopeKind, SyntaxUtil, attrs::EvalAttrs,
16    eval_tplt_arg, ty_eval_ty,
17};
18
19use wgsl_parse::{SyntaxNode, span::Spanned, syntax::*};
20
21type E = EvalError;
22
23// reference: https://www.w3.org/TR/WGSL/#behaviors
24#[derive(Clone, Debug, PartialEq)]
25pub enum Flow {
26    Next,
27    Break,
28    Continue,
29    Return(Option<Instance>),
30}
31
32impl From<Instance> for Flow {
33    fn from(inst: Instance) -> Self {
34        Self::Return(Some(inst))
35    }
36}
37
38macro_rules! with_stage {
39    ($ctx:expr, $stage:expr, $body:tt) => {{
40        let stage = $ctx.stage;
41        $ctx.stage = $stage;
42        #[allow(clippy::redundant_closure_call)]
43        let body = (|| $body)();
44        $ctx.stage = stage;
45        body
46    }};
47}
48
49// because some places in the grammar requires that no scope is created when executing a
50// CompoundStatement, such as for loops with initializer or function invocations.
51//
52// * 'Regular': compute statements in a regular scope, can shadow parent scope.
53// * 'Transparent': parent scope declarations cannot be shadowed.
54// * 'Leaking': local declarations are leaked into the parent scope.
55#[derive(Clone, Copy)]
56pub(crate) enum CompoundScope {
57    Regular,
58    Transparent,
59    Leaking,
60}
61
62impl CompoundScope {
63    pub(crate) fn push(&self, ctx: &mut Context) {
64        match self {
65            CompoundScope::Regular => {
66                ctx.scope.push();
67            }
68            CompoundScope::Transparent => {
69                ctx.scope.push_transparent();
70            }
71            CompoundScope::Leaking => {}
72        }
73    }
74
75    pub(crate) fn pop(&self, ctx: &mut Context) {
76        match self {
77            CompoundScope::Regular | CompoundScope::Transparent => {
78                ctx.scope.pop();
79            }
80            CompoundScope::Leaking => {}
81        }
82    }
83}
84
85macro_rules! with_scope {
86    ($ctx:expr, $body:tt) => {{ with_scope!($ctx, CompoundScope::Regular, $body) }};
87    ($ctx:expr, $scoping:expr, $body:tt) => {{
88        $scoping.push($ctx);
89        #[allow(clippy::redundant_closure_call)]
90        let body = (|| $body)();
91        $scoping.pop($ctx);
92        body
93    }};
94}
95
96macro_rules! module_scope {
97    ($ctx:expr, $body:tt) => {{
98        assert!($ctx.scope.is_root());
99        let kind = $ctx.kind;
100        $ctx.kind = ScopeKind::Module;
101        #[allow(clippy::redundant_closure_call)]
102        let body = (|| $body)();
103        $ctx.kind = kind;
104        body
105    }};
106}
107
108macro_rules! function_scope {
109    ($ctx:expr, $body:tt) => {{
110        assert!(!$ctx.scope.is_root());
111        let kind = $ctx.kind;
112        $ctx.kind = ScopeKind::Function;
113        #[allow(clippy::redundant_closure_call)]
114        let body = (|| $body)();
115        $ctx.kind = kind;
116        body
117    }};
118}
119
120pub(super) use with_scope;
121pub(super) use with_stage;
122
123impl Display for Flow {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        match self {
126            Flow::Next => write!(f, "void"),
127            Flow::Break => write!(f, "break"),
128            Flow::Continue => write!(f, "continue"),
129            Flow::Return(_) => write!(f, "return"),
130        }
131    }
132}
133
134pub trait Exec {
135    fn exec(&self, ctx: &mut Context) -> Result<Flow, E>;
136}
137
138impl<T: Exec> Exec for Spanned<T> {
139    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
140        self.node().exec(ctx).inspect_err(|_| {
141            ctx.set_err_span_ctx(self.span());
142        })
143    }
144}
145
146impl Exec for TranslationUnit {
147    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
148        module_scope!(ctx, {
149            for decl in PRELUDE
150                .global_declarations
151                .iter()
152                .chain(&ctx.source.global_declarations)
153            {
154                let flow = decl.exec(ctx)?;
155                match flow {
156                    Flow::Next => (),
157                    Flow::Break | Flow::Continue | Flow::Return(_) => {
158                        if let Some(ident) = decl.ident() {
159                            ctx.set_err_decl_ctx(ident.to_string());
160                        }
161                        return Err(E::FlowInModule(flow));
162                    }
163                }
164            }
165
166            Ok(Flow::Next)
167        })
168    }
169}
170
171impl Exec for GlobalDeclaration {
172    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
173        match self {
174            GlobalDeclaration::Declaration(decl) => {
175                if ctx.scope.contains(&decl.ident.name()) {
176                    // because of module-scope hoisting, declarations may be executed out-of-order.
177                    // TODO: check no duplicate declarations?
178                    Ok(Flow::Next)
179                } else {
180                    decl.exec(ctx)
181                }
182            }
183            GlobalDeclaration::ConstAssert(decl) => decl.exec(ctx),
184            _ => Ok(Flow::Next),
185        }
186        .inspect_err(|_| {
187            if let Some(ident) = self.ident() {
188                ctx.set_err_decl_ctx(ident.to_string());
189            }
190        })
191    }
192}
193
194impl Exec for Statement {
195    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
196        match self {
197            Statement::Void => Ok(Flow::Next),
198            Statement::Compound(s) => s.exec(ctx),
199            Statement::Assignment(s) => s.exec(ctx),
200            Statement::Increment(s) => s.exec(ctx),
201            Statement::Decrement(s) => s.exec(ctx),
202            Statement::If(s) => s.exec(ctx),
203            Statement::Switch(s) => s.exec(ctx),
204            Statement::Loop(s) => s.exec(ctx),
205            Statement::For(s) => s.exec(ctx),
206            Statement::While(s) => s.exec(ctx),
207            Statement::Break(s) => s.exec(ctx),
208            Statement::Continue(s) => s.exec(ctx),
209            Statement::Return(s) => s.exec(ctx),
210            Statement::Discard(s) => s.exec(ctx),
211            Statement::FunctionCall(s) => s.exec(ctx),
212            Statement::ConstAssert(s) => s.exec(ctx),
213            Statement::Declaration(s) => s.exec(ctx),
214        }
215    }
216}
217
218impl Exec for CompoundStatement {
219    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
220        compound_exec(self, ctx, CompoundScope::Regular)
221    }
222}
223
224pub(crate) fn compound_exec(
225    stmt: &CompoundStatement,
226    ctx: &mut Context,
227    scoping: CompoundScope,
228) -> Result<Flow, E> {
229    with_scope!(ctx, scoping, {
230        for stmt in &stmt.statements {
231            let flow = stmt.exec(ctx)?;
232            match flow {
233                Flow::Next => (),
234                Flow::Break | Flow::Continue | Flow::Return(_) => {
235                    return Ok(flow);
236                }
237            }
238        }
239        Ok(Flow::Next)
240    })
241}
242
243impl Exec for AssignmentStatement {
244    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
245        let is_phony = matches!(self.lhs.node(), Expression::TypeOrIdentifier(TypeExpression { path: None, ident, template_args: None }) if *ident.name() == "_");
246        if self.operator == AssignmentOperator::Equal && is_phony {
247            let _ = self.rhs.eval(ctx)?;
248            return Ok(Flow::Next);
249        }
250
251        let lhs = self.lhs.eval(ctx)?;
252
253        if let Instance::Ref(r) = lhs {
254            let rhs = self.rhs.eval_value(ctx)?;
255            match self.operator {
256                AssignmentOperator::Equal => {
257                    let rhs = rhs
258                        .convert_to(&r.ty)
259                        .ok_or_else(|| E::AssignType(rhs.ty(), r.ty.clone()))?;
260                    r.write(rhs)?;
261                }
262                AssignmentOperator::PlusEqual => {
263                    let val = r.read()?.op_add(&rhs, ctx.stage)?;
264                    r.write(val)?;
265                }
266                AssignmentOperator::MinusEqual => {
267                    let val = r.read()?.op_sub(&rhs, ctx.stage)?;
268                    r.write(val)?;
269                }
270                AssignmentOperator::TimesEqual => {
271                    let val = r.read()?.op_mul(&rhs, ctx.stage)?;
272                    r.write(val)?;
273                }
274                AssignmentOperator::DivisionEqual => {
275                    let val = r.read()?.op_div(&rhs, ctx.stage)?;
276                    r.write(val)?;
277                }
278                AssignmentOperator::ModuloEqual => {
279                    let val = r.read()?.op_rem(&rhs, ctx.stage)?;
280                    r.write(val)?;
281                }
282                AssignmentOperator::AndEqual => {
283                    let val = r.read()?.op_bitand(&rhs)?;
284                    r.write(val)?;
285                }
286                AssignmentOperator::OrEqual => {
287                    let val = r.read()?.op_bitor(&rhs)?;
288                    r.write(val)?;
289                }
290                AssignmentOperator::XorEqual => {
291                    let val = r.read()?.op_bitxor(&rhs)?;
292                    r.write(val)?;
293                }
294                AssignmentOperator::ShiftRightAssign => {
295                    let val = r.read()?.op_shr(&rhs, ctx.stage)?;
296                    r.write(val)?;
297                }
298                AssignmentOperator::ShiftLeftAssign => {
299                    let val = r.read()?.op_shl(&rhs, ctx.stage)?;
300                    r.write(val)?;
301                }
302            }
303            Ok(Flow::Next)
304        } else {
305            Err(E::NotRef(lhs))
306        }
307    }
308}
309
310impl Exec for IncrementStatement {
311    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
312        let expr = self.expression.eval(ctx)?;
313        if let Instance::Ref(r) = expr {
314            let mut r = r.read_write()?;
315            match &*r {
316                Instance::Literal(LiteralInstance::I32(n)) => {
317                    let val = n.checked_add(1).ok_or(E::IncrOverflow)?;
318                    let _ = r.write(LiteralInstance::I32(val).into());
319                    Ok(Flow::Next)
320                }
321                Instance::Literal(LiteralInstance::U32(n)) => {
322                    let val = n.checked_add(1).ok_or(E::IncrOverflow)?;
323                    let _ = r.write(LiteralInstance::U32(val).into());
324                    Ok(Flow::Next)
325                }
326                i => Err(E::IncrType(i.ty())),
327            }
328        } else {
329            Err(E::NotRef(expr))
330        }
331    }
332}
333
334impl Exec for DecrementStatement {
335    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
336        let expr = self.expression.eval(ctx)?;
337        if let Instance::Ref(r) = expr {
338            let mut r = r.read_write()?;
339            match &*r {
340                Instance::Literal(LiteralInstance::I32(n)) => {
341                    let val = n.checked_sub(1).ok_or(E::DecrOverflow)?;
342                    let _ = r.write(LiteralInstance::I32(val).into());
343                    Ok(Flow::Next)
344                }
345                Instance::Literal(LiteralInstance::U32(n)) => {
346                    let val = n.checked_sub(1).ok_or(E::DecrOverflow)?;
347                    let _ = r.write(LiteralInstance::U32(val).into());
348                    Ok(Flow::Next)
349                }
350                r => Err(E::DecrType(r.ty())),
351            }
352        } else {
353            Err(E::NotRef(expr))
354        }
355    }
356}
357
358impl Exec for IfStatement {
359    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
360        {
361            let expr = self.if_clause.expression.eval_value(ctx)?;
362            let cond = match expr {
363                Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
364                _ => Err(E::Type(Type::Bool, expr.ty())),
365            }?;
366
367            if cond {
368                let flow = self.if_clause.body.exec(ctx)?;
369                return Ok(flow);
370            }
371        }
372
373        for elif in &self.else_if_clauses {
374            let expr = elif.expression.eval_value(ctx)?;
375            let cond = match expr {
376                Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
377                _ => Err(E::Type(Type::Bool, expr.ty())),
378            }?;
379            if cond {
380                let flow = elif.body.exec(ctx)?;
381                return Ok(flow);
382            }
383        }
384
385        if let Some(el) = &self.else_clause {
386            let flow = el.body.exec(ctx)?;
387            return Ok(flow);
388        }
389
390        Ok(Flow::Next)
391    }
392}
393
394impl Exec for SwitchStatement {
395    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
396        let expr = self.expression.eval_value(ctx)?;
397        let ty = expr.ty();
398
399        for clause in &self.clauses {
400            for selector in &clause.case_selectors {
401                match selector {
402                    CaseSelector::Default => {
403                        let flow = clause.body.exec(ctx)?;
404                        if flow == Flow::Break {
405                            return Ok(Flow::Next);
406                        } else {
407                            return Ok(flow);
408                        }
409                    }
410                    CaseSelector::Expression(e) => {
411                        let e = with_stage!(ctx, ShaderStage::Const, { e.eval_value(ctx) })?;
412                        let e = e
413                            .convert_to(&ty)
414                            .ok_or_else(|| E::Conversion(e.ty(), ty.clone()))?;
415                        if e == expr {
416                            let flow = clause.body.exec(ctx)?;
417                            if flow == Flow::Break {
418                                return Ok(Flow::Next);
419                            } else {
420                                return Ok(flow);
421                            }
422                        }
423                    }
424                }
425            }
426        }
427
428        Ok(Flow::Next)
429    }
430}
431
432impl Exec for LoopStatement {
433    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
434        loop {
435            let flow = with_scope!(ctx, {
436                // we track scope manually because the continuing statement is actually
437                // part of the body block.
438                let flow = compound_exec(&self.body, ctx, CompoundScope::Leaking)?;
439                match flow {
440                    Flow::Next | Flow::Continue => {
441                        if let Some(cont) = &self.continuing {
442                            cont.exec(ctx) // may be Flow::Next or Flow::Break (for break-if)
443                        } else {
444                            Ok(Flow::Next)
445                        }
446                    }
447                    Flow::Break | Flow::Return(_) => Ok(flow),
448                }
449            })?;
450
451            // we end the loop if there was a return, break or break-if.
452            match flow {
453                Flow::Next | Flow::Continue => (),
454                Flow::Break => return Ok(Flow::Next),
455                Flow::Return(_) => return Ok(flow),
456            }
457        }
458    }
459}
460
461impl Exec for ContinuingStatement {
462    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
463        with_scope!(ctx, {
464            // we track scope manually because the break-if statement is actually
465            // part of the body block.
466            let flow = compound_exec(&self.body, ctx, CompoundScope::Leaking)?;
467            match flow {
468                Flow::Next => {
469                    if let Some(break_if) = &self.break_if {
470                        let expr = break_if.expression.eval_value(ctx)?;
471                        let cond = match expr {
472                            Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
473                            _ => Err(E::Type(Type::Bool, expr.ty())),
474                        }?;
475                        if cond {
476                            Ok(Flow::Break)
477                        } else {
478                            Ok(Flow::Next)
479                        }
480                    } else {
481                        Ok(Flow::Next)
482                    }
483                }
484                Flow::Break | Flow::Continue | Flow::Return(_) => Err(E::FlowInContinuing(flow)),
485            }
486        })
487    }
488}
489
490impl Exec for ForStatement {
491    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
492        // the initializer is in the same scope as the body.
493        // https://github.com/gpuweb/gpuweb/issues/5024
494        with_scope!(ctx, {
495            if let Some(init) = &self.initializer {
496                let flow = init.exec(ctx)?;
497                if flow != Flow::Next {
498                    return Ok(flow);
499                }
500            }
501
502            loop {
503                let cond = self
504                    .condition
505                    .as_ref()
506                    .map(|expr| {
507                        let expr = expr.eval_value(ctx)?;
508                        match expr {
509                            Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
510                            _ => Err(E::Type(Type::Bool, expr.ty())),
511                        }
512                    })
513                    .unwrap_or(Ok(false))?;
514
515                if !cond {
516                    break;
517                }
518
519                // the body has to run in the same scope as the initializer.
520                let flow = compound_exec(&self.body, ctx, CompoundScope::Transparent)?;
521
522                match flow {
523                    Flow::Next | Flow::Continue => {
524                        if let Some(updt) = &self.update {
525                            updt.exec(ctx)?;
526                        }
527                    }
528                    Flow::Break => {
529                        break;
530                    }
531                    Flow::Return(_) => {
532                        return Ok(flow);
533                    }
534                }
535            }
536
537            Ok(Flow::Next)
538        })
539    }
540}
541
542impl Exec for WhileStatement {
543    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
544        loop {
545            let expr = self.condition.eval_value(ctx)?;
546            let cond = match expr {
547                Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
548                _ => Err(E::Type(Type::Bool, expr.ty())),
549            }?;
550
551            if cond {
552                let flow = self.body.exec(ctx)?;
553                match flow {
554                    Flow::Next | Flow::Continue => (),
555                    Flow::Break => return Ok(Flow::Next),
556                    Flow::Return(_) => return Ok(flow),
557                }
558            } else {
559                return Ok(Flow::Next);
560            }
561        }
562    }
563}
564
565impl Exec for BreakStatement {
566    fn exec(&self, _ctx: &mut Context) -> Result<Flow, E> {
567        Ok(Flow::Break)
568    }
569}
570
571impl Exec for ContinueStatement {
572    fn exec(&self, _ctx: &mut Context) -> Result<Flow, E> {
573        Ok(Flow::Continue)
574    }
575}
576
577impl Exec for ReturnStatement {
578    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
579        if let Some(e) = &self.expression {
580            let inst = e.eval_value(ctx)?;
581            Ok(Flow::Return(Some(inst)))
582        } else {
583            Ok(Flow::Return(None))
584        }
585    }
586}
587
588impl Exec for DiscardStatement {
589    fn exec(&self, _ctx: &mut Context) -> Result<Flow, E> {
590        Err(E::DiscardInConst)
591    }
592}
593
594impl Exec for FunctionCallStatement {
595    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
596        let ty = ctx.source.resolve_ty(&self.call.ty);
597        let fn_name = ty.ident.to_string();
598
599        let is_must_use = match ctx.source.decl(&fn_name) {
600            Some(GlobalDeclaration::Function(decl)) => decl.contains_attribute(&Attribute::MustUse),
601            Some(GlobalDeclaration::Struct(_)) => true,
602            Some(_) => return Err(E::NotCallable(fn_name)),
603            None => {
604                if is_ctor(&fn_name) {
605                    true
606                } else {
607                    return Err(E::UnknownFunction(fn_name));
608                }
609            }
610        };
611
612        if is_must_use {
613            return Err(E::MustUse(fn_name));
614        }
615
616        self.call.exec(ctx).map(|_| Flow::Next)
617    }
618}
619
620fn exec_fn(
621    decl: &Function,
622    tplt: Option<Vec<TpltParam>>,
623    args: Vec<Instance>,
624    ctx: &mut Context,
625) -> Result<Option<Instance>, E> {
626    let fn_name = decl.ident.to_string();
627
628    if ctx.stage == ShaderStage::Const && !decl.contains_attribute(&Attribute::Const) {
629        return Err(E::NotConst(decl.ident.to_string()));
630    }
631
632    if decl.body.contains_attribute(&ATTR_INTRINSIC) {
633        let call_res = call_builtin_fn(&fn_name, tplt.as_deref(), &args, ctx.stage)?;
634        return Ok(call_res);
635    }
636
637    if args.len() != decl.parameters.len() {
638        return Err(E::ParamCount(
639            decl.ident.to_string(),
640            decl.parameters.len(),
641            args.len(),
642        ));
643    }
644
645    let ret_ty = decl
646        .return_type
647        .as_ref()
648        .map(|expr| ty_eval_ty(expr, ctx))
649        .transpose()?;
650
651    let flow = with_scope!(ctx, {
652        let args = args
653            .iter()
654            .zip(&decl.parameters)
655            .map(|(arg, param)| {
656                let param_ty = ty_eval_ty(&param.ty, ctx)?;
657                arg.convert_to(&param_ty)
658                    .ok_or_else(|| E::ParamType(param_ty.clone(), arg.ty()))
659            })
660            .collect::<Result<Vec<_>, _>>()
661            .inspect_err(|_| ctx.set_err_decl_ctx(fn_name.clone()))?;
662
663        for (a, p) in zip(args, &decl.parameters) {
664            if !ctx.scope.add(p.ident.to_string(), a) {
665                return Err(E::DuplicateDecl(p.ident.to_string()));
666            }
667        }
668
669        let flow = function_scope!(ctx, {
670            // the arguments must be in the same scope as the function body.
671            compound_exec(&decl.body, ctx, CompoundScope::Transparent)
672                .inspect_err(|_| ctx.set_err_decl_ctx(fn_name.clone()))
673        })?;
674
675        Ok(flow)
676    })?;
677
678    match (flow, ret_ty) {
679        (flow @ (Flow::Break | Flow::Continue), _) => Err(E::FlowInFunction(flow)),
680        (Flow::Return(Some(inst)), Some(ret_ty)) => inst
681            .convert_to(&ret_ty)
682            .ok_or(E::ReturnType(inst.ty(), fn_name.clone(), ret_ty))
683            .map(Into::into)
684            .inspect_err(|_| ctx.set_err_decl_ctx(fn_name)),
685        (Flow::Return(Some(inst)), None) => Err(E::UnexpectedReturn(fn_name, inst.ty())),
686        (Flow::Next | Flow::Return(None), Some(ret_ty)) => Err(E::NoReturn(fn_name, ret_ty)),
687        (Flow::Next | Flow::Return(None), None) => Ok(None),
688    }
689}
690
691impl Exec for FunctionCall {
692    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
693        let ty = ctx.source.resolve_ty(&self.ty);
694        let fn_name = ty.ident.to_string();
695
696        let tplt = ty
697            .template_args
698            .as_ref()
699            .map(|t| {
700                t.iter()
701                    .map(|arg| eval_tplt_arg(arg, ctx))
702                    .collect::<Result<Vec<_>, _>>()
703            })
704            .transpose()?;
705
706        let args = self
707            .arguments
708            .iter()
709            .map(|a| a.eval_value(ctx))
710            .collect::<Result<Vec<_>, _>>()?;
711
712        if let Some(decl) = ctx.source.decl(&fn_name) {
713            if let GlobalDeclaration::Function(decl) = decl {
714                exec_fn(decl, tplt, args, ctx).map(Flow::Return)
715            } else if let GlobalDeclaration::Struct(decl) = decl {
716                let struct_ty = *decl.eval_ty(ctx)?.unwrap_struct();
717                let inst = struct_ctor(&struct_ty, &args)?;
718                Ok(Flow::Return(Some(Instance::from(inst))))
719            } else {
720                Err(E::NotCallable(fn_name))
721            }
722        } else if is_ctor(&fn_name) {
723            let call_res = call_builtin_fn(&fn_name, tplt.as_deref(), &args, ctx.stage)?;
724            Ok(Flow::Return(call_res))
725        } else {
726            Err(E::UnknownFunction(fn_name))
727        }
728    }
729}
730
731/// shader inputs passed to the entry point function.
732/// user-defined inputs must be scalars, max. 16 bytes in size.
733/// see <https://www.w3.org/TR/WGSL/#input-output-locations>
734#[derive(Debug, Clone, Default)]
735pub struct Inputs {
736    pub vertex_index: Option<u32>,
737    pub instance_index: Option<u32>,
738    pub position: Option<[f32; 4]>,
739    pub front_facing: Option<bool>,
740    pub sample_index: Option<u32>,
741    pub sample_mask: Option<u32>,
742    pub local_invocation_id: Option<[u32; 3]>,
743    pub local_invocation_index: Option<u32>,
744    pub global_invocation_id: Option<[u32; 3]>,
745    pub workgroup_id: Option<[u32; 3]>,
746    pub num_workgroups: Option<[u32; 3]>,
747    /// Within the range [0, subgroup_size - 1]
748    pub subgroup_invocation_id: Option<u32>,
749    /// A power of two within the range [4, 128]
750    pub subgroup_size: Option<u32>,
751    #[cfg(feature = "naga-ext")]
752    pub subgroup_id: Option<u32>,
753    #[cfg(feature = "naga-ext")]
754    pub num_subgroups: Option<u32>,
755    #[cfg(feature = "naga-ext")]
756    pub primitive_index: Option<u32>,
757    #[cfg(feature = "naga-ext")]
758    pub barycentric: Option<[f32; 3]>,
759    #[cfg(feature = "naga-ext")]
760    pub barycentric_no_perspective: Option<[f32; 3]>,
761    #[cfg(feature = "naga-ext")]
762    pub view_index: Option<u32>,
763
764    // ray tracing pipelines
765    #[cfg(feature = "naga-ext")]
766    pub ray_invocation_id: Option<[u32; 3]>,
767    #[cfg(feature = "naga-ext")]
768    pub num_ray_invocations: Option<[u32; 3]>,
769    #[cfg(feature = "naga-ext")]
770    pub instance_custom_data: Option<u32>,
771    #[cfg(feature = "naga-ext")]
772    pub geometry_index: Option<u32>,
773    #[cfg(feature = "naga-ext")]
774    pub world_ray_origin: Option<[f32; 3]>,
775    #[cfg(feature = "naga-ext")]
776    pub world_ray_direction: Option<[f32; 3]>,
777    #[cfg(feature = "naga-ext")]
778    pub object_ray_origin: Option<[f32; 3]>,
779    #[cfg(feature = "naga-ext")]
780    pub object_ray_direction: Option<[f32; 3]>,
781    #[cfg(feature = "naga-ext")]
782    pub ray_t_min: Option<f32>,
783    #[cfg(feature = "naga-ext")]
784    pub ray_t_current_max: Option<f32>,
785    /// Column-major, 4 columns of 3 rows.
786    #[cfg(feature = "naga-ext")]
787    pub object_to_world: Option<[[f32; 3]; 4]>,
788    /// Column-major, 4 columns of 3 rows.
789    #[cfg(feature = "naga-ext")]
790    pub world_to_object: Option<[[f32; 3]; 4]>,
791    #[cfg(feature = "naga-ext")]
792    pub hit_kind: Option<u32>,
793
794    pub user_defined: HashMap<u32, Instance>,
795}
796
797impl Inputs {
798    pub fn new_zero_initialized() -> Self {
799        Self {
800            vertex_index: Some(0),
801            instance_index: Some(0),
802            position: Some([0.0, 0.0, 0.0, 0.0]),
803            front_facing: Some(true),
804            sample_index: Some(0),
805            sample_mask: Some(0),
806            local_invocation_id: Some([0, 0, 0]),
807            local_invocation_index: Some(0),
808            global_invocation_id: Some([0, 0, 0]),
809            workgroup_id: Some([0, 0, 0]),
810            num_workgroups: Some([1, 1, 1]),
811            subgroup_invocation_id: Some(0),
812            subgroup_size: Some(4),
813            #[cfg(feature = "naga-ext")]
814            subgroup_id: Some(0),
815            #[cfg(feature = "naga-ext")]
816            num_subgroups: Some(1),
817            #[cfg(feature = "naga-ext")]
818            primitive_index: Some(0),
819            #[cfg(feature = "naga-ext")]
820            barycentric: Some([0.0, 0.0, 0.0]),
821            #[cfg(feature = "naga-ext")]
822            barycentric_no_perspective: Some([0.0, 0.0, 0.0]),
823            #[cfg(feature = "naga-ext")]
824            view_index: Some(0),
825            #[cfg(feature = "naga-ext")]
826            ray_invocation_id: Some([0, 0, 0]),
827            #[cfg(feature = "naga-ext")]
828            num_ray_invocations: Some([1, 1, 1]),
829            #[cfg(feature = "naga-ext")]
830            instance_custom_data: Some(0),
831            #[cfg(feature = "naga-ext")]
832            geometry_index: Some(0),
833            #[cfg(feature = "naga-ext")]
834            world_ray_origin: Some([0.0, 0.0, 0.0]),
835            #[cfg(feature = "naga-ext")]
836            world_ray_direction: Some([0.0, 0.0, 0.0]),
837            #[cfg(feature = "naga-ext")]
838            object_ray_origin: Some([0.0, 0.0, 0.0]),
839            #[cfg(feature = "naga-ext")]
840            object_ray_direction: Some([0.0, 0.0, 0.0]),
841            #[cfg(feature = "naga-ext")]
842            ray_t_min: Some(0.0),
843            #[cfg(feature = "naga-ext")]
844            ray_t_current_max: Some(0.0),
845            #[cfg(feature = "naga-ext")]
846            object_to_world: Some([[0.0; 3]; 4]),
847            #[cfg(feature = "naga-ext")]
848            world_to_object: Some([[0.0; 3]; 4]),
849            #[cfg(feature = "naga-ext")]
850            hit_kind: Some(0),
851            user_defined: Default::default(),
852        }
853    }
854}
855
856/// Build a `mat4x3<f32>` instance from 4 columns of 3 rows.
857#[cfg(feature = "naga-ext")]
858fn mat4x3(cols: [[f32; 3]; 4]) -> Instance {
859    wgsl_types::inst::MatInstance::from_cols(cols.map(|col| VecInstance::from(col).into()).to_vec())
860        .into()
861}
862
863pub fn exec_entrypoint(
864    entrypoint: &Function,
865    inputs: Inputs,
866    ctx: &mut Context,
867) -> Result<Option<Instance>, E> {
868    let fn_name = entrypoint.ident.to_string();
869
870    let is_entrypoint = entrypoint
871        .attributes
872        .iter()
873        .any(|attr| attr.node().is_entry_point());
874    if !is_entrypoint {
875        return Err(E::NotEntrypoint(fn_name));
876    }
877
878    let args = entrypoint
879        .parameters
880        .iter()
881        .map(|p| {
882            let param_ty = ty_eval_ty(&p.ty, ctx)?;
883            let inst = if let Some(builtin) = p.attr_builtin() {
884                // TODO: check that the builtin value is available in the entrypoint type
885                match builtin {
886                    BuiltinValue::VertexIndex => inputs.vertex_index.map(Instance::from),
887                    BuiltinValue::InstanceIndex => inputs.instance_index.map(Instance::from),
888                    BuiltinValue::Position => inputs.position.map(|v| VecInstance::from(v).into()),
889                    BuiltinValue::FrontFacing => inputs.front_facing.map(Instance::from),
890                    BuiltinValue::SampleIndex => inputs.sample_index.map(Instance::from),
891                    BuiltinValue::SampleMask => inputs.sample_mask.map(Instance::from),
892                    BuiltinValue::LocalInvocationId => inputs
893                        .local_invocation_id
894                        .map(|v| VecInstance::from(v).into()),
895                    BuiltinValue::LocalInvocationIndex => {
896                        inputs.local_invocation_index.map(Instance::from)
897                    }
898                    BuiltinValue::GlobalInvocationId => inputs
899                        .global_invocation_id
900                        .map(|v| VecInstance::from(v).into()),
901                    BuiltinValue::WorkgroupId => {
902                        inputs.workgroup_id.map(|v| VecInstance::from(v).into())
903                    }
904                    BuiltinValue::NumWorkgroups => {
905                        inputs.num_workgroups.map(|v| VecInstance::from(v).into())
906                    }
907                    BuiltinValue::SubgroupInvocationId => {
908                        inputs.subgroup_invocation_id.map(Instance::from)
909                    }
910                    BuiltinValue::SubgroupSize => inputs.subgroup_size.map(Instance::from),
911                    #[cfg(feature = "naga-ext")]
912                    BuiltinValue::SubgroupId => inputs.subgroup_id.map(Instance::from),
913                    #[cfg(feature = "naga-ext")]
914                    BuiltinValue::NumSubgroups => inputs.num_subgroups.map(Instance::from),
915                    #[cfg(feature = "naga-ext")]
916                    BuiltinValue::PrimitiveIndex => inputs.primitive_index.map(Instance::from),
917                    #[cfg(feature = "naga-ext")]
918                    BuiltinValue::Barycentric => {
919                        inputs.barycentric.map(|v| VecInstance::from(v).into())
920                    }
921                    #[cfg(feature = "naga-ext")]
922                    BuiltinValue::BarycentricNoPerspective => inputs
923                        .barycentric_no_perspective
924                        .map(|v| VecInstance::from(v).into()),
925                    #[cfg(feature = "naga-ext")]
926                    BuiltinValue::ViewIndex => inputs.view_index.map(Instance::from),
927                    #[cfg(feature = "naga-ext")]
928                    BuiltinValue::RayInvocationId => inputs
929                        .ray_invocation_id
930                        .map(|v| VecInstance::from(v).into()),
931                    #[cfg(feature = "naga-ext")]
932                    BuiltinValue::NumRayInvocations => inputs
933                        .num_ray_invocations
934                        .map(|v| VecInstance::from(v).into()),
935                    #[cfg(feature = "naga-ext")]
936                    BuiltinValue::InstanceCustomData => {
937                        inputs.instance_custom_data.map(Instance::from)
938                    }
939                    #[cfg(feature = "naga-ext")]
940                    BuiltinValue::GeometryIndex => inputs.geometry_index.map(Instance::from),
941                    #[cfg(feature = "naga-ext")]
942                    BuiltinValue::WorldRayOrigin => {
943                        inputs.world_ray_origin.map(|v| VecInstance::from(v).into())
944                    }
945                    #[cfg(feature = "naga-ext")]
946                    BuiltinValue::WorldRayDirection => inputs
947                        .world_ray_direction
948                        .map(|v| VecInstance::from(v).into()),
949                    #[cfg(feature = "naga-ext")]
950                    BuiltinValue::ObjectRayOrigin => inputs
951                        .object_ray_origin
952                        .map(|v| VecInstance::from(v).into()),
953                    #[cfg(feature = "naga-ext")]
954                    BuiltinValue::ObjectRayDirection => inputs
955                        .object_ray_direction
956                        .map(|v| VecInstance::from(v).into()),
957                    #[cfg(feature = "naga-ext")]
958                    BuiltinValue::RayTMin => inputs.ray_t_min.map(Instance::from),
959                    #[cfg(feature = "naga-ext")]
960                    BuiltinValue::RayTCurrentMax => inputs.ray_t_current_max.map(Instance::from),
961                    #[cfg(feature = "naga-ext")]
962                    BuiltinValue::ObjectToWorld => inputs.object_to_world.map(mat4x3),
963                    #[cfg(feature = "naga-ext")]
964                    BuiltinValue::WorldToObject => inputs.world_to_object.map(mat4x3),
965                    #[cfg(feature = "naga-ext")]
966                    BuiltinValue::HitKind => inputs.hit_kind.map(Instance::from),
967                    BuiltinValue::ClipDistances | BuiltinValue::FragDepth => {
968                        return Err(E::OutputBuiltin(builtin));
969                    }
970                    #[cfg(feature = "naga-ext")]
971                    BuiltinValue::MeshTaskSize
972                    | BuiltinValue::Vertices
973                    | BuiltinValue::Primitives
974                    | BuiltinValue::VertexCount
975                    | BuiltinValue::PrimitiveCount
976                    | BuiltinValue::TriangleIndices
977                    | BuiltinValue::CullPrimitive => {
978                        return Err(E::OutputBuiltin(builtin));
979                    }
980                }
981                .ok_or_else(|| E::MissingBuiltinInput(builtin, p.ident.to_string()))
982            } else if let Some(location) = p.attr_location(ctx)? {
983                let inst = inputs
984                    .user_defined
985                    .get(&location)
986                    .ok_or_else(|| E::MissingUserInput(p.ident.to_string(), location))?
987                    .clone();
988                Ok(inst)
989            } else {
990                // TODO: struct of inputs is allowed
991                Err(E::InvalidEntrypointParam(p.ident.to_string()))
992            }?;
993
994            if inst.ty() != param_ty {
995                Err(E::ParamType(param_ty, inst.ty()))
996            } else {
997                Ok(inst)
998            }
999        })
1000        .collect::<Result<Vec<_>, _>>()
1001        .inspect_err(|_| ctx.set_err_decl_ctx(fn_name.clone()))?;
1002
1003    let ret_ty = entrypoint
1004        .return_type
1005        .as_ref()
1006        .map(|expr| ty_eval_ty(expr, ctx))
1007        .transpose()?;
1008
1009    let flow = with_scope!(ctx, {
1010        for (a, p) in zip(args, &entrypoint.parameters) {
1011            if !ctx.scope.add(p.ident.to_string(), a) {
1012                return Err(E::DuplicateDecl(p.ident.to_string()));
1013            }
1014        }
1015
1016        // the arguments must be in the same scope as the function body.
1017        let flow = compound_exec(&entrypoint.body, ctx, CompoundScope::Transparent)
1018            .inspect_err(|_| ctx.set_err_decl_ctx(fn_name.clone()))?;
1019
1020        Ok(flow)
1021    })?;
1022
1023    match (flow, ret_ty) {
1024        (flow @ (Flow::Break | Flow::Continue), _) => Err(E::FlowInFunction(flow)),
1025        (Flow::Return(Some(inst)), Some(ret_ty)) => inst
1026            .convert_to(&ret_ty)
1027            .ok_or(E::ReturnType(inst.ty(), fn_name.clone(), ret_ty))
1028            .map(Some)
1029            .inspect_err(|_| ctx.set_err_decl_ctx(fn_name)),
1030        (Flow::Return(Some(inst)), None) => Err(E::UnexpectedReturn(fn_name, inst.ty())),
1031        (Flow::Next | Flow::Return(None), Some(ret_ty)) => Err(E::NoReturn(fn_name, ret_ty)),
1032        (Flow::Next | Flow::Return(None), None) => Ok(None),
1033    }
1034}
1035
1036impl Exec for ConstAssertStatement {
1037    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
1038        with_stage!(ctx, ShaderStage::Const, {
1039            let expr = self.expression.eval_value(ctx)?;
1040            let cond = match expr {
1041                Instance::Literal(LiteralInstance::Bool(b)) => Ok(b),
1042                _ => Err(E::Type(Type::Bool, expr.ty())),
1043            }?;
1044
1045            if cond {
1046                Ok(Flow::Next)
1047            } else {
1048                Err(E::ConstAssertFailure(self.expression.clone()))
1049            }
1050        })
1051    }
1052}
1053
1054impl Exec for Declaration {
1055    fn exec(&self, ctx: &mut Context) -> Result<Flow, E> {
1056        if ctx.scope.local_contains(&self.ident.name()) {
1057            return Err(E::DuplicateDecl(self.ident.to_string()));
1058        }
1059
1060        let ty = match (&self.ty, &self.initializer) {
1061            (None, None) => return Err(E::UntypedDecl),
1062            (None, Some(init)) => {
1063                let ty = init.eval_ty(ctx)?.loaded();
1064                if self.kind.is_const() {
1065                    ty // only const declarations can be of abstract type.
1066                } else {
1067                    ty.concretize()
1068                }
1069            }
1070            (Some(ty), _) => ty_eval_ty(ty, ctx)?,
1071        };
1072
1073        let init = |ctx: &mut Context, stage: ShaderStage| {
1074            self.initializer
1075                .as_ref()
1076                .map(|init| {
1077                    let inst = with_stage!(ctx, stage, { init.eval_value(ctx) })?;
1078                    inst.convert_to(&ty)
1079                        .ok_or_else(|| E::Conversion(inst.ty(), ty.clone()))
1080                })
1081                .transpose()
1082        };
1083
1084        let inst = match (self.kind, ctx.kind) {
1085            (DeclarationKind::Const, _) => init(ctx, ShaderStage::Const)?
1086                .ok_or_else(|| E::UninitConst(self.ident.to_string()))?,
1087            (DeclarationKind::Override, ScopeKind::Function) => return Err(E::OverrideInFn),
1088            (DeclarationKind::Let, ScopeKind::Function) => {
1089                init(ctx, ctx.stage)?.ok_or_else(|| E::UninitLet(self.ident.to_string()))?
1090            }
1091            (DeclarationKind::Var(a_s), ScopeKind::Function) => {
1092                if !matches!(a_s, Some((AddressSpace::Function, None)) | None) {
1093                    return Err(E::ForbiddenDecl(self.kind, ctx.kind));
1094                }
1095                let inst = init(ctx, ctx.stage)?
1096                    .map(Ok)
1097                    .unwrap_or_else(|| Instance::zero_value(&ty))?;
1098
1099                RefInstance::new(inst, AddressSpace::Function, AccessMode::ReadWrite).into()
1100            }
1101            (DeclarationKind::Override, ScopeKind::Module) => {
1102                if ctx.stage == ShaderStage::Const {
1103                    Instance::Deferred(ty)
1104                } else if let Some(inst) = ctx.overridable(&self.ident.name()) {
1105                    inst.convert_to(&ty)
1106                        .ok_or_else(|| E::Conversion(inst.ty(), ty))?
1107                } else if let Some(inst) = init(ctx, ShaderStage::Override)? {
1108                    inst
1109                } else {
1110                    return Err(E::UninitOverride(self.ident.to_string()));
1111                }
1112            }
1113            (DeclarationKind::Let, ScopeKind::Module) => return Err(E::LetInMod),
1114            (DeclarationKind::Var(as_am), ScopeKind::Module) => {
1115                let (a_s, a_m) = match as_am {
1116                    Some((a_s, Some(a_m))) => (a_s, a_m),
1117                    Some((a_s, None)) => (a_s, a_s.default_access_mode()),
1118                    None => (AddressSpace::Handle, AccessMode::Read),
1119                };
1120                if ctx.stage == ShaderStage::Const && ctx.kind == ScopeKind::Module {
1121                    Instance::Deferred(Type::Ref(a_s, Box::new(ty), a_m))
1122                } else {
1123                    match a_s {
1124                        AddressSpace::Function => {
1125                            return Err(E::ForbiddenDecl(self.kind, ctx.kind));
1126                        }
1127                        AddressSpace::Private => {
1128                            // the initializer for a private variable must be a const- or override-expression
1129                            let inst = if let Some(inst) = init(ctx, ShaderStage::Override)? {
1130                                inst
1131                            } else {
1132                                Instance::zero_value(&ty)?
1133                            };
1134
1135                            RefInstance::new(inst, a_s, a_m).into()
1136                        }
1137                        AddressSpace::Uniform => {
1138                            if self.initializer.is_some() {
1139                                return Err(E::ForbiddenInitializer(a_s));
1140                            }
1141                            let (group, binding) = self.attr_group_binding(ctx)?;
1142                            let inst = ctx
1143                                .resource(group, binding)
1144                                .ok_or(E::MissingResource(group, binding))?;
1145                            if inst.ty != ty {
1146                                return Err(E::Type(ty, inst.ty.clone()));
1147                            }
1148                            if inst.space != AddressSpace::Uniform {
1149                                return Err(E::AddressSpace(a_s, inst.space));
1150                            }
1151                            if inst.access != AccessMode::Read {
1152                                return Err(E::AccessMode(AccessMode::Read, inst.access));
1153                            }
1154                            inst.clone().into()
1155                        }
1156                        AddressSpace::Storage => {
1157                            if self.initializer.is_some() {
1158                                return Err(E::ForbiddenInitializer(a_s));
1159                            }
1160                            let Some(ty) = &self.ty else {
1161                                return Err(E::UntypedDecl);
1162                            };
1163                            let ty = ty_eval_ty(ty, ctx)?;
1164                            let (group, binding) = self.attr_group_binding(ctx)?;
1165                            let inst = ctx
1166                                .resource(group, binding)
1167                                .ok_or(E::MissingResource(group, binding))?;
1168                            if ty != inst.ty {
1169                                return Err(E::Type(ty, inst.ty.clone()));
1170                            }
1171                            if inst.space != AddressSpace::Storage {
1172                                return Err(E::AddressSpace(a_s, inst.space));
1173                            }
1174                            if inst.access != a_m {
1175                                return Err(E::AccessMode(a_m, inst.access));
1176                            }
1177                            inst.clone().into()
1178                        }
1179                        AddressSpace::Workgroup => {
1180                            if self.initializer.is_some() {
1181                                return Err(E::ForbiddenInitializer(a_s));
1182                            }
1183
1184                            // the initial value for a workgroup variable is the zero-value
1185                            // TODO: there is a special case with atomics to handle.
1186                            let inst = Instance::zero_value(&ty)?;
1187
1188                            RefInstance::new(inst, a_s, a_m).into()
1189                        }
1190                        AddressSpace::Handle => todo!("handle address space"),
1191                        AddressSpace::Immediate => todo!("immediate address space"),
1192                        #[cfg(feature = "naga-ext")]
1193                        AddressSpace::TaskPayload => todo!("task_payload address space"),
1194                        #[cfg(feature = "naga-ext")]
1195                        AddressSpace::RayPayload => todo!("ray_payload address space"),
1196                        #[cfg(feature = "naga-ext")]
1197                        AddressSpace::IncomingRayPayload => {
1198                            todo!("incoming_ray_payload address space")
1199                        }
1200                    }
1201                }
1202            }
1203        };
1204
1205        if ctx.scope.add(self.ident.to_string(), inst) {
1206            Ok(Flow::Next)
1207        } else {
1208            Err(E::DuplicateDecl(self.ident.to_string()))
1209        }
1210    }
1211}