Quelle block.rs
Sprache: unbekannt
|
|
/*!
Implementations for `BlockContext` methods.
*/
use super::{
index::BoundsCheckResult, selection::Selection, Block, BlockContext, Dimension, Error ,
Instruction, LocalType, LookupType, NumericType, ResultMember, Writer, WriterFlags,
};
use crate::{arena::Handle, proc::index::GuardedIndex, Statement};
use spirv::Word;
fn get_dimension(type_inner: &crate::TypeInner) -> Dimension {
match *type_inner {
crate::TypeInner::Scalar(_) => Dimension::Scalar,
crate::TypeInner::Vector { .. } => Dimension::Vector,
crate::TypeInner::Matrix { .. } => Dimension::Matrix,
_ => unreachable!(),
}
}
/// How to derive the type of `OpAccessChain` instructions from Naga IR.
///
/// Most of the time, we compile Naga IR to SPIR-V instructions whose result
/// types are simply the direct SPIR-V analog of the Naga IR's. But in some
/// cases, the Naga IR and SPIR-V types need to diverge.
///
/// This enum specifies how [`BlockContext::write_access_chain`] should
/// choose a SPIR-V result type for the `OpAccessChain` it generates, based on
/// the type of the given Naga IR [`Expression`] it's generating code for.
///
/// [`Expression`]: crate::Expression
enum AccessTypeAdjustment {
/// No adjustment needed: the SPIR-V type should be the direct
/// analog of the Naga IR expression type.
///
/// For most access chains, this is the right thing: the Naga IR access
/// expression produces a [`Pointer`] to the element / component, and the
/// SPIR-V `OpAccessChain` instruction does the same.
///
/// [`Pointer`]: crate::TypeInner::Pointer
None,
/// The SPIR-V type should be an `OpPointer` to the direct analog of the
/// Naga IR expression's type.
///
/// This is necessary for indexing binding arrays in the [`Handle`] address
/// space:
///
/// - In Naga IR, referencing a binding array [`GlobalVariable`] in the
/// [`Handle`] address space produces a value of type [`BindingArray`],
/// not a pointer to such. And [`Access`] and [`AccessIndex`] expressions
/// operate on handle binding arrays by value, and produce handle values,
/// not pointers.
///
/// - In SPIR-V, a binding array `OpVariable` produces a pointer to an
/// array, and `OpAccessChain` instructions operate on pointers,
/// regardless of whether the elements are opaque types or not.
///
/// See also the documentation for [`BindingArray`].
///
/// [`Handle`]: crate::AddressSpace::Handle
/// [`GlobalVariable`]: crate::GlobalVariable
/// [`BindingArray`]: crate::TypeInner::BindingArray
/// [`Access`]: crate::Expression::Access
/// [`AccessIndex`]: crate::Expression::AccessIndex
IntroducePointer(spirv::StorageClass),
}
/// The results of emitting code for a left-hand-side expression.
///
/// On success, `write_access_chain` returns one of these.
enum ExpressionPointer {
/// The pointer to the expression's value is available, as the value of the
/// expression with the given id.
Ready { pointer_id: Word },
/// The access expression must be conditional on the value of `condition`, a boolean
/// expression that is true if all indices are in bounds. If `condition` is true, then
/// `access` is an `OpAccessChain` instruction that will compute a pointer to the
/// expression's value. If `condition` is false, then executing `access` would be
/// undefined behavior.
Conditional {
condition: Word,
access: Instruction,
},
}
/// The termination statement to be added to the end of the block
enum BlockExit {
/// Generates an OpReturn (void return)
Return,
/// Generates an OpBranch to the specified block
Branch {
/// The branch target block
target: Word,
},
/// Translates a loop `break if` into an `OpBranchConditional` to the
/// merge block if true (the merge block is passed through [`LoopContext::break_id`]
/// or else to the loop header (passed through [`preamble_id`])
///
/// [`preamble_id`]: Self::BreakIf::preamble_id
BreakIf {
/// The condition of the `break if`
condition: Handle<crate::Expression>,
/// The loop header block id
preamble_id: Word,
},
}
/// What code generation did with a provided [`BlockExit`] value.
///
/// A function that accepts a [`BlockExit`] argument should return a value of
/// this type, to indicate whether the code it generated ended up using the
/// provided exit, or ignored it and did a non-local exit of some other kind
/// (say, [`Break`] or [`Continue`]). Some callers must use this information to
/// decide whether to generate the target block at all.
///
/// [`Break`]: Statement::Break
/// [`Continue`]: Statement::Continue
#[must_use]
enum BlockExitDisposition {
/// The generated code used the provided `BlockExit` value. If it included a
/// block label, the caller should be sure to actually emit the block it
/// refers to.
Used,
/// The generated code did not use the provided `BlockExit` value. If it
/// included a block label, the caller should not bother to actually emit
/// the block it refers to, unless it knows the block is needed for
/// something else.
Discarded,
}
#[derive(Clone, Copy, Default)]
struct LoopContext {
continuing_id: Option<Word>,
break_id: Option<Word>,
}
#[derive(Debug)]
pub(crate) struct DebugInfoInner<'a> {
pub source_code: &'a str,
pub source_file_id: Word,
}
impl Writer {
// Flip Y coordinate to adjust for coordinate space difference
// between SPIR-V and our IR.
// The `position_id` argument is a pointer to a `vecN<f32>`,
// whose `y` component we will negate.
fn write_epilogue_position_y_flip(
&mut self,
position_id: Word,
body: &mut Vec<Instruction>,
) -> Result<(), Error> {
let float_ptr_type_id = self.get_type_id(LookupType::Local(LocalType::LocalPointer {
base: NumericType::Scalar(crate::Scalar::F32),
class: spirv::StorageClass::Output,
}));
let index_y_id = self.get_index_constant(1);
let access_id = self.id_gen.next();
body.push(Instruction::access_chain(
float_ptr_type_id,
access_id,
position_id,
&[index_y_id],
));
let float_type_id = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar::F32),
)));
let load_id = self.id_gen.next();
body.push(Instruction::load(float_type_id, load_id, access_id, None));
let neg_id = self.id_gen.next();
body.push(Instruction::unary(
spirv::Op::FNegate,
float_type_id,
neg_id,
load_id,
));
body.push(Instruction::store(access_id, neg_id, None));
Ok(())
}
// Clamp fragment depth between 0 and 1.
fn write_epilogue_frag_depth_clamp(
&mut self,
frag_depth_id: Word,
body: &mut Vec<Instruction>,
) -> Result<(), Error> {
let float_type_id = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar::F32),
)));
let zero_scalar_id = self.get_constant_scalar(crate::Literal::F32(0.0));
let one_scalar_id = self.get_constant_scalar(crate::Literal::F32(1.0));
let original_id = self.id_gen.next();
body.push(Instruction::load(
float_type_id,
original_id,
frag_depth_id,
None,
));
let clamp_id = self.id_gen.next();
body.push(Instruction::ext_inst(
self.gl450_ext_inst_id,
spirv::GLOp::FClamp,
float_type_id,
clamp_id,
&[original_id, zero_scalar_id, one_scalar_id],
));
body.push(Instruction::store(frag_depth_id, clamp_id, None));
Ok(())
}
fn write_entry_point_return(
&mut self,
value_id: Word,
ir_result: &crate::FunctionResult,
result_members: &[ResultMember],
body: &mut Vec<Instruction>,
) -> Result<(), Error> {
for (index, res_member) in result_members.iter().enumerate() {
let member_value_id = match ir_result.binding {
Some(_) => value_id,
None => {
let member_value_id = self.id_gen.next();
body.push(Instruction::composite_extract(
res_member.type_id,
member_value_id,
value_id,
&[index as u32],
));
member_value_id
}
};
body.push(Instruction::store(res_member.id, member_value_id, None));
match res_member.built_in {
Some(crate::BuiltIn::Position { .. })
if self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE) =>
{
self.write_epilogue_position_y_flip(res_member.id, body)?;
}
Some(crate::BuiltIn::FragDepth)
if self.flags.contains(WriterFlags::CLAMP_FRAG_DEPTH) =>
{
self.write_epilogue_frag_depth_clamp(res_member.id, body)?;
}
_ => {}
}
}
Ok(())
}
}
impl BlockContext<'_> {
/// Cache an expression for a value.
pub(super) fn cache_expression_value(
&mut self,
expr_handle: Handle<crate::Expression>,
block: &mut Block,
) -> Result<(), Error> {
let is_named_expression = self
.ir_function
.named_expressions
.contains_key(&expr_handle);
if self.fun_info[expr_handle].ref_count == 0 && !is_named_expression {
return Ok(());
}
let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty);
let id = match self.ir_function.expressions[expr_handle] {
crate::Expression::Literal(literal) => self.writer.get_constant_scalar(literal),
crate::Expression::Constant(handle) => {
let init = self.ir_module.constants[handle].init;
self.writer.constant_ids[init]
}
crate::Expression::Override(_) => return Err(Error::Override),
crate::Expression::ZeroValue(_) => self.writer.get_constant_null(result_type_id),
crate::Expression::Compose { ty, ref components } => {
self.temp_list.clear();
if self.expression_constness.is_const(expr_handle) {
self.temp_list.extend(
crate::proc::flatten_compose(
ty,
components,
&self.ir_function.expressions,
&self.ir_module.types,
)
.map(|component| self.cached[component]),
);
self.writer
.get_constant_composite(LookupType::Handle(ty), &self.temp_list)
} else {
self.temp_list
.extend(components.iter().map(|&component| self.cached[component]));
let id = self.gen_id();
block.body.push(Instruction::composite_construct(
result_type_id,
id,
&self.temp_list,
));
id
}
}
crate::Expression::Splat { size, value } => {
let value_id = self.cached[value];
let components = &[value_id; 4][..size as usize];
if self.expression_constness.is_const(expr_handle) {
let ty = self
.writer
.get_expression_lookup_type(&self.fun_info[expr_handle].ty);
self.writer.get_constant_composite(ty, components)
} else {
let id = self.gen_id();
block.body.push(Instruction::composite_construct(
result_type_id,
id,
components,
));
id
}
}
crate::Expression::Access { base, index } => {
let base_ty_inner = self.fun_info[base].ty.inner_with(&self.ir_module.types);
match *base_ty_inner {
crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } => {
// When we have a chain of `Access` and `AccessIndex` expressions
// operating on pointers, we want to generate a single
// `OpAccessChain` instruction for the whole chain. Put off
// generating any code for this until we find the `Expression`
// that actually dereferences the pointer.
0
}
_ if self.function.spilled_accesses.contains(base) => {
// As far as Naga IR is concerned, this expression does not yield
// a pointer (we just checked, above), but this backend spilled it
// to a temporary variable, so SPIR-V thinks we're accessing it
// via a pointer.
// Since the base expression was spilled, mark this access to it
// as spilled, too.
self.function.spilled_accesses.insert(expr_handle);
self.maybe_access_spilled_composite(expr_handle, block, result_type_id)?
}
crate::TypeInner::Vector { .. } => {
self.write_vector_access(expr_handle, base, index, block)?
}
crate::TypeInner::Array { .. } | crate::TypeInner::Matrix { .. } => {
// See if `index` is known at compile time.
match GuardedIndex::from_expression(
index,
&self.ir_function.expressions,
self.ir_module,
) {
GuardedIndex::Known(value) => {
// If `index` is known and in bounds, we can just use
// `OpCompositeExtract`.
//
// At the moment, validation rejects programs if this
// index is out of bounds, so we don't need bounds checks.
// However, that rejection is incorrect, since WGSL says
// that `let` bindings are not constant expressions
// (#6396). So eventually we will need to emulate bounds
// checks here.
let id = self.gen_id();
let base_id = self.cached[base];
block.body.push(Instruction::composite_extract(
result_type_id,
id,
base_id,
&[value],
));
id
}
GuardedIndex::Expression(_) => {
// We are subscripting an array or matrix that is not
// behind a pointer, using an index computed at runtime.
// SPIR-V has no instructions that do this, so the best we
// can do is spill the value to a new temporary variable,
// at which point we can get a pointer to that and just
// use `OpAccessChain` in the usual way.
self.spill_to_internal_variable(base, block);
// Since the base was spilled, mark this access to it as
// spilled, too.
self.function.spilled_accesses.insert(expr_handle);
self.maybe_access_spilled_composite(
expr_handle,
block,
result_type_id,
)?
}
}
}
crate::TypeInner::BindingArray {
base: binding_type, ..
} => {
// Only binding arrays in the `Handle` address space will take
// this path, since we handled the `Pointer` case above.
let result_id = match self.write_access_chain(
expr_handle,
block,
AccessTypeAdjustment::IntroducePointer(
spirv::StorageClass::UniformConstant,
),
)? {
ExpressionPointer::Ready { pointer_id } => pointer_id,
ExpressionPointer::Conditional { .. } => {
return Err(Error::FeatureNotImplemented(
"Texture array out-of-bounds handling",
));
}
};
let binding_type_id = self.get_type_id(LookupType::Handle(binding_type));
let load_id = self.gen_id();
block.body.push(Instruction::load(
binding_type_id,
load_id,
result_id,
None,
));
// Subsequent image operations require the image/sampler to be decorated as NonUniform
// if the image/sampler binding array was accessed with a non-uniform index
// see VUID-RuntimeSpirv-NonUniform-06274
if self.fun_info[index].uniformity.non_uniform_result.is_some() {
self.writer
.decorate_non_uniform_binding_array_access(load_id)?;
}
load_id
}
ref other => {
log::error!(
"Unable to access base {:?} of type {:?}",
self.ir_function.expressions[base],
other
);
return Err(Error::Validation(
"only vectors and arrays may be dynamically indexed by value",
));
}
}
}
crate::Expression::AccessIndex { base, index } => {
match *self.fun_info[base].ty.inner_with(&self.ir_module.types) {
crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } => {
// When we have a chain of `Access` and `AccessIndex` expressions
// operating on pointers, we want to generate a single
// `OpAccessChain` instruction for the whole chain. Put off
// generating any code for this until we find the `Expression`
// that actually dereferences the pointer.
0
}
_ if self.function.spilled_accesses.contains(base) => {
// As far as Naga IR is concerned, this expression does not yield
// a pointer (we just checked, above), but this backend spilled it
// to a temporary variable, so SPIR-V thinks we're accessing it
// via a pointer.
// Since the base expression was spilled, mark this access to it
// as spilled, too.
self.function.spilled_accesses.insert(expr_handle);
self.maybe_access_spilled_composite(expr_handle, block, result_type_id)?
}
crate::TypeInner::Vector { .. }
| crate::TypeInner::Matrix { .. }
| crate::TypeInner::Array { .. }
| crate::TypeInner::Struct { .. } => {
// We never need bounds checks here: dynamically sized arrays can
// only appear behind pointers, and are thus handled by the
// `is_intermediate` case above. Everything else's size is
// statically known and checked in validation.
let id = self.gen_id();
let base_id = self.cached[base];
block.body.push(Instruction::composite_extract(
result_type_id,
id,
base_id,
&[index],
));
id
}
crate::TypeInner::BindingArray {
base: binding_type, ..
} => {
// Only binding arrays in the `Handle` address space will take
// this path, since we handled the `Pointer` case above.
let result_id = match self.write_access_chain(
expr_handle,
block,
AccessTypeAdjustment::IntroducePointer(
spirv::StorageClass::UniformConstant,
),
)? {
ExpressionPointer::Ready { pointer_id } => pointer_id,
ExpressionPointer::Conditional { .. } => {
return Err(Error::FeatureNotImplemented(
"Texture array out-of-bounds handling",
));
}
};
let binding_type_id = self.get_type_id(LookupType::Handle(binding_type));
let load_id = self.gen_id();
block.body.push(Instruction::load(
binding_type_id,
load_id,
result_id,
None,
));
load_id
}
ref other => {
log::error!("Unable to access index of {:?}", other);
return Err(Error::FeatureNotImplemented("access index for type"));
}
}
}
crate::Expression::GlobalVariable(handle) => {
self.writer.global_variables[handle].access_id
}
crate::Expression::Swizzle {
size,
vector,
pattern,
} => {
let vector_id = self.cached[vector];
self.temp_list.clear();
for &sc in pattern[..size as usize].iter() {
self.temp_list.push(sc as Word);
}
let id = self.gen_id();
block.body.push(Instruction::vector_shuffle(
result_type_id,
id,
vector_id,
vector_id,
&self.temp_list,
));
id
}
crate::Expression::Unary { op, expr } => {
let id = self.gen_id();
let expr_id = self.cached[expr];
let expr_ty_inner = self.fun_info[expr].ty.inner_with(&self.ir_module.types);
let spirv_op = match op {
crate::UnaryOperator::Negate => match expr_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Float) => spirv::Op::FNegate,
Some(crate::ScalarKind::Sint) => spirv::Op::SNegate,
_ => return Err(Error::Validation("Unexpected kind for negation")),
},
crate::UnaryOperator::LogicalNot => spirv::Op::LogicalNot,
crate::UnaryOperator::BitwiseNot => spirv::Op::Not,
};
block
.body
.push(Instruction::unary(spirv_op, result_type_id, id, expr_id));
id
}
crate::Expression::Binary { op, left, right } => {
let id = self.gen_id();
let left_id = self.cached[left];
let right_id = self.cached[right];
let left_ty_inner = self.fun_info[left].ty.inner_with(&self.ir_module.types);
let right_ty_inner = self.fun_info[right].ty.inner_with(&self.ir_module.types);
let left_dimension = get_dimension(left_ty_inner);
let right_dimension = get_dimension(right_ty_inner);
let mut reverse_operands = false;
let spirv_op = match op {
crate::BinaryOperator::Add => match *left_ty_inner {
crate::TypeInner::Scalar(scalar)
| crate::TypeInner::Vector { scalar, .. } => match scalar.kind {
crate::ScalarKind::Float => spirv::Op::FAdd,
_ => spirv::Op::IAdd,
},
crate::TypeInner::Matrix {
columns,
rows,
scalar,
} => {
self.write_matrix_matrix_column_op(
block,
id,
result_type_id,
left_id,
right_id,
columns,
rows,
scalar.width,
spirv::Op::FAdd,
);
self.cached[expr_handle] = id;
return Ok(());
}
_ => unimplemented!(),
},
crate::BinaryOperator::Subtract => match *left_ty_inner {
crate::TypeInner::Scalar(scalar)
| crate::TypeInner::Vector { scalar, .. } => match scalar.kind {
crate::ScalarKind::Float => spirv::Op::FSub,
_ => spirv::Op::ISub,
},
crate::TypeInner::Matrix {
columns,
rows,
scalar,
} => {
self.write_matrix_matrix_column_op(
block,
id,
result_type_id,
left_id,
right_id,
columns,
rows,
scalar.width,
spirv::Op::FSub,
);
self.cached[expr_handle] = id;
return Ok(());
}
_ => unimplemented!(),
},
crate::BinaryOperator::Multiply => match (left_dimension, right_dimension) {
(Dimension::Scalar, Dimension::Vector) => {
self.write_vector_scalar_mult(
block,
id,
result_type_id,
right_id,
left_id,
right_ty_inner,
);
self.cached[expr_handle] = id;
return Ok(());
}
(Dimension::Vector, Dimension::Scalar) => {
self.write_vector_scalar_mult(
block,
id,
result_type_id,
left_id,
right_id,
left_ty_inner,
);
self.cached[expr_handle] = id;
return Ok(());
}
(Dimension::Vector, Dimension::Matrix) => spirv::Op::VectorTimesMatrix,
(Dimension::Matrix, Dimension::Scalar) => spirv::Op::MatrixTimesScalar,
(Dimension::Scalar, Dimension::Matrix) => {
reverse_operands = true;
spirv::Op::MatrixTimesScalar
}
(Dimension::Matrix, Dimension::Vector) => spirv::Op::MatrixTimesVector,
(Dimension::Matrix, Dimension::Matrix) => spirv::Op::MatrixTimesMatrix,
(Dimension::Vector, Dimension::Vector)
| (Dimension::Scalar, Dimension::Scalar)
if left_ty_inner.scalar_kind() == Some(crate::ScalarKind::Float) =>
{
spirv::Op::FMul
}
(Dimension::Vector, Dimension::Vector)
| (Dimension::Scalar, Dimension::Scalar) => spirv::Op::IMul,
},
crate::BinaryOperator::Divide => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::SDiv,
Some(crate::ScalarKind::Uint) => spirv::Op::UDiv,
Some(crate::ScalarKind::Float) => spirv::Op::FDiv,
_ => unimplemented!(),
},
crate::BinaryOperator::Modulo => match left_ty_inner.scalar_kind() {
// TODO: handle undefined behavior
// if right == 0 return 0
// if left == min(type_of(left)) && right == -1 return 0
Some(crate::ScalarKind::Sint) => spirv::Op::SRem,
// TODO: handle undefined behavior
// if right == 0 return 0
Some(crate::ScalarKind::Uint) => spirv::Op::UMod,
// TODO: handle undefined behavior
// if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798
Some(crate::ScalarKind::Float) => spirv::Op::FRem,
_ => unimplemented!(),
},
crate::BinaryOperator::Equal => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => {
spirv::Op::IEqual
}
Some(crate::ScalarKind::Float) => spirv::Op::FOrdEqual,
Some(crate::ScalarKind::Bool) => spirv::Op::LogicalEqual,
_ => unimplemented!(),
},
crate::BinaryOperator::NotEqual => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => {
spirv::Op::INotEqual
}
Some(crate::ScalarKind::Float) => spirv::Op::FOrdNotEqual,
Some(crate::ScalarKind::Bool) => spirv::Op::LogicalNotEqual,
_ => unimplemented!(),
},
crate::BinaryOperator::Less => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::SLessThan,
Some(crate::ScalarKind::Uint) => spirv::Op::ULessThan,
Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThan,
_ => unimplemented!(),
},
crate::BinaryOperator::LessEqual => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::SLessThanEqual,
Some(crate::ScalarKind::Uint) => spirv::Op::ULessThanEqual,
Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThanEqual,
_ => unimplemented!(),
},
crate::BinaryOperator::Greater => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThan,
Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThan,
Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThan,
_ => unimplemented!(),
},
crate::BinaryOperator::GreaterEqual => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThanEqual,
Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThanEqual,
Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThanEqual,
_ => unimplemented!(),
},
crate::BinaryOperator::And => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Bool) => spirv::Op::LogicalAnd,
_ => spirv::Op::BitwiseAnd,
},
crate::BinaryOperator::ExclusiveOr => spirv::Op::BitwiseXor,
crate::BinaryOperator::InclusiveOr => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Bool) => spirv::Op::LogicalOr,
_ => spirv::Op::BitwiseOr,
},
crate::BinaryOperator::LogicalAnd => spirv::Op::LogicalAnd,
crate::BinaryOperator::LogicalOr => spirv::Op::LogicalOr,
crate::BinaryOperator::ShiftLeft => spirv::Op::ShiftLeftLogical,
crate::BinaryOperator::ShiftRight => match left_ty_inner.scalar_kind() {
Some(crate::ScalarKind::Sint) => spirv::Op::ShiftRightArithmetic,
Some(crate::ScalarKind::Uint) => spirv::Op::ShiftRightLogical,
_ => unimplemented!(),
},
};
block.body.push(Instruction::binary(
spirv_op,
result_type_id,
id,
if reverse_operands { right_id } else { left_id },
if reverse_operands { left_id } else { right_id },
));
id
}
crate::Expression::Math {
fun,
arg,
arg1,
arg2,
arg3,
} => {
use crate::MathFunction as Mf;
enum MathOp {
Ext(spirv::GLOp),
Custom(Instruction),
}
let arg0_id = self.cached[arg];
let arg_ty = self.fun_info[arg].ty.inner_with(&self.ir_module.types);
let arg_scalar_kind = arg_ty.scalar_kind();
let arg1_id = match arg1 {
Some(handle) => self.cached[handle],
None => 0,
};
let arg2_id = match arg2 {
Some(handle) => self.cached[handle],
None => 0,
};
let arg3_id = match arg3 {
Some(handle) => self.cached[handle],
None => 0,
};
let id = self.gen_id();
let math_op = match fun {
// comparison
Mf::Abs => {
match arg_scalar_kind {
Some(crate::ScalarKind::Float) => MathOp::Ext(spirv::GLOp::FAbs),
Some(crate::ScalarKind::Sint) => MathOp::Ext(spirv::GLOp::SAbs),
Some(crate::ScalarKind::Uint) => {
MathOp::Custom(Instruction::unary(
spirv::Op::CopyObject, // do nothing
result_type_id,
id,
arg0_id,
))
}
other => unimplemented!("Unexpected abs({:?})", other),
}
}
Mf::Min => MathOp::Ext(match arg_scalar_kind {
Some(crate::ScalarKind::Float) => spirv::GLOp::FMin,
Some(crate::ScalarKind::Sint) => spirv::GLOp::SMin,
Some(crate::ScalarKind::Uint) => spirv::GLOp::UMin,
other => unimplemented!("Unexpected min({:?})", other),
}),
Mf::Max => MathOp::Ext(match arg_scalar_kind {
Some(crate::ScalarKind::Float) => spirv::GLOp::FMax,
Some(crate::ScalarKind::Sint) => spirv::GLOp::SMax,
Some(crate::ScalarKind::Uint) => spirv::GLOp::UMax,
other => unimplemented!("Unexpected max({:?})", other),
}),
Mf::Clamp => match arg_scalar_kind {
// Clamp is undefined if min > max. In practice this means it can use a median-of-three
// instruction to determine the value. This is fine according to the WGSL spec for float
// clamp, but integer clamp _must_ use min-max. As such we write out min/max.
Some(crate::ScalarKind::Float) => MathOp::Ext(spirv::GLOp::FClamp),
Some(_) => {
let (min_op, max_op) = match arg_scalar_kind {
Some(crate::ScalarKind::Sint) => {
(spirv::GLOp::SMin, spirv::GLOp::SMax)
}
Some(crate::ScalarKind::Uint) => {
(spirv::GLOp::UMin, spirv::GLOp::UMax)
}
_ => unreachable!(),
};
let max_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
max_op,
result_type_id,
max_id,
&[arg0_id, arg1_id],
));
MathOp::Custom(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
min_op,
result_type_id,
id,
&[max_id, arg2_id],
))
}
other => unimplemented!("Unexpected max({:?})", other),
},
Mf::Saturate => {
let (maybe_size, scalar) = match *arg_ty {
crate::TypeInner::Vector { size, scalar } => (Some(size), scalar),
crate::TypeInner::Scalar(scalar) => (None, scalar),
ref other => unimplemented!("Unexpected saturate({:?})", other),
};
let scalar = crate::Scalar::float(scalar.width);
let mut arg1_id = self.writer.get_constant_scalar_with(0, scalar)?;
let mut arg2_id = self.writer.get_constant_scalar_with(1, scalar)?;
if let Some(size) = maybe_size {
let ty =
LocalType::Numeric(NumericType::Vector { size, scalar }).into();
self.temp_list.clear();
self.temp_list.resize(size as _, arg1_id);
arg1_id = self.writer.get_constant_composite(ty, &self.temp_list);
self.temp_list.fill(arg2_id);
arg2_id = self.writer.get_constant_composite(ty, &self.temp_list);
}
MathOp::Custom(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::FClamp,
result_type_id,
id,
&[arg0_id, arg1_id, arg2_id],
))
}
// trigonometry
Mf::Sin => MathOp::Ext(spirv::GLOp::Sin),
Mf::Sinh => MathOp::Ext(spirv::GLOp::Sinh),
Mf::Asin => MathOp::Ext(spirv::GLOp::Asin),
Mf::Cos => MathOp::Ext(spirv::GLOp::Cos),
Mf::Cosh => MathOp::Ext(spirv::GLOp::Cosh),
Mf::Acos => MathOp::Ext(spirv::GLOp::Acos),
Mf::Tan => MathOp::Ext(spirv::GLOp::Tan),
Mf::Tanh => MathOp::Ext(spirv::GLOp::Tanh),
Mf::Atan => MathOp::Ext(spirv::GLOp::Atan),
Mf::Atan2 => MathOp::Ext(spirv::GLOp::Atan2),
Mf::Asinh => MathOp::Ext(spirv::GLOp::Asinh),
Mf::Acosh => MathOp::Ext(spirv::GLOp::Acosh),
Mf::Atanh => MathOp::Ext(spirv::GLOp::Atanh),
Mf::Radians => MathOp::Ext(spirv::GLOp::Radians),
Mf::Degrees => MathOp::Ext(spirv::GLOp::Degrees),
// decomposition
Mf::Ceil => MathOp::Ext(spirv::GLOp::Ceil),
Mf::Round => MathOp::Ext(spirv::GLOp::RoundEven),
Mf::Floor => MathOp::Ext(spirv::GLOp::Floor),
Mf::Fract => MathOp::Ext(spirv::GLOp::Fract),
Mf::Trunc => MathOp::Ext(spirv::GLOp::Trunc),
Mf::Modf => MathOp::Ext(spirv::GLOp::ModfStruct),
Mf::Frexp => MathOp::Ext(spirv::GLOp::FrexpStruct),
Mf::Ldexp => MathOp::Ext(spirv::GLOp::Ldexp),
// geometry
Mf::Dot => match *self.fun_info[arg].ty.inner_with(&self.ir_module.types) {
crate::TypeInner::Vector {
scalar:
crate::Scalar {
kind: crate::ScalarKind::Float,
..
},
..
} => MathOp::Custom(Instruction::binary(
spirv::Op::Dot,
result_type_id,
id,
arg0_id,
arg1_id,
)),
// TODO: consider using integer dot product if VK_KHR_shader_integer_dot_product is available
crate::TypeInner::Vector { size, .. } => {
self.write_dot_product(
id,
result_type_id,
arg0_id,
arg1_id,
size as u32,
block,
);
self.cached[expr_handle] = id;
return Ok(());
}
_ => unreachable!(
"Correct TypeInner for dot product should be already validated"
),
},
Mf::Outer => MathOp::Custom(Instruction::binary(
spirv::Op::OuterProduct,
result_type_id,
id,
arg0_id,
arg1_id,
)),
Mf::Cross => MathOp::Ext(spirv::GLOp::Cross),
Mf::Distance => MathOp::Ext(spirv::GLOp::Distance),
Mf::Length => MathOp::Ext(spirv::GLOp::Length),
Mf::Normalize => MathOp::Ext(spirv::GLOp::Normalize),
Mf::FaceForward => MathOp::Ext(spirv::GLOp::FaceForward),
Mf::Reflect => MathOp::Ext(spirv::GLOp::Reflect),
Mf::Refract => MathOp::Ext(spirv::GLOp::Refract),
// exponent
Mf::Exp => MathOp::Ext(spirv::GLOp::Exp),
Mf::Exp2 => MathOp::Ext(spirv::GLOp::Exp2),
Mf::Log => MathOp::Ext(spirv::GLOp::Log),
Mf::Log2 => MathOp::Ext(spirv::GLOp::Log2),
Mf::Pow => MathOp::Ext(spirv::GLOp::Pow),
// computational
Mf::Sign => MathOp::Ext(match arg_scalar_kind {
Some(crate::ScalarKind::Float) => spirv::GLOp::FSign,
Some(crate::ScalarKind::Sint) => spirv::GLOp::SSign,
other => unimplemented!("Unexpected sign({:?})", other),
}),
Mf::Fma => MathOp::Ext(spirv::GLOp::Fma),
Mf::Mix => {
let selector = arg2.unwrap();
let selector_ty =
self.fun_info[selector].ty.inner_with(&self.ir_module.types);
match (arg_ty, selector_ty) {
// if the selector is a scalar, we need to splat it
(
&crate::TypeInner::Vector { size, .. },
&crate::TypeInner::Scalar(scalar),
) => {
let selector_type_id = self.get_type_id(LookupType::Local(
LocalType::Numeric(NumericType::Vector { size, scalar }),
));
self.temp_list.clear();
self.temp_list.resize(size as usize, arg2_id);
let selector_id = self.gen_id();
block.body.push(Instruction::composite_construct(
selector_type_id,
selector_id,
&self.temp_list,
));
MathOp::Custom(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::FMix,
result_type_id,
id,
&[arg0_id, arg1_id, selector_id],
))
}
_ => MathOp::Ext(spirv::GLOp::FMix),
}
}
Mf::Step => MathOp::Ext(spirv::GLOp::Step),
Mf::SmoothStep => MathOp::Ext(spirv::GLOp::SmoothStep),
Mf::Sqrt => MathOp::Ext(spirv::GLOp::Sqrt),
Mf::InverseSqrt => MathOp::Ext(spirv::GLOp::InverseSqrt),
Mf::Inverse => MathOp::Ext(spirv::GLOp::MatrixInverse),
Mf::Transpose => MathOp::Custom(Instruction::unary(
spirv::Op::Transpose,
result_type_id,
id,
arg0_id,
)),
Mf::Determinant => MathOp::Ext(spirv::GLOp::Determinant),
Mf::QuantizeToF16 => MathOp::Custom(Instruction::unary(
spirv::Op::QuantizeToF16,
result_type_id,
id,
arg0_id,
)),
Mf::ReverseBits => MathOp::Custom(Instruction::unary(
spirv::Op::BitReverse,
result_type_id,
id,
arg0_id,
)),
Mf::CountTrailingZeros => {
let uint_id = match *arg_ty {
crate::TypeInner::Vector { size, scalar } => {
let ty =
LocalType::Numeric(NumericType::Vector { size, scalar }).into();
self.temp_list.clear();
self.temp_list.resize(
size as _,
self.writer
.get_constant_scalar_with(scalar.width * 8, scalar)?,
);
self.writer.get_constant_composite(ty, &self.temp_list)
}
crate::TypeInner::Scalar(scalar) => self
.writer
.get_constant_scalar_with(scalar.width * 8, scalar)?,
_ => unreachable!(),
};
let lsb_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::FindILsb,
result_type_id,
lsb_id,
&[arg0_id],
));
MathOp::Custom(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::UMin,
result_type_id,
id,
&[uint_id, lsb_id],
))
}
Mf::CountLeadingZeros => {
let (int_type_id, int_id, width) = match *arg_ty {
crate::TypeInner::Vector { size, scalar } => {
let ty =
LocalType::Numeric(NumericType::Vector { size, scalar }).into();
self.temp_list.clear();
self.temp_list.resize(
size as _,
self.writer
.get_constant_scalar_with(scalar.width * 8 - 1, scalar)?,
);
(
self.get_type_id(ty),
self.writer.get_constant_composite(ty, &self.temp_list),
scalar.width,
)
}
crate::TypeInner::Scalar(scalar) => (
self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(scalar),
))),
self.writer
.get_constant_scalar_with(scalar.width * 8 - 1, scalar)?,
scalar.width,
),
_ => unreachable!(),
};
if width != 4 {
unreachable!("This is validated out until a polyfill is implemented. https://github.com/gfx-rs/wgpu/issues/5276");
};
let msb_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
if width != 4 {
spirv::GLOp::FindILsb
} else {
spirv::GLOp::FindUMsb
},
int_type_id,
msb_id,
&[arg0_id],
));
MathOp::Custom(Instruction::binary(
spirv::Op::ISub,
result_type_id,
id,
int_id,
msb_id,
))
}
Mf::CountOneBits => MathOp::Custom(Instruction::unary(
spirv::Op::BitCount,
result_type_id,
id,
arg0_id,
)),
Mf::ExtractBits => {
let op = match arg_scalar_kind {
Some(crate::ScalarKind::Uint) => spirv::Op::BitFieldUExtract,
Some(crate::ScalarKind::Sint) => spirv::Op::BitFieldSExtract,
other => unimplemented!("Unexpected sign({:?})", other),
};
// The behavior of ExtractBits is undefined when offset + count > bit_width. We need
// to first sanitize the offset and count first. If we don't do this, AMD and Intel
// will return out-of-spec values if the extracted range is not within the bit width.
//
// This encodes the exact formula specified by the wgsl spec:
// https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin
//
// w = sizeof(x) * 8
// o = min(offset, w)
// tmp = w - o
// c = min(count, tmp)
//
// bitfieldExtract(x, o, c)
let bit_width = arg_ty.scalar_width().unwrap() * 8;
let width_constant = self
.writer
.get_constant_scalar(crate::Literal::U32(bit_width as u32));
let u32_type = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar::U32),
)));
// o = min(offset, w)
let offset_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::UMin,
u32_type,
offset_id,
&[arg1_id, width_constant],
));
// tmp = w - o
let max_count_id = self.gen_id();
block.body.push(Instruction::binary(
spirv::Op::ISub,
u32_type,
max_count_id,
width_constant,
offset_id,
));
// c = min(count, tmp)
let count_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::UMin,
u32_type,
count_id,
&[arg2_id, max_count_id],
));
MathOp::Custom(Instruction::ternary(
op,
result_type_id,
id,
arg0_id,
offset_id,
count_id,
))
}
Mf::InsertBits => {
// The behavior of InsertBits has the same undefined behavior as ExtractBits.
let bit_width = arg_ty.scalar_width().unwrap() * 8;
let width_constant = self
.writer
.get_constant_scalar(crate::Literal::U32(bit_width as u32));
let u32_type = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar::U32),
)));
// o = min(offset, w)
let offset_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::UMin,
u32_type,
offset_id,
&[arg2_id, width_constant],
));
// tmp = w - o
let max_count_id = self.gen_id();
block.body.push(Instruction::binary(
spirv::Op::ISub,
u32_type,
max_count_id,
width_constant,
offset_id,
));
// c = min(count, tmp)
let count_id = self.gen_id();
block.body.push(Instruction::ext_inst(
self.writer.gl450_ext_inst_id,
spirv::GLOp::UMin,
u32_type,
count_id,
&[arg3_id, max_count_id],
));
MathOp::Custom(Instruction::quaternary(
spirv::Op::BitFieldInsert,
result_type_id,
id,
arg0_id,
arg1_id,
offset_id,
count_id,
))
}
Mf::FirstTrailingBit => MathOp::Ext(spirv::GLOp::FindILsb),
Mf::FirstLeadingBit => {
if arg_ty.scalar_width() == Some(4) {
let thing = match arg_scalar_kind {
Some(crate::ScalarKind::Uint) => spirv::GLOp::FindUMsb,
Some(crate::ScalarKind::Sint) => spirv::GLOp::FindSMsb,
other => unimplemented!("Unexpected firstLeadingBit({:?})", other),
};
MathOp::Ext(thing)
} else {
unreachable!("This is validated out until a polyfill is implemented. https://github.com/gfx-rs/wgpu/issues/5276");
}
}
Mf::Pack4x8unorm => MathOp::Ext(spirv::GLOp::PackUnorm4x8),
Mf::Pack4x8snorm => MathOp::Ext(spirv::GLOp::PackSnorm4x8),
Mf::Pack2x16float => MathOp::Ext(spirv::GLOp::PackHalf2x16),
Mf::Pack2x16unorm => MathOp::Ext(spirv::GLOp::PackUnorm2x16),
Mf::Pack2x16snorm => MathOp::Ext(spirv::GLOp::PackSnorm2x16),
fun @ (Mf::Pack4xI8 | Mf::Pack4xU8) => {
let (int_type, is_signed) = match fun {
Mf::Pack4xI8 => (crate::ScalarKind::Sint, true),
Mf::Pack4xU8 => (crate::ScalarKind::Uint, false),
_ => unreachable!(),
};
let uint_type_id = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar::U32),
)));
let int_type_id = self.get_type_id(LookupType::Local(LocalType::Numeric(
NumericType::Scalar(crate::Scalar {
kind: int_type,
width: 4,
}),
)));
let mut last_instruction = Instruction::new(spirv::Op::Nop);
let zero = self.writer.get_constant_scalar(crate::Literal::U32(0));
let mut preresult = zero;
block
.body
.reserve(usize::from(VEC_LENGTH) * (2 + usize::from(is_signed)));
let eight = self.writer.get_constant_scalar(crate::Literal::U32(8));
const VEC_LENGTH: u8 = 4;
for i in 0..u32::from(VEC_LENGTH) {
let offset =
self.writer.get_constant_scalar(crate::Literal::U32(i * 8));
let mut extracted = self.gen_id();
block.body.push(Instruction::binary(
spirv::Op::CompositeExtract,
int_type_id,
extracted,
arg0_id,
i,
));
if is_signed {
let casted = self.gen_id();
block.body.push(Instruction::unary(
spirv::Op::Bitcast,
uint_type_id,
casted,
extracted,
));
extracted = casted;
}
let is_last = i == u32::from(VEC_LENGTH - 1);
if is_last {
last_instruction = Instruction::quaternary(
spirv::Op::BitFieldInsert,
result_type_id,
id,
preresult,
extracted,
offset,
eight,
)
} else {
let new_preresult = self.gen_id();
block.body.push(Instruction::quaternary(
spirv::Op::BitFieldInsert,
result_type_id,
new_preresult,
preresult,
extracted,
offset,
eight,
));
preresult = new_preresult;
}
}
MathOp::Custom(last_instruction)
}
Mf::Unpack4x8unorm => MathOp::Ext(spirv::GLOp::UnpackUnorm4x8),
Mf::Unpack4x8snorm => MathOp::Ext(spirv::GLOp::UnpackSnorm4x8),
Mf::Unpack2x16float => MathOp::Ext(spirv::GLOp::UnpackHalf2x16),
Mf::Unpack2x16unorm => MathOp::Ext(spirv::GLOp::UnpackUnorm2x16),
Mf::Unpack2x16snorm => MathOp::Ext(spirv::GLOp::UnpackSnorm2x16),
fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => {
let (int_type, extract_op, is_signed) = match fun {
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.85 Sekunden
(vorverarbeitet)
]
|
2026-04-04
|