// 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. const fn 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 pub fn 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. pubconst fn 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. pubconst fn 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 pub fn 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(
&mut self,
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.
if let 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.
if let Expression::Splat { size, value } = self.expressions[expr] {
expr = self.splat(value, size, span)?;
}
if let 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(
&mut self,
ty: Handle<Type>, span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
match self.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();
let mut 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.
pub fn cast(
&mut self,
expr: Handle<Expression>,
target: crate::Scalar, span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
use crate::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 })
};
use crate::proc::type_methods::IntFloatLimits;
let expr = match self.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 = match self.types[ty].inner {
TypeInner::Vector { size, .. } => TypeInner::Vector {
size,
scalar: target,
},
TypeInner::Matrix { columns, rows, .. } => TypeInner::Matrix {
columns,
rows,
scalar: target,
},
_ => return make_error(),
};
let mut 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
pub fn cast_array(
&mut self,
expr: Handle<Expression>,
target: crate::Scalar, span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
let expr = self.check_and_get(expr)?;
let mut components = components.clone();
for component in &mut components {
*component = self.cast_array(*component, target, span)?;
}
let first = components.first().unwrap();
let new_base = match self.resolve_type(*first)? {
crate::proc::TypeResolution::Handle(ty) => ty,
crate::proc::TypeResolution::Value(inner) => {
self.types.insert(Type { name: None, inner }, span)
}
};
let mut layouter = core::mem::take(self.layouter);
layouter.update(self.to_ctx()).map_err(|err| {
// The layouter operates lazily, so the error
// could be for any pending type.
let crate::proc::LayoutErrorInner::TooLarge = err.inner else {
unreachable!("unexpected layout error: {err:?}");
};
ConstantEvaluatorError::TypeTooLarge(err.ty)
})?;
*self.layouter = layouter;
let new_base_stride = self.layouter[new_base].to_stride();
let new_array_ty = self.types.insert(
Type {
name: None,
inner: TypeInner::Array { base: new_base,
size,
stride: new_base_stride,
},
}, span,
);
fn binary_op_vector(
&mut self,
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);
}
};
let flatten = self.flatten_matrix(mat_components);
let nr = mat_rows as usize;
let components = (0..nr)
.map(|r| {
let row = flatten.iter().skip(r).step_by(nr).cloned();
self.dot_exprs(row, vec_components.iter().cloned(), span)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Expression::Compose { ty, components })
}
fn multiply_matrix_matrix(
&mut self,
left_components: &[Handle<Expression>],
right_components: &[Handle<Expression>],
left_rows: crate::VectorSize,
right_columns: crate::VectorSize,
scalar: crate::Scalar, span: Span,
) -> Result<Expression, ConstantEvaluatorError> {
let left_nc = left_components.len();
let left_nr = left_rows as usize;
let right_nc = right_columns as usize;
let right_nr = left_nc;
let mut result = Vec::with_capacity(right_nc);
let result_ty = self.types.insert(
Type {
name: None,
inner: TypeInner::Matrix {
columns: right_columns,
rows: left_rows,
scalar,
},
}, span,
);
let result_column_ty = self.types.insert(
Type {
name: None,
inner: TypeInner::Vector {
size: left_rows,
scalar,
},
}, span,
);
let left_flattened = self.flatten_matrix(left_components);
let right_flattened = self.flatten_matrix(right_components);
for c in 0..right_nc {
let result_column = (0..left_nr)
.map(|r| {
let row = left_flattened.iter().skip(r).step_by(left_nr);
let column = right_flattened.iter().skip(c * right_nr).take(right_nr);
self.dot_exprs(row.cloned(), column.cloned(), span)
})
.collect::<Result<Vec<_>, _>>()?;
let expr = Expression::Compose {
ty: result_column_ty,
components: result_column,
};
let handle = self.register_evaluated_expr(expr, span)?;
result.push(handle);
}
Ok(Expression::Compose {
ty: result_ty,
components: result,
})
}
fn flatten_matrix(&self, columns: &[Handle<Expression>]) -> ArrayVec<Handle<Expression>, 16> {
let mut flattened = ArrayVec::<_, 16>::new();
for &column in columns {
let Expression::Compose { ref components, .. } = self.expressions[column] else {
unreachable!()
};
flattened.extend(components.iter().cloned());
}
flattened
}
fn dot_exprs(
&mut self,
left: impl Iterator<Item = Handle<Expression>>,
right: impl Iterator<Item = Handle<Expression>>, span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
let mut acc = None;
for (l, r) in left.zip(right) {
let result = self.binary_op(BinaryOperator::Multiply, l, r, span)?;
match acc.as_mut() {
Some(acc) => *acc = self.binary_op(BinaryOperator::Add, *acc, result, span)?,
None => acc = Some(result),
}
}
Ok(acc.unwrap())
}
fn relational(
&mut self,
fun: RelationalFunction,
arg: Handle<Expression>, span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
let arg = self.eval_zero_value_and_splat(arg, span)?;
match fun {
RelationalFunction::All | RelationalFunction::Any => match self.expressions[arg] {
Expression::Literal(Literal::Bool(_)) => Ok(arg),
Expression::Compose { ty, ref components }
if matches!(self.types[ty].inner, TypeInner::Vector { .. }) =>
{
let mut bool_components = ArrayVec::<bool, { crate::VectorSize::MAX }>::new();
for component in
crate::proc::flatten_compose(ty, components, self.expressions, self.types)
{
match self.expressions[component] {
Expression::Literal(Literal::Bool(val)) => {
bool_components.push(val);
}
_ => {
return Err(ConstantEvaluatorError::InvalidRelationalArg(fun));
}
}
}
let components = bool_components;
let result = match fun {
RelationalFunction::All => components.iter().all(|c| *c),
RelationalFunction::Any => components.iter().any(|c| *c),
_ => unreachable!(),
};
self.register_evaluated_expr(Expression::Literal(Literal::Bool(result)), span)
}
_ => Err(ConstantEvaluatorError::InvalidRelationalArg(fun)),
},
_ => Err(ConstantEvaluatorError::NotImplemented(format!( "{fun:?} built-in function"
))),
}
}
/// 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(
&mut self,
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 } => {
let mut 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(
&mut self,
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 atime, `Compose`
// expressions can only refer to other expressions, and `ZeroValue`
// expressions are always okay.
if let 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.
if let Expression::Compose { ty, ref components } = expr {
if let 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 append_expr(
&mut self,
expr: Expression, span: Span,
expr_type: ExpressionKind,
) -> Handle<Expression> {
let h = match self.behavior {
Behavior::Wgsl(
WgslRestrictions::Runtime(ref mut function_local_data)
| WgslRestrictions::Const(Some(ref mut function_local_data)),
)
| Behavior::Glsl(GlslRestrictions::Runtime(ref mut function_local_data)) => {
let is_running = function_local_data.emitter.is_running();
let needs_pre_emit = expr.needs_pre_emit();
if is_running && needs_pre_emit {
function_local_data
.block
.extend(function_local_data.emitter.finish(self.expressions));
let h = self.expressions.append(expr, span);
function_local_data.emitter.start(self.expressions);
h
} else {
self.expressions.append(expr, span)
}
}
_ => self.expressions.append(expr, span),
};
self.expression_kind_tracker.insert(h, expr_type);
h
}
/// Resolve the type of `expr` if it is a constant expression.
///
/// If `expr` was evaluated to a constant, returns its type.
/// Otherwise, returns an error.
fn resolve_type(
&self,
expr: Handle<Expression>,
) -> Result<crate::proc::TypeResolution, ConstantEvaluatorError> {
use crate::proc::TypeResolution as Tr;
use crate::Expression as Ex;
let resolution = match self.expressions[expr] {
Ex::Literal(ref literal) => Tr::Value(literal.ty_inner()),
Ex::Constant(c) => Tr::Handle(self.constants[c].ty),
Ex::ZeroValue(ty) | Ex::Compose { ty, .. } => Tr::Handle(ty),
Ex::Splat { size, value } => {
let Tr::Value(TypeInner::Scalar(scalar)) = self.resolve_type(value)? else {
return Err(ConstantEvaluatorError::SplatScalarOnly);
}; Tr::Value(TypeInner::Vector { scalar, size })
}
_ => {
log::debug!("resolve_type: SubexpressionsAreNotConstant");
return Err(ConstantEvaluatorError::SubexpressionsAreNotConstant);
}
};
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())]),
}
}
#[test]
fn first_leading_bit_smoke() {
assert_eq!(
first_leading_bit(ConcreteInt::I32([-1])),
ConcreteInt::I32([-1])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([0])),
ConcreteInt::I32([-1])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([1])),
ConcreteInt::I32([0])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([-2])),
ConcreteInt::I32([0])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([1234 + 4567])),
ConcreteInt::I32([12])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([i32::MAX])),
ConcreteInt::I32([30])
);
assert_eq!(
first_leading_bit(ConcreteInt::I32([i32::MIN])),
ConcreteInt::I32([30])
); // NOTE: Ignore the sign bit, which is a separate (above) case. for idx in 0..(32 - 1) {
assert_eq!(
first_leading_bit(ConcreteInt::I32([1 << idx])),
ConcreteInt::I32([idx])
);
} for idx in 1..(32 - 1) {
assert_eq!(
first_leading_bit(ConcreteInt::I32([-(1 << idx)])),
ConcreteInt::I32([idx - 1])
);
}
/// 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)
}// without pulling monitor/provider.ts into extension startup.
}
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.
use crate::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.
use crate::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,
};
#[test]
fn matrix_op() {
let mut helper = MatrixTestHelper::new();
for nc in 2..=4 { for nr in 2..=4 { // Validates multiplication on vector-matrix. // vecR(0, 1, .., r) * matCxR(0, 1, .., nc * nr)
let evaluated = helper.eval_vector_multiply_matrix(nc, nr);
let expected = (0..nc)
.map(|c| (0..nr).map(|r| (r * (c * nr + r)) as f32).sum())
.collect::<Vec<f32>>();
assert_eq!(evaluated, expected);
// Validates multiplication on matrix-vector. // matCxR(0, 1, .., nc * nr) * vecC(0, 1, .., nc)
let evaluated = helper.eval_matrix_multiply_vector(nc, nr);
let expected = (0..nr)
.map(|r| (0..nc).map(|c| (c * (c * nr + r)) as f32).sum())
.collect::<Vec<f32>>();
assert_eq!(evaluated, expected);
for k in 2..=4 { // Validates multiplication on matrix-matrix. // matKxR(0, 1, .., k * nr) * matCxK(0, 1, .., nc * k)
let evaluated = helper.eval_matrix_multiply_matrix(nr, nc, k);
let expected = (0..nc)
.flat_map(|c| {
(0..nr).map(move |r| {
(0..k).map(|v| ((v * nr + r) * (c * k + v)) as f32).sum()
})
})
.collect::<Vec<f32>>();
assert_eq!(evaluated, expected);
}
}
}
}
/// Test fixture providing pre-built f32 vector and matrix constant /// expressions with sequential element values, used to evaluate and verify /// matrix operations.
struct MatrixTestHelper {
types: UniqueArena<Type>,
expressions: Arena<Expression>, /// Vector expressions from [0, 1] to [0, 1, 2, 3].
vec_exprs: FastHashMap<usize, Handle<Expression>>, /// Matrix expressions from [0, .., 3] to [0, .., 15].
mat_exprs: FastHashMap<(usize, usize), Handle<Expression>>,
}
impl MatrixTestHelper {
fn new() -> Self {
let mut types = UniqueArena::new();
let mut expressions = Arena::new();
let span = crate::Span::default();
let (mut vec_tys, mut mat_tys) = (FastHashMap::default(), FastHashMap::default()); for c in 2..=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 in 2..=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);
}
}
let mut lit_exprs = FastHashMap::default(); for i in 0..16 {
let expr = expressions.append(Expression::Literal(Literal::F32(i as f32)), span);
lit_exprs.insert(i, expr);
}
let mut vec_exprs = FastHashMap::default(); for c in 2..=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);
}
let mut mat_exprs = FastHashMap::default(); for c in 2..=4 { for r in 2..=4 {
let mut columns = Vec::with_capacity(c); for cc in 0..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.135Bemerkung:
(vorverarbeitet am 2026-10-11)
¤
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.