Skip to content

Commit 6405dcf

Browse files
schelljimblandy
authored andcommitted
[naga spv-in] Adjust types of globals used by atomic instructions.
To support atomic instructions in the SPIR-V front end, observe which global variables the input accesses using atomic instructions, and adjust their types from ordinary scalars to atomic values. See comments in `naga::front::atomic_upgrade`.
1 parent 7175079 commit 6405dcf

File tree

9 files changed

+724
-65
lines changed

9 files changed

+724
-65
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ By @atlv24 in [#5383](https://github.com/gfx-rs/wgpu/pull/5383)
126126

127127
#### Naga
128128

129+
- Added type upgrades to SPIR-V atomic support. Added related infrastructure. Tracking issue is [here](https://github.com/gfx-rs/wgpu/issues/4489). By @schell in [#5775](https://github.com/gfx-rs/wgpu/pull/5775).
129130
- Implement `WGSL`'s `unpack4xI8`,`unpack4xU8`,`pack4xI8` and `pack4xU8`. By @VlaDexa in [#5424](https://github.com/gfx-rs/wgpu/pull/5424)
130131
- Began work adding support for atomics to the SPIR-V frontend. Tracking issue is [here](https://github.com/gfx-rs/wgpu/issues/4489). By @schell in [#5702](https://github.com/gfx-rs/wgpu/pull/5702).
131132

naga/src/front/atomic_upgrade.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//! Upgrade the types of scalars observed to be accessed as atomics to [`Atomic`] types.
2+
//!
3+
//! In SPIR-V, atomic operations can be applied to any scalar value, but in Naga
4+
//! IR atomic operations can only be applied to values of type [`Atomic`]. Naga
5+
//! IR's restriction matches Metal Shading Language and WGSL, so we don't want
6+
//! to relax that. Instead, when the SPIR-V front end observes a value being
7+
//! accessed using atomic instructions, it promotes the value's type from
8+
//! [`Scalar`] to [`Atomic`]. This module implements `Module::upgrade_atomics`,
9+
//! the function that makes that change.
10+
//!
11+
//! Atomics can only appear in global variables in the [`Storage`] and
12+
//! [`Workgroup`] address spaces. These variables can either have `Atomic` types
13+
//! themselves, or be [`Array`]s of such, or be [`Struct`]s containing such.
14+
//! So we only need to change the types of globals and struct fields.
15+
//!
16+
//! Naga IR [`Load`] expressions and [`Store`] statements can operate directly
17+
//! on [`Atomic`] values, retrieving and depositing ordinary [`Scalar`] values,
18+
//! so changing the types doesn't have much effect on the code that operates on
19+
//! those values.
20+
//!
21+
//! Future work:
22+
//!
23+
//! - Atomics in structs are not implemented yet.
24+
//!
25+
//! - The GLSL front end could use this transformation as well.
26+
//!
27+
//! [`Atomic`]: TypeInner::Atomic
28+
//! [`Scalar`]: TypeInner::Scalar
29+
//! [`Storage`]: crate::AddressSpace::Storage
30+
//! [`WorkGroup`]: crate::AddressSpace::WorkGroup
31+
//! [`Array`]: TypeInner::Array
32+
//! [`Struct`]: TypeInner::Struct
33+
//! [`Load`]: crate::Expression::Load
34+
//! [`Store`]: crate::Statement::Store
35+
use std::sync::{atomic::AtomicUsize, Arc};
36+
37+
use crate::{GlobalVariable, Handle, Module, Type, TypeInner};
38+
39+
#[derive(Clone, Debug, thiserror::Error)]
40+
pub enum Error {
41+
#[error("encountered an unsupported expression")]
42+
Unsupported,
43+
#[error("upgrading structs of more than one member is not yet implemented")]
44+
MultiMemberStruct,
45+
#[error("encountered unsupported global initializer in an atomic variable")]
46+
GlobalInitUnsupported,
47+
}
48+
49+
impl From<Error> for crate::front::spv::Error {
50+
fn from(source: Error) -> Self {
51+
crate::front::spv::Error::AtomicUpgradeError(source)
52+
}
53+
}
54+
55+
#[derive(Clone, Default)]
56+
struct Padding(Arc<AtomicUsize>);
57+
58+
impl std::fmt::Display for Padding {
59+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
for _ in 0..self.0.load(std::sync::atomic::Ordering::Relaxed) {
61+
f.write_str(" ")?;
62+
}
63+
Ok(())
64+
}
65+
}
66+
67+
impl Drop for Padding {
68+
fn drop(&mut self) {
69+
let _ = self.0.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
70+
}
71+
}
72+
73+
impl Padding {
74+
fn trace(&self, msg: impl std::fmt::Display, t: impl std::fmt::Debug) {
75+
format!("{msg} {t:#?}")
76+
.split('\n')
77+
.for_each(|ln| log::trace!("{self}{ln}"));
78+
}
79+
80+
fn debug(&self, msg: impl std::fmt::Display, t: impl std::fmt::Debug) {
81+
format!("{msg} {t:#?}")
82+
.split('\n')
83+
.for_each(|ln| log::debug!("{self}{ln}"));
84+
}
85+
86+
fn inc_padding(&self) -> Padding {
87+
let _ = self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
88+
self.clone()
89+
}
90+
}
91+
92+
struct UpgradeState<'a> {
93+
padding: Padding,
94+
module: &'a mut Module,
95+
}
96+
97+
impl<'a> UpgradeState<'a> {
98+
fn inc_padding(&self) -> Padding {
99+
self.padding.inc_padding()
100+
}
101+
102+
/// Upgrade the type, recursing until we reach the leaves.
103+
/// At the leaves, replace scalars with atomic scalars.
104+
fn upgrade_type(&mut self, ty: Handle<Type>) -> Result<Handle<Type>, Error> {
105+
let padding = self.inc_padding();
106+
padding.trace("upgrading type: ", ty);
107+
108+
let inner = match self.module.types[ty].inner {
109+
TypeInner::Scalar(scalar) => {
110+
log::trace!("{padding}hit the scalar leaf, replacing with an atomic");
111+
TypeInner::Atomic(scalar)
112+
}
113+
TypeInner::Pointer { base, space } => TypeInner::Pointer {
114+
base: self.upgrade_type(base)?,
115+
space,
116+
},
117+
TypeInner::Array { base, size, stride } => TypeInner::Array {
118+
base: self.upgrade_type(base)?,
119+
size,
120+
stride,
121+
},
122+
TypeInner::Struct { ref members, span } => {
123+
// In the future we should have to figure out which member needs
124+
// upgrading, but for now we'll only cover the single-member
125+
// case.
126+
let &[crate::StructMember {
127+
ref name,
128+
ty,
129+
ref binding,
130+
offset,
131+
}] = &members[..]
132+
else {
133+
return Err(Error::MultiMemberStruct);
134+
};
135+
136+
// Take our own clones of these values now, so that
137+
// `upgrade_type` can mutate the module.
138+
let name = name.clone();
139+
let binding = binding.clone();
140+
let upgraded_member_type = self.upgrade_type(ty)?;
141+
TypeInner::Struct {
142+
members: vec![crate::StructMember {
143+
name,
144+
ty: upgraded_member_type,
145+
binding,
146+
offset,
147+
}],
148+
span,
149+
}
150+
}
151+
TypeInner::BindingArray { base, size } => TypeInner::BindingArray {
152+
base: self.upgrade_type(base)?,
153+
size,
154+
},
155+
_ => return Ok(ty),
156+
};
157+
158+
// Now that we've upgraded any subtypes, re-borrow a reference to our
159+
// type and update its `inner`.
160+
let r#type = &self.module.types[ty];
161+
let span = self.module.types.get_span(ty);
162+
let new_type = Type {
163+
name: r#type.name.clone(),
164+
inner,
165+
};
166+
padding.debug("ty: ", ty);
167+
padding.debug("from: ", r#type);
168+
padding.debug("to: ", &new_type);
169+
let new_handle = self.module.types.insert(new_type, span);
170+
Ok(new_handle)
171+
}
172+
173+
fn upgrade_global_variable(&mut self, handle: Handle<GlobalVariable>) -> Result<(), Error> {
174+
let padding = self.inc_padding();
175+
padding.trace("upgrading global variable: ", handle);
176+
177+
let var = &self.module.global_variables[handle];
178+
179+
if var.init.is_some() {
180+
return Err(Error::GlobalInitUnsupported);
181+
}
182+
183+
let var_ty = var.ty;
184+
let new_ty = self.upgrade_type(var.ty)?;
185+
if new_ty != var_ty {
186+
padding.debug("upgrading global variable: ", handle);
187+
padding.debug("from ty: ", var_ty);
188+
padding.debug("to ty: ", new_ty);
189+
self.module.global_variables[handle].ty = new_ty;
190+
}
191+
Ok(())
192+
}
193+
}
194+
195+
impl Module {
196+
/// Upgrade `global_var_handles` to have [`Atomic`] leaf types.
197+
///
198+
/// [`Atomic`]: TypeInner::Atomic
199+
pub(crate) fn upgrade_atomics(
200+
&mut self,
201+
global_var_handles: impl IntoIterator<Item = Handle<GlobalVariable>>,
202+
) -> Result<(), Error> {
203+
let mut state = UpgradeState {
204+
padding: Default::default(),
205+
module: self,
206+
};
207+
208+
for handle in global_var_handles {
209+
state.upgrade_global_variable(handle)?;
210+
}
211+
212+
Ok(())
213+
}
214+
}

naga/src/front/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Frontend parsers that consume binary and text shaders and load them into [`Modul
55
mod interpolator;
66
mod type_gen;
77

8+
#[cfg(feature = "spv-in")]
9+
pub mod atomic_upgrade;
810
#[cfg(feature = "glsl-in")]
911
pub mod glsl;
1012
#[cfg(feature = "spv-in")]

naga/src/front/spv/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::ModuleState;
2-
use crate::arena::Handle;
2+
use crate::{arena::Handle, front::atomic_upgrade};
33
use codespan_reporting::diagnostic::Diagnostic;
44
use codespan_reporting::files::SimpleFile;
55
use codespan_reporting::term;
@@ -134,6 +134,9 @@ pub enum Error {
134134
NonBindingArrayOfImageOrSamplers,
135135
#[error("naga only supports specialization constant IDs up to 65535 but was given {0}")]
136136
SpecIdTooHigh(u32),
137+
138+
#[error("atomic upgrade error: {0}")]
139+
AtomicUpgradeError(atomic_upgrade::Error),
137140
}
138141

139142
impl Error {

0 commit comments

Comments
 (0)