use naga::{
BinaryOperator as Op, Block, Expression, Function, Handle, Literal, MathFunction, Module,
ScalarKind, Statement, UnaryOperator,
};
#[derive(Clone, Copy, Debug, PartialEq)]
enum Value {
Bool(bool),
U32(u32),
I32(i32),
}
impl Value {
fn of(literal: &Literal) -> Option<Self> {
match *literal {
Literal::Bool(value) => Some(Self::Bool(value)),
Literal::U32(value) => Some(Self::U32(value)),
Literal::I32(value) => Some(Self::I32(value)),
_ => None,
}
}
fn int(self) -> Option<i64> {
match self {
Self::U32(value) => Some(i64::from(value)),
Self::I32(value) => Some(i64::from(value)),
Self::Bool(_) => None,
}
}
fn like(self, number: i64) -> Self {
match self {
Self::U32(_) => Self::U32(number as u32),
Self::I32(_) => Self::I32(number as i32),
Self::Bool(_) => Self::Bool(number != 0),
}
}
}
fn decisive(op: Op, left: Option<Value>, right: Option<Value>) -> Option<Value> {
let decides = |value| left == Some(Value::Bool(value)) || right == Some(Value::Bool(value));
match op {
Op::LogicalAnd | Op::And if decides(false) => Some(Value::Bool(false)),
Op::LogicalOr | Op::InclusiveOr if decides(true) => Some(Value::Bool(true)),
_ => None,
}
}
fn logical(op: Op, a: bool, b: bool) -> Option<bool> {
match op {
Op::LogicalAnd | Op::And => Some(a & b),
Op::LogicalOr | Op::InclusiveOr => Some(a | b),
Op::ExclusiveOr | Op::NotEqual => Some(a ^ b),
Op::Equal => Some(a == b),
_ => None,
}
}
fn compare(op: Op, a: i64, b: i64) -> Option<bool> {
match op {
Op::Equal => Some(a == b),
Op::NotEqual => Some(a != b),
Op::Less => Some(a < b),
Op::LessEqual => Some(a <= b),
Op::Greater => Some(a > b),
Op::GreaterEqual => Some(a >= b),
_ => None,
}
}
fn arithmetic(op: Op, a: i64, b: i64) -> Option<i64> {
match op {
Op::And => Some(a & b),
Op::InclusiveOr => Some(a | b),
Op::ExclusiveOr => Some(a ^ b),
Op::Add => Some(a.wrapping_add(b)),
Op::Subtract => Some(a.wrapping_sub(b)),
Op::Multiply => Some(a.wrapping_mul(b)),
Op::ShiftLeft => Some(a << (b & 31)),
Op::ShiftRight => Some(a >> (b & 31)),
_ => None,
}
}
fn binary(op: Op, left: Option<Value>, right: Option<Value>) -> Option<Value> {
if let Some(value) = decisive(op, left, right) {
return Some(value);
}
let (left, right) = (left?, right?);
if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
return logical(op, a, b).map(Value::Bool);
}
let (a, b) = (left.int()?, right.int()?);
compare(op, a, b)
.map(Value::Bool)
.or_else(|| arithmetic(op, a, b).map(|number| left.like(number)))
}
fn extreme(fun: MathFunction, a: Option<Value>, b: Option<Value>) -> Option<Value> {
let (a, b) = (a?, b?);
let (x, y) = (a.int()?, b.int()?);
Some(a.like(if fun == MathFunction::Max {
x.max(y)
} else {
x.min(y)
}))
}
fn cast(value: Value, kind: ScalarKind) -> Option<Value> {
let number = match value {
Value::Bool(value) => i64::from(value),
other => other.int()?,
};
match kind {
ScalarKind::Uint => Some(Value::U32(number as u32)),
ScalarKind::Sint => Some(Value::I32(number as i32)),
ScalarKind::Bool => Some(Value::Bool(number != 0)),
_ => None,
}
}
fn decided(module: &Module, function: &Function) -> Vec<Option<Value>> {
let mut values: Vec<Option<Value>> = Vec::with_capacity(function.expressions.len());
for (_, expression) in function.expressions.iter() {
let value = |handle: Handle<Expression>| values[handle.index()];
let decided = match *expression {
Expression::Literal(ref literal) => Value::of(literal),
Expression::Constant(constant) => {
match module.global_expressions[module.constants[constant].init] {
Expression::Literal(ref literal) => Value::of(literal),
_ => None,
}
}
Expression::Unary {
op: UnaryOperator::LogicalNot,
expr,
} => match value(expr) {
Some(Value::Bool(value)) => Some(Value::Bool(!value)),
_ => None,
},
Expression::Binary { op, left, right } => binary(op, value(left), value(right)),
Expression::Select {
condition,
accept,
reject,
} => match value(condition) {
Some(Value::Bool(true)) => value(accept),
Some(Value::Bool(false)) => value(reject),
_ => None,
},
Expression::As {
expr,
kind,
convert: Some(_),
} => value(expr).and_then(|value| cast(value, kind)),
Expression::Math {
fun: fun @ (MathFunction::Max | MathFunction::Min),
arg,
arg1: Some(arg1),
..
} => extreme(fun, value(arg), value(arg1)),
_ => None,
};
values.push(decided);
}
values
}
fn ends(statement: &Statement) -> bool {
match statement {
Statement::Return { .. } | Statement::Kill | Statement::Break | Statement::Continue => true,
Statement::Block(block) => block.last().is_some_and(ends),
_ => false,
}
}
fn fold_block(block: Block, values: &[Option<Value>]) -> Block {
let mut folded = Block::with_capacity(block.len());
for (statement, span) in block.span_into_iter() {
let statement = match statement {
Statement::If {
condition,
accept,
reject,
} => match values[condition.index()] {
Some(Value::Bool(taken)) => {
let kept = fold_block(if taken { accept } else { reject }, values);
if kept.is_empty() {
continue;
}
Statement::Block(kept)
}
_ => Statement::If {
condition,
accept: fold_block(accept, values),
reject: fold_block(reject, values),
},
},
Statement::Block(inner) => Statement::Block(fold_block(inner, values)),
Statement::Loop {
body,
continuing,
break_if,
} => Statement::Loop {
body: fold_block(body, values),
continuing: fold_block(continuing, values),
break_if,
},
Statement::Switch { selector, cases } => Statement::Switch {
selector,
cases: cases
.into_iter()
.map(|case| naga::SwitchCase {
body: fold_block(case.body, values),
..case
})
.collect(),
},
other => other,
};
let leaves = ends(&statement);
folded.push(statement, span);
if leaves {
break;
}
}
folded
}
fn mark_evaluated(block: &Block, evaluated: &mut [bool]) {
for statement in block {
match statement {
Statement::Emit(range) => range
.clone()
.for_each(|handle| evaluated[handle.index()] = true),
Statement::Block(inner) => mark_evaluated(inner, evaluated),
Statement::If { accept, reject, .. } => {
mark_evaluated(accept, evaluated);
mark_evaluated(reject, evaluated);
}
Statement::Loop {
body, continuing, ..
} => {
mark_evaluated(body, evaluated);
mark_evaluated(continuing, evaluated);
}
Statement::Switch { cases, .. } => cases
.iter()
.for_each(|case| mark_evaluated(&case.body, evaluated)),
Statement::Call {
result: Some(result),
..
}
| Statement::Atomic {
result: Some(result),
..
}
| Statement::WorkGroupUniformLoad { result, .. } => evaluated[result.index()] = true,
_ => {}
}
}
}
fn evaluated_in_place(expression: &Expression) -> bool {
!matches!(
expression,
Expression::Literal(_)
| Expression::Constant(_)
| Expression::Override(_)
| Expression::ZeroValue(_)
| Expression::FunctionArgument(_)
| Expression::GlobalVariable(_)
| Expression::LocalVariable(_)
)
}
fn fold_function(module: &Module, function: &mut Function) {
let values = decided(module, function);
let body = std::mem::take(&mut function.body);
function.body = fold_block(body, &values);
let mut evaluated = vec![false; function.expressions.len()];
mark_evaluated(&function.body, &mut evaluated);
let expressions = &function.expressions;
function.named_expressions.retain(|handle, _| {
evaluated[handle.index()] || !evaluated_in_place(&expressions[*handle])
});
}
pub(crate) fn fold(module: &mut Module) {
let mut functions = std::mem::take(&mut module.functions);
for (_, function) in functions.iter_mut() {
fold_function(module, function);
}
module.functions = functions;
let mut entry_points = std::mem::take(&mut module.entry_points);
for entry_point in &mut entry_points {
fold_function(module, &mut entry_point.function);
}
module.entry_points = entry_points;
naga::compact::compact(module, naga::compact::KeepUnused::No);
}