use construction::Constructor; use template_list::TemplateListIter;
mod construction; mod conversion; mod template_list;
/// Resolves the inner type of a given expression. /// /// Expects a &mut [`ExpressionContext`] and a [`Handle<Expression>`]. /// /// Returns a &[`ir::TypeInner`]. /// /// Ideally, we would simply have a function that takes a `&mut ExpressionContext` /// and returns a `&TypeResolution`. Unfortunately, this leads the borrow checker /// to conclude that the mutable borrow lasts for as long as we are using the /// `&TypeResolution`, so we can't use the `ExpressionContext` for anything else - /// like, say, resolving another operand's type. Using a macro that expands to /// two separate calls, only the first of which needs a `&mut`, /// lets the borrow checker see that the mutable borrow is over.
macro_rules! resolve_inner {
($ctx:ident, $expr:expr) => {{
$ctx.grow_types($expr)?;
$ctx.typifier()[$expr].inner_with(&$ctx.module.types)
}};
} pub(super) use resolve_inner;
/// Resolves the inner types of two given expressions. /// /// Expects a &mut [`ExpressionContext`] and two [`Handle<Expression>`]s. /// /// Returns a tuple containing two &[`ir::TypeInner`]. /// /// See the documentation of [`resolve_inner!`] for why this macro is necessary.
macro_rules! resolve_inner_binary {
($ctx:ident, $left:expr, $right:expr) => {{
$ctx.grow_types($left)?;
$ctx.grow_types($right)?;
(
$ctx.typifier()[$left].inner_with(&$ctx.module.types),
$ctx.typifier()[$right].inner_with(&$ctx.module.types),
)
}};
}
/// Resolves the type of a given expression. /// /// Expects a &mut [`ExpressionContext`] and a [`Handle<Expression>`]. /// /// Returns a &[`TypeResolution`]. /// /// See the documentation of [`resolve_inner!`] for why this macro is necessary. /// /// [`TypeResolution`]: proc::TypeResolution
macro_rules! resolve {
($ctx:ident, $expr:expr) => {{ let expr = $expr;
$ctx.grow_types(expr)?;
&$ctx.typifier()[expr]
}};
} pub(super) use resolve;
/// State for constructing a `ir::Module`. pubstruct GlobalContext<'source, 'temp, 'out> {
enable_extensions: EnableExtensions,
/// The `TranslationUnit`'s expressions arena.
ast_expressions: &'temp Arena<ast::Expression<'source>>,
// Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`.
globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>,
/// The module we're constructing.
module: &'out mut ir::Module,
/// State for lowering a statement within a function. pubstruct StatementContext<'source, 'temp, 'out> {
enable_extensions: EnableExtensions,
// WGSL AST values. /// A reference to [`TranslationUnit::expressions`] for the translation unit /// we're lowering. /// /// [`TranslationUnit::expressions`]: ast::TranslationUnit::expressions
ast_expressions: &'temp Arena<ast::Expression<'source>>,
// Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`.
globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>,
/// A map from each `ast::Local` handle to the Naga expression /// we've built for it: /// /// - WGSL function arguments become Naga [`FunctionArgument`] expressions. /// /// - WGSL `var` declarations become Naga [`LocalVariable`] expressions. /// /// - WGSL `let` declararations become arbitrary Naga expressions. /// /// This always borrows the `local_table` local variable in /// [`Lowerer::function`]. /// /// [`LocalVariable`]: ir::Expression::LocalVariable /// [`FunctionArgument`]: ir::Expression::FunctionArgument
local_table:
&'temp mut FastHashMap<Handle<ast::Local>, Declared<Typed<Handle<ir::Expression>>>>,
const_typifier: &'temp mut Typifier,
typifier: &'temp mut Typifier,
layouter: &'temp mut proc::Layouter,
function: &'out mut ir::Function, /// Stores the names of expressions that are assigned in `let` statement /// Also stores the spans of the names, for use in errors.
named_expressions: &'out mut FastIndexMap<Handle<ir::Expression>, (String, Span)>,
module: &'out mut ir::Module,
/// Which `Expression`s in `self.naga_expressions` are const expressions, in /// the WGSL sense. /// /// According to the WGSL spec, a const expression must not refer to any /// `let` declarations, even if those declarations' initializers are /// themselves const expressions. So this tracker is not simply concerned /// with the form of the expressions; it is also tracking whether WGSL says /// we should consider them to be const. See the use of `force_non_const` in /// the code for lowering `let` bindings.
local_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker,
global_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker,
}
pubstruct LocalExpressionContext<'temp, 'out> { /// A map from [`ast::Local`] handles to the Naga expressions we've built for them. /// /// This is always [`StatementContext::local_table`] for the /// enclosing statement; see that documentation for details.
local_table: &'temp FastHashMap<Handle<ast::Local>, Declared<Typed<Handle<ir::Expression>>>>,
/// Which `Expression`s in `self.naga_expressions` are const expressions, in /// the WGSL sense. /// /// See [`StatementContext::local_expression_kind_tracker`] for details.
local_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker,
}
/// The type of Naga IR expression we are lowering an [`ast::Expression`] to. pubenum ExpressionContextType<'temp, 'out> { /// We are lowering to an arbitrary runtime expression, to be /// included in a function's body. /// /// The given [`LocalExpressionContext`] holds information about local /// variables, arguments, and other definitions available only to runtime /// expressions, not constant or override expressions.
Runtime(LocalExpressionContext<'temp, 'out>),
/// We are lowering to a constant expression, to be included in the module's /// constant expression arena. /// /// Everything global constant expressions are allowed to refer to is /// available in the [`ExpressionContext`], but local constant expressions can /// also refer to other
Constant(Option<LocalExpressionContext<'temp, 'out>>),
/// We are lowering to an override expression, to be included in the module's /// constant expression arena. /// /// Everything override expressions are allowed to refer to is /// available in the [`ExpressionContext`], so this variant /// carries no further information. Override,
}
/// State for lowering an [`ast::Expression`] to Naga IR. /// /// [`ExpressionContext`]s come in two kinds, distinguished by /// the value of the [`expr_type`] field: /// /// - A [`Runtime`] context contributes [`naga::Expression`]s to a [`naga::Function`]'s /// runtime expression arena. /// /// - A [`Constant`] context contributes [`naga::Expression`]s to a [`naga::Module`]'s /// constant expression arena. /// /// [`ExpressionContext`]s are constructed in restricted ways: /// /// - To get a [`Runtime`] [`ExpressionContext`], call /// [`StatementContext::as_expression`]. /// /// - To get a [`Constant`] [`ExpressionContext`], call /// [`GlobalContext::as_const`]. /// /// - You can demote a [`Runtime`] context to a [`Constant`] context /// by calling [`as_const`], but there's no way to go in the other /// direction, producing a runtime context from a constant one. This /// is because runtime expressions can refer to constant /// expressions, via [`Expression::Constant`], but constant /// expressions can't refer to a function's expressions. /// /// Not to be confused with `wgsl::parse::ExpressionContext`, which is /// for parsing the `ast::Expression` in the first place. /// /// [`expr_type`]: ExpressionContext::expr_type /// [`Runtime`]: ExpressionContextType::Runtime /// [`naga::Expression`]: ir::Expression /// [`naga::Function`]: ir::Function /// [`Constant`]: ExpressionContextType::Constant /// [`naga::Module`]: ir::Module /// [`as_const`]: ExpressionContext::as_const /// [`Expression::Constant`]: ir::Expression::Constant pubstruct ExpressionContext<'source, 'temp, 'out> {
enable_extensions: EnableExtensions,
// Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`.
globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>,
/// The IR [`Module`] we're constructing. /// /// [`Module`]: ir::Module
module: &'out mut ir::Module,
/// Whether we are lowering a constant expression or a general /// runtime expression, and the data needed in each case.
expr_type: ExpressionContextType<'temp, 'out>,
}
fn write_unnamed_struct<W: core::fmt::Write>(
&self,
_: &ir::TypeInner,
_: &mut W,
) -> core::fmt::Result {
unreachable!("the WGSL front end should always know the type name");
}
}
/// Return a wrapper around `value` suitable for formatting. /// /// Return a wrapper around `value` that implements /// [`core::fmt::Display`] in a form suitable for use in /// diagnostic messages. constfn as_diagnostic_display<T>(
&self,
value: T,
) -> crate::common::DiagnosticDisplay<(T, proc::GlobalCtx<'_>)> { let ctx = self.module.to_ctx(); crate::common::DiagnosticDisplay((value, ctx))
}
let index = self
.module
.to_ctx()
.get_const_val_from::<u32, _>(expr, &rctx.function.expressions)
.map_err(|err| match err {
proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => {
Error::ExpectedConstExprConcreteIntegerScalar(component_span)
}
proc::ConstValueError::Negative => {
Error::ExpectedNonNegative(component_span)
}
})?;
ir::SwizzleComponent::XYZW
.get(index as usize)
.copied()
.ok_or(Box::new(Error::InvalidGatherComponent(component_span)))
} // This means a `gather` operation appeared in a constant expression. // This error refers to the `gather` itself, not its "component" argument.
ExpressionContextType::Constant(_) | ExpressionContextType::Override => Err(Box::new(
Error::UnexpectedOperationInConstContext(gather_span),
)),
}
}
/// Determine the type of `handle`, and add it to the module's arena. /// /// If you just need a `TypeInner` for `handle`'s type, use the /// [`resolve_inner!`] macro instead. This function /// should only be used when the type of `handle` needs to appear /// in the module's final `Arena<Type>`, for example, if you're /// creating a [`LocalVariable`] whose type is inferred from its /// initializer. /// /// [`LocalVariable`]: ir::LocalVariable fn register_type(
&mutself,
handle: Handle<ir::Expression>,
) -> Result<'source, Handle<ir::Type>> { self.grow_types(handle)?; // This is equivalent to calling ExpressionContext::typifier(), // except that this lets the borrow checker see that it's okay // to also borrow self.module.types mutably below. let typifier = matchself.expr_type {
ExpressionContextType::Runtime(ref ctx)
| ExpressionContextType::Constant(Some(ref ctx)) => ctx.typifier,
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
&*self.const_typifier
}
};
Ok(typifier.register_type(handle, &mutself.module.types))
}
/// Resolve the types of all expressions up through `handle`. /// /// Ensure that [`self.typifier`] has a [`TypeResolution`] for /// every expression in `self.function.expressions`. /// /// This does not add types to any arena. The [`Typifier`] /// documentation explains the steps we take to avoid filling /// arenas with intermediate types. /// /// This function takes `&mut self`, so it can't conveniently /// return a shared reference to the resulting `TypeResolution`: /// the shared reference would extend the mutable borrow, and you /// wouldn't be able to use `self` for anything else. Instead, you /// should use [`register_type`] or one of [`resolve!`], /// [`resolve_inner!`] or [`resolve_inner_binary!`]. /// /// [`self.typifier`]: ExpressionContext::typifier /// [`TypeResolution`]: proc::TypeResolution /// [`register_type`]: Self::register_type /// [`Typifier`]: Typifier fn grow_types(&mutself, handle: Handle<ir::Expression>) -> Result<'source, &mut Self> { let empty_arena = Arena::new(); let resolve_ctx; let typifier; let expressions; matchself.expr_type {
ExpressionContextType::Runtime(refmut ctx)
| ExpressionContextType::Constant(Some(refmut ctx)) => {
resolve_ctx = proc::ResolveContext::with_locals( self.module,
&ctx.function.local_variables,
&ctx.function.arguments,
);
typifier = &mut *ctx.typifier;
expressions = &ctx.function.expressions;
}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
resolve_ctx = proc::ResolveContext::with_locals(self.module, &empty_arena, &[]);
typifier = self.const_typifier;
expressions = &self.module.global_expressions;
}
};
typifier
.grow(handle, expressions, &resolve_ctx)
.map_err(Error::InvalidResolve)?;
/// Insert splats, if needed by the non-'*' operations. /// /// See the "Binary arithmetic expressions with mixed scalar and vector operands" /// table in the WebGPU Shading Language specification for relevant operators. /// /// Multiply is not handled here as backends are expected to handle vec*scalar /// operations, so inserting splats into the IR increases size needlessly. fn binary_op_splat(
&mutself,
op: ir::BinaryOperator,
left: &mut Handle<ir::Expression>,
right: &mut Handle<ir::Expression>,
) -> Result<'source, ()> { if matches!(
op,
ir::BinaryOperator::Add
| ir::BinaryOperator::Subtract
| ir::BinaryOperator::Divide
| ir::BinaryOperator::Modulo
) { match resolve_inner_binary!(self, *left, *right) {
(&ir::TypeInner::Vector { size, .. }, &ir::TypeInner::Scalar { .. }) => {
*right = self.append_expression(
ir::Expression::Splat {
size,
value: *right,
}, self.get_expression_span(*right),
)?;
}
(&ir::TypeInner::Scalar { .. }, &ir::TypeInner::Vector { size, .. }) => {
*left = self.append_expression(
ir::Expression::Splat { size, value: *left }, self.get_expression_span(*left),
)?;
}
_ => {}
}
}
Ok(())
}
/// Add a single expression to the expression table that is not covered by `self.emitter`. /// /// This is useful for `CallResult` and `AtomicResult` expressions, which should not be covered by /// `Emit` statements. fn interrupt_emitter(
&mutself,
expression: ir::Expression,
span: Span,
) -> Result<'source, Handle<ir::Expression>> { matchself.expr_type {
ExpressionContextType::Runtime(refmut rctx)
| ExpressionContextType::Constant(Some(refmut rctx)) => {
rctx.block
.extend(rctx.emitter.finish(&rctx.function.expressions));
}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {}
} let result = self.append_expression(expression, span); matchself.expr_type {
ExpressionContextType::Runtime(refmut rctx)
| ExpressionContextType::Constant(Some(refmut rctx)) => {
rctx.emitter.start(&rctx.function.expressions);
}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {}
}
result
}
/// Apply the WGSL Load Rule to `expr`. /// /// If `expr` is has type `ref<SC, T, A>`, perform a load to produce a value of type /// `T`. Otherwise, return `expr` unchanged. fn apply_load_rule(
&mutself,
expr: Typed<Handle<ir::Expression>>,
) -> Result<'source, Handle<ir::Expression>> { match expr {
Typed::Reference(pointer) => { let span = self.get_expression_span(pointer);
// Reject direct access to atomic variables that does not go // through a built-in function. if resolve_inner!(self, pointer).is_atomic_pointer(&self.module.types) { return Err(Box::new(Error::InvalidAtomicAccess(span)));
}
/// Check that `expr` is an identifier resolving to a predeclared enumerant. /// /// The identifier must not have any template parameters. /// /// Return the name of the identifier, together with its span. /// /// Actually, this only checks that the identifier refers to some /// predeclared object, not necessarily an enumerant. This should be good /// enough, since the caller is going to compare the name against some list /// of permitted enumerants anyway. fn enumerant(
&self,
expr: Handle<ast::Expression<'source>>,
) -> Result<'source, (&'source str, Span)> { let span = self.ast_expressions.get_span(expr); let expr = &self.ast_expressions[expr];
let ast::Expression::Ident(ref ident) = *expr else { return Err(Box::new(Error::UnexpectedExprForEnumerant(span)));
};
/// WGSL type annotations on expressions, types, values, etc. /// /// Naga and WGSL types are very close, but Naga lacks WGSL's `ref` types, which /// we need to know to apply the Load Rule. This enum carries some WGSL or Naga /// datum along with enough information to determine its corresponding WGSL /// type. /// /// The `T` type parameter can be any expression-like thing: /// /// - `Typed<Handle<ir::Type>>` can represent a full WGSL type. For example, /// given some Naga `Pointer` type `ptr`, a WGSL reference type is a /// `Typed::Reference(ptr)` whereas a WGSL pointer type is a /// `Typed::Plain(ptr)`. /// /// - `Typed<ir::Expression>` or `Typed<Handle<ir::Expression>>` can /// represent references similarly. /// /// Use the `map` and `try_map` methods to convert from one expression /// representation to another. /// /// [`Expression`]: ir::Expression #[derive(Debug, Copy, Clone)] enum Typed<T> { /// A WGSL reference.
Reference(T),
/// A single vector component or swizzle. /// /// This represents the things that can appear after the `.` in a vector access /// expression: either a single component name, or a series of them, /// representing a swizzle. enum Components {
Single(u32),
Swizzle {
size: ir::VectorSize,
pattern: [ir::SwizzleComponent; 4],
},
}
/// Construct a `Components` value from a 'member' name, like `"wzy"` or `"x"`. /// /// Use `name_span` for reporting errors in parsing the component string. fn new(name: &str, name_span: Span) -> Result<'_, Self> { let size = match name.len() { 1 => return Ok(Components::Single(Self::single_component(name, name_span)?)), 2 => ir::VectorSize::Bi, 3 => ir::VectorSize::Tri, 4 => ir::VectorSize::Quad,
_ => return Err(Box::new(Error::BadAccessor(name_span))),
};
letmut pattern = [ir::SwizzleComponent::X; 4]; for (comp, ch) in pattern.iter_mut().zip(name.chars()) {
*comp = Self::letter_component(ch).ok_or(Error::BadAccessor(name_span))?;
}
/// An `ast::GlobalDecl` for which we have built the Naga IR equivalent. enum LoweredGlobalDecl {
Function {
handle: Handle<ir::Function>,
must_use: bool,
},
Var(Handle<ir::GlobalVariable>), Const(Handle<ir::Constant>), Override(Handle<ir::Override>), Type(Handle<ir::Type>),
EntryPoint(usize),
}
/// Whether a declaration accepts abstract types, or concretizes. enum AbstractRule { /// This declaration concretizes its initialization expression.
Concretize,
/// This declaration can accept initializers with abstract types.
Allow,
}
/// Whether `@must_use` applies to a call expression. #[derive(Debug, Copy, Clone)] enum MustUse {
Yes,
No,
}
impl From<bool> for MustUse { fn from(value: bool) -> Self { if value {
MustUse::Yes
} else {
MustUse::No
}
}
}
let explicit_ty =
c.ty.as_ref()
.map(|ast| self.resolve_ast_type(ast, &mut ectx))
.transpose()?;
let (ty, init) = self.type_and_init(
c.name,
Some(c.init),
explicit_ty,
AbstractRule::Allow,
&mut ectx,
)?; let init = init.expect("Global const must have init");
// Constant evaluation may leave abstract-typed literals and // compositions in expression arenas, so we need to compact the module // to remove unused expressions and types. crate::compact::compact(&mut module, KeepUnused::Yes);
Ok(module)
}
/// Obtain (inferred) type and initializer after automatic conversion fn type_and_init(
&mutself,
name: ast::Ident<'source>,
init: Option<Handle<ast::Expression<'source>>>,
explicit_ty: Option<Handle<ir::Type>>,
abstract_rule: AbstractRule,
ectx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, (Handle<ir::Type>, Option<Handle<ir::Expression>>)> { let ty; let initializer; match (init, explicit_ty) {
(Some(init), Some(explicit_ty)) => { let init = self.expression_for_abstract(init, ectx)?; let ty_res = proc::TypeResolution::Handle(explicit_ty); let init = ectx
.try_automatic_conversions(init, &ty_res, name.span)
.map_err(|error| match *error {
Error::AutoConversion(e) => Box::new(Error::InitializationTypeMismatch {
name: name.span,
expected: e.dest_type,
got: e.source_type,
}),
_ => error,
})?;
// We have this special check here for `let` declarations because the // validator doesn't check them (they are comingled with other things in // `named_expressions`; see <https://github.com/gfx-rs/wgpu/issues/7393>). // The check could go in `type_and_init`, but then we'd have to // distinguish whether override-sized is allowed. The error ought to use // the type's span, but `module.types.get_span(ty)` is `Span::UNDEFINED` // (see <https://github.com/gfx-rs/wgpu/issues/7951>). if ctx.module.types[ty]
.inner
.is_dynamically_sized(&ctx.module.types)
{ return Err(Box::new(Error::TypeNotConstructible(l.name.span)));
}
// We passed `Some()` to `type_and_init`, so we // will get a lowered initializer expression back. let initializer =
initializer.expect("type_and_init did not return an initializer");
// The WGSL spec says that any expression that refers to a // `let`-bound variable is not a const expression. This // affects when errors must be reported, so we can't even // treat suitable `let` bindings as constant as an // optimization.
ctx.local_expression_kind_tracker
.force_non_const(initializer);
let (const_initializer, initializer) = { match initializer {
Some(init) => { // It's not correct to hoist the initializer up // to the top of the function if: // - the initialization is inside a loop, and should // take place on every iteration, or // - the initialization is not a constant // expression, so its value depends on the // state at the point of initialization. if is_inside_loop
|| !ctx.local_expression_kind_tracker.is_const_or_override(init)
{
(None, Some(init))
} else {
(Some(init), None)
}
}
None => (None, None),
}
};
let var = ctx.function.local_variables.append(
ir::LocalVariable {
name: Some(v.name.name.to_string()),
ty,
init: const_initializer,
},
stmt.span,
);
let ectx = &mut ctx.as_const(block, &mut emitter);
let explicit_ty =
c.ty.as_ref()
.map(|ast| self.resolve_ast_type(ast, &mut ectx.as_const()))
.transpose()?;
let (_ty, init) = self.type_and_init(
c.name,
Some(c.init),
explicit_ty,
AbstractRule::Allow,
&mut ectx.as_const(),
)?; let init = init.expect("Local const must have init");
// Determine the scalar type of the selector and case expressions, find the // consensus type for automatic conversion, then convert them. let (mut exprs, spans) = core::iter::once(selector)
.chain(cases.iter().filter_map(|case| match case.value {
ast::SwitchValue::Expr(expr) => Some(expr),
ast::SwitchValue::Default => None,
}))
.enumerate()
.map(|(i, expr)| { let span = ectx.ast_expressions.get_span(expr); let expr = self.expression_for_abstract(expr, &mut ectx)?; let ty = resolve_inner!(ectx, expr); match *ty {
ir::TypeInner::Scalar(
ir::Scalar::I32 | ir::Scalar::U32 | ir::Scalar::ABSTRACT_INT,
) => Ok((expr, span)),
_ => match i { 0 => Err(Box::new(Error::InvalidSwitchSelector { span })),
_ => Err(Box::new(Error::InvalidSwitchCase { span })),
},
}
})
.collect::<Result<(Vec<_>, Vec<_>)>>()?;
letmut consensus =
ectx.automatic_conversion_consensus(None, &exprs)
.map_err(|span_idx| Error::SwitchCaseTypeMismatch {
span: spans[span_idx],
})?; // Concretize to I32 if the selector and all cases were abstract if consensus == ir::Scalar::ABSTRACT_INT {
consensus = ir::Scalar::I32;
} for expr in &mut exprs {
ectx.convert_to_leaf_scalar(expr, consensus)?;
}
letmut ectx = ctx.as_expression(block, &mut emitter); let target = self.expression_for_reference(ast_target, &mut ectx)?; let target_handle = match target {
Typed::Reference(handle) => handle,
Typed::Plain(handle) => { let ty = ctx.invalid_assignment_type(handle); return Err(Box::new(Error::InvalidAssignment {
span: target_span,
ty,
}));
}
};
// Usually the value needs to be converted to match the type of // the memory view you're assigning it to. The bit shift // operators are exceptions, in that the right operand is always // a `u32` or `vecN<u32>`. let target_scalar = match op {
Some(ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight) => {
Some(ir::Scalar::U32)
}
_ => resolve_inner!(ectx, target_handle)
.pointer_automatically_convertible_scalar(&ectx.module.types),
};
// Need to emit the LHS _before_ the RHS so that it is evaluated first. let op_assign = iflet Some(op) = op {
Some((op, ectx.apply_load_rule(target)?))
} else {
None
};
let value = self.expression_for_abstract(value, &mut ectx)?; letmut value = match target_scalar {
Some(target_scalar) => ectx.try_automatic_conversion_for_leaf_scalar(
value,
target_scalar,
target_span,
)?,
None => value,
};
let right =
ectx.interrupt_emitter(ir::Expression::Literal(literal), Span::UNDEFINED)?; let rctx = ectx.runtime_expression_ctx(stmt.span)?; let left = rctx.function.expressions.append(
ir::Expression::Load {
pointer: target_handle,
},
value_span,
); let value = rctx
.function
.expressions
.append(ir::Expression::Binary { op, left, right }, stmt.span);
rctx.local_expression_kind_tracker
.insert(left, proc::ExpressionKind::Runtime);
rctx.local_expression_kind_tracker
.insert(value, proc::ExpressionKind::Runtime);
return Ok(());
}
ast::StatementKind::Phony(expr) => { // Remembered the RHS of the phony assignment as a named expression. This // is important (1) to preserve the RHS for validation, (2) to track any // referenced globals. letmut emitter = proc::Emitter::default();
emitter.start(&ctx.function.expressions);
let value = self.expression(expr, &mut ctx.as_expression(block, &<span style='color:red'>mut emitter))?;
block.extend(emitter.finish(&ctx.function.expressions));
ctx.named_expressions
.insert(value, ("phony".to_string(), stmt.span)); return Ok(());
}
};
block.push(out, stmt.span);
Ok(())
}
/// Lower `expr` and apply the Load Rule if possible. /// /// For the time being, this concretizes abstract values, to support /// consumers that haven't been adapted to consume them yet. Consumers /// prepared for abstract values can call [`expression_for_abstract`]. /// /// [`expression_for_abstract`]: Lowerer::expression_for_abstract fn expression(
&mutself,
expr: Handle<ast::Expression<'source>>,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, Handle<ir::Expression>> { let expr = self.expression_for_abstract(expr, ctx)?;
ctx.concretize(expr)
}
lowered_base.try_map(|base| match ctx.get_const_val(index).ok() {
Some(index) => Ok::<_, Box<Error>>(ir::Expression::AccessIndex { base, index }),
None => { // When an abstract array value e is indexed by an expression // that is not a const-expression, then the array is concretized // before the index is applied. // https://www.w3.org/TR/WGSL/#array-access-expr // Also applies to vectors and matrices. let base = ctx.concretize(base)?;
Ok(ir::Expression::Access { base, index })
}
})?
}
ast::Expression::Member { base, ref field } => { letmut lowered_base = self.expression_for_reference(base, ctx)?;
let temp_ty; let composite_type: &ir::TypeInner = match lowered_base {
Typed::Reference(handle) => {
temp_ty = resolve_inner!(ctx, handle)
.pointer_base_type()
.expect("In Typed::Reference(handle), handle must be a Naga pointer");
temp_ty.inner_with(&ctx.module.types)
}
let access = match *composite_type {
ir::TypeInner::Struct { ref members, .. } => { let index = members
.iter()
.position(|m| m.name.as_deref() == Some(field.name))
.ok_or(Error::BadAccessor(field.span))? as u32;
lowered_base.map(|base| ir::Expression::AccessIndex { base, index })
}
ir::TypeInner::Vector { size: vec_size, .. } => { match Components::new(field.name, field.span)? {
Components::Swizzle { size, pattern } => { for &component in pattern[..size as usize].iter() { if component as u8 >= vec_size as u8 { return Err(Box::new(Error::BadAccessor(field.span)));
}
}
Typed::Plain(ir::Expression::Swizzle {
size,
vector: ctx.apply_load_rule(lowered_base)?,
pattern,
})
}
Components::Single(index) => { if index >= vec_size as u32 { return Err(Box::new(Error::BadAccessor(field.span)));
}
lowered_base.map(|base| ir::Expression::AccessIndex { base, index })
}
}
}
_ => return Err(Box::new(Error::BadAccessor(field.span))),
};
/// Generate IR for the short-circuiting operators `&&` and `||`. /// /// `binary` has already lowered the LHS expression and resolved its type. fn logical(
&mutself,
op: crate::BinaryOperator,
left: Handle<crate::Expression>,
right: Handle<ast::Expression<'source>>,
span: Span,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, Typed<crate::Expression>> {
debug_assert!(
op == crate::BinaryOperator::LogicalAnd || op == crate::BinaryOperator::LogicalOr
);
if ctx.is_runtime() { // To simulate short-circuiting behavior, we want to generate IR // like the following for `&&`. For `||`, the condition is `!_lhs` // and the else value is `true`. // // var _e0: bool; // if _lhs { // _e0 = _rhs; // } else { // _e0 = false; // }
let (condition, else_val) = if op == crate::BinaryOperator::LogicalAnd { let condition = left; let else_val = ctx.append_expression( crate::Expression::Literal(crate::Literal::Bool(false)),
span,
)?;
(condition, else_val)
} else { let condition = ctx.append_expression( crate::Expression::Unary {
op: crate::UnaryOperator::LogicalNot,
expr: left,
},
span,
)?; let else_val = ctx.append_expression( crate::Expression::Literal(crate::Literal::Bool(true)),
span,
)?;
(condition, else_val)
};
let bool_ty = ctx.ensure_type_exists(crate::TypeInner::Scalar(crate::Scalar::BOOL));
let rctx = ctx.runtime_expression_ctx(span)?; let result_var = rctx.function.local_variables.append( crate::LocalVariable {
name: None,
ty: bool_ty,
init: None,
},
span,
); let pointer =
ctx.append_expression(crate::Expression::LocalVariable(result_var), span)?;
let (right, mut accept) = ctx.with_nested_runtime_expression_ctx(span, |ctx| { let right = self.expression_for_abstract(right, ctx)?;
ctx.grow_types(right)?;
Ok(right)
})?;
if left_val.is_some_and(|left_val| {
op == crate::BinaryOperator::LogicalAnd && !left_val
|| op == crate::BinaryOperator::LogicalOr && left_val
}) { // Short-circuit behavior: don't evaluate the RHS.
// TODO(https://github.com/gfx-rs/wgpu/issues/8440): We shouldn't ignore the // RHS completely, it should still be type-checked. Preserving it for type // checking is a bit tricky, because we're trying to produce an expression // for a const context, but the RHS is allowed to have things that aren't // const.
Ok(Typed::Plain(ctx.get(left).clone()))
} else { // Evaluate the RHS and construct the entire binary expression as we // normally would. This case applies to well-formed constant logical // expressions that don't short-circuit (handled by the constant evaluator // shortly), to override expressions (handled when overrides are processed) // and to non-well-formed expressions (rejected by type checking). let right = self.expression_for_abstract(right, ctx)?;
ctx.grow_types(right)?;
Ok(Typed::Plain(crate::Expression::Binary { op, left, right }))
}
}
}
let ident = match *ident {
ast::IdentExpr::Unresolved(ident) => ident,
ast::IdentExpr::Local(_) => { // Since WGSL only supports module-scope type definitions and // aliases, a local identifier can't possibly refer to a type. return Err(Box::new(Error::UnexpectedExprForTypeExpression(ident_span)));
}
};
iflet Some(global) = ctx.globals.get(ident) { let &LoweredGlobalDecl::Type(handle) = global else { return Err(Box::new(Error::UnexpectedExprForTypeExpression(ident_span)));
};
// Type generators can only be predeclared, so since `ident` refers // to a module-scope declaration, the template parameter list should // be empty.
tl.finish(ctx)?; return Ok(handle);
}
// If `ident` doesn't resolve to a module-scope declaration, then it // must resolve to a predeclared type or type generator. let ty = conv::map_predeclared_type(&ctx.enable_extensions, ident_span, ident)?
.ok_or_else(|| Box::new(Error::UnknownIdent(ident_span, ident)))?; let ty = self.finalize_type(ctx, ty, &mut tl, alias_name)?;
tl.finish(ctx)?;
Ok(ty)
}
/// Construct an [`ir::Type`] from a [`conv::PredeclaredType`] and a list of /// template parameters. /// /// If we're processing a type alias, then `alias_name` is the name we /// should use in the new `ir::Type`. /// /// For example, when parsing `vec3<f32>`, the caller would pass: /// /// - for `ty`, [`TypeGenerator::Vector`], and /// /// - for `tl`, an iterator producing a single [`Expression::Ident`] representing `f32`. /// /// From those arguments this function will return a handle for the /// [`ir::Type`] representing `vec3<f32>`. /// /// [`TypeGenerator::Vector`]: conv::TypeGenerator::Vector /// [`Expression::Ident`]: crate::front::wgsl::parse::ast::Expression::Ident fn finalize_type(
&mutself,
ctx: &mut ExpressionContext<'source, '_, '_>,
ty: conv::PredeclaredType,
tl: &mut TemplateListIter<'_, 'source>,
alias_name: Option<String>,
) -> Result<'source, Handle<ir::Type>> { let ty = match ty {
conv::PredeclaredType::TypeInner(ty_inner) => { iflet ir::TypeInner::Image {
class: ir::ImageClass::External,
..
} = ty_inner
{ // Other than the WGSL backend, every backend that supports // external textures does so by lowering them to a set of // ordinary textures and some parameters saying how to // sample from them. We don't know which backend will // consume the `Module` we're building, but in case it's not // WGSL, populate `SpecialTypes::external_texture_params` // and `SpecialTypes::external_texture_transfer_function` // with the types the backend will use for the parameter // buffer. // // Neither of these are the type we are lowering here: // that's an ordinary `TypeInner::Image`. But the fact we // are lowering a `texture_external` implies the backends // may need these additional types too.
ctx.module.generate_external_texture_types();
}
ctx.as_global().ensure_type_exists(alias_name, ty_inner)
}
conv::PredeclaredType::RayDesc => ctx.module.generate_ray_desc_type(),
conv::PredeclaredType::RayIntersection => ctx.module.generate_ray_intersection_type(),
conv::PredeclaredType::TypeGenerator(type_generator) => { let ty_inner = match type_generator {
conv::TypeGenerator::Vector { size } => { let (scalar, _) = tl.scalar_ty(self, ctx)?;
ir::TypeInner::Vector { size, scalar }
}
conv::TypeGenerator::Matrix { columns, rows } => { let (scalar, span) = tl.scalar_ty(self, ctx)?; if scalar.kind != ir::ScalarKind::Float { return Err(Box::new(Error::BadMatrixScalarKind(span, scalar)));
}
ir::TypeInner::Matrix {
columns,
rows,
scalar,
}
}
conv::TypeGenerator::Array => { let base = tl.ty(self, ctx)?; let size = tl.maybe_array_size(self, ctx)?;
// Determine the size of the base type, if needed.
ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| { let LayoutErrorInner::TooLarge = err.inner else {
unreachable!("unexpected layout error: {err:?}");
}; // Lots of type definitions don't get spans, so this error // message may not be very useful. Box::new(Error::TypeTooLarge {
span: ctx.module.types.get_span(err.ty),
})
})?; let stride = ctx.layouter[base].to_stride();
ir::TypeInner::Array { base, size, stride }
}
conv::TypeGenerator::Atomic => { let (scalar, _) = tl.scalar_ty(self, ctx)?;
ir::TypeInner::Atomic(scalar)
}
conv::TypeGenerator::Pointer => { letmut space = tl.address_space(ctx)?; let base = tl.ty(self, ctx)?;
tl.maybe_access_mode(&mut space, ctx)?;
ir::TypeInner::Pointer { base, space }
}
conv::TypeGenerator::SampledTexture {
dim,
arrayed,
multi,
} => { let (scalar, span) = tl.scalar_ty(self, ctx)?; let ir::Scalar { kind, width } = scalar; if width != 4 { return Err(Box::new(Error::BadTextureSampleType { span, scalar }));
}
ir::TypeInner::Image {
dim,
arrayed,
class: ir::ImageClass::Sampled { kind, multi },
}
}
conv::TypeGenerator::StorageTexture { dim, arrayed } => { let format = tl.storage_format(ctx)?; let access = tl.access_mode(ctx)?;
ir::TypeInner::Image {
dim,
arrayed,
class: ir::ImageClass::Storage { format, access },
}
}
conv::TypeGenerator::BindingArray => { let base = tl.ty(self, ctx)?; let size = tl.maybe_array_size(self, ctx)?;
ir::TypeInner::BindingArray { base, size }
}
conv::TypeGenerator::AccelerationStructure => { let vertex_return = tl.maybe_vertex_return(ctx)?;
ir::TypeInner::AccelerationStructure { vertex_return }
}
conv::TypeGenerator::RayQuery => { let vertex_return = tl.maybe_vertex_return(ctx)?;
ir::TypeInner::RayQuery { vertex_return }
}
conv::TypeGenerator::CooperativeMatrix { columns, rows } => { let (ty, span) = tl.ty_with_span(self, ctx)?; let ir::TypeInner::Scalar(scalar) = ctx.module.types[ty].inner else { return Err(Box::new(Error::UnsupportedCooperativeScalar(span)));
}; let role = tl.cooperative_role(ctx)?;
ir::TypeInner::CooperativeMatrix {
columns,
rows,
scalar,
role,
}
}
};
ctx.as_global().ensure_type_exists(alias_name, ty_inner)
}
};
Ok(ty)
}
fn binary(
&mutself,
op: ir::BinaryOperator,
left: Handle<ast::Expression<'source>>,
right: Handle<ast::Expression<'source>>,
span: Span,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, Typed<ir::Expression>> { if op == ir::BinaryOperator::LogicalAnd || op == ir::BinaryOperator::LogicalOr { let left = self.expression_for_abstract(left, ctx)?;
ctx.grow_types(left)?;
if !matches!(
resolve_inner!(ctx, left),
&ir::TypeInner::Scalar(ir::Scalar::BOOL)
) { // Pass it through as-is, will fail validation let right = self.expression_for_abstract(right, ctx)?;
ctx.grow_types(right)?;
Ok(Typed::Plain(crate::Expression::Binary { op, left, right }))
} else { self.logical(op, left, right, span, ctx)
}
} else { // Load both operands. letmut left = self.expression_for_abstract(left, ctx)?; letmut right = self.expression_for_abstract(right, ctx)?;
// Convert `scalar op vector` to `vector op vector` by introducing // `Splat` expressions.
ctx.binary_op_splat(op, &mut left, &mutright)?;
// Apply automatic conversions. match op {
ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight => { // Shift operators require the right operand to be `u32` or // `vecN<u32>`. We can let the validator sort out vector length // issues, but the right operand must be, or convert to, a u32 leaf // scalar.
right =
ctx.try_automatic_conversion_for_leaf_scalar(right, ir::Scalar::U32, span)?;
// Additionally, we must concretize the left operand if the right operand // is not a const-expression. // See https://www.w3.org/TR/WGSL/#overload-resolution-section. // // 2. Eliminate any candidate where one of its subexpressions resolves to // an abstract type after feasible automatic conversions, but another of // the candidate’s subexpressions is not a const-expression. // // We only have to explicitly do so for shifts as their operands may be // of different types - for other binary ops this is achieved by finding // the conversion consensus for both operands. if !ctx.is_const(right) {
left = ctx.concretize(left)?;
}
}
// All other operators follow the same pattern: reconcile the // scalar leaf types. If there's no reconciliation possible, // leave the expressions as they are: validation will report the // problem.
_ => {
ctx.grow_types(left)?;
ctx.grow_types(right)?; iflet Ok(consensus_scalar) =
ctx.automatic_conversion_consensus(None, [left, right].iter())
{
ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?;
ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?;
}
}
}
Ok(Typed::Plain(ir::Expression::Binary { op, left, right }))
}
}
/// Generate Naga IR for a call to a WGSL builtin function. #[allow(clippy::too_many_arguments)] fn call_builtin<'phrase>(
&mutself,
function_name: &'source str,
function_span: Span,
arguments: &[Handle<ast::Expression<'source>>],
template_params: &mut TemplateListIter<'phrase, 'source>,
call_span: Span,
ctx: &mut ExpressionContext<'source, '_, '_>,
is_statement: bool,
) -> Result<'source, Option<(Handle<ir::Expression>, MustUse)>> { let (expr, must_use) = iflet Some(fun) = conv::map_relational_fun(function_name) { letmut args = ctx.prepare_args(arguments, 1, function_span); let argument = self.expression(args.next()?, ctx)?;
args.finish()?;
// Check for no-op all(bool) and any(bool): let argument_unmodified = matches!(
fun,
ir::RelationalFunction::All | ir::RelationalFunction::Any
) && {
matches!(
resolve_inner!(ctx, argument),
&ir::TypeInner::Scalar(ir::Scalar {
kind: ir::ScalarKind::Bool,
..
})
)
};
let expr = ctx.append_expression(expr, function_span)?;
Ok(Some((expr, must_use)))
}
/// Generate Naga IR for call expressions and statements, and type /// constructor expressions. /// /// The "function" being called is simply an `Ident` that we know refers to /// some module-scope definition. /// /// - If it is the name of a type, then the expression is a type constructor /// expression: either constructing a value from components, a conversion /// expression, or a zero value expression. /// /// - If it is the name of a function, then we're generating a [`Call`] /// statement. We may be in the midst of generating code for an /// expression, in which case we must generate an `Emit` statement to /// force evaluation of the IR expressions we've generated so far, add the /// `Call` statement to the current block, and then resume generating /// expressions. /// /// [`Call`]: ir::Statement::Call fn call(
&mutself,
call_phrase: &ast::CallPhrase<'source>,
span: Span,
ctx: &mut ExpressionContext<'source, '_, '_>,
is_statement: bool,
) -> Result<'source, Option<Handle<ir::Expression>>> { let function_name = match call_phrase.function.ident {
ast::IdentExpr::Unresolved(name) => name,
ast::IdentExpr::Local(_) => { return Err(Box::new(Error::CalledLocalDecl(
call_phrase.function.ident_span,
)))
}
}; letmut function_span = call_phrase.function.ident_span;
function_span.subsume(call_phrase.function.template_list_span); let arguments = call_phrase.arguments.as_slice();
let result = match ctx.globals.get(function_name) {
Some(&LoweredGlobalDecl::Type(ty)) => { // user-declared types can't make use of template lists
tl.finish(ctx)?;
let arguments = arguments
.iter()
.enumerate()
.map(|(i, &arg)| { // Try to convert abstract values to the known argument types let Some(&ir::FunctionArgument {
ty: parameter_ty, ..
}) = ctx.module.functions[function].arguments.get(i) else { // Wrong number of arguments... just concretize the type here // and let the validator report the error. returnself.expression(arg, ctx);
};
let has_result = ctx.module.functions[function].result.is_some();
let rctx = ctx.runtime_expression_ctx(span)?; // we need to always do this before a fn call since all arguments need to be emitted before the fn call
rctx.block
.extend(rctx.emitter.finish(&rctx.function.expressions)); let result = has_result.then(|| { let result = rctx
.function
.expressions
.append(ir::Expression::CallResult(function), span);
rctx.local_expression_kind_tracker
.insert(result, proc::ExpressionKind::Runtime);
(result, must_use.into())
});
rctx.emitter.start(&rctx.function.expressions);
rctx.block.push(
ir::Statement::Call {
function,
arguments,
result: result.map(|(expr, _)| expr),
},
span,
);
result
}
None => { // If the name refers to a predeclared type, this is a construction expression. let ty = conv::map_predeclared_type(
&ctx.enable_extensions,
function_span,
function_name,
)?; iflet Some(ty) = ty { let empty_template_list = call_phrase.function.template_list.is_empty(); let constructor_ty = match ty {
conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Vector {
size,
}) if empty_template_list => Constructor::PartialVector { size },
conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Matrix {
columns,
rows,
}) if empty_template_list => Constructor::PartialMatrix { columns, rows },
conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Array) if empty_template_list =>
{
Constructor::PartialArray
}
conv::PredeclaredType::TypeGenerator(
conv::TypeGenerator::CooperativeMatrix { .. },
) if empty_template_list => { return Err(Box::new(Error::UnderspecifiedCooperativeMatrix));
}
_ => Constructor::Type(self.finalize_type(ctx, ty, &mut tl, None)?),
};
tl.finish(ctx)?; let handle = self.construct(span, constructor_ty, function_span, arguments, ctx)?;
Some((handle, MustUse::Yes))
} else { // Otherwise, it must be a call to a builtin function. let result = self.call_builtin(
function_name,
function_span,
arguments,
&mut tl,
span,
ctx,
is_statement,
)?;
tl.finish(ctx)?;
result
}
}
};
let result_used = !is_statement; if matches!(result, Some((_, MustUse::Yes))) && !result_used { return Err(Box::new(Error::FunctionMustUseUnused(function_span)));
}
Ok(result.map(|(expr, _)| expr))
}
/// Generate a Naga IR [`Math`] expression. /// /// Generate Naga IR for a call to the [`MathFunction`] `fun`, whose /// unlowered arguments are `ast_arguments`. /// /// The `span` argument should give the span of the function name in the /// call expression. /// /// [`Math`]: ir::Expression::Math /// [`MathFunction`]: ir::MathFunction fn math_function_helper(
&mutself,
span: Span,
fun: ir::MathFunction,
ast_arguments: &[Handle<ast::Expression<'source>>],
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, ir::Expression> { letmut lowered_arguments = Vec::with_capacity(ast_arguments.len()); for &arg in ast_arguments { let lowered = self.expression_for_abstract(arg, ctx)?;
ctx.grow_types(lowered)?;
lowered_arguments.push(lowered);
}
let fun_overloads = fun.overloads(); let rule = self.resolve_overloads(span, fun, fun_overloads, &lowered_arguments, ctx)?; self.apply_automatic_conversions_for_call(&rule, &mut lowered_arguments, ctx)?;
// If this function returns a predeclared type, register it // in `Module::special_types`. The typifier will expect to // be able to find it there. iflet proc::Conclusion::Predeclared(predeclared) = rule.conclusion {
ctx.module.generate_predeclared_type(predeclared);
}
/// Choose the right overload for a function call. /// /// Return a [`Rule`] representing the most preferred overload in /// `overloads` to apply to `arguments`, or return an error explaining why /// the call is not valid. /// /// Use `fun` to identify the function being called in error messages; /// `span` should be the span of the function name in the call expression. /// /// [`Rule`]: proc::Rule fn resolve_overloads<O, F>(
&self,
span: Span,
fun: F,
overloads: O,
arguments: &[Handle<ir::Expression>],
ctx: &ExpressionContext<'source, '_, '_>,
) -> Result<'source, proc::Rule> where
O: proc::OverloadSet,
F: TryToWgsl + core::fmt::Debug + Copy,
{ letmut remaining_overloads = overloads.clone(); let min_arguments = remaining_overloads.min_arguments(); let max_arguments = remaining_overloads.max_arguments(); if arguments.len() < min_arguments { return Err(Box::new(Error::WrongArgumentCount {
span,
expected: min_arguments as u32..max_arguments as u32,
found: arguments.len() as u32,
}));
} if arguments.len() > max_arguments { return Err(Box::new(Error::TooManyArguments {
function: fun.to_wgsl_for_diagnostics(),
call_span: span,
arg_span: ctx.get_expression_span(arguments[max_arguments]),
max_arguments: max_arguments as _,
}));
}
for (arg_index, &arg) in arguments.iter().enumerate() { let arg_type_resolution = &ctx.typifier()[arg]; let arg_inner = arg_type_resolution.inner_with(&ctx.module.types);
log::debug!( "Supplying argument {arg_index} of type {:?}",
arg_type_resolution.for_debug(&ctx.module.types)
); let next_remaining_overloads =
remaining_overloads.arg(arg_index, arg_inner, &ctx.module.types);
// If any argument is not a constant expression, then no overloads // that accept abstract values should be considered. // (`OverloadSet::concrete_only` is supposed to help impose this // restriction.) However, no `MathFunction` accepts a mix of // abstract and concrete arguments, so we don't need to worry // about that here.
// If the set of remaining overloads is empty, then this argument's type // was unacceptable. Diagnose the problem and produce an error message. if next_remaining_overloads.is_empty() { let function = fun.to_wgsl_for_diagnostics(); let call_span = span; let arg_span = ctx.get_expression_span(arg); let arg_ty = ctx.as_diagnostic_display(arg_type_resolution).to_string();
// Is this type *ever* permitted for the arg_index'th argument? // For example, `bool` is never permitted for `max`. let only_this_argument = overloads.arg(arg_index, arg_inner, &ctx.module.types); if only_this_argument.is_empty() { // No overload of `fun` accepts this type as the // arg_index'th argument. Determine the set of types that // would ever be allowed there. let allowed: Vec<String> = overloads
.allowed_args(arg_index, &ctx.module.to_ctx())
.iter()
.map(|ty| ctx.type_resolution_to_string(ty))
.collect();
if allowed.is_empty() { // No overload of `fun` accepts any argument at this // index, so it's a simple case of excess arguments. // However, since each `MathFunction`'s overloads all // have the same arity, we should have detected this // earlier.
unreachable!("expected all overloads to have the same arity");
}
// Some overloads of `fun` do accept this many arguments, // but none accept one of this type. return Err(Box::new(Error::WrongArgumentType {
function,
call_span,
arg_span,
arg_index: arg_index as u32,
arg_ty,
allowed,
}));
}
// This argument's type is accepted by some overloads---just // not those overloads that remain, given the prior arguments. // For example, `max` accepts `f32` as its second argument - // but not if the first was `i32`.
// Build a list of the types that would have been accepted here, // given the prior arguments. let allowed: Vec<String> = remaining_overloads
.allowed_args(arg_index, &ctx.module.to_ctx())
.iter()
.map(|ty| ctx.type_resolution_to_string(ty))
.collect();
// Re-run the argument list to determine which prior argument // made this one unacceptable. letmut remaining_overloads = overloads; for (prior_index, &prior_expr) in arguments.iter().enumerate() { let prior_type_resolution = &ctx.typifier()[prior_expr]; let prior_ty = prior_type_resolution.inner_with(&ctx.module.types);
remaining_overloads =
remaining_overloads.arg(prior_index, prior_ty, &ctx.module.types); if remaining_overloads
.arg(arg_index, arg_inner, &ctx.module.types)
.is_empty()
{ // This is the argument that killed our dreams. let inconsistent_span = ctx.get_expression_span(arguments[prior_index]); let inconsistent_ty =
ctx.as_diagnostic_display(prior_type_resolution).to_string();
if allowed.is_empty() { // Some overloads did accept `ty` at `arg_index`, but // given the arguments up through `prior_expr`, we see // no types acceptable at `arg_index`. This means that some // overloads expect fewer arguments than others. However, // each `MathFunction`'s overloads have the same arity, so this // should be impossible.
unreachable!("expected all overloads to have the same arity");
}
// Report `arg`'s type as inconsistent with `prior_expr`'s return Err(Box::new(Error::InconsistentArgumentType {
function,
call_span,
arg_span,
arg_index: arg_index as u32,
arg_ty,
inconsistent_span,
inconsistent_index: prior_index as u32,
inconsistent_ty,
allowed,
}));
}
}
unreachable!("Failed to eliminate argument type when re-tried");
}
remaining_overloads = next_remaining_overloads;
}
// Select the most preferred type rule for this call, // given the argument types supplied above.
Ok(remaining_overloads.most_preferred())
}
/// Apply automatic type conversions for a function call. /// /// Apply whatever automatic conversions are needed to pass `arguments` to /// the function overload described by `rule`. Update `arguments` to refer /// to the converted arguments. fn apply_automatic_conversions_for_call(
&self,
rule: &proc::Rule,
arguments: &mut [Handle<ir::Expression>],
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, ()> { for (i, argument) in arguments.iter_mut().enumerate() { let goal_inner = rule.arguments[i].inner_with(&ctx.module.types); let converted = match goal_inner.scalar_for_conversions(&ctx.module.types) {
Some(goal_scalar) => { let arg_span = ctx.get_expression_span(*argument);
ctx.try_automatic_conversion_for_leaf_scalar(*argument, goal_scalar, arg_span)?
} // No conversion is necessary.
None => *argument,
};
let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; let value_inner = resolve_inner!(ctx, value);
args.finish()?;
// If we don't use the return value of a 64-bit `min` or `max` // operation, generate a no-result form of the `Atomic` statement, so // that we can pass validation with only `SHADER_INT64_ATOMIC_MIN_MAX` // whenever possible. let is_64_bit_min_max = matches!(fun, ir::AtomicFunction::Min | ir::AtomicFunction::Max)
&& matches!(
*value_inner,
ir::TypeInner::Scalar(ir::Scalar { width: 8, .. })
); let result = if is_64_bit_min_max && is_statement { let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block
.extend(rctx.emitter.finish(&rctx.function.expressions));
rctx.emitter.start(&rctx.function.expressions);
None
} else { let ty = ctx.register_type(value)?;
Some(ctx.interrupt_emitter(
ir::Expression::AtomicResult {
ty,
comparison: false,
},
span,
)?)
}; let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block.push(
ir::Statement::Atomic {
pointer,
fun,
value,
result,
},
span,
);
Ok(result)
}
let image; let image_span; let gather; match fun {
Texture::Gather => { let image_or_component = args.next()?; let image_or_component_span = ctx.ast_expressions.get_span(image_or_component); // Gathers from depth textures don't take an initial `component` argument. let lowered_image_or_component = self.expression(image_or_component, ctx)?;
let sampler = self.expression_for_abstract(args.next()?, ctx)?;
let coordinate = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; let clamp_to_edge = matches!(fun, Texture::SampleBaseClampToEdge);
let (class, arrayed) = ctx.image_data(image, image_span)?; let array_index = arrayed
.then(|| self.expression(args.next()?, ctx))
.transpose()?;
let level; let depth_ref; match fun {
Texture::Gather => {
level = ir::SampleLevel::Zero;
depth_ref = None;
}
Texture::GatherCompare => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?;
level = ir::SampleLevel::Zero;
depth_ref = Some(reference);
}
Texture::Sample => {
level = ir::SampleLevel::Auto;
depth_ref = None;
}
Texture::SampleBias => { let bias = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?;
level = ir::SampleLevel::Bias(bias);
depth_ref = None;
}
Texture::SampleCompare => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?;
level = ir::SampleLevel::Auto;
depth_ref = Some(reference);
}
Texture::SampleCompareLevel => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?;
level = ir::SampleLevel::Zero;
depth_ref = Some(reference);
}
Texture::SampleGrad => { let x = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; let y = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?;
level = ir::SampleLevel::Gradient { x, y };
depth_ref = None;
}
Texture::SampleLevel => { let exact = match class { // When applied to depth textures, `textureSampleLevel`'s // `level` argument is an `i32` or `u32`.
ir::ImageClass::Depth { .. } => self.expression(args.next()?, ctx)?,
// When applied to other sampled types, its `level` argument // is an `f32`.
ir::ImageClass::Sampled { .. } => { self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?
}
// Sampling `External` textures with a specified level isn't // allowed, and sampling `Storage` textures isn't allowed at // all. Let the validator report the error.
ir::ImageClass::Storage { .. } | ir::ImageClass::External => { self.expression(args.next()?, ctx)?
}
};
level = ir::SampleLevel::Exact(exact);
depth_ref = None;
}
Texture::SampleBaseClampToEdge => {
level = crate::SampleLevel::Zero;
depth_ref = None;
}
};
for member in s.members.iter() { let ty = self.resolve_ast_type(&member.ty, &mut ctx.as_const())?;
ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| { let LayoutErrorInner::TooLarge = err.inner else {
unreachable!("unexpected layout error: {err:?}");
}; // Since anonymous types of struct members don't get a span, // associate the error with the member. The layouter could have // failed on any type that was pending layout, but if it wasn't // the current struct member, it wasn't a struct member at all, // because we resolve struct members one-by-one. if ty == err.ty { Box::new(Error::StructMemberTooLarge {
member_name_span: member.name.span,
})
} else { // Lots of type definitions don't get spans, so this error // message may not be very useful. Box::new(Error::TypeTooLarge {
span: ctx.module.types.get_span(err.ty),
})
}
})?;
let member_min_size = ctx.layouter[ty].size; let member_min_alignment = ctx.layouter[ty].alignment;
let size = struct_alignment.round_up(offset); let inner = ir::TypeInner::Struct {
members,
span: size,
};
let handle = ctx.module.types.insert(
ir::Type {
name: Some(s.name.name.to_string()),
inner,
},
span,
); for (i, c) in doc_comments.drain(..).enumerate() { iflet Some(comment) = c {
ctx.module
.get_or_insert_default_doc_comments()
.struct_members
.insert((handle, i), comment);
}
}
Ok(handle)
}
fn const_u32(
&mutself,
expr: Handle<ast::Expression<'source>>,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, (u32, Span)> { let span = ctx.ast_expressions.get_span(expr); let expr = self.expression(expr, ctx)?; let value = ctx
.module
.to_ctx()
.get_const_val(expr)
.map_err(|err| match err {
proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => {
Error::ExpectedConstExprConcreteIntegerScalar(span)
}
proc::ConstValueError::Negative => Error::ExpectedNonNegative(span),
})?;
Ok((value, span))
}
fn array_size(
&mutself,
expr: Handle<ast::Expression<'source>>,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, ir::ArraySize> { let span = ctx.ast_expressions.get_span(expr); let const_ctx = &mut ctx.as_const(); let const_expr = self.expression(expr, const_ctx); match const_expr {
Ok(value) => { let len = const_ctx.get_const_val(value).map_err(|err| { Box::new(match err {
proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => {
Error::ExpectedConstExprConcreteIntegerScalar(span)
}
proc::ConstValueError::Negative => Error::ExpectedPositiveArrayLength(span),
})
})?; let size = NonZeroU32::new(len).ok_or(Error::ExpectedPositiveArrayLength(span))?;
Ok(ir::ArraySize::Constant(size))
}
Err(err) => { // If the error is simply that `expr` was an override expression, then we // can represent that as an array length. let Error::ConstantEvaluatorError(ref ty, _) = *err else { return Err(err);
};
let proc::ConstantEvaluatorError::OverrideExpr = **ty else { return Err(err);
};
/// Build the Naga equivalent of a named AST type. /// /// Return a Naga `Handle<Type>` representing the front-end type /// `handle`, which should be named `name`, if given. /// /// If `handle` refers to a type cached in [`SpecialTypes`], /// `name` may be ignored. /// /// [`SpecialTypes`]: ir::SpecialTypes fn resolve_named_ast_type(
&mutself,
ident: &ast::TemplateElaboratedIdent<'source>,
name: String,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, Handle<ir::Type>> { self.type_specifier(ident, ctx, Some(name))
}
/// Return a Naga `Handle<Type>` representing the front-end type `handle`. fn resolve_ast_type(
&mutself,
ident: &ast::TemplateElaboratedIdent<'source>,
ctx: &mut ExpressionContext<'source, '_, '_>,
) -> Result<'source, Handle<ir::Type>> { self.type_specifier(ident, ctx, None)
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.