mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[naga] Introduce Literal::AbstractInt and AbstractFloat.
Introduce new variants of `naga::Literal`, `AbstractInt` and `AbstractFloat`, for representing WGSL abstract values.
This commit is contained in:
committed by
Teodor Tanasoaia
parent
276c978b70
commit
e75fb3c224
@@ -2451,6 +2451,11 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
crate::Literal::I64(_) => {
|
||||
return Err(Error::Custom("GLSL has no 64-bit integer type".into()));
|
||||
}
|
||||
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
|
||||
return Err(Error::Custom(
|
||||
"Abstract types should not appear in IR presented to backends".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::Constant(handle) => {
|
||||
|
||||
@@ -2040,6 +2040,11 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
crate::Literal::I32(value) => write!(self.out, "{}", value)?,
|
||||
crate::Literal::I64(value) => write!(self.out, "{}L", value)?,
|
||||
crate::Literal::Bool(value) => write!(self.out, "{}", value)?,
|
||||
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
|
||||
return Err(Error::Custom(
|
||||
"Abstract types should not appear in IR presented to backends".into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
Expression::Constant(handle) => {
|
||||
let constant = &module.constants[handle];
|
||||
|
||||
@@ -1279,6 +1279,9 @@ impl<W: Write> Writer<W> {
|
||||
crate::Literal::Bool(value) => {
|
||||
write!(self.out, "{value}")?;
|
||||
}
|
||||
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
|
||||
return Err(Error::Validation);
|
||||
}
|
||||
},
|
||||
crate::Expression::Constant(handle) => {
|
||||
let constant = &module.constants[handle];
|
||||
|
||||
@@ -1187,6 +1187,9 @@ impl Writer {
|
||||
}
|
||||
crate::Literal::Bool(true) => Instruction::constant_true(type_id, id),
|
||||
crate::Literal::Bool(false) => Instruction::constant_false(type_id, id),
|
||||
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
|
||||
unreachable!("Abstract types should not appear in IR presented to backends");
|
||||
}
|
||||
};
|
||||
|
||||
instruction.to_words(&mut self.logical_layout.declarations);
|
||||
|
||||
@@ -1099,6 +1099,11 @@ impl<W: Write> Writer<W> {
|
||||
crate::Literal::I64(_) => {
|
||||
return Err(Error::Custom("unsupported i64 literal".to_string()));
|
||||
}
|
||||
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
|
||||
return Err(Error::Custom(
|
||||
"Abstract types should not appear in IR presented to backends".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::Constant(handle) => {
|
||||
|
||||
@@ -300,6 +300,9 @@ use serde::Serialize;
|
||||
/// Width of a boolean type, in bytes.
|
||||
pub const BOOL_WIDTH: Bytes = 1;
|
||||
|
||||
/// Width of abstract types, in bytes.
|
||||
pub const ABSTRACT_WIDTH: Bytes = 8;
|
||||
|
||||
/// Hash map that is faster but not resilient to DoS attacks.
|
||||
pub type FastHashMap<K, T> = rustc_hash::FxHashMap<K, T>;
|
||||
/// Hash set that is faster but not resilient to DoS attacks.
|
||||
@@ -881,6 +884,8 @@ pub enum Literal {
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
Bool(bool),
|
||||
AbstractInt(i64),
|
||||
AbstractFloat(f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
||||
@@ -167,6 +167,15 @@ pub enum ConstantEvaluatorError {
|
||||
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("abstract floating-point values cannot be automatically converted to integers")]
|
||||
AutomaticConversionFloatToInt { to_type: &'static str },
|
||||
#[error("Division by zero")]
|
||||
DivisionByZero,
|
||||
#[error("Remainder by zero")]
|
||||
@@ -979,6 +988,8 @@ impl<'a> ConstantEvaluator<'a> {
|
||||
Literal::F64(_) | Literal::I64(_) => {
|
||||
return Err(ConstantEvaluatorError::InvalidCastArg)
|
||||
}
|
||||
Literal::AbstractInt(v) => i32::try_from_abstract(v)?,
|
||||
Literal::AbstractFloat(v) => i32::try_from_abstract(v)?,
|
||||
}),
|
||||
Sc::U32 => Literal::U32(match literal {
|
||||
Literal::I32(v) => v as u32,
|
||||
@@ -988,6 +999,8 @@ impl<'a> ConstantEvaluator<'a> {
|
||||
Literal::F64(_) | Literal::I64(_) => {
|
||||
return Err(ConstantEvaluatorError::InvalidCastArg)
|
||||
}
|
||||
Literal::AbstractInt(v) => u32::try_from_abstract(v)?,
|
||||
Literal::AbstractFloat(v) => u32::try_from_abstract(v)?,
|
||||
}),
|
||||
Sc::F32 => Literal::F32(match literal {
|
||||
Literal::I32(v) => v as f32,
|
||||
@@ -997,21 +1010,28 @@ impl<'a> ConstantEvaluator<'a> {
|
||||
Literal::F64(_) | Literal::I64(_) => {
|
||||
return Err(ConstantEvaluatorError::InvalidCastArg)
|
||||
}
|
||||
Literal::AbstractInt(v) => f32::try_from_abstract(v)?,
|
||||
Literal::AbstractFloat(v) => f32::try_from_abstract(v)?,
|
||||
}),
|
||||
Sc::F64 => Literal::F64(match literal {
|
||||
Literal::I32(v) => v as f64,
|
||||
Literal::U32(v) => v as f64,
|
||||
Literal::F32(v) => v as f64,
|
||||
Literal::Bool(v) => v as u32 as f64,
|
||||
Literal::F64(v) => v,
|
||||
Literal::Bool(v) => v as u32 as f64,
|
||||
Literal::I64(_) => return Err(ConstantEvaluatorError::InvalidCastArg),
|
||||
Literal::AbstractInt(v) => f64::try_from_abstract(v)?,
|
||||
Literal::AbstractFloat(v) => f64::try_from_abstract(v)?,
|
||||
}),
|
||||
Sc::BOOL => Literal::Bool(match literal {
|
||||
Literal::I32(v) => v != 0,
|
||||
Literal::U32(v) => v != 0,
|
||||
Literal::F32(v) => v != 0.0,
|
||||
Literal::Bool(v) => v,
|
||||
Literal::F64(_) | Literal::I64(_) => {
|
||||
Literal::F64(_)
|
||||
| Literal::I64(_)
|
||||
| Literal::AbstractInt(_)
|
||||
| Literal::AbstractFloat(_) => {
|
||||
return Err(ConstantEvaluatorError::InvalidCastArg)
|
||||
}
|
||||
}),
|
||||
@@ -1828,3 +1848,92 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// to integers are either lossless or an error.
|
||||
///
|
||||
/// - WGSL §14.6.4 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.
|
||||
///
|
||||
/// [`AbstractInt`]: crate::Literal::AbstractInt
|
||||
/// [`Float`]: crate::Literal::Float
|
||||
fn try_from_abstract(value: T) -> Result<Self, ConstantEvaluatorError>;
|
||||
}
|
||||
|
||||
impl TryFromAbstract<i64> for i32 {
|
||||
fn try_from_abstract(value: i64) -> Result<i32, ConstantEvaluatorError> {
|
||||
i32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy {
|
||||
value: format!("{value:?}"),
|
||||
to_type: "i32",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromAbstract<i64> for u32 {
|
||||
fn try_from_abstract(value: i64) -> Result<u32, ConstantEvaluatorError> {
|
||||
u32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy {
|
||||
value: format!("{value:?}"),
|
||||
to_type: "u32",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 f64 {
|
||||
fn try_from_abstract(value: f64) -> Result<f64, ConstantEvaluatorError> {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromAbstract<f64> for i32 {
|
||||
fn try_from_abstract(_: f64) -> Result<Self, ConstantEvaluatorError> {
|
||||
Err(ConstantEvaluatorError::AutomaticConversionFloatToInt { to_type: "i32" })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromAbstract<f64> for u32 {
|
||||
fn try_from_abstract(_: f64) -> Result<Self, ConstantEvaluatorError> {
|
||||
Err(ConstantEvaluatorError::AutomaticConversionFloatToInt { to_type: "u32" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,14 @@ impl super::Scalar {
|
||||
kind: crate::ScalarKind::Bool,
|
||||
width: crate::BOOL_WIDTH,
|
||||
};
|
||||
pub const ABSTRACT_INT: Self = Self {
|
||||
kind: crate::ScalarKind::AbstractInt,
|
||||
width: crate::ABSTRACT_WIDTH,
|
||||
};
|
||||
pub const ABSTRACT_FLOAT: Self = Self {
|
||||
kind: crate::ScalarKind::AbstractFloat,
|
||||
width: crate::ABSTRACT_WIDTH,
|
||||
};
|
||||
|
||||
/// Construct a float `Scalar` with the given width.
|
||||
///
|
||||
@@ -148,7 +156,7 @@ impl Eq for crate::Literal {}
|
||||
impl std::hash::Hash for crate::Literal {
|
||||
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
||||
match *self {
|
||||
Self::F64(v) => {
|
||||
Self::F64(v) | Self::AbstractFloat(v) => {
|
||||
hasher.write_u8(0);
|
||||
v.to_bits().hash(hasher);
|
||||
}
|
||||
@@ -168,7 +176,7 @@ impl std::hash::Hash for crate::Literal {
|
||||
hasher.write_u8(4);
|
||||
v.hash(hasher);
|
||||
}
|
||||
Self::I64(v) => {
|
||||
Self::I64(v) | Self::AbstractInt(v) => {
|
||||
hasher.write_u8(5);
|
||||
v.hash(hasher);
|
||||
}
|
||||
@@ -202,7 +210,8 @@ impl crate::Literal {
|
||||
match *self {
|
||||
Self::F64(_) | Self::I64(_) => 8,
|
||||
Self::F32(_) | Self::U32(_) | Self::I32(_) => 4,
|
||||
Self::Bool(_) => 1,
|
||||
Self::Bool(_) => crate::BOOL_WIDTH,
|
||||
Self::AbstractInt(_) | Self::AbstractFloat(_) => crate::ABSTRACT_WIDTH,
|
||||
}
|
||||
}
|
||||
pub const fn scalar(&self) -> crate::Scalar {
|
||||
@@ -213,6 +222,8 @@ impl crate::Literal {
|
||||
Self::I32(_) => crate::Scalar::I32,
|
||||
Self::I64(_) => crate::Scalar::I64,
|
||||
Self::Bool(_) => crate::Scalar::BOOL,
|
||||
Self::AbstractInt(_) => crate::Scalar::ABSTRACT_INT,
|
||||
Self::AbstractFloat(_) => crate::Scalar::ABSTRACT_FLOAT,
|
||||
}
|
||||
}
|
||||
pub const fn scalar_kind(&self) -> crate::ScalarKind {
|
||||
|
||||
Reference in New Issue
Block a user