#[derive(Error, Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pubenum PipelineConstantError { #[error("Missing value for pipeline-overridable constant with identifier string: '{0}'")]
MissingValue(String), #[error( "Source f64 value needs to be finite ({}) for number destinations", "NaNs and Inifinites are not allowed"
)]
SrcNeedsToBeFinite, #[error("Source f64 value doesn't fit in destination")]
DstRangeTooSmall, #[error(transparent)]
ConstantEvaluatorError(#[from] ConstantEvaluatorError), #[error(transparent)]
ValidationError(#[from] WithSpan<ValidationError>), #[error("workgroup_size override isn't strictly positive")]
NegativeWorkgroupSize,
}
/// Replace all overrides in `module` with constants. /// /// If no changes are needed, this just returns `Cow::Borrowed` /// references to `module` and `module_info`. Otherwise, it clones /// `module`, edits its [`global_expressions`] arena to contain only /// fully-evaluated expressions, and returns `Cow::Owned` values /// holding the simplified module and its validation results. /// /// In either case, the module returned has an empty `overrides` /// arena, and the `global_expressions` arena contains only /// fully-evaluated expressions. /// /// [`global_expressions`]: Module::global_expressions pubfn process_overrides<'a>(
module: &'a Module,
module_info: &'a ModuleInfo,
pipeline_constants: &PipelineConstants,
) -> Result<(Cow<'a, Module>, Cow<'a, ModuleInfo>), PipelineConstantError> { if module.overrides.is_empty() { return Ok((Cow::Borrowed(module), Cow::Borrowed(module_info)));
}
letmut module = module.clone();
// A map from override handles to the handles of the constants // we've replaced them with. letmut override_map = HandleVec::with_capacity(module.overrides.len());
// A map from `module`'s original global expression handles to // handles in the new, simplified global expression arena. letmut adjusted_global_expressions = HandleVec::with_capacity(module.global_expressions.len());
// The set of constants whose initializer handles we've already // updated to refer to the newly built global expression arena. // // All constants in `module` must have their `init` handles // updated to point into the new, simplified global expression // arena. Some of these we can most easily handle as a side effect // during the simplification process, but we must handle the rest // in a final fixup pass, guided by `adjusted_global_expressions`. We // add their handles to this set, so that the final fixup step can // leave them alone. letmut adjusted_constant_initializers = HashSet::with_capacity(module.constants.len());
// An iterator through the original overrides table, consumed in // approximate tandem with the global expressions. letmut override_iter = module.overrides.drain();
// Do two things in tandem: // // - Rebuild the global expression arena from scratch, fully // evaluating all expressions, and replacing each `Override` // expression in `module.global_expressions` with a `Constant` // expression. // // - Build a new `Constant` in `module.constants` to take the // place of each `Override`. // // Build a map from old global expression handles to their // fully-evaluated counterparts in `adjusted_global_expressions` as we // go. // // Why in tandem? Overrides refer to expressions, and expressions // refer to overrides, so we can't disentangle the two into // separate phases. However, we can take advantage of the fact // that the overrides and expressions must form a DAG, and work // our way from the leaves to the roots, replacing and evaluating // as we go. // // Although the two loops are nested, this is really two // alternating phases: we adjust and evaluate constant expressions // until we hit an `Override` expression, at which point we switch // to building `Constant`s for `Overrides` until we've handled the // one used by the expression. Then we switch back to processing // expressions. Because we know they form a DAG, we know the // `Override` expressions we encounter can only have initializers // referring to global expressions we've already simplified. for (old_h, expr, span) in module.global_expressions.drain() { letmut expr = match expr {
Expression::Override(h) => { let c_h = iflet Some(new_h) = override_map.get(h) {
*new_h
} else { letmut new_h = None; for entry in override_iter.by_ref() { let stop = entry.0 == h;
new_h = Some(process_override(
entry,
pipeline_constants,
&mut module,
&mut override_map,
&adjusted_global_expressions,
&mut adjusted_constant_initializers,
&mut global_expression_kind_tracker,
)?); if stop { break;
}
}
new_h.unwrap()
};
Expression::Constant(c_h)
}
Expression::Constant(c_h) => { if adjusted_constant_initializers.insert(c_h) { let init = &mut module.constants[c_h].init;
*init = adjusted_global_expressions[*init];
}
expr
}
expr => expr,
}; letmut evaluator = ConstantEvaluator::for_wgsl_module(
&mut module,
&mut global_expression_kind_tracker, false,
);
adjust_expr(&adjusted_global_expressions, &mut expr); let h = evaluator.try_eval_and_append(expr, span)?;
adjusted_global_expressions.insert(old_h, h);
}
// Finish processing any overrides we didn't visit in the loop above. for entry in override_iter {
process_override(
entry,
pipeline_constants,
&mut module,
&mut override_map,
&adjusted_global_expressions,
&mut adjusted_constant_initializers,
&mut global_expression_kind_tracker,
)?;
}
// Update the initialization expression handles of all `Constant`s // and `GlobalVariable`s. Skip `Constant`s we'd already updated en // passant. for (_, c) in module
.constants
.iter_mut()
.filter(|&(c_h, _)| !adjusted_constant_initializers.contains(&c_h))
{
c.init = adjusted_global_expressions[c.init];
}
for (_, v) in module.global_variables.iter_mut() { iflet Some(refmut init) = v.init {
*init = adjusted_global_expressions[*init];
}
}
letmut functions = mem::take(&mut module.functions); for (_, function) in functions.iter_mut() {
process_function(&mut module, &override_map, function)?;
}
module.functions = functions;
letmut entry_points = mem::take(&mut module.entry_points); for ep in entry_points.iter_mut() {
process_function(&mut module, &override_map, &mut ep.function)?;
process_workgroup_size_override(&mut module, &adjusted_global_expressions, ep)?;
}
module.entry_points = entry_points;
// Now that we've rewritten all the expressions, we need to // recompute their types and other metadata. For the time being, // do a full re-validation. letmut validator = Validator::new(ValidationFlags::all(), Capabilities::all()); let module_info = validator.validate_no_overrides(&module)?;
/// Add a [`Constant`] to `module` for the override `old_h`. /// /// Add the new `Constant` to `override_map` and `adjusted_constant_initializers`. fn process_override(
(old_h, override_, span): (Handle<Override>, Override, Span),
pipeline_constants: &PipelineConstants,
module: &mut Module,
override_map: &mut HandleVec<Override, Handle<Constant>>,
adjusted_global_expressions: &HandleVec<Expression, Handle<Expression>>,
adjusted_constant_initializers: &mut HashSet<Handle<Constant>>,
global_expression_kind_tracker: &mutcrate::proc::ExpressionKindTracker,
) -> Result<Handle<Constant>, PipelineConstantError> { // Determine which key to use for `override_` in `pipeline_constants`. let key = iflet Some(id) = override_.id {
Cow::Owned(id.to_string())
} elseiflet Some(ref name) = override_.name {
Cow::Borrowed(name)
} else {
unreachable!();
};
// Generate a global expression for `override_`'s value, either // from the provided `pipeline_constants` table or its initializer // in the module. let init = iflet Some(value) = pipeline_constants.get::<str>(&key) { let literal = match module.types[override_.ty].inner {
TypeInner::Scalar(scalar) => map_value_to_literal(*value, scalar)?,
_ => unreachable!(),
}; let expr = module
.global_expressions
.append(Expression::Literal(literal), Span::UNDEFINED);
global_expression_kind_tracker.insert(expr, crate::proc::ExpressionKind::Const);
expr
} elseiflet Some(init) = override_.init {
adjusted_global_expressions[init]
} else { return Err(PipelineConstantError::MissingValue(key.to_string()));
};
// Generate a new `Constant` to represent the override's value. let constant = Constant {
name: override_.name,
ty: override_.ty,
init,
}; let h = module.constants.append(constant, span);
override_map.insert(old_h, h);
adjusted_constant_initializers.insert(h);
Ok(h)
}
/// Replace all override expressions in `function` with fully-evaluated constants. /// /// Replace all `Expression::Override`s in `function`'s expression arena with /// the corresponding `Expression::Constant`s, as given in `override_map`. /// Replace any expressions whose values are now known with their fully /// evaluated form. /// /// If `h` is a `Handle<Override>`, then `override_map[h]` is the /// `Handle<Constant>` for the override's final value. fn process_function(
module: &mut Module,
override_map: &HandleVec<Override, Handle<Constant>>,
function: &mut Function,
) -> Result<(), ConstantEvaluatorError> { // A map from original local expression handles to // handles in the new, local expression arena. letmut adjusted_local_expressions = HandleVec::with_capacity(function.expressions.len());
// Dummy `emitter` and `block` for the constant evaluator. // We can ignore the concept of emitting expressions here since // expressions have already been covered by a `Statement::Emit` // in the frontend. // The only thing we might have to do is remove some expressions // that have been covered by a `Statement::Emit`. See the docs of // `filter_emits_in_block` for the reasoning. letmut emitter = Emitter::default(); letmut block = Block::new();
// Update local expression initializers. for (_, local) in function.local_variables.iter_mut() { iflet &mut Some(refmut init) = &mut local.init {
*init = adjusted_local_expressions[*init];
}
}
// We've changed the keys of `function.named_expression`, so we have to // rebuild it from scratch. let named_expressions = mem::take(&mut function.named_expressions); for (expr_h, name) in named_expressions {
function
.named_expressions
.insert(adjusted_local_expressions[expr_h], name);
}
/// Replace every expression handle in `block` with its counterpart /// given by `new_pos`. fn adjust_block(new_pos: &HandleVec<Expression, Handle<Expression>>, block: &mut Block) { for stmt in block.iter_mut() {
adjust_stmt(new_pos, stmt);
}
}
/// Adjust [`Emit`] statements in `block` to skip [`needs_pre_emit`] expressions we have introduced. /// /// According to validation, [`Emit`] statements must not cover any expressions /// for which [`Expression::needs_pre_emit`] returns true. All expressions built /// by successful constant evaluation fall into that category, meaning that /// `process_function` will usually rewrite [`Override`] expressions and those /// that use their values into pre-emitted expressions, leaving any [`Emit`] /// statements that cover them invalid. /// /// This function rewrites all [`Emit`] statements into zero or more new /// [`Emit`] statements covering only those expressions in the original range /// that are not pre-emitted. /// /// [`Emit`]: Statement::Emit /// [`needs_pre_emit`]: Expression::needs_pre_emit /// [`Override`]: Expression::Override fn filter_emits_in_block(block: &mut Block, expressions: &Arena<Expression>) { let original = mem::replace(block, Block::with_capacity(block.len())); for (stmt, span) in original.span_into_iter() { match stmt {
Statement::Emit(range) => { letmut current = None; for expr_h in range { if expressions[expr_h].needs_pre_emit() { iflet Some((first, last)) = current {
block.push(Statement::Emit(Range::new_from_bounds(first, last)), span);
}
fn map_value_to_literal(value: f64, scalar: Scalar) -> Result<Literal, PipelineConstantError> { // note that in rust 0.0 == -0.0 match scalar {
Scalar::BOOL => { // https://webidl.spec.whatwg.org/#js-boolean let value = value != 0.0 && !value.is_nan();
Ok(Literal::Bool(value))
}
Scalar::I32 => { // https://webidl.spec.whatwg.org/#js-long if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite);
}
let value = value.trunc(); if value < f64::from(i32::MIN) || value > f64::from(i32::MAX) { return Err(PipelineConstantError::DstRangeTooSmall);
}
let value = value as i32;
Ok(Literal::I32(value))
}
Scalar::U32 => { // https://webidl.spec.whatwg.org/#js-unsigned-long if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite);
}
let value = value.trunc(); if value < f64::from(u32::MIN) || value > f64::from(u32::MAX) { return Err(PipelineConstantError::DstRangeTooSmall);
}
let value = value as u32;
Ok(Literal::U32(value))
}
Scalar::F32 => { // https://webidl.spec.whatwg.org/#js-float if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite);
}
let value = value as f32; if !value.is_finite() { return Err(PipelineConstantError::DstRangeTooSmall);
}
#[test] fn test_map_value_to_literal() { let bool_test_cases = [
(0.0, false),
(-0.0, false),
(f64::NAN, false),
(1.0, true),
(f64::INFINITY, true),
(f64::NEG_INFINITY, true),
]; for (value, out) in bool_test_cases { let res = Ok(Literal::Bool(out));
assert_eq!(map_value_to_literal(value, Scalar::BOOL), res);
}
for scalar in [Scalar::I32, Scalar::U32, Scalar::F32, Scalar::F64] { for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { let res = Err(PipelineConstantError::SrcNeedsToBeFinite);
assert_eq!(map_value_to_literal(value, scalar), res);
}
}
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.