/// A macro that allows dollar signs (`$`) to be emitted by other macros. Useful for generating /// `macro_rules!` items that, in turn, emit their own `macro_rules!` items. /// /// Technique stolen directly from /// <https://github.com/rust-lang/rust/issues/35853#issuecomment-415993963>.
macro_rules! with_dollar_sign {
($($body:tt)*) => {
macro_rules! __with_dollar_sign { $($body)* }
__with_dollar_sign!($);
}
}
macro_rules! gen_component_wise_extractor {
(
$ident:ident -> $target:ident,
literals: [$( $literal:ident => $mapping:ident: $ty:ident ),+ $(,)?],
scalar_kinds: [$( $scalar_kind:ident ),* $(,)?],
) => { /// A subset of [`Literal`]s intended to be used for implementing numeric built-ins. #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] enum $target<const N: usize> {
$( #[doc = concat!( "Maps to [`Literal::",
stringify!($literal), "`]",
)]
$mapping([$ty; N]),
)+
}
impl From<$target<1>> for Expression { fn from(value: $target<1>) -> Self { match value {
$(
$target::$mapping([value]) => {
Expression::Literal(Literal::$literal(value))
}
)+
}
}
}
#[doc = concat!( "Attempts to evaluate multiple `exprs` as a combined [`",
stringify!($target), "`] to pass to `handler`. ",
)] /// If `exprs` are vectors of the same length, `handler` is called for each corresponding /// component of each vector. /// /// `handler`'s output is registered as a new expression. If `exprs` are vectors of the /// same length, a new vector expression is registered, composed of each component emitted /// by `handler`. fn $ident<const N: usize, const M: usize, F>(
eval: &mut ConstantEvaluator<'_>,
span: Span,
exprs: [Handle<Expression>; N], mut handler: F,
) -> Result<Handle<Expression>, ConstantEvaluatorError> where
$target<M>: Into<Expression>,
F: FnMut($target<N>) -> Result<$target<M>, ConstantEvaluatorError> + Clone,
{
assert!(N > 0); let err = ConstantEvaluatorError::InvalidMathArg; letmut exprs = exprs.into_iter();
impl Behavior<'_> { /// Returns `true` if the inner WGSL/GLSL restrictions are runtime restrictions. constfn has_runtime_restrictions(&self) -> bool {
matches!( self,
&Behavior::Wgsl(WgslRestrictions::Runtime(_))
| &Behavior::Glsl(GlslRestrictions::Runtime(_))
)
}
}
/// A context for evaluating constant expressions. /// /// A `ConstantEvaluator` points at an expression arena to which it can append /// newly evaluated expressions: you pass [`try_eval_and_append`] whatever kind /// of Naga [`Expression`] you like, and if its value can be computed at compile /// time, `try_eval_and_append` appends an expression representing the computed /// value - a tree of [`Literal`], [`Compose`], [`ZeroValue`], and [`Swizzle`] /// expressions - to the arena. See the [`try_eval_and_append`] method for details. /// /// A `ConstantEvaluator` also holds whatever information we need to carry out /// that evaluation: types, other constants, and so on. /// /// [`try_eval_and_append`]: ConstantEvaluator::try_eval_and_append /// [`Compose`]: Expression::Compose /// [`ZeroValue`]: Expression::ZeroValue /// [`Literal`]: Expression::Literal /// [`Swizzle`]: Expression::Swizzle #[derive(Debug)] pubstruct ConstantEvaluator<'a> { /// Which language's evaluation rules we should follow.
behavior: Behavior<'a>,
/// The module's type arena. /// /// Because expressions like [`Splat`] contain type handles, we need to be /// able to add new types to produce those expressions. /// /// [`Splat`]: Expression::Splat
types: &'a mut UniqueArena<Type>,
/// The module's constant arena.
constants: &'a Arena<Constant>,
/// The module's override arena.
overrides: &'a Arena<Override>,
/// The arena to which we are contributing expressions.
expressions: &'a mut Arena<Expression>,
/// Tracks the constness of expressions residing in [`Self::expressions`]
expression_kind_tracker: &'a mut ExpressionKindTracker,
}
#[derive(Debug)] enum WgslRestrictions<'a> { /// - const-expressions will be evaluated and inserted in the arena Const(Option<FunctionLocalData<'a>>), /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena Override, /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena /// - runtime-expressions will be inserted in the arena
Runtime(FunctionLocalData<'a>),
}
#[derive(Debug)] enum GlslRestrictions<'a> { /// - const-expressions will be evaluated and inserted in the arena Const, /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena /// - runtime-expressions will be inserted in the arena
Runtime(FunctionLocalData<'a>),
}
/// Forces the the expression to not be const pubfn force_non_const(&mutself, value: Handle<Expression>) { self.inner[value] = ExpressionKind::Runtime;
}
#[derive(Clone, Debug, thiserror::Error)] #[cfg_attr(test, derive(PartialEq))] pubenum ConstantEvaluatorError { #[error("Constants cannot access function arguments")]
FunctionArg, #[error("Constants cannot access global variables")]
GlobalVariable, #[error("Constants cannot access local variables")]
LocalVariable, #[error("Cannot get the array length of a non array type")]
InvalidArrayLengthArg, #[error("Constants cannot get the array length of a dynamically sized array")]
ArrayLengthDynamic, #[error("Cannot call arrayLength on array sized by override-expression")]
ArrayLengthOverridden, #[error("Constants cannot call functions")]
Call, #[error("Constants don't support workGroupUniformLoad")]
WorkGroupUniformLoadResult, #[error("Constants don't support atomic functions")]
Atomic, #[error("Constants don't support derivative functions")]
Derivative, #[error("Constants don't support load expressions")]
Load, #[error("Constants don't support image expressions")]
ImageExpression, #[error("Constants don't support ray query expressions")]
RayQueryExpression, #[error("Constants don't support subgroup expressions")]
SubgroupExpression, #[error("Cannot access the type")]
InvalidAccessBase, #[error("Cannot access at the index")]
InvalidAccessIndex, #[error("Cannot access with index of type")]
InvalidAccessIndexTy, #[error("Constants don't support array length expressions")]
ArrayLength, #[error("Cannot cast scalar components of expression `{from}` to type `{to}`")]
InvalidCastArg { from: String, to: String }, #[error("Cannot apply the unary op to the argument")]
InvalidUnaryOpArg, #[error("Cannot apply the binary op to the arguments")]
InvalidBinaryOpArgs, #[error("Cannot apply math function to type")]
InvalidMathArg, #[error("{0:?} built-in function expects {1:?} arguments but {2:?} were supplied")]
InvalidMathArgCount(crate::MathFunction, usize, usize), #[error("value of `low` is greater than `high` for clamp built-in function")]
InvalidClamp, #[error("Splat is defined only on scalar values")]
SplatScalarOnly, #[error("Can only swizzle vector constants")]
SwizzleVectorOnly, #[error("swizzle component not present in source expression")]
SwizzleOutOfBounds, #[error("Type is not constructible")]
TypeNotConstructible, #[error("Subexpression(s) are not constant")]
SubexpressionsAreNotConstant, #[error("Not implemented as constant expression: {0}")]
NotImplemented(String), #[error("{0} operation overflowed")]
Overflow(String), #[error( "the concrete type `{to_type}` cannot represent the abstract value `{value}` accurately"
)]
AutomaticConversionLossy {
value: String,
to_type: &'static str,
}, #[error("abstract floating-point values cannot be automatically converted to integers")]
AutomaticConversionFloatToInt { to_type: &'static str }, #[error("Division by zero")]
DivisionByZero, #[error("Remainder by zero")]
RemainderByZero, #[error("RHS of shift operation is greater than or equal to 32")]
ShiftedMoreThan32Bits, #[error(transparent)]
Literal(#[from] crate::valid::LiteralError), #[error("Can't use pipeline-overridable constants in const-expressions")] Override, #[error("Unexpected runtime-expression")]
RuntimeExpr, #[error("Unexpected override-expression")]
OverrideExpr,
}
impl<'a> ConstantEvaluator<'a> { /// Return a [`ConstantEvaluator`] that will add expressions to `module`'s /// constant expression arena. /// /// Report errors according to WGSL's rules for constant evaluation. pubfn for_wgsl_module(
module: &'a mut crate::Module,
global_expression_kind_tracker: &'a mut ExpressionKindTracker,
in_override_ctx: bool,
) -> Self { Self::for_module(
Behavior::Wgsl(if in_override_ctx {
WgslRestrictions::Override
} else {
WgslRestrictions::Const(None)
}),
module,
global_expression_kind_tracker,
)
}
/// Return a [`ConstantEvaluator`] that will add expressions to `module`'s /// constant expression arena. /// /// Report errors according to GLSL's rules for constant evaluation. pubfn for_glsl_module(
module: &'a mut crate::Module,
global_expression_kind_tracker: &'a mut ExpressionKindTracker,
) -> Self { Self::for_module(
Behavior::Glsl(GlslRestrictions::Const),
module,
global_expression_kind_tracker,
)
}
fn check_and_get(
&mutself,
expr: Handle<Expression>,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { matchself.expressions[expr] {
Expression::Constant(c) => { // Are we working in a function's expression arena, or the // module's constant expression arena? iflet Some(function_local_data) = self.function_local_data() { // Deep-copy the constant's value into our arena. self.copy_from( self.constants[c].init,
function_local_data.global_expressions,
)
} else { // "See through" the constant and use its initializer.
Ok(self.constants[c].init)
}
}
_ => { self.check(expr)?;
Ok(expr)
}
}
}
/// Try to evaluate `expr` at compile time. /// /// The `expr` argument can be any sort of Naga [`Expression`] you like. If /// we can determine its value at compile time, we append an expression /// representing its value - a tree of [`Literal`], [`Compose`], /// [`ZeroValue`], and [`Swizzle`] expressions - to the expression arena /// `self` contributes to. /// /// If `expr`'s value cannot be determined at compile time, and `self` is /// contributing to some function's expression arena, then append `expr` to /// that arena unchanged (and thus unevaluated). Otherwise, `self` must be /// contributing to the module's constant expression arena; since `expr`'s /// value is not a constant, return an error. /// /// We only consider `expr` itself, without recursing into its operands. Its /// operands must all have been produced by prior calls to /// `try_eval_and_append`, to ensure that they have already been reduced to /// an evaluated form if possible. /// /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose /// [`ZeroValue`]: Expression::ZeroValue /// [`Swizzle`]: Expression::Swizzle pubfn try_eval_and_append(
&mutself,
expr: Expression,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { matchself.expression_kind_tracker.type_of_with_expr(&expr) {
ExpressionKind::ImplConst => self.try_eval_and_append_impl(&expr, span),
ExpressionKind::Const => { let eval_result = self.try_eval_and_append_impl(&expr, span); // We should be able to evaluate `Const` expressions at this // point. If we failed to, then that probably means we just // haven't implemented that part of constant evaluation. Work // around this by simply emitting it as a run-time expression. ifself.behavior.has_runtime_restrictions()
&& matches!(
eval_result,
Err(ConstantEvaluatorError::NotImplemented(_)
| ConstantEvaluatorError::InvalidBinaryOpArgs,)
)
{
Ok(self.append_expr(expr, span, ExpressionKind::Runtime))
} else {
eval_result
}
}
ExpressionKind::Override => matchself.behavior {
Behavior::Wgsl(WgslRestrictions::Override | WgslRestrictions::Runtime(_)) => {
Ok(self.append_expr(expr, span, ExpressionKind::Override))
}
Behavior::Wgsl(WgslRestrictions::Const(_)) => {
Err(ConstantEvaluatorError::OverrideExpr)
}
Behavior::Glsl(_) => {
unreachable!()
}
},
ExpressionKind::Runtime => { ifself.behavior.has_runtime_restrictions() {
Ok(self.append_expr(expr, span, ExpressionKind::Runtime))
} else {
Err(ConstantEvaluatorError::RuntimeExpr)
}
}
}
}
/// Is the [`Self::expressions`] arena the global module expression arena? constfn is_global_arena(&self) -> bool {
matches!( self.behavior,
Behavior::Wgsl(WgslRestrictions::Const(None) | WgslRestrictions::Override)
| Behavior::Glsl(GlslRestrictions::Const)
)
}
fn try_eval_and_append_impl(
&mutself,
expr: &Expression,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
log::trace!("try_eval_and_append: {:?}", expr); match *expr {
Expression::Constant(c) ifself.is_global_arena() => { // "See through" the constant and use its initializer. // This is mainly done to avoid having constants pointing to other constants.
Ok(self.constants[c].init)
}
Expression::Override(_) => Err(ConstantEvaluatorError::Override),
Expression::Literal(_) | Expression::ZeroValue(_) | Expression::Constant(_) => { self.register_evaluated_expr(expr.clone(), span)
}
Expression::Compose { ty, ref components } => { let components = components
.iter()
.map(|component| self.check_and_get(*component))
.collect::<Result<Vec<_>, _>>()?; self.register_evaluated_expr(Expression::Compose { ty, components }, span)
}
Expression::Splat { size, value } => { let value = self.check_and_get(value)?; self.register_evaluated_expr(Expression::Splat { size, value }, span)
}
Expression::AccessIndex { base, index } => { let base = self.check_and_get(base)?;
self.access(base, index as usize, span)
}
Expression::Access { base, index } => { let base = self.check_and_get(base)?; let index = self.check_and_get(index)?;
self.swizzle(size, span, vector, pattern)
}
Expression::Unary { expr, op } => { let expr = self.check_and_get(expr)?;
self.unary_op(op, expr, span)
}
Expression::Binary { left, right, op } => { let left = self.check_and_get(left)?; let right = self.check_and_get(right)?;
self.binary_op(op, left, right, span)
}
Expression::Math {
fun,
arg,
arg1,
arg2,
arg3,
} => { let arg = self.check_and_get(arg)?; let arg1 = arg1.map(|arg| self.check_and_get(arg)).transpose()?; let arg2 = arg2.map(|arg| self.check_and_get(arg)).transpose()?; let arg3 = arg3.map(|arg| self.check_and_get(arg)).transpose()?;
/// Lower [`ZeroValue`] expressions to [`Literal`] and [`Compose`] expressions. /// /// [`ZeroValue`]: Expression::ZeroValue /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose fn eval_zero_value_impl(
&mutself,
ty: Handle<Type>,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { matchself.types[ty].inner {
TypeInner::Scalar(scalar) => { let expr = Expression::Literal(
Literal::zero(scalar).ok_or(ConstantEvaluatorError::TypeNotConstructible)?,
); self.register_evaluated_expr(expr, span)
}
TypeInner::Vector { size, scalar } => { let scalar_ty = self.types.insert( Type {
name: None,
inner: TypeInner::Scalar(scalar),
},
span,
); let el = self.eval_zero_value_impl(scalar_ty, span)?; let expr = Expression::Compose {
ty,
components: vec![el; size as usize],
}; self.register_evaluated_expr(expr, span)
}
TypeInner::Matrix {
columns,
rows,
scalar,
} => { let vec_ty = self.types.insert( Type {
name: None,
inner: TypeInner::Vector { size: rows, scalar },
},
span,
); let el = self.eval_zero_value_impl(vec_ty, span)?; let expr = Expression::Compose {
ty,
components: vec![el; columns as usize],
}; self.register_evaluated_expr(expr, span)
}
TypeInner::Array {
base,
size: ArraySize::Constant(size),
..
} => { let el = self.eval_zero_value_impl(base, span)?; let expr = Expression::Compose {
ty,
components: vec![el; size.get() as usize],
}; self.register_evaluated_expr(expr, span)
}
TypeInner::Struct { ref members, .. } => { let types: Vec<_> = members.iter().map(|m| m.ty).collect(); letmut components = Vec::with_capacity(members.len()); for ty in types {
components.push(self.eval_zero_value_impl(ty, span)?);
} let expr = Expression::Compose { ty, components }; self.register_evaluated_expr(expr, span)
}
_ => Err(ConstantEvaluatorError::TypeNotConstructible),
}
}
/// Convert the scalar components of `expr` to `target`. /// /// Treat `span` as the location of the resulting expression. pubfn cast(
&mutself,
expr: Handle<Expression>,
target: crate::Scalar,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { usecrate::Scalar as Sc;
let expr = self.eval_zero_value(expr, span)?;
let make_error = || -> Result<_, ConstantEvaluatorError> { let from = format!("{:?} {:?}", expr, self.expressions[expr]);
#[cfg(feature = "wgsl-in")] let to = target.to_wgsl();
#[cfg(not(feature = "wgsl-in"))] let to = format!("{target:?}");
Err(ConstantEvaluatorError::InvalidCastArg { from, to })
};
let expr = matchself.expressions[expr] {
Expression::Literal(literal) => { let literal = match target {
Sc::I32 => Literal::I32(match literal {
Literal::I32(v) => v,
Literal::U32(v) => v as i32,
Literal::F32(v) => v as i32,
Literal::Bool(v) => v as i32,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error();
}
Literal::AbstractInt(v) => i32::try_from_abstract(v)?,
Literal::AbstractFloat(v) => i32::try_from_abstract(v)?,
}),
Sc::U32 => Literal::U32(match literal {
Literal::I32(v) => v as u32,
Literal::U32(v) => v,
Literal::F32(v) => v as u32,
Literal::Bool(v) => v as u32,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error();
}
Literal::AbstractInt(v) => u32::try_from_abstract(v)?,
Literal::AbstractFloat(v) => u32::try_from_abstract(v)?,
}),
Sc::I64 => Literal::I64(match literal {
Literal::I32(v) => v as i64,
Literal::U32(v) => v as i64,
Literal::F32(v) => v as i64,
Literal::Bool(v) => v as i64,
Literal::F64(v) => v as i64,
Literal::I64(v) => v,
Literal::U64(v) => v as i64,
Literal::AbstractInt(v) => i64::try_from_abstract(v)?,
Literal::AbstractFloat(v) => i64::try_from_abstract(v)?,
}),
Sc::U64 => Literal::U64(match literal {
Literal::I32(v) => v as u64,
Literal::U32(v) => v as u64,
Literal::F32(v) => v as u64,
Literal::Bool(v) => v as u64,
Literal::F64(v) => v as u64,
Literal::I64(v) => v as u64,
Literal::U64(v) => v,
Literal::AbstractInt(v) => u64::try_from_abstract(v)?,
Literal::AbstractFloat(v) => u64::try_from_abstract(v)?,
}),
Sc::F32 => Literal::F32(match literal {
Literal::I32(v) => v as f32,
Literal::U32(v) => v as f32,
Literal::F32(v) => v,
Literal::Bool(v) => v as u32 as f32,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error();
}
Literal::AbstractInt(v) => f32::try_from_abstract(v)?,
Literal::AbstractFloat(v) => f32::try_from_abstract(v)?,
}),
Sc::F64 => Literal::F64(match literal {
Literal::I32(v) => v as f64,
Literal::U32(v) => v as f64,
Literal::F32(v) => v as f64,
Literal::F64(v) => v,
Literal::Bool(v) => v as u32 as f64,
Literal::I64(_) | Literal::U64(_) => return make_error(),
Literal::AbstractInt(v) => f64::try_from_abstract(v)?,
Literal::AbstractFloat(v) => f64::try_from_abstract(v)?,
}),
Sc::BOOL => Literal::Bool(match literal {
Literal::I32(v) => v != 0,
Literal::U32(v) => v != 0,
Literal::F32(v) => v != 0.0,
Literal::Bool(v) => v,
Literal::F64(_)
| Literal::I64(_)
| Literal::U64(_)
| Literal::AbstractInt(_)
| Literal::AbstractFloat(_) => { return make_error();
}
}),
Sc::ABSTRACT_FLOAT => Literal::AbstractFloat(match literal {
Literal::AbstractInt(v) => { // Overflow is forbidden, but inexact conversions // are fine. The range of f64 is far larger than // that of i64, so we don't have to check anything // here.
v as f64
}
Literal::AbstractFloat(v) => v,
_ => return make_error(),
}),
_ => {
log::debug!("Constant evaluator refused to convert value to {target:?}"); return make_error();
}
};
Expression::Literal(literal)
}
Expression::Compose {
ty,
components: ref src_components,
} => { let ty_inner = matchself.types[ty].inner {
TypeInner::Vector { size, .. } => TypeInner::Vector {
size,
scalar: target,
},
TypeInner::Matrix { columns, rows, .. } => TypeInner::Matrix {
columns,
rows,
scalar: target,
},
_ => return make_error(),
};
letmut components = src_components.clone(); for component in &mut components {
*component = self.cast(*component, target, span)?;
}
let ty = self.types.insert( Type {
name: None,
inner: ty_inner,
},
span,
);
/// Convert the scalar leaves of `expr` to `target`, handling arrays. /// /// `expr` must be a `Compose` expression whose type is a scalar, vector, /// matrix, or nested arrays of such. /// /// This is basically the same as the [`cast`] method, except that that /// should only handle Naga [`As`] expressions, which cannot convert arrays. /// /// Treat `span` as the location of the resulting expression. /// /// [`cast`]: ConstantEvaluator::cast /// [`As`]: crate::Expression::As pubfn cast_array(
&mutself,
expr: Handle<Expression>,
target: crate::Scalar,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { let Expression::Compose { ty, ref components } = self.expressions[expr] else { returnself.cast(expr, target, span);
};
fn binary_op(
&mutself,
op: BinaryOperator,
left: Handle<Expression>,
right: Handle<Expression>,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { let left = self.eval_zero_value_and_splat(left, span)?; let right = self.eval_zero_value_and_splat(right, span)?;
let expr = match (&self.expressions[left], &self.expressions[right]) {
(&Expression::Literal(left_value), &Expression::Literal(right_value)) => { let literal = match op {
BinaryOperator::Equal => Literal::Bool(left_value == right_value),
BinaryOperator::NotEqual => Literal::Bool(left_value != right_value),
BinaryOperator::Less => Literal::Bool(left_value < right_value),
BinaryOperator::LessEqual => Literal::Bool(left_value <= right_value),
BinaryOperator::Greater => Literal::Bool(left_value > right_value),
BinaryOperator::GreaterEqual => Literal::Bool(left_value >= right_value),
_ => match (left_value, right_value) {
(Literal::I32(a), Literal::I32(b)) => Literal::I32(match op {
BinaryOperator::Add => a.wrapping_add(b),
BinaryOperator::Subtract => a.wrapping_sub(b),
BinaryOperator::Multiply => a.wrapping_mul(b),
BinaryOperator::Divide => { if b == 0 { return Err(ConstantEvaluatorError::DivisionByZero);
} else {
a.wrapping_div(b)
}
}
BinaryOperator::Modulo => { if b == 0 { return Err(ConstantEvaluatorError::RemainderByZero);
} else {
a.wrapping_rem(b)
}
}
BinaryOperator::And => a & b,
BinaryOperator::ExclusiveOr => a ^ b,
BinaryOperator::InclusiveOr => a | b,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
(Literal::I32(a), Literal::U32(b)) => Literal::I32(match op {
BinaryOperator::ShiftLeft => { if (if a.is_negative() { !a } else { a }).leading_zeros() <= b { return Err(ConstantEvaluatorError::Overflow("<<".to_string()));
}
a.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?
}
BinaryOperator::ShiftRight => a
.checked_shr(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
(Literal::U32(a), Literal::U32(b)) => Literal::U32(match op {
BinaryOperator::Add => a.checked_add(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("addition".into())
})?,
BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("subtraction".into())
})?,
BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("multiplication".into())
})?,
BinaryOperator::Divide => a
.checked_div(b)
.ok_or(ConstantEvaluatorError::DivisionByZero)?,
BinaryOperator::Modulo => a
.checked_rem(b)
.ok_or(ConstantEvaluatorError::RemainderByZero)?,
BinaryOperator::And => a & b,
BinaryOperator::ExclusiveOr => a ^ b,
BinaryOperator::InclusiveOr => a | b,
BinaryOperator::ShiftLeft => a
.checked_mul( 1u32.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
)
.ok_or(ConstantEvaluatorError::Overflow("<<".to_string()))?,
BinaryOperator::ShiftRight => a
.checked_shr(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
(Literal::F32(a), Literal::F32(b)) => Literal::F32(match op {
BinaryOperator::Add => a + b,
BinaryOperator::Subtract => a - b,
BinaryOperator::Multiply => a * b,
BinaryOperator::Divide => a / b,
BinaryOperator::Modulo => a % b,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
(Literal::AbstractInt(a), Literal::U32(b)) => {
Literal::AbstractInt(match op {
BinaryOperator::ShiftLeft => { if (if a.is_negative() { !a } else { a }).leading_zeros() <= b { return Err(ConstantEvaluatorError::Overflow( "<<".to_string(),
));
}
a.checked_shl(b).unwrap_or(0)
}
BinaryOperator::ShiftRight => a.checked_shr(b).unwrap_or(0),
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
})
}
(Literal::AbstractInt(a), Literal::AbstractInt(b)) => {
Literal::AbstractInt(match op {
BinaryOperator::Add => a.checked_add(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("addition".into())
})?,
BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("subtraction".into())
})?,
BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| {
ConstantEvaluatorError::Overflow("multiplication".into())
})?,
BinaryOperator::Divide => a.checked_div(b).ok_or_else(|| { if b == 0 {
ConstantEvaluatorError::DivisionByZero
} else {
ConstantEvaluatorError::Overflow("division".into())
}
})?,
BinaryOperator::Modulo => a.checked_rem(b).ok_or_else(|| { if b == 0 {
ConstantEvaluatorError::RemainderByZero
} else {
ConstantEvaluatorError::Overflow("remainder".into())
}
})?,
BinaryOperator::And => a & b,
BinaryOperator::ExclusiveOr => a ^ b,
BinaryOperator::InclusiveOr => a | b,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
})
}
(Literal::AbstractFloat(a), Literal::AbstractFloat(b)) => {
Literal::AbstractFloat(match op {
BinaryOperator::Add => a + b,
BinaryOperator::Subtract => a - b,
BinaryOperator::Multiply => a * b,
BinaryOperator::Divide => a / b,
BinaryOperator::Modulo => a % b,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
})
}
(Literal::Bool(a), Literal::Bool(b)) => Literal::Bool(match op {
BinaryOperator::LogicalAnd => a && b,
BinaryOperator::LogicalOr => a || b,
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
},
};
Expression::Literal(literal)
}
(
&Expression::Compose {
components: ref src_components,
ty,
},
&Expression::Literal(_),
) => { letmut components = src_components.clone(); for component in &mut components {
*component = self.binary_op(op, *component, right, span)?;
}
Expression::Compose { ty, components }
}
(
&Expression::Literal(_),
&Expression::Compose {
components: ref src_components,
ty,
},
) => { letmut components = src_components.clone(); for component in &mut components {
*component = self.binary_op(op, left, *component, span)?;
}
Expression::Compose { ty, components }
}
(
&Expression::Compose {
components: ref left_components,
ty: left_ty,
},
&Expression::Compose {
components: ref right_components,
ty: right_ty,
},
) => { // We have to make a copy of the component lists, because the // call to `binary_op_vector` needs `&mut self`, but `self` owns // the component lists. let left_flattened = crate::proc::flatten_compose(
left_ty,
left_components, self.expressions, self.types,
); let right_flattened = crate::proc::flatten_compose(
right_ty,
right_components, self.expressions, self.types,
);
// `flatten_compose` doesn't return an `ExactSizeIterator`, so // make a reasonable guess of the capacity we'll need. letmut flattened = Vec::with_capacity(left_components.len());
flattened.extend(left_flattened.zip(right_flattened));
fn binary_op_vector(
&mutself,
op: BinaryOperator,
size: crate::VectorSize,
components: &[(Handle<Expression>, Handle<Expression>)],
left_ty: Handle<Type>,
span: Span,
) -> Result<Expression, ConstantEvaluatorError> { let ty = match op { // Relational operators produce vectors of booleans.
BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::Less
| BinaryOperator::LessEqual
| BinaryOperator::Greater
| BinaryOperator::GreaterEqual => self.types.insert( Type {
name: None,
inner: TypeInner::Vector {
size,
scalar: crate::Scalar::BOOL,
},
},
span,
),
// Other operators produce the same type as their left // operand.
BinaryOperator::Add
| BinaryOperator::Subtract
| BinaryOperator::Multiply
| BinaryOperator::Divide
| BinaryOperator::Modulo
| BinaryOperator::And
| BinaryOperator::ExclusiveOr
| BinaryOperator::InclusiveOr
| BinaryOperator::LogicalAnd
| BinaryOperator::LogicalOr
| BinaryOperator::ShiftLeft
| BinaryOperator::ShiftRight => left_ty,
};
/// Deep copy `expr` from `expressions` into `self.expressions`. /// /// Return the root of the new copy. /// /// This is used when we're evaluating expressions in a function's /// expression arena that refer to a constant: we need to copy the /// constant's value into the function's arena so we can operate on it. fn copy_from(
&mutself,
expr: Handle<Expression>,
expressions: &Arena<Expression>,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { let span = expressions.get_span(expr); match expressions[expr] { ref expr @ (Expression::Literal(_)
| Expression::Constant(_)
| Expression::ZeroValue(_)) => self.register_evaluated_expr(expr.clone(), span),
Expression::Compose { ty, ref components } => { letmut components = components.clone(); for component in &mut components {
*component = self.copy_from(*component, expressions)?;
} self.register_evaluated_expr(Expression::Compose { ty, components }, span)
}
Expression::Splat { size, value } => { let value = self.copy_from(value, expressions)?; self.register_evaluated_expr(Expression::Splat { size, value }, span)
}
_ => {
log::debug!("copy_from: SubexpressionsAreNotConstant");
Err(ConstantEvaluatorError::SubexpressionsAreNotConstant)
}
}
}
fn register_evaluated_expr(
&mutself,
expr: Expression,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { // It suffices to only check literals, since we only register one // expression at a time, `Compose` expressions can only refer to other // expressions, and `ZeroValue` expressions are always okay. iflet Expression::Literal(literal) = expr { crate::valid::check_literal_value(literal)?;
}
fn first_trailing_bit(concrete_int: ConcreteInt<1>) -> ConcreteInt<1> { // NOTE: Bit indices for this built-in start at 0 at the "right" (or LSB). For example, a value // of 1 means the least significant bit is set. Therefore, an input of `0x[80 00…]` would // return a right-to-left bit index of 0. let trailing_zeros_to_bit_idx = |e: u32| -> u32 { match e {
idx @ 0..=31 => idx, 32 => u32::MAX,
_ => unreachable!(),
}
}; match concrete_int {
ConcreteInt::U32([e]) => ConcreteInt::U32([trailing_zeros_to_bit_idx(e.trailing_zeros())]),
ConcreteInt::I32([e]) => {
ConcreteInt::I32([trailing_zeros_to_bit_idx(e.trailing_zeros()) as i32])
}
}
}
fn first_leading_bit(concrete_int: ConcreteInt<1>) -> ConcreteInt<1> { // NOTE: Bit indices for this built-in start at 0 at the "right" (or LSB). For example, 1 means // the least significant bit is set. Therefore, an input of 1 would return a right-to-left bit // index of 0. let rtl_to_ltr_bit_idx = |e: u32| -> u32 { match e {
idx @ 0..=31 => 31 - idx, 32 => u32::MAX,
_ => unreachable!(),
}
}; match concrete_int {
ConcreteInt::I32([e]) => ConcreteInt::I32([{ let rtl_bit_index = if e.is_negative() {
e.leading_ones()
} else {
e.leading_zeros()
};
rtl_to_ltr_bit_idx(rtl_bit_index) as i32
}]),
ConcreteInt::U32([e]) => ConcreteInt::U32([rtl_to_ltr_bit_idx(e.leading_zeros())]),
}
}
/// Trait for conversions of abstract values to concrete types. trait TryFromAbstract<T>: Sized { /// Convert an abstract literal `value` to `Self`. /// /// Since Naga's `AbstractInt` and `AbstractFloat` exist to support /// WGSL, we follow WGSL's conversion rules here: /// /// - WGSL §6.1.2. Conversion Rank says that automatic conversions /// to integers are either lossless or an error. /// /// - WGSL §14.6.4 Floating Point Conversion says that conversions /// to floating point in constant expressions and override /// expressions are errors if the value is out of range for the /// destination type, but rounding is okay. /// /// [`AbstractInt`]: crate::Literal::AbstractInt /// [`Float`]: crate::Literal::Float fn try_from_abstract(value: T) -> Result<Self, ConstantEvaluatorError>;
}
impl TryFromAbstract<i64> for f32 { fn try_from_abstract(value: i64) -> Result<Self, ConstantEvaluatorError> { let f = value as f32; // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of // `f32` is roughly ±3.4 × 10³⁸, so there's no opportunity for // overflow here.
Ok(f)
}
}
impl TryFromAbstract<f64> for f32 { fn try_from_abstract(value: f64) -> Result<f32, ConstantEvaluatorError> { let f = value as f32; if f.is_infinite() { return Err(ConstantEvaluatorError::AutomaticConversionLossy {
value: format!("{value:?}"),
to_type: "f32",
});
}
Ok(f)
}
}
impl TryFromAbstract<i64> for f64 { fn try_from_abstract(value: i64) -> Result<Self, ConstantEvaluatorError> { let f = value as f64; // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of // `f64` is roughly ±1.8 × 10³⁰⁸, so there's no opportunity for // overflow here.
Ok(f)
}
}
let expr = global_expressions.append(Expression::Constant(h), Default::default()); let expr1 = global_expressions.append(Expression::Constant(vec_h), Default::default());
let expr2 = Expression::Unary {
op: UnaryOperator::Negate,
expr,
};
let expr3 = Expression::Unary {
op: UnaryOperator::BitwiseNot,
expr,
};
let expr4 = Expression::Unary {
op: UnaryOperator::BitwiseNot,
expr: expr1,
};
let pass = match global_expressions[solved_negate] {
Expression::Compose { ty, ref components } => {
ty == vec2_i32_ty
&& components.iter().all(|&component| { let component = &global_expressions[component];
matches!(*component, Expression::Literal(Literal::I32(-4)))
})
}
_ => false,
}; if !pass {
panic!("unexpected evaluation result")
}
}
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.47Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-21)
¤
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.