// Code in this file intentionally uses `for` loops and `.push()` rather than // `ArrayVec::from_iter`, because the latter is monomorphized by all three of // the item type, the capacity, and the iterator type, which can easily bloat // the compiled executable (by ~260 KiB, when it was removed).
use alloc::{
format,
string::{String, ToString},
vec,
vec::Vec,
}; use core::iter;
use arrayvec::ArrayVec; use half::f16; use num_traits::{real::Real, FromPrimitive, One, ToPrimitive, Zero};
/// 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>(
eval: &mut ConstantEvaluator<'_>,
span: Span,
exprs: [Handle<Expression>; N],
handler: fn($target<N>) -> Result<$target<M>, ConstantEvaluatorError>,
) -> Result<Handle<Expression>, ConstantEvaluatorError> where
$target<M>: Into<Expression>,
{
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,
layouter: &'a mut crate::proc::Layouter,
}
#[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("{0} built-in function argument is out of valid range")]
InvalidMathArgValue(String), #[error("Cannot apply relational function to type")]
InvalidRelationalArg(RelationalFunction), #[error("value of `low` is greater than `high` for clamp built-in function")]
InvalidClamp, #[error("Constructor expects {expected} components, found {actual}")]
InvalidVectorComposeLength { expected: usize, actual: usize }, #[error("Constructor must only contain vector or scalar arguments")]
InvalidVectorComposeComponent, #[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("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, #[error("Expected boolean expression for condition argument of `select`, got something else")]
SelectScalarConditionNotABool, #[error( "Expected vectors of the same size for reject and accept args., got {:?} and {:?}",
reject,
accept
)]
SelectVecRejectAcceptSizeMismatch {
reject: crate::VectorSize,
accept: crate::VectorSize,
}, #[error("Expected boolean vector for condition arg., got something else")]
SelectConditionNotAVecBool, #[error( "Expected same number of vector components between condition, accept, and reject args., got something else",
)]
SelectConditionVecSizeMismatch, #[error( "Expected reject and accept args. to be scalars of vectors of the same type, got something else",
)]
SelectAcceptRejectTypeMismatch, #[error("Cooperative operations can't be constant")]
CooperativeOperation, #[error("Type is too large")]
TypeTooLarge(Handle<Type>),
}
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. pubconstfn for_wgsl_module(
module: &'a mut crate::Module,
global_expression_kind_tracker: &'a mut ExpressionKindTracker,
layouter: &'a mut crate::proc::Layouter,
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,
layouter,
)
}
/// Return a [`ConstantEvaluator`] that will add expressions to `module`'s /// constant expression arena. /// /// Report errors according to GLSL's rules for constant evaluation. pubconstfn for_glsl_module(
module: &'a mut crate::Module,
global_expression_kind_tracker: &'a mut ExpressionKindTracker,
layouter: &'a mut crate::proc::Layouter,
) -> Self { Self::for_module(
Behavior::Glsl(GlslRestrictions::Const),
module,
global_expression_kind_tracker,
layouter,
)
}
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::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)
}
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)?;
let index_val: u32 = self
.to_ctx()
.get_const_val_from(index, self.expressions)
.map_err(|_| ConstantEvaluatorError::InvalidAccessIndexTy)?; self.access(base, index_val as usize, span)
}
Expression::Swizzle {
size,
vector,
pattern,
} => { let vector = self.check_and_get(vector)?;
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()?;
let (a, ty) = self.extract_vec_with_size::<3>(a)?; let (b, _) = self.extract_vec_with_size::<3>(b)?;
let product = match (a, b) {
(
[Li::AbstractInt(a0), Li::AbstractInt(a1), Li::AbstractInt(a2)],
[Li::AbstractInt(b0), Li::AbstractInt(b1), Li::AbstractInt(b2)],
) => { // `cross` has no overload for AbstractInt, so AbstractInt // arguments are automatically converted to AbstractFloat. Since // `f64` has a much wider range than `i64`, there's no danger of // overflow here. let p = cross_product(
[a0 as f64, a1 as f64, a2 as f64],
[b0 as f64, b1 as f64, b2 as f64],
);
[
Li::AbstractFloat(p[0]),
Li::AbstractFloat(p[1]),
Li::AbstractFloat(p[2]),
]
}
(
[Li::AbstractFloat(a0), Li::AbstractFloat(a1), Li::AbstractFloat(a2)],
[Li::AbstractFloat(b0), Li::AbstractFloat(b1), Li::AbstractFloat(b2)],
) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]);
[
Li::AbstractFloat(p[0]),
Li::AbstractFloat(p[1]),
Li::AbstractFloat(p[2]),
]
}
([Li::F16(a0), Li::F16(a1), Li::F16(a2)], [Li::F16(b0), Li::F16(b1), Li::F16(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]);
[Li::F16(p[0]), Li::F16(p[1]), Li::F16(p[2])]
}
([Li::F32(a0), Li::F32(a1), Li::F32(a2)], [Li::F32(b0), Li::F32(b1), Li::F32(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]);
[Li::F32(p[0]), Li::F32(p[1]), Li::F32(p[2])]
}
([Li::F64(a0), Li::F64(a1), Li::F64(a2)], [Li::F64(b0), Li::F64(b1), Li::F64(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]);
[Li::F64(p[0]), Li::F64(p[1]), Li::F64(p[2])]
}
_ => return Err(ConstantEvaluatorError::InvalidMathArg),
};
let p0 = self.register_evaluated_expr(Expression::Literal(product[0]), span)?; let p1 = self.register_evaluated_expr(Expression::Literal(product[1]), span)?; let p2 = self.register_evaluated_expr(Expression::Literal(product[2]), span)?;
/// Extract the values of a `vecN` from `expr`. /// /// Return the value of `expr`, whose type is `vecN<S>` for some /// vector size `N` and scalar `S`, as an array of `N` [`Literal`] /// values. /// /// Also return the type handle from the `Compose` expression. fn extract_vec_with_size<const N: usize>(
&mutself,
expr: Handle<Expression>,
) -> Result<([Literal; N], Handle<Type>), ConstantEvaluatorError> { let span = self.expressions.get_span(expr); let expr = self.eval_zero_value_and_splat(expr, span)?; let Expression::Compose { ty, ref components } = self.expressions[expr] else { return Err(ConstantEvaluatorError::InvalidMathArg);
};
letmut value = [Literal::Bool(false); N]; for (component, elt) in crate::proc::flatten_compose(ty, components, self.expressions, self.types)
.zip(value.iter_mut())
{ let Expression::Literal(literal) = self.expressions[component] else { return Err(ConstantEvaluatorError::InvalidMathArg);
};
*elt = literal;
}
Ok((value, ty))
}
/// Extract the values of a `vecN` from `expr`. /// /// Return the value of `expr`, whose type is `vecN<S>` for some /// vector size `N` and scalar `S`, as an array of `N` [`Literal`] /// values. /// /// Also return the type handle from the `Compose` expression. fn extract_vec(
&mutself,
expr: Handle<Expression>,
allow_single: bool,
) -> Result<LiteralVector, ConstantEvaluatorError> { let span = self.expressions.get_span(expr); let expr = self.eval_zero_value_and_splat(expr, span)?;
/// Lower [`ZeroValue`] and [`Splat`] expressions to [`Literal`] and [`Compose`] expressions. /// /// [`ZeroValue`]: Expression::ZeroValue /// [`Splat`]: Expression::Splat /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose fn eval_zero_value_and_splat(
&mutself, mut expr: Handle<Expression>,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { // If expr is a Compose expression, eliminate ZeroValue and Splat expressions for // each of its components. iflet Expression::Compose { ty, ref components } = self.expressions[expr] { let components = components
.clone()
.iter()
.map(|component| self.eval_zero_value_and_splat(*component, span))
.collect::<Result<_, _>>()?;
expr = self.register_evaluated_expr(Expression::Compose { ty, components }, span)?;
}
// The result of the splat() for a Splat of a scalar ZeroValue is a // vector ZeroValue, so we must call eval_zero_value_impl() after // splat() in order to ensure we have no ZeroValues remaining. iflet Expression::Splat { size, value } = self.expressions[expr] {
expr = self.splat(value, size, span)?;
} iflet Expression::ZeroValue(ty) = self.expressions[expr] {
expr = self.eval_zero_value_impl(ty, span)?;
}
Ok(expr)
}
/// 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_for_diagnostics();
#[cfg(not(feature = "wgsl-in"))] let to = format!("{target:?}");
Err(ConstantEvaluatorError::InvalidCastArg { from, to })
};
usecrate::proc::type_methods::IntFloatLimits;
let expr = matchself.expressions[expr] {
Expression::Literal(literal) => { let literal = match target {
Sc::I16 => Literal::I16(match literal {
Literal::I16(v) => v,
Literal::U16(v) => v as i16,
Literal::I32(v) => v as i16,
Literal::U32(v) => v as i16,
Literal::F32(v) => v.clamp(i16::MIN as f32, i16::MAX as f32) as i16,
Literal::F16(v) => {
f16::to_f32(v).clamp(i16::MIN as f32, i16::MAX as f32) as i16
}
Literal::Bool(v) => v as i16,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error();
}
Literal::AbstractInt(v) => v as i16,
Literal::AbstractFloat(v) => v as i16,
}),
Sc::U16 => Literal::U16(match literal {
Literal::I16(v) => v as u16,
Literal::U16(v) => v,
Literal::I32(v) => v as u16,
Literal::U32(v) => v as u16,
Literal::F32(v) => v.clamp(u16::MIN as f32, u16::MAX as f32) as u16,
Literal::F16(v) => f16::to_u16(&v.max(f16::ZERO)).unwrap(),
Literal::Bool(v) => v as u16,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error();
}
Literal::AbstractInt(v) => v as u16,
Literal::AbstractFloat(v) => v as u16,
}),
Sc::I32 => Literal::I32(match literal {
Literal::I16(v) => v as i32,
Literal::U16(v) => v as i32,
Literal::I32(v) => v,
Literal::U32(v) => v as i32,
Literal::F32(v) => v.clamp(i32::min_float(), i32::max_float()) as i32,
Literal::F16(v) => f16::to_i32(&v).unwrap(), //Only None on NaN or Inf
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::I16(v) => v as u32,
Literal::U16(v) => v as u32,
Literal::I32(v) => v as u32,
Literal::U32(v) => v,
Literal::F32(v) => v.clamp(u32::min_float(), u32::max_float()) as u32, // max(0) avoids None due to negative, therefore only None on NaN or Inf
Literal::F16(v) => f16::to_u32(&v.max(f16::ZERO)).unwrap(),
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::I16(v) => v as i64,
Literal::U16(v) => v as i64,
Literal::I32(v) => v as i64,
Literal::U32(v) => v as i64,
Literal::F32(v) => v.clamp(i64::min_float(), i64::max_float()) as i64,
Literal::Bool(v) => v as i64,
Literal::F64(v) => v.clamp(i64::min_float(), i64::max_float()) as i64,
Literal::I64(v) => v,
Literal::U64(v) => v as i64,
Literal::F16(v) => f16::to_i64(&v).unwrap(), //Only None on NaN or Inf
Literal::AbstractInt(v) => i64::try_from_abstract(v)?,
Literal::AbstractFloat(v) => i64::try_from_abstract(v)?,
}),
Sc::U64 => Literal::U64(match literal {
Literal::I16(v) => v as u64,
Literal::U16(v) => v as u64,
Literal::I32(v) => v as u64,
Literal::U32(v) => v as u64,
Literal::F32(v) => v.clamp(u64::min_float(), u64::max_float()) as u64,
Literal::Bool(v) => v as u64,
Literal::F64(v) => v.clamp(u64::min_float(), u64::max_float()) as u64,
Literal::I64(v) => v as u64,
Literal::U64(v) => v, // max(0) avoids None due to negative, therefore only None on NaN or Inf
Literal::F16(v) => f16::to_u64(&v.max(f16::ZERO)).unwrap(),
Literal::AbstractInt(v) => u64::try_from_abstract(v)?,
Literal::AbstractFloat(v) => u64::try_from_abstract(v)?,
}),
Sc::F16 => Literal::F16(match literal {
Literal::F16(v) => v,
Literal::F32(v) => f16::from_f32(v),
Literal::F64(v) => f16::from_f64(v),
Literal::Bool(v) => f16::from_u32(v as u32).unwrap(),
Literal::I16(v) => f16::from_f32(v as f32),
Literal::U16(v) => f16::from_f32(v as f32),
Literal::I64(v) => f16::from_i64(v).unwrap(),
Literal::U64(v) => f16::from_u64(v).unwrap(),
Literal::I32(v) => f16::from_i32(v).unwrap(),
Literal::U32(v) => f16::from_u32(v).unwrap(),
Literal::AbstractFloat(v) => f16::try_from_abstract(v)?,
Literal::AbstractInt(v) => f16::try_from_abstract(v)?,
}),
Sc::F32 => Literal::F32(match literal {
Literal::I16(v) => v as f32,
Literal::U16(v) => v as f32,
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::F16(v) => f16::to_f32(v),
Literal::AbstractInt(v) => f32::try_from_abstract(v)?,
Literal::AbstractFloat(v) => f32::try_from_abstract(v)?,
}),
Sc::F64 => Literal::F64(match literal {
Literal::I16(v) => v as f64,
Literal::U16(v) => v as f64,
Literal::I32(v) => v as f64,
Literal::U32(v) => v as f64,
Literal::F16(v) => f16::to_f64(v),
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::I16(v) => v != 0,
Literal::U16(v) => v != 0,
Literal::I32(v) => v != 0,
Literal::U32(v) => v != 0,
Literal::F32(v) => v != 0.0,
Literal::F16(v) => v != f16::zero(),
Literal::Bool(v) => v,
Literal::AbstractInt(v) => v != 0,
Literal::AbstractFloat(v) => v != 0.0,
Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { 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(),
}),
Sc::ABSTRACT_INT => Literal::AbstractInt(match literal {
Literal::AbstractInt(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 expr = self.check_and_get(expr)?;
fn binary_op_vector(
&mutself,
op: BinaryOperator,
size: crate::VectorSize,
left_components: &[Handle<Expression>],
right_components: &[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::ShiftLeft
| BinaryOperator::ShiftRight => left_ty,
BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { // Not supported on vectors return Err(ConstantEvaluatorError::InvalidBinaryOpArgs);
}
};
/// 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)
}
}
}
/// Returns the total number of components, after flattening, of a vector compose expression. fn vector_compose_flattened_size(
&self,
components: &[Handle<Expression>],
) -> Result<usize, ConstantEvaluatorError> {
components
.iter()
.try_fold(0, |acc, c| -> Result<_, ConstantEvaluatorError> { let size = match *self.resolve_type(*c)?.inner_with(self.types) {
TypeInner::Scalar(_) => 1, // We trust that the vector size of `component` is correct, // as it will have already been validated when `component` // was registered.
TypeInner::Vector { size, .. } => size as usize,
_ => return Err(ConstantEvaluatorError::InvalidVectorComposeComponent),
};
Ok(acc + size)
})
}
fn register_evaluated_expr(
&mutself,
expr: Expression,
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> { // It suffices to only check_literal_value() for `Literal` expressions, // 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)?;
}
// Ensure vector composes contain the correct number of components. We // do so here when each compose is registered to avoid having to deal // with the mess each time the compose is used in another expression. iflet Expression::Compose { ty, ref components } = expr { iflet TypeInner::Vector { size, scalar: _ } = self.types[ty].inner { let expected = size as usize; let actual = self.vector_compose_flattened_size(components)?; if expected != actual { return Err(ConstantEvaluatorError::InvalidVectorComposeLength {
expected,
actual,
});
}
}
}
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 /// from [`AbstractInt`] to an integer type are either lossless or an /// error. /// /// - WGSL §15.7.6 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. /// /// - WGSL §17.1.2 i32()/u32() constructors treat AbstractFloat as any /// other floating point type, following the scalar floating point to /// integral conversion algorithm (§15.7.6). There is no automatic /// conversion from AbstractFloat to integer types. /// /// [`AbstractInt`]: crate::Literal::AbstractInt /// [`AbstractFloat`]: crate::Literal::AbstractFloat 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)
}
}
impl TryFromAbstract<f64> for i32 { fn try_from_abstract(value: f64) -> Result<Self, ConstantEvaluatorError> { // https://www.w3.org/TR/WGSL/#floating-point-conversion // To convert a floating point scalar value X to an integer scalar type T: // * If X is a NaN, the result is an indeterminate value in T. // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to truncate(X) and // also exactly representable in the original floating point type. // // A rust cast satisfies these requirements apart from "the result // is... exactly representable in the original floating point type". // However, i32::MIN and i32::MAX are exactly representable by f64, so // we're all good.
Ok(value as i32)
}
}
impl TryFromAbstract<f64> for u32 { fn try_from_abstract(value: f64) -> Result<Self, ConstantEvaluatorError> { // As above, u32::MIN and u32::MAX are exactly representable by f64, // so a simple rust cast is sufficient.
Ok(value as u32)
}
}
impl TryFromAbstract<f64> for i64 { fn try_from_abstract(value: f64) -> Result<Self, ConstantEvaluatorError> { // As above, except we clamp to the minimum and maximum values // representable by both f64 and i64. usecrate::proc::type_methods::IntFloatLimits;
Ok(value.clamp(i64::min_float(), i64::max_float()) as i64)
}
}
impl TryFromAbstract<f64> for u64 { fn try_from_abstract(value: f64) -> Result<Self, ConstantEvaluatorError> { // As above, this time clamping to the minimum and maximum values // representable by both f64 and u64. usecrate::proc::type_methods::IntFloatLimits;
Ok(value.clamp(u64::min_float(), u64::max_float()) as u64)
}
}
impl TryFromAbstract<f64> for f16 { fn try_from_abstract(value: f64) -> Result<f16, ConstantEvaluatorError> { let f = f16::from_f64(value); if f.is_infinite() { return Err(ConstantEvaluatorError::AutomaticConversionLossy {
value: format!("{value:?}"),
to_type: "f16",
});
}
Ok(f)
}
}
impl TryFromAbstract<i64> for f16 { fn try_from_abstract(value: i64) -> Result<f16, ConstantEvaluatorError> { let f = f16::from_i64(value); if f.is_none() { return Err(ConstantEvaluatorError::AutomaticConversionLossy {
value: format!("{value:?}"),
to_type: "f16",
});
}
Ok(f.unwrap())
}
}
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 (mut vec_tys, mut mat_tys) = (FastHashMap::default(), FastHashMap::default()); for c in2..=4 { let vec_ty = types.insert( Type {
name: None,
inner: TypeInner::Vector {
size: Self::int_to_vector_size(c),
scalar: crate::Scalar::F32,
},
},
span,
);
vec_tys.insert(c, vec_ty); for r in2..=4 { let mat_ty = types.insert( Type {
name: None,
inner: TypeInner::Matrix {
columns: Self::int_to_vector_size(c),
rows: Self::int_to_vector_size(r),
scalar: crate::Scalar::F32,
},
},
span,
);
mat_tys.insert((c, r), mat_ty);
}
}
letmut lit_exprs = FastHashMap::default(); for i in0..16 { let expr = expressions.append(Expression::Literal(Literal::F32(i as f32)), span);
lit_exprs.insert(i, expr);
}
letmut vec_exprs = FastHashMap::default(); for c in2..=4 { let expr = expressions.append(
Expression::Compose {
ty: *vec_tys.get(&c).unwrap(),
components: (0..c)
.map(|i| *lit_exprs.get(&i).unwrap())
.collect::<Vec<_>>(),
},
span,
);
vec_exprs.insert(c, expr);
}
letmut mat_exprs = FastHashMap::default(); for c in2..=4 { for r in2..=4 { letmut columns = Vec::with_capacity(c); for cc in0..c { let start = cc * r; let expr = expressions.append(
Expression::Compose {
ty: *vec_tys.get(&r).unwrap(),
components: (start..start + r)
.map(|i| *lit_exprs.get(&i).unwrap())
.collect::<Vec<_>>(),
},
span,
);
columns.push(expr);
}
let pass = match global_expressions[solved_add] {
Expression::Compose { ty, ref components } => {
ty == vec2_f32_ty
&& components.iter().all(|&component| { let component = &global_expressions[component];
matches!(*component, Expression::Literal(Literal::F32(5.0)))
})
}
_ => false,
}; if !pass {
panic!("unexpected evaluation result")
}
}
}
Messung V0.5 in Prozent
¤ 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.0.170Bemerkung:
¤
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.