/// Resolves the inner type of a given expression. /// /// Expects a &mut [`ExpressionContext`] and a [`Handle<Expression>`]. /// /// Returns a &[`crate::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 &[`crate::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`]: crate::proc::TypeResolution
macro_rules! resolve {
($ctx:ident, $expr:expr) => {{
$ctx.grow_types($expr)?;
&$ctx.typifier()[$expr]
}};
} pub(super) use resolve;
/// State for constructing a `crate::Module`. pubstruct GlobalContext<'source, 'temp, 'out> { /// The `TranslationUnit`'s expressions arena.
ast_expressions: &'temp Arena<ast::Expression<'source>>,
/// The `TranslationUnit`'s types arena.
types: &'temp Arena<ast::Type<'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 crate::Module,
/// State for lowering a statement within a function. pubstruct StatementContext<'source, 'temp, 'out> { // 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>>,
/// A reference to [`TranslationUnit::types`] for the translation unit /// we're lowering. /// /// [`TranslationUnit::types`]: ast::TranslationUnit::types
types: &'temp Arena<ast::Type<'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`]: crate::Expression::LocalVariable /// [`FunctionArgument`]: crate::Expression::FunctionArgument
local_table:
&'temp mut FastHashMap<Handle<ast::Local>, Declared<Typed<Handle<crate::Expression>>>>,
const_typifier: &'temp mut Typifier,
typifier: &'temp mut Typifier,
function: &'out mut crate::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<crate::Expression>, (String, Span)>,
module: &'out mut crate::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 crate::proc::ExpressionKindTracker,
global_expression_kind_tracker: &'temp mut crate::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<crate::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 crate::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`]: crate::Expression /// [`naga::Function`]: crate::Function /// [`Constant`]: ExpressionContextType::Constant /// [`naga::Module`]: crate::Module /// [`as_const`]: ExpressionContext::as_const /// [`Expression::Constant`]: crate::Expression::Constant pubstruct ExpressionContext<'source, 'temp, 'out> { // WGSL AST values.
ast_expressions: &'temp Arena<ast::Expression<'source>>,
types: &'temp Arena<ast::Type<'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 IR [`Module`] we're constructing. /// /// [`Module`]: crate::Module
module: &'out mut crate::Module,
/// Type judgments for [`module::global_expressions`]. /// /// [`module::global_expressions`]: crate::Module::global_expressions
const_typifier: &'temp mut Typifier,
global_expression_kind_tracker: &'temp mut crate::proc::ExpressionKindTracker,
/// Whether we are lowering a constant expression or a general /// runtime expression, and the data needed in each case.
expr_type: ExpressionContextType<'temp, 'out>,
}
let index = self
.module
.to_ctx()
.eval_expr_to_u32_from(expr, &rctx.function.expressions)
.map_err(|err| match err { crate::proc::U32EvalError::NonConst => {
Error::ExpectedConstExprConcreteIntegerScalar(component_span)
} crate::proc::U32EvalError::Negative => {
Error::ExpectedNonNegative(component_span)
}
})?; crate::SwizzleComponent::XYZW
.get(index as usize)
.copied()
.ok_or(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(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`]: crate::LocalVariable fn register_type(
&mutself,
handle: Handle<crate::Expression>,
) -> Result<Handle<crate::Type>, Error<'source>> { 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`]: crate::proc::TypeResolution /// [`register_type`]: Self::register_type /// [`Typifier`]: Typifier fn grow_types(
&mutself,
handle: Handle<crate::Expression>,
) -> Result<&mutSelf, Error<'source>> { 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 = 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 = 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: crate::BinaryOperator,
left: &mut Handle<crate::Expression>,
right: &mut Handle<crate::Expression>,
) -> Result<(), Error<'source>> { if matches!(
op, crate::BinaryOperator::Add
| crate::BinaryOperator::Subtract
| crate::BinaryOperator::Divide
| crate::BinaryOperator::Modulo
) { match resolve_inner_binary!(self, *left, *right) {
(&crate::TypeInner::Vector { size, .. }, &>crate::TypeInner::Scalar { .. }) => {
*right = self.append_expression( crate::Expression::Splat {
size,
value: *right,
}, self.get_expression_span(*right),
)?;
}
(&crate::TypeInner::Scalar { .. }, &crate::TypeInner::Vector { size, .. }) => {
*left = self.append_expression( crate::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: crate::Expression,
span: Span,
) -> Result<Handle<crate::Expression>, Error<'source>> { 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<crate::Expression>>,
) -> Result<Handle<crate::Expression>, Error<'source>> { match expr {
Typed::Reference(pointer) => { let load = crate::Expression::Load { pointer }; let span = self.get_expression_span(pointer); self.append_expression(load, span)
}
Typed::Plain(handle) => Ok(handle),
}
}
/// 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<crate::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<crate::Expression>` or `Typed<Handle<crate::Expression>>` can /// represent references similarly. /// /// Use the `map` and `try_map` methods to convert from one expression /// representation to another. /// /// [`Expression`]: crate::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: crate::VectorSize,
pattern: [crate::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, Error> { let size = match name.len() { 1 => return Ok(Components::Single(Self::single_component(name, name_span)?)), 2 => crate::VectorSize::Bi, 3 => crate::VectorSize::Tri, 4 => crate::VectorSize::Quad,
_ => return Err(Error::BadAccessor(name_span)),
};
letmut pattern = [crate::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<crate::Function>),
Var(Handle<crate::GlobalVariable>), Const(Handle<crate::Constant>), Override(Handle<crate::Override>), Type(Handle<crate::Type>),
EntryPoint,
}
// 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);
let value = self.expression(l.init, &mut ctx.as_expression(block, &yle='color:red'>mut emitter))?;
// 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(value);
let explicit_ty =
l.ty.map(|ty| self.resolve_ast_type(ty, &mut ctx.as_global()))
.transpose()?;
let ty; let initializer; match (v.init, explicit_ty) {
(Some(init), Some(explicit_ty)) => { let init = self.expression_for_abstract(init, &mut ectx)?; let ty_res = crate::proc::TypeResolution::Handle(explicit_ty); let init = ectx
.try_automatic_conversions(init, &ty_res, v.name.span)
.map_err(|error| match error {
Error::AutoConversion(e) => Error::InitializationTypeMismatch {
name: v.name.span,
expected: e.dest_type,
got: e.source_type,
},
other => other,
})?;
ty = explicit_ty;
initializer = Some(init);
}
(Some(init), None) => { let concretized = self.expression(init, &mut ectx)?;
ty = ectx.register_type(concretized)?;
initializer = Some(concretized);
}
(None, Some(explicit_ty)) => {
ty = explicit_ty;
initializer = None;
}
(None, None) => return Err(Error::DeclMissingTypeAndInit(v.name.span)),
}
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( crate::LocalVariable {
name: Some(v.name.name.to_string()),
ty,
init: const_initializer,
},
stmt.span,
);
let value = value
.map(|expr| self.expression(expr, &mut ctx.as_expression(block, &mut emitter)))
.transpose()?;
block.extend(emitter.finish(&ctx.function.expressions));
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(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(crate::BinaryOperator::ShiftLeft | crate::BinaryOperator::ShiftRight) => {
Some(crate::Scalar::U32)
}
_ => resolve_inner!(ectx, target_handle)
.pointer_automatically_convertible_scalar(&ectx.module.types),
};
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 value = match op {
Some(op) => { letmut left = ectx.apply_load_rule(target)?;
ectx.binary_op_splat(op, &mut left, &mut value)?;
ectx.append_expression( crate::Expression::Binary {
op,
left,
right: value,
},
stmt.span,
)?
}
None => value,
};
block.extend(emitter.finish(&ctx.function.expressions));
let right =
ectx.interrupt_emitter(crate::Expression::Literal(literal), Span::UNDEFINED)?; let rctx = ectx.runtime_expression_ctx(stmt.span)?; let left = rctx.function.expressions.append( crate::Expression::Load {
pointer: target_handle,
},
value_span,
); let value = rctx
.function
.expressions
.append(crate::Expression::Binary { op, left, right }, stmt.span);
rctx.local_expression_kind_tracker
.insert(left, crate::proc::ExpressionKind::Runtime);
rctx.local_expression_kind_tracker
.insert(value, crate::proc::ExpressionKind::Runtime);
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<Handle<crate::Expression>, Error<'source>> { let expr = self.expression_for_abstract(expr, ctx)?;
ctx.concretize(expr)
}
return expr.try_map(|handle| ctx.interrupt_emitter(handle, span));
}
ast::Expression::Construct { ref ty,
ty_span, ref components,
} => { let handle = self.construct(span, ty, ty_span, components, ctx)?; return Ok(Typed::Plain(handle));
}
ast::Expression::Unary { op, expr } => { let expr = self.expression_for_abstract(expr, ctx)?;
Typed::Plain(crate::Expression::Unary { op, expr })
}
ast::Expression::AddrOf(expr) => { // The `&` operator simply converts a reference to a pointer. And since a // reference is required, the Load Rule is not applied. matchself.expression_for_reference(expr, ctx)? {
Typed::Reference(handle) => { // No code is generated. We just declare the reference a pointer now. return Ok(Typed::Plain(handle));
}
Typed::Plain(_) => { return Err(Error::NotReference("the operand of the `&` operator", span));
}
}
}
ast::Expression::Deref(expr) => { // The pointer we dereference must be loaded. let pointer = self.expression(expr, ctx)?;
if resolve_inner!(ctx, pointer).pointer_space().is_none() { return Err(Error::NotPointer(span));
}
// No code is generated. We just declare the pointer a reference now. return Ok(Typed::Reference(pointer));
}
ast::Expression::Binary { op, left, right } => { self.binary(op, left, right, span, ctx)?
}
ast::Expression::Call { ref function, ref arguments,
} => { let handle = self
.call(span, function, arguments, ctx, false)?
.ok_or(Error::FunctionReturnsVoid(function.span))?; return Ok(Typed::Plain(handle));
}
ast::Expression::Index { base, index } => { let lowered_base = self.expression_for_reference(base, ctx)?; let index = self.expression(index, ctx)?;
iflet Typed::Plain(handle) = lowered_base { if resolve_inner!(ctx, handle).pointer_space().is_some() { return Err(Error::Pointer( "the value indexed by a `[]` subscripting expression",
ctx.ast_expressions.get_span(base),
));
}
}
lowered_base.map(|base| match ctx.const_access(index) {
Some(index) => crate::Expression::AccessIndex { base, index },
None => crate::Expression::Access { base, index },
})
}
ast::Expression::Member { base, ref field } => { let lowered_base = self.expression_for_reference(base, ctx)?;
let temp_inner; let composite_type: &crate::TypeInner = match lowered_base {
Typed::Reference(handle) => { let inner = resolve_inner!(ctx, handle); match *inner { crate::TypeInner::Pointer { base, .. } => &ctx.module.types[base].inner, crate::TypeInner::ValuePointer {
size: None, scalar, ..
} => {
temp_inner = crate::TypeInner::Scalar(scalar);
&temp_inner
} crate::TypeInner::ValuePointer {
size: Some(size),
scalar,
..
} => {
temp_inner = crate::TypeInner::Vector { size, scalar };
&temp_inner
}
_ => unreachable!( "In Typed::Reference(handle), handle must be a Naga pointer"
),
}
}
Typed::Plain(handle) => { let inner = resolve_inner!(ctx, handle); ifletcrate::TypeInner::Pointer { .. }
| crate::TypeInner::ValuePointer { .. } = *inner
{ return Err(Error::Pointer( "the value accessed by a `.member` expression",
ctx.ast_expressions.get_span(base),
));
}
inner
}
};
let access = match *composite_type { crate::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;
// 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 { // 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. crate::BinaryOperator::ShiftLeft | crate::BinaryOperator::ShiftRight => {
right =
ctx.try_automatic_conversion_for_leaf_scalar(right, crate::Scalar::U32, span)?;
}
// 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([left, right].iter())
{
ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?;
ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?;
}
}
}
Ok(Typed::Plain(crate::Expression::Binary { op, left, right }))
}
/// 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`]: crate::Statement::Call fn call(
&mutself,
span: Span,
function: &ast::Ident<'source>,
arguments: &[Handle<ast::Expression<'source>>],
ctx: &mut ExpressionContext<'source, '_, '_>,
is_statement: bool,
) -> Result<Option<Handle<crate::Expression>>, Error<'source>> { match ctx.globals.get(function.name) {
Some(&LoweredGlobalDecl::Type(ty)) => { let handle = self.construct(
span,
&ast::ConstructorType::Type(ty),
function.span,
arguments,
ctx,
)?;
Ok(Some(handle))
}
Some(
&LoweredGlobalDecl::Const(_)
| &LoweredGlobalDecl::Override(_)
| &LoweredGlobalDecl::Var(_),
) => Err(Error::Unexpected(function.span, ExpectedToken::Function)),
Some(&LoweredGlobalDecl::EntryPoint) => Err(Error::CalledEntryPoint(function.span)),
Some(&LoweredGlobalDecl::Function(function)) => { let arguments = arguments
.iter()
.enumerate()
.map(|(i, &arg)| { // Try to convert abstract values to the known argument types let Some(&crate::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(crate::Expression::CallResult(function), span);
rctx.local_expression_kind_tracker
.insert(result, crate::proc::ExpressionKind::Runtime);
result
});
rctx.emitter.start(&rctx.function.expressions);
rctx.block.push( crate::Statement::Call {
function,
arguments,
result,
},
span,
);
Ok(result)
}
None => { let span = function.span; let expr = iflet Some(fun) = conv::map_relational_fun(function.name) { letmut args = ctx.prepare_args(arguments, 1, span); let argument = self.expression(args.next()?, ctx)?;
args.finish()?;
// Check for no-op all(bool) and any(bool): let argument_unmodified = matches!(
fun, crate::RelationalFunction::All | crate::RelationalFunction::Any
) && {
matches!(
resolve_inner!(ctx, argument),
&crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Bool,
..
})
)
};
let reject = self.expression(args.next()?, ctx)?; let accept = self.expression(args.next()?, ctx)?; let condition = self.expression(args.next()?, ctx)?;
let pointer = self.atomic_pointer(args.next()?, ctx)?; let value = self.expression(args.next()?, 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, crate::AtomicFunction::Min | crate::AtomicFunction::Max)
&& matches!(
*value_inner, crate::TypeInner::Scalar(crate::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( crate::Expression::AtomicResult {
ty,
comparison: false,
},
span,
)?)
}; let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block.push( crate::Statement::Atomic {
pointer,
fun,
value,
result,
},
span,
);
Ok(result)
}
let (image, image_span, 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)?;
match *resolve_inner!(ctx, pointer) { crate::TypeInner::Pointer { base, .. } => match ctx.module.types[base].inner { crate::TypeInner::RayQuery => Ok(pointer), ref other => {
log::error!("Pointer type to {:?} passed to ray query op", other);
Err(Error::InvalidRayQueryPointer(span))
}
}, ref other => {
log::error!("Type {:?} passed to ray query op", other);
Err(Error::InvalidRayQueryPointer(span))
}
}
}
}
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.