Repository navigation
Make Scope and Semantics normal arguments rather than const generics #414
Description
Activity
Scope and Memory Semantics args are impossible
Scope and Memory Semantics must be a constant and cannot be runtime dynamic args, see spec:
All used for Scope and Memory Semantics must be of an OpConstant.
Which forces us to use const generics and forward them to the
asm!block as constants:rust-gpu/crates/spirv-std/src/arch/barrier.rs
Lines 36 to 53 in 7e15027
pub fn control_barrier< const EXECUTION: u32, // Scope const MEMORY: u32, // Scope const SEMANTICS: u32, // Semantics >() { unsafe { asm! { "%u32 = OpTypeInt 32 0", "%execution = OpConstant %u32 {execution}", "%memory = OpConstant %u32 {memory}", "%semantics = OpConstant %u32 {semantics}", "OpControlBarrier %execution %memory %semantics", execution = const EXECUTION, memory = const MEMORY, semantics = const SEMANTICS, } } } I also tried replacing the
OpConstantwith ordinaryOpLoads, but that fails validation:asm! { "%u32 = OpTypeInt 32 0", "%execution = OpLoad %u32 {execution}", "%memory = OpLoad %u32 {memory}", "%semantics = OpLoad %u32 {semantics}", "OpControlBarrier %execution %memory %semantics", execution = in(reg) &EXECUTION, memory = in(reg) &MEMORY, semantics = in(reg) &SEMANTICS, }
error: error:0:0 - Scope ids must be OpConstant when Shader capability is present OpControlBarrier %21 %22 %23So we're unfortunately tied to constants by the spec.
This is in interesting constraint. I believe it'll be possible to use const generics with arbitrary types in the future, but we're not there yet (or maybe codegen backend can opt-in into this experimental feature already given the narrow scope?).
I'm wondering if
const {}can be exploited here somehow and/or#[inline(always)](I'm just brainstorming here, but the intuition is that at some abstraction level arguments will become inlined constants). Or maybe special-case these functions for compiler somehow such that it is a normal argument, but compiler knows the arguments must be known at compile time. There are a few tricks of this nature in Rust standard library that are not directly implementable in normal Rust code.Or maybe replace these functions with macros, such that it is possible to use normal types and have compile-time guarantees of type safety.
Alternatives
Const generics with enums / ADT types
This would solve this issues at once, but it looks like rustc is still quite a ways off...
Wrapping functions
You've already mentioned them, they are probably the easiest way to wrap the const complexity
rust-gpu/crates/spirv-std/src/arch/barrier.rs
Lines 113 to 122 in 7e15027
pub fn workgroup_memory_barrier_with_group_sync() { control_barrier::< { crate::memory::Scope::Workgroup as u32 }, { crate::memory::Scope::Workgroup as u32 }, { crate::memory::Semantics::WORKGROUP_MEMORY.bits() | crate::memory::Semantics::ACQUIRE_RELEASE.bits() }, >(); } Image! macro
Images are similarly full of const generics that are effectively enums:
rust-gpu/crates/spirv-std/src/image.rs
Lines 103 to 112 in adfe6b8
pub struct Image< SampledType: SampleType<FORMAT, COMPONENTS>, const DIM: u32, // Dimensionality, const DEPTH: u32, // ImageDepth, const ARRAYED: u32, // Arrayed, const MULTISAMPLED: u32, // Multisampled, const SAMPLED: u32, // Sampled, const FORMAT: u32, // ImageFormat, const COMPONENTS: u32, // NumberOfComponents, > { In fact there are so many const generics, that we can't reasonably assume users will correctly specify them. So the
Image!macro exists to correctly specify them, and we have some common image types typedef'ed:rust-gpu/crates/spirv-std/src/image.rs
Lines 30 to 35 in adfe6b8
/// A 1d image used with a sampler. pub type Image1d = crate::Image!(1D, type=f32, sampled, __crate_root=crate); /// A 2d image used with a sampler. This is pretty typical and probably what you want. pub type Image2d = crate::Image!(2D, type=f32, sampled, __crate_root=crate); /// A 3d image used with a sampler. pub type Image3d = crate::Image!(3D, type=f32, sampled, __crate_root=crate); Type wrapping
This is something I've used in my own engine with great success, and could be a model for rust-gpu as well?
- Declare an enum with all the variants:
#[repr(u8)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, ToPrimitive)] pub enum BufferAccess { ShaderRead,, ShaderReadWrite, [....] }
- Create a trait with an associated constant of said enum:
pub unsafe trait BufferAccessType { const BUFFER_ACCESS: BufferAccess; }
- Define a type that implements said trait:
pub struct ShaderRead; unsafe impl BufferAccessType for ShaderRead { const BUFFER_ACCESS: BufferAccess = BufferAccess::ShaderRead; }
- preferably implement this with some
macro_rules! - a function can then be declared like this, which
asm!should hopefully also accept as a const:
fn my_func<A: BufferAccessType>() { let buffer_access = A::BUFFER_ACCESS; } my_func::<ShaderRead>()
@eddyb @LegNeato @schell @nnethercote Design thoughts on where we should head to in the future?
Reacted by Tendsin Mende- marked Consider
adt_const_paramsfor intrinsics #607 as a duplicate of this issueon Aug 4, 2026 Implemented
adt_const_paramsalternative in #635
The usage of atomics is quite awkward:
Not only one needs to know what each
u32is, it is not even consistent with the need to cast in one case and calling.bits()in another.Similarly, barriers are awkward too:
It is even worse when memory semantics is involved (though many of such cases have helper methods):
Subjectively, three
u32s that need to be composed in a very particular way are quite ugly.Consider changing API to use normal types for these arguments, so we can all enjoy autocomplete in IDE, better formatting and implement ability to combine semantics variants, like this:
The result of
Semantics::WorkgroupMemory | Semantics::AcquireReleasecan beSemantics::Combined(u32)for example, withDebugimplementation overridden to render human-readable set of enabled flags.Alternatively, a builder pattern could be used for
Semanticswithconst fnto have guarantees of sane composition in case some invariants fundamentally do not make sense.At some point non-integer const generics will become available, but even then I see no benefit from const generics for this particular use case.