spv-in parse more atomic ops (#5824)

* add parsing for spirv::Op::AtomicLoad and spirv::Op::AtomicStore

* spv-in parse AtomicExchange and AtomicCompareExchange

* add atomic i decrement

* bookend atomic store statement with emmitter.finish/emitter.start to suppress a double load expression

bookend atomic result expressions with emitter.finish/start to prevent double defs

* add atomic iadd, isub, smin, umin, smax, umax, and, or, xor

* parse atomic flag test and set, parse atomic flag clear

* remove atomic compare exchange work

* changelog

* moved spirv tests into front/spv/mod.rs

* feature gate atomic spv tests because they require wgsl-[in,out]

* BlockContext::get_contained_global_variable returns Result

* Generate spans covering the entire instruction.

Granted, there is pre-existing code in the SPIR-V front end that gets
this wrong, but:

It doesn't make sense to read `self.data_offset`, and then immediately
pass that to `self.span_from_with_op`. The point of that function is
to make the span cover the entire instruction, operands included.

* Move `From` implementation into spv front end

* doc comments, minor cleanups

* remove parsing of OpAtomicFlagClear and OpAtomicFlagTestAndSet

* sync atomic spvasm files

---------

Co-authored-by: Jim Blandy <jimb@red-bean.com>
This commit is contained in:
Schell Carl Scivally 2024-09-18 17:39:36 +12:00 committed by GitHub
parent 434f197410
commit fc85e4f970
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 585 additions and 57 deletions

View File

@ -80,6 +80,7 @@ By @bradwerth [#6216](https://github.com/gfx-rs/wgpu/pull/6216).
- Support constant evaluation for `firstLeadingBit` and `firstTrailingBit` numeric built-ins in WGSL. Front-ends that translate to these built-ins also benefit from constant evaluation. By @ErichDonGubler in [#5101](https://github.com/gfx-rs/wgpu/pull/5101). - Support constant evaluation for `firstLeadingBit` and `firstTrailingBit` numeric built-ins in WGSL. Front-ends that translate to these built-ins also benefit from constant evaluation. By @ErichDonGubler in [#5101](https://github.com/gfx-rs/wgpu/pull/5101).
- Add `first` and `either` sampling types for `@interpolate(flat, …)` in WGSL. By @ErichDonGubler in [#6181](https://github.com/gfx-rs/wgpu/pull/6181). - Add `first` and `either` sampling types for `@interpolate(flat, …)` in WGSL. By @ErichDonGubler in [#6181](https://github.com/gfx-rs/wgpu/pull/6181).
- Support for more atomic ops in the SPIR-V frontend. By @schell in [#5824](https://github.com/gfx-rs/wgpu/pull/5824).
#### Vulkan #### Vulkan

View File

@ -44,12 +44,8 @@ pub enum Error {
MultiMemberStruct, MultiMemberStruct,
#[error("encountered unsupported global initializer in an atomic variable")] #[error("encountered unsupported global initializer in an atomic variable")]
GlobalInitUnsupported, GlobalInitUnsupported,
} #[error("expected to find a global variable")]
GlobalVariableMissing,
impl From<Error> for crate::front::spv::Error {
fn from(source: Error) -> Self {
crate::front::spv::Error::AtomicUpgradeError(source)
}
} }
#[derive(Clone, Default)] #[derive(Clone, Default)]

View File

@ -159,3 +159,9 @@ impl Error {
String::from_utf8(writer.into_inner()).unwrap() String::from_utf8(writer.into_inner()).unwrap()
} }
} }
impl From<atomic_upgrade::Error> for Error {
fn from(source: atomic_upgrade::Error) -> Self {
crate::front::spv::Error::AtomicUpgradeError(source)
}
}

View File

@ -565,11 +565,15 @@ impl<'a> BlockContext<'a> {
/// Descend into the expression with the given handle, locating a contained /// Descend into the expression with the given handle, locating a contained
/// global variable. /// global variable.
/// ///
/// If the expression doesn't actually refer to something in a global
/// variable, we can't upgrade its type in a way that Naga validation would
/// pass, so reject the input instead.
///
/// This is used to track atomic upgrades. /// This is used to track atomic upgrades.
fn get_contained_global_variable( fn get_contained_global_variable(
&self, &self,
mut handle: Handle<crate::Expression>, mut handle: Handle<crate::Expression>,
) -> Option<Handle<crate::GlobalVariable>> { ) -> Result<Handle<crate::GlobalVariable>, Error> {
log::debug!("\t\tlocating global variable in {handle:?}"); log::debug!("\t\tlocating global variable in {handle:?}");
loop { loop {
match self.expressions[handle] { match self.expressions[handle] {
@ -583,14 +587,16 @@ impl<'a> BlockContext<'a> {
} }
crate::Expression::GlobalVariable(h) => { crate::Expression::GlobalVariable(h) => {
log::debug!("\t\t found {h:?}"); log::debug!("\t\t found {h:?}");
return Some(h); return Ok(h);
} }
_ => { _ => {
break; break;
} }
} }
} }
None Err(Error::AtomicUpgradeError(
crate::front::atomic_upgrade::Error::GlobalVariableMissing,
))
} }
} }
@ -1323,6 +1329,109 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
)) ))
} }
/// Return the Naga [`Expression`] for `pointer_id`, and its referent [`Type`].
///
/// Return a [`Handle`] for a Naga [`Expression`] that holds the value of
/// the SPIR-V instruction `pointer_id`, along with the [`Type`] to which it
/// is a pointer.
///
/// This may entail spilling `pointer_id`'s value to a temporary:
/// see [`get_expr_handle`]'s documentation.
///
/// [`Expression`]: crate::Expression
/// [`Type`]: crate::Type
/// [`Handle`]: crate::Handle
/// [`get_expr_handle`]: Frontend::get_expr_handle
fn get_exp_and_base_ty_handles(
&self,
pointer_id: spirv::Word,
ctx: &mut BlockContext,
emitter: &mut crate::proc::Emitter,
block: &mut crate::Block,
body_idx: usize,
) -> Result<(Handle<crate::Expression>, Handle<crate::Type>), Error> {
log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id);
let p_lexp_handle;
let p_lexp_ty_id;
{
let lexp = self.lookup_expression.lookup(pointer_id)?;
p_lexp_handle = self.get_expr_handle(pointer_id, lexp, ctx, emitter, block, body_idx);
p_lexp_ty_id = lexp.type_id;
};
log::trace!("\t\t\tlooking up pointer type {pointer_id:?}");
let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?;
let p_ty_base_id = p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?;
log::trace!("\t\t\tlooking up pointer base type {p_ty_base_id:?} of {p_ty:?}");
let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?;
Ok((p_lexp_handle, p_base_ty.handle))
}
#[allow(clippy::too_many_arguments)]
fn parse_atomic_expr_with_value(
&mut self,
inst: Instruction,
emitter: &mut crate::proc::Emitter,
ctx: &mut BlockContext,
block: &mut crate::Block,
block_id: spirv::Word,
body_idx: usize,
atomic_function: crate::AtomicFunction,
) -> Result<(), Error> {
inst.expect(7)?;
let start = self.data_offset;
let result_type_id = self.next()?;
let result_id = self.next()?;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let value_id = self.next()?;
let span = self.span_from_with_op(start);
let (p_lexp_handle, p_base_ty_handle) =
self.get_exp_and_base_ty_handles(pointer_id, ctx, emitter, block, body_idx)?;
log::trace!("\t\t\tlooking up value expr {value_id:?}");
let v_lexp_handle = self.lookup_expression.lookup(value_id)?.handle;
block.extend(emitter.finish(ctx.expressions));
// Create an expression for our result
let r_lexp_handle = {
let expr = crate::Expression::AtomicResult {
ty: p_base_ty_handle,
comparison: false,
};
let handle = ctx.expressions.append(expr, span);
self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
handle
};
emitter.start(ctx.expressions);
// Create a statement for the op itself
let stmt = crate::Statement::Atomic {
pointer: p_lexp_handle,
fun: atomic_function,
value: v_lexp_handle,
result: Some(r_lexp_handle),
};
block.push(stmt, span);
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.insert(ctx.get_contained_global_variable(p_lexp_handle)?);
Ok(())
}
/// Add the next SPIR-V block's contents to `block_ctx`. /// Add the next SPIR-V block's contents to `block_ctx`.
/// ///
/// Except for the function's entry block, `block_id` should be the label of /// Except for the function's entry block, `block_id` should be the label of
@ -3985,35 +4094,91 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
); );
emitter.start(ctx.expressions); emitter.start(ctx.expressions);
} }
Op::AtomicIIncrement => { Op::AtomicLoad => {
inst.expect(6)?; inst.expect(6)?;
let start = self.data_offset; let start = self.data_offset;
let span = self.span_from_with_op(start);
let result_type_id = self.next()?; let result_type_id = self.next()?;
let result_id = self.next()?; let result_id = self.next()?;
let pointer_id = self.next()?; let pointer_id = self.next()?;
let _scope_id = self.next()?; let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?; let _memory_semantics_id = self.next()?;
let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up expr {:?}", pointer_id); log::trace!("\t\t\tlooking up expr {:?}", pointer_id);
let (p_lexp_handle, p_lexp_ty_id) = { let p_lexp_handle =
let lexp = self.lookup_expression.lookup(pointer_id)?; get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
let handle = get_expr_handle!(pointer_id, &lexp);
(handle, lexp.type_id) // Create an expression for our result
let expr = crate::Expression::Load {
pointer: p_lexp_handle,
}; };
let handle = ctx.expressions.append(expr, span);
self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
log::trace!("\t\t\tlooking up type {pointer_id:?}"); // Store any associated global variables so we can upgrade their types later
let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?; self.upgrade_atomics
let p_ty_base_id = .insert(ctx.get_contained_global_variable(p_lexp_handle)?);
p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?; }
Op::AtomicStore => {
inst.expect(5)?;
let start = self.data_offset;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let value_id = self.next()?;
let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up base type {p_ty_base_id:?} of {p_ty:?}"); log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id);
let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?; let p_lexp_handle =
get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
log::trace!("\t\t\tlooking up value expr {:?}", pointer_id);
let v_lexp_handle =
get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?);
block.extend(emitter.finish(ctx.expressions));
// Create a statement for the op itself
let stmt = crate::Statement::Store {
pointer: p_lexp_handle,
value: v_lexp_handle,
};
block.push(stmt, span);
emitter.start(ctx.expressions);
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.insert(ctx.get_contained_global_variable(p_lexp_handle)?);
}
Op::AtomicIIncrement | Op::AtomicIDecrement => {
inst.expect(6)?;
let start = self.data_offset;
let result_type_id = self.next()?;
let result_id = self.next()?;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let span = self.span_from_with_op(start);
let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles(
pointer_id,
ctx,
&mut emitter,
&mut block,
body_idx,
)?;
block.extend(emitter.finish(ctx.expressions));
// Create an expression for our result // Create an expression for our result
let r_lexp_handle = { let r_lexp_handle = {
let expr = crate::Expression::AtomicResult { let expr = crate::Expression::AtomicResult {
ty: p_base_ty.handle, ty: p_base_ty_h,
comparison: false, comparison: false,
}; };
let handle = ctx.expressions.append(expr, span); let handle = ctx.expressions.append(expr, span);
@ -4027,22 +4192,26 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
); );
handle handle
}; };
emitter.start(ctx.expressions);
// Create a literal "1" since WGSL lacks an increment operation // Create a literal "1" to use as our value
let one_lexp_handle = make_index_literal( let one_lexp_handle = make_index_literal(
ctx, ctx,
1, 1,
&mut block, &mut block,
&mut emitter, &mut emitter,
p_base_ty.handle, p_base_ty_h,
p_lexp_ty_id, result_type_id,
span, span,
)?; )?;
// Create a statement for the op itself // Create a statement for the op itself
let stmt = crate::Statement::Atomic { let stmt = crate::Statement::Atomic {
pointer: p_lexp_handle, pointer: p_exp_h,
fun: crate::AtomicFunction::Add, fun: match inst.op {
Op::AtomicIIncrement => crate::AtomicFunction::Add,
_ => crate::AtomicFunction::Subtract,
},
value: one_lexp_handle, value: one_lexp_handle,
result: Some(r_lexp_handle), result: Some(r_lexp_handle),
}; };
@ -4050,8 +4219,38 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
// Store any associated global variables so we can upgrade their types later // Store any associated global variables so we can upgrade their types later
self.upgrade_atomics self.upgrade_atomics
.extend(ctx.get_contained_global_variable(p_lexp_handle)); .insert(ctx.get_contained_global_variable(p_exp_h)?);
} }
Op::AtomicExchange
| Op::AtomicIAdd
| Op::AtomicISub
| Op::AtomicSMin
| Op::AtomicUMin
| Op::AtomicSMax
| Op::AtomicUMax
| Op::AtomicAnd
| Op::AtomicOr
| Op::AtomicXor => self.parse_atomic_expr_with_value(
inst,
&mut emitter,
ctx,
&mut block,
block_id,
body_idx,
match inst.op {
Op::AtomicExchange => crate::AtomicFunction::Exchange { compare: None },
Op::AtomicIAdd => crate::AtomicFunction::Add,
Op::AtomicISub => crate::AtomicFunction::Subtract,
Op::AtomicSMin => crate::AtomicFunction::Min,
Op::AtomicUMin => crate::AtomicFunction::Min,
Op::AtomicSMax => crate::AtomicFunction::Max,
Op::AtomicUMax => crate::AtomicFunction::Max,
Op::AtomicAnd => crate::AtomicFunction::And,
Op::AtomicOr => crate::AtomicFunction::InclusiveOr,
_ => crate::AtomicFunction::ExclusiveOr,
},
)?,
_ => { _ => {
return Err(Error::UnsupportedInstruction(self.state, inst.op)); return Err(Error::UnsupportedInstruction(self.state, inst.op));
} }
@ -5709,33 +5908,48 @@ mod test {
]; ];
let _ = super::parse_u8_slice(&bin, &Default::default()).unwrap(); let _ = super::parse_u8_slice(&bin, &Default::default()).unwrap();
} }
}
#[cfg(all(feature = "wgsl-in", wgsl_out))] #[cfg(all(test, feature = "wgsl-in", wgsl_out))]
#[test] mod test_atomic {
fn atomic_i_inc() { fn atomic_test(bytes: &[u8]) {
let _ = env_logger::builder().is_test(true).try_init(); let _ = env_logger::builder().is_test(true).try_init();
let bytes = include_bytes!("../../../tests/in/spv/atomic_i_increment.spv"); let m = crate::front::spv::parse_u8_slice(bytes, &Default::default()).unwrap();
let m = super::parse_u8_slice(bytes, &Default::default()).unwrap();
let mut validator = crate::valid::Validator::new( let mut wgsl = String::new();
let mut should_panic = false;
for vflags in [
crate::valid::ValidationFlags::all(),
crate::valid::ValidationFlags::empty(), crate::valid::ValidationFlags::empty(),
Default::default(), ] {
); let mut validator = crate::valid::Validator::new(vflags, Default::default());
let info = match validator.validate(&m) { match validator.validate(&m) {
Err(e) => { Err(e) => {
log::error!("{}", e.emit_to_string("")); log::error!("SPIR-V validation {}", e.emit_to_string(""));
return; should_panic = true;
} }
Ok(i) => i, Ok(i) => {
}; wgsl = crate::back::wgsl::write_string(
let wgsl = &m,
crate::back::wgsl::write_string(&m, &info, crate::back::wgsl::WriterFlags::empty()) &i,
.unwrap(); crate::back::wgsl::WriterFlags::empty(),
log::info!("atomic_i_increment:\n{wgsl}"); )
.unwrap();
log::info!("wgsl-out:\n{wgsl}");
break;
}
};
}
if should_panic {
panic!("validation error");
}
let m = match crate::front::wgsl::parse_str(&wgsl) { let m = match crate::front::wgsl::parse_str(&wgsl) {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
log::error!("{}", e.emit_to_string(&wgsl)); log::error!("round trip WGSL validation {}", e.emit_to_string(&wgsl));
panic!("invalid module"); panic!("invalid module");
} }
}; };
@ -5746,4 +5960,35 @@ mod test {
panic!("invalid generated wgsl"); panic!("invalid generated wgsl");
} }
} }
#[test]
fn atomic_i_inc() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_i_increment.spv"
));
}
#[test]
fn atomic_load_and_store() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_load_and_store.spv"
));
}
#[test]
fn atomic_exchange() {
atomic_test(include_bytes!("../../../tests/in/spv/atomic_exchange.spv"));
}
#[test]
fn atomic_i_decrement() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_i_decrement.spv"
));
}
#[test]
fn atomic_i_add_and_sub() {
atomic_test(include_bytes!("../../../tests/in/spv/atomic_i_add_sub.spv"));
}
} }

Binary file not shown.

View File

@ -0,0 +1,88 @@
; SPIR-V
; Version: 1.5
; Generator: Google rspirv; 0
; Bound: 63
; Schema: 0
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %1 "stage::test_atomic_exchange" %2 %3
OpExecutionMode %1 LocalSize 32 1 1
OpMemberDecorate %_struct_11 0 Offset 0
OpMemberDecorate %_struct_11 1 Offset 4
OpDecorate %_struct_12 Block
OpMemberDecorate %_struct_12 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
OpDecorate %3 NonWritable
OpDecorate %3 Binding 1
OpDecorate %3 DescriptorSet 0
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%15 = OpTypeFunction %void
%bool = OpTypeBool
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%false = OpConstantFalse %bool
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%uint_1 = OpConstant %uint 1
%_struct_11 = OpTypeStruct %uint %uint
%22 = OpUndef %_struct_11
%int = OpTypeInt 32 1
%true = OpConstantTrue %bool
%_struct_12 = OpTypeStruct %uint
%_ptr_StorageBuffer__struct_12 = OpTypePointer StorageBuffer %_struct_12
%2 = OpVariable %_ptr_StorageBuffer__struct_12 StorageBuffer
%3 = OpVariable %_ptr_StorageBuffer__struct_12 StorageBuffer
%26 = OpUndef %uint
%1 = OpFunction %void None %15
%27 = OpLabel
%28 = OpAccessChain %_ptr_StorageBuffer_uint %2 %uint_0
%29 = OpAccessChain %_ptr_StorageBuffer_uint %3 %uint_0
%30 = OpLoad %uint %29
%31 = OpCompositeConstruct %_struct_11 %uint_0 %30
OpBranch %32
%32 = OpLabel
%33 = OpPhi %_struct_11 %31 %27 %34 %35
%36 = OpPhi %uint %uint_0 %27 %37 %35
OpLoopMerge %38 %35 None
OpBranch %39
%39 = OpLabel
%40 = OpCompositeExtract %uint %33 0
%41 = OpCompositeExtract %uint %33 1
%42 = OpULessThan %bool %40 %41
OpSelectionMerge %43 None
OpBranchConditional %42 %44 %45
%44 = OpLabel
%47 = OpIAdd %uint %40 %uint_1
%49 = OpCompositeInsert %_struct_11 %47 %33 0
%50 = OpCompositeConstruct %_struct_11 %uint_1 %40
OpBranch %43
%45 = OpLabel
%51 = OpCompositeInsert %_struct_11 %uint_0 %22 0
OpBranch %43
%43 = OpLabel
%52 = OpPhi %_struct_11 %49 %44 %33 %45
%53 = OpPhi %_struct_11 %50 %44 %51 %45
%54 = OpCompositeExtract %uint %53 0
%55 = OpBitcast %int %54
OpSelectionMerge %56 None
OpSwitch %55 %57 0 %58 1 %59
%57 = OpLabel
OpBranch %56
%58 = OpLabel
OpBranch %56
%59 = OpLabel
%60 = OpAtomicExchange %uint %28 %uint_2 %uint_0 %36
%61 = OpIAdd %uint %36 %60
OpBranch %56
%56 = OpLabel
%62 = OpPhi %bool %false %57 %false %58 %true %59
%34 = OpPhi %_struct_11 %22 %57 %22 %58 %52 %59
%37 = OpPhi %uint %26 %57 %26 %58 %61 %59
OpBranch %35
%35 = OpLabel
OpBranchConditional %62 %32 %38
%38 = OpLabel
OpReturn
OpFunctionEnd

Binary file not shown.

View File

@ -0,0 +1,51 @@
; SPIR-V
; Version: 1.5
; Generator: Google rspirv; 0
; Bound: 30
; Schema: 0
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %1 "stage::test_atomic_i_add_sub" %2 %3
OpExecutionMode %1 LocalSize 32 1 1
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_7 Block
OpMemberDecorate %_struct_7 0 Offset 0
OpDecorate %_struct_8 Block
OpMemberDecorate %_struct_8 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
OpDecorate %3 Binding 1
OpDecorate %3 DescriptorSet 0
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%11 = OpTypeFunction %void
%bool = OpTypeBool
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_7 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_7 = OpTypePointer StorageBuffer %_struct_7
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%_struct_8 = OpTypeStruct %uint
%_ptr_StorageBuffer__struct_8 = OpTypePointer StorageBuffer %_struct_8
%2 = OpVariable %_ptr_StorageBuffer__struct_8 StorageBuffer
%3 = OpVariable %_ptr_StorageBuffer__struct_7 StorageBuffer
%1 = OpFunction %void None %11
%19 = OpLabel
%20 = OpAccessChain %_ptr_StorageBuffer_uint %2 %uint_0
%22 = OpArrayLength %uint %3 0
%23 = OpAtomicIAdd %uint %20 %uint_2 %uint_0 %uint_2
%24 = OpAtomicISub %uint %20 %uint_2 %uint_0 %23
%25 = OpULessThan %bool %23 %22
OpSelectionMerge %26 None
OpBranchConditional %25 %27 %28
%27 = OpLabel
%29 = OpAccessChain %_ptr_StorageBuffer_uint %3 %uint_0 %23
OpStore %29 %24
OpBranch %26
%28 = OpLabel
OpBranch %26
%26 = OpLabel
OpReturn
OpFunctionEnd

Binary file not shown.

View File

@ -0,0 +1,64 @@
; SPIR-V
; Version: 1.5
; Generator: Google rspirv; 0
; Bound: 42
; Schema: 0
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %1 "stage::test_atomic_i_decrement" %2 %3
OpExecutionMode %1 LocalSize 32 1 1
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_7 Block
OpMemberDecorate %_struct_7 0 Offset 0
OpDecorate %_struct_8 Block
OpMemberDecorate %_struct_8 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
OpDecorate %3 Binding 1
OpDecorate %3 DescriptorSet 0
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%11 = OpTypeFunction %void
%bool = OpTypeBool
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_7 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_7 = OpTypePointer StorageBuffer %_struct_7
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%false = OpConstantFalse %bool
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%true = OpConstantTrue %bool
%_struct_8 = OpTypeStruct %uint
%_ptr_StorageBuffer__struct_8 = OpTypePointer StorageBuffer %_struct_8
%2 = OpVariable %_ptr_StorageBuffer__struct_8 StorageBuffer
%3 = OpVariable %_ptr_StorageBuffer__struct_7 StorageBuffer
%1 = OpFunction %void None %11
%21 = OpLabel
%22 = OpAccessChain %_ptr_StorageBuffer_uint %2 %uint_0
%24 = OpArrayLength %uint %3 0
OpBranch %25
%25 = OpLabel
OpLoopMerge %26 %27 None
OpBranch %28
%28 = OpLabel
%29 = OpAtomicIDecrement %uint %22 %uint_2 %uint_0
%30 = OpULessThan %bool %29 %24
OpSelectionMerge %31 None
OpBranchConditional %30 %32 %33
%32 = OpLabel
%34 = OpAccessChain %_ptr_StorageBuffer_uint %3 %uint_0 %29
OpStore %34 %29
%35 = OpIEqual %bool %29 %uint_0
%41 = OpSelect %bool %35 %false %true
OpBranch %31
%33 = OpLabel
OpBranch %31
%31 = OpLabel
%40 = OpPhi %bool %41 %32 %false %33
OpBranch %27
%27 = OpLabel
OpBranchConditional %40 %25 %26
%26 = OpLabel
OpReturn
OpFunctionEnd

View File

@ -59,4 +59,3 @@
%26 = OpLabel %26 = OpLabel
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd

Binary file not shown.

View File

@ -0,0 +1,86 @@
; SPIR-V
; Version: 1.5
; Generator: Google rspirv; 0
; Bound: 60
; Schema: 0
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %1 "stage::test_atomic_load_and_store" %2 %3
OpExecutionMode %1 LocalSize 32 1 1
OpMemberDecorate %_struct_11 0 Offset 0
OpMemberDecorate %_struct_11 1 Offset 4
OpDecorate %_struct_12 Block
OpMemberDecorate %_struct_12 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
OpDecorate %3 NonWritable
OpDecorate %3 Binding 1
OpDecorate %3 DescriptorSet 0
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%15 = OpTypeFunction %void
%bool = OpTypeBool
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%false = OpConstantFalse %bool
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%uint_1 = OpConstant %uint 1
%_struct_11 = OpTypeStruct %uint %uint
%22 = OpUndef %_struct_11
%int = OpTypeInt 32 1
%true = OpConstantTrue %bool
%_struct_12 = OpTypeStruct %uint
%_ptr_StorageBuffer__struct_12 = OpTypePointer StorageBuffer %_struct_12
%2 = OpVariable %_ptr_StorageBuffer__struct_12 StorageBuffer
%3 = OpVariable %_ptr_StorageBuffer__struct_12 StorageBuffer
%1 = OpFunction %void None %15
%26 = OpLabel
%27 = OpAccessChain %_ptr_StorageBuffer_uint %2 %uint_0
%28 = OpAccessChain %_ptr_StorageBuffer_uint %3 %uint_0
%29 = OpLoad %uint %28
%30 = OpCompositeConstruct %_struct_11 %uint_0 %29
OpBranch %31
%31 = OpLabel
%32 = OpPhi %_struct_11 %30 %26 %33 %34
OpLoopMerge %35 %34 None
OpBranch %36
%36 = OpLabel
%37 = OpCompositeExtract %uint %32 0
%38 = OpCompositeExtract %uint %32 1
%39 = OpULessThan %bool %37 %38
OpSelectionMerge %40 None
OpBranchConditional %39 %41 %42
%41 = OpLabel
%44 = OpIAdd %uint %37 %uint_1
%46 = OpCompositeInsert %_struct_11 %44 %32 0
%47 = OpCompositeConstruct %_struct_11 %uint_1 %37
OpBranch %40
%42 = OpLabel
%48 = OpCompositeInsert %_struct_11 %uint_0 %22 0
OpBranch %40
%40 = OpLabel
%49 = OpPhi %_struct_11 %46 %41 %32 %42
%50 = OpPhi %_struct_11 %47 %41 %48 %42
%51 = OpCompositeExtract %uint %50 0
%52 = OpBitcast %int %51
OpSelectionMerge %53 None
OpSwitch %52 %54 0 %55 1 %56
%54 = OpLabel
OpBranch %53
%55 = OpLabel
OpBranch %53
%56 = OpLabel
%57 = OpAtomicLoad %uint %27 %uint_2 %uint_0
%58 = OpIAdd %uint %57 %uint_2
OpAtomicStore %27 %uint_2 %uint_0 %58
OpBranch %53
%53 = OpLabel
%59 = OpPhi %bool %false %54 %false %55 %true %56
%33 = OpPhi %_struct_11 %22 %54 %22 %55 %49 %56
OpBranch %34
%34 = OpLabel
OpBranchConditional %59 %31 %35
%35 = OpLabel
OpReturn
OpFunctionEnd

View File

@ -216,10 +216,6 @@
), ),
], ],
reject: [ reject: [
Emit((
start: 13,
end: 14,
)),
Atomic( Atomic(
pointer: 7, pointer: 7,
fun: Add, fun: Add,

View File

@ -241,10 +241,6 @@
), ),
], ],
reject: [ reject: [
Emit((
start: 14,
end: 15,
)),
Atomic( Atomic(
pointer: 8, pointer: 8,
fun: Add, fun: Add,