Skip to content

Commit 27adeee

Browse files
committed
(Naga) Implement OpSpecConstantOp for the SPIR-V frontend
1 parent 854664a commit 27adeee

12 files changed

+1145
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ This is a breaking change
5454

5555
By @R-Cramer4 in [#8230](https://github.com/gfx-rs/wgpu/pull/8230)
5656

57+
### Changes
58+
59+
#### naga
60+
61+
- The SPIR-V frontend implements OpSpecConstantOp. By @hasenbanck in [8308](https://github.com/gfx-rs/wgpu/pull/8308).
62+
5763
## v27.0.2 (2025-10-03)
5864

5965
### Bug Fixes

naga/src/front/spv/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ pub enum Error {
2626
UnknownCapability(spirv::Word),
2727
#[error("unsupported instruction {1:?} at {0:?}")]
2828
UnsupportedInstruction(ModuleState, spirv::Op),
29+
#[error("unsupported opcode in specialization constant operation {0:?}")]
30+
UnsupportedSpecConstantOp(spirv::Op),
31+
#[error("invalid opcode in specialization constant operation {0:?}")]
32+
InvalidSpecConstantOp(spirv::Op),
2933
#[error("unsupported capability {0:?}")]
3034
UnsupportedCapability(spirv::Capability),
3135
#[error("unsupported extension {0}")]

naga/src/front/spv/mod.rs

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4803,6 +4803,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
48034803
Op::ConstantFalse | Op::SpecConstantFalse => {
48044804
self.parse_bool_constant(inst, false, &mut module)
48054805
}
4806+
Op::SpecConstantOp => self.parse_spec_constant_op(inst, &mut module),
48064807
Op::Variable => self.parse_global_variable(inst, &mut module),
48074808
Op::Function => {
48084809
self.switch(ModuleState::Function, inst.op)?;
@@ -5899,6 +5900,296 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
58995900
self.insert_parsed_constant(module, id, type_id, ty, init, span)
59005901
}
59015902

5903+
fn parse_spec_constant_op(
5904+
&mut self,
5905+
inst: Instruction,
5906+
module: &mut crate::Module,
5907+
) -> Result<(), Error> {
5908+
use spirv::Op;
5909+
5910+
let start = self.data_offset;
5911+
self.switch(ModuleState::Type, inst.op)?;
5912+
inst.expect_at_least(4)?;
5913+
5914+
let result_type_id = self.next()?;
5915+
let result_id = self.next()?;
5916+
let opcode_word = self.next()?;
5917+
5918+
let type_lookup = self.lookup_type.lookup(result_type_id)?;
5919+
let ty = type_lookup.handle;
5920+
let span = self.span_from_with_op(start);
5921+
5922+
let opcode = Op::from_u32(opcode_word).ok_or(Error::UnsupportedInstruction(
5923+
self.state,
5924+
Op::SpecConstantOp,
5925+
))?;
5926+
5927+
let mut get_const_expr =
5928+
|frontend: &Self, const_id: spirv::Word| -> Result<Handle<crate::Expression>, Error> {
5929+
let lookup = frontend.lookup_constant.lookup(const_id)?;
5930+
match lookup.inner {
5931+
// Wrap regular constants in overrides to avoid creating Expression::Constant
5932+
// nodes, which would mark the entire expression tree as Const and fail
5933+
// validation for complex operations (Binary, As, Math) in override
5934+
// initializers.
5935+
//
5936+
// The downside is, that unused intermediate constants will get created, which
5937+
// I would like to avoid.
5938+
Constant::Constant(const_handle) => {
5939+
let const_init = module.constants[const_handle].init;
5940+
let const_ty = module.constants[const_handle].ty;
5941+
let wrapper_override = crate::Override {
5942+
name: Some(format!("_spec_const_op_const_{const_id}")),
5943+
id: None,
5944+
ty: const_ty,
5945+
init: Some(const_init),
5946+
};
5947+
let override_handle = module.overrides.append(wrapper_override, span);
5948+
Ok(module
5949+
.global_expressions
5950+
.append(crate::Expression::Override(override_handle), span))
5951+
}
5952+
Constant::Override(_) => Ok(module
5953+
.global_expressions
5954+
.append(lookup.inner.to_expr(), span)),
5955+
}
5956+
};
5957+
5958+
let init = match opcode {
5959+
Op::SConvert | Op::UConvert | Op::FConvert => {
5960+
let value_id = self.next()?;
5961+
let value_expr = get_const_expr(self, value_id)?;
5962+
5963+
let scalar = match module.types[ty].inner {
5964+
crate::TypeInner::Scalar(scalar)
5965+
| crate::TypeInner::Vector { scalar, .. }
5966+
| crate::TypeInner::Matrix { scalar, .. } => scalar,
5967+
_ => return Err(Error::InvalidAsType(ty)),
5968+
};
5969+
5970+
module.global_expressions.append(
5971+
crate::Expression::As {
5972+
expr: value_expr,
5973+
kind: scalar.kind,
5974+
convert: Some(scalar.width),
5975+
},
5976+
span,
5977+
)
5978+
}
5979+
5980+
Op::SNegate | Op::Not | Op::LogicalNot => {
5981+
let value_id = self.next()?;
5982+
let value_expr = get_const_expr(self, value_id)?;
5983+
5984+
let op = match opcode {
5985+
Op::SNegate => crate::UnaryOperator::Negate,
5986+
Op::Not => crate::UnaryOperator::BitwiseNot,
5987+
Op::LogicalNot => crate::UnaryOperator::LogicalNot,
5988+
_ => unreachable!(),
5989+
};
5990+
5991+
module.global_expressions.append(
5992+
crate::Expression::Unary {
5993+
op,
5994+
expr: value_expr,
5995+
},
5996+
span,
5997+
)
5998+
}
5999+
6000+
Op::IAdd
6001+
| Op::ISub
6002+
| Op::IMul
6003+
| Op::UDiv
6004+
| Op::SDiv
6005+
| Op::SRem
6006+
| Op::UMod
6007+
| Op::BitwiseOr
6008+
| Op::BitwiseXor
6009+
| Op::BitwiseAnd
6010+
| Op::ShiftLeftLogical
6011+
| Op::ShiftRightLogical
6012+
| Op::ShiftRightArithmetic
6013+
| Op::LogicalOr
6014+
| Op::LogicalAnd
6015+
| Op::LogicalEqual
6016+
| Op::LogicalNotEqual
6017+
| Op::IEqual
6018+
| Op::INotEqual
6019+
| Op::ULessThan
6020+
| Op::SLessThan
6021+
| Op::UGreaterThan
6022+
| Op::SGreaterThan
6023+
| Op::ULessThanEqual
6024+
| Op::SLessThanEqual
6025+
| Op::UGreaterThanEqual
6026+
| Op::SGreaterThanEqual => {
6027+
let left_id = self.next()?;
6028+
let right_id = self.next()?;
6029+
let left_expr = get_const_expr(self, left_id)?;
6030+
let right_expr = get_const_expr(self, right_id)?;
6031+
6032+
let op = match opcode {
6033+
Op::IAdd => crate::BinaryOperator::Add,
6034+
Op::ISub => crate::BinaryOperator::Subtract,
6035+
Op::IMul => crate::BinaryOperator::Multiply,
6036+
Op::UDiv | Op::SDiv => crate::BinaryOperator::Divide,
6037+
Op::SRem | Op::UMod => crate::BinaryOperator::Modulo,
6038+
Op::BitwiseOr => crate::BinaryOperator::InclusiveOr,
6039+
Op::BitwiseXor => crate::BinaryOperator::ExclusiveOr,
6040+
Op::BitwiseAnd => crate::BinaryOperator::And,
6041+
Op::ShiftLeftLogical => crate::BinaryOperator::ShiftLeft,
6042+
Op::ShiftRightLogical | Op::ShiftRightArithmetic => {
6043+
crate::BinaryOperator::ShiftRight
6044+
}
6045+
Op::LogicalOr => crate::BinaryOperator::LogicalOr,
6046+
Op::LogicalAnd => crate::BinaryOperator::LogicalAnd,
6047+
Op::LogicalEqual => crate::BinaryOperator::Equal,
6048+
Op::LogicalNotEqual => crate::BinaryOperator::NotEqual,
6049+
Op::IEqual => crate::BinaryOperator::Equal,
6050+
Op::INotEqual => crate::BinaryOperator::NotEqual,
6051+
Op::ULessThan | Op::SLessThan => crate::BinaryOperator::Less,
6052+
Op::UGreaterThan | Op::SGreaterThan => crate::BinaryOperator::Greater,
6053+
Op::ULessThanEqual | Op::SLessThanEqual => crate::BinaryOperator::LessEqual,
6054+
Op::UGreaterThanEqual | Op::SGreaterThanEqual => {
6055+
crate::BinaryOperator::GreaterEqual
6056+
}
6057+
_ => unreachable!(),
6058+
};
6059+
6060+
module.global_expressions.append(
6061+
crate::Expression::Binary {
6062+
op,
6063+
left: left_expr,
6064+
right: right_expr,
6065+
},
6066+
span,
6067+
)
6068+
}
6069+
6070+
Op::SMod => {
6071+
// x - y * int(floor(float(x) / float(y)))
6072+
6073+
let left_id = self.next()?;
6074+
let right_id = self.next()?;
6075+
let left = get_const_expr(self, left_id)?;
6076+
let right = get_const_expr(self, right_id)?;
6077+
6078+
let scalar = match module.types[ty].inner {
6079+
crate::TypeInner::Scalar(scalar) => scalar,
6080+
crate::TypeInner::Vector { scalar, .. } => scalar,
6081+
_ => return Err(Error::InvalidAsType(ty)),
6082+
};
6083+
6084+
let left_cast = module.global_expressions.append(
6085+
crate::Expression::As {
6086+
expr: left,
6087+
kind: crate::ScalarKind::Float,
6088+
convert: Some(scalar.width),
6089+
},
6090+
span,
6091+
);
6092+
let right_cast = module.global_expressions.append(
6093+
crate::Expression::As {
6094+
expr: right,
6095+
kind: crate::ScalarKind::Float,
6096+
convert: Some(scalar.width),
6097+
},
6098+
span,
6099+
);
6100+
let div = module.global_expressions.append(
6101+
crate::Expression::Binary {
6102+
op: crate::BinaryOperator::Divide,
6103+
left: left_cast,
6104+
right: right_cast,
6105+
},
6106+
span,
6107+
);
6108+
let floor = module.global_expressions.append(
6109+
crate::Expression::Math {
6110+
fun: crate::MathFunction::Floor,
6111+
arg: div,
6112+
arg1: None,
6113+
arg2: None,
6114+
arg3: None,
6115+
},
6116+
span,
6117+
);
6118+
let cast = module.global_expressions.append(
6119+
crate::Expression::As {
6120+
expr: floor,
6121+
kind: scalar.kind,
6122+
convert: Some(scalar.width),
6123+
},
6124+
span,
6125+
);
6126+
let mult = module.global_expressions.append(
6127+
crate::Expression::Binary {
6128+
op: crate::BinaryOperator::Multiply,
6129+
left: cast,
6130+
right,
6131+
},
6132+
span,
6133+
);
6134+
module.global_expressions.append(
6135+
crate::Expression::Binary {
6136+
op: crate::BinaryOperator::Subtract,
6137+
left,
6138+
right: mult,
6139+
},
6140+
span,
6141+
)
6142+
}
6143+
6144+
Op::Select => {
6145+
let condition_id = self.next()?;
6146+
let o1_id = self.next()?;
6147+
let o2_id = self.next()?;
6148+
6149+
let cond = get_const_expr(self, condition_id)?;
6150+
let o1 = get_const_expr(self, o1_id)?;
6151+
let o2 = get_const_expr(self, o2_id)?;
6152+
6153+
module.global_expressions.append(
6154+
crate::Expression::Select {
6155+
condition: cond,
6156+
accept: o1,
6157+
reject: o2,
6158+
},
6159+
span,
6160+
)
6161+
}
6162+
6163+
Op::VectorShuffle | Op::CompositeExtract | Op::CompositeInsert | Op::QuantizeToF16 => {
6164+
// Nothing stops us from implementing these cases in general.
6165+
// I just couldn't get them to work properly.
6166+
return Err(Error::UnsupportedSpecConstantOp(opcode));
6167+
}
6168+
6169+
_ => return Err(Error::InvalidSpecConstantOp(opcode)),
6170+
};
6171+
6172+
// IMPORTANT: Overrides must have either a name or an id to be processed correctly
6173+
// by process_overrides(). OpSpecConstantOp results don't have a SpecId (they're
6174+
// not user-overridable), so we assign them a name based on the result_id.
6175+
let op_override = crate::Override {
6176+
name: Some(format!("_spec_const_op_{result_id}")),
6177+
id: None,
6178+
ty,
6179+
init: Some(init),
6180+
};
6181+
6182+
self.lookup_constant.insert(
6183+
result_id,
6184+
LookupConstant {
6185+
inner: Constant::Override(module.overrides.append(op_override, span)),
6186+
type_id: result_type_id,
6187+
},
6188+
);
6189+
6190+
Ok(())
6191+
}
6192+
59026193
fn insert_parsed_constant(
59036194
&mut self,
59046195
module: &mut crate::Module,

0 commit comments

Comments
 (0)