[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:
Jim Blandy
2023-11-09 13:21:33 -08:00
committed by Teodor Tanasoaia
parent 276c978b70
commit e75fb3c224
8 changed files with 151 additions and 5 deletions

View File

@@ -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) => {

View File

@@ -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];

View File

@@ -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];

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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)]

View File

@@ -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" })
}
}

View File

@@ -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 {