diff --git a/crates/spirv-std/macros/src/debug_printf.rs b/crates/spirv-std/macros/src/debug_printf.rs new file mode 100644 index 0000000000..7a7283aad0 --- /dev/null +++ b/crates/spirv-std/macros/src/debug_printf.rs @@ -0,0 +1,249 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use std::fmt::Write; + +pub struct DebugPrintfInput { + pub span: Span, + pub format_string: String, + pub variables: Vec, +} + +impl syn::parse::Parse for DebugPrintfInput { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { + let span = input.span(); + + if input.is_empty() { + return Ok(Self { + span, + format_string: Default::default(), + variables: Default::default(), + }); + } + + let format_string = input.parse::()?; + if !input.is_empty() { + input.parse::()?; + } + let variables = + syn::punctuated::Punctuated::::parse_terminated(input)?; + + Ok(Self { + span, + format_string: format_string.value(), + variables: variables.into_iter().collect(), + }) + } +} + +fn parsing_error(message: &str, span: Span) -> TokenStream { + syn::Error::new(span, message).to_compile_error().into() +} + +enum FormatType { + Scalar { + ty: proc_macro2::TokenStream, + }, + Vector { + ty: proc_macro2::TokenStream, + width: usize, + }, +} + +pub fn debug_printf_inner(input: DebugPrintfInput) -> TokenStream { + let DebugPrintfInput { + format_string, + variables, + span, + } = input; + + fn map_specifier_to_type( + specifier: char, + chars: &mut std::str::Chars<'_>, + ) -> Option { + let mut peekable = chars.peekable(); + + Some(match specifier { + 'd' | 'i' => quote::quote! { i32 }, + 'o' | 'x' | 'X' => quote::quote! { u32 }, + 'a' | 'A' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' => quote::quote! { f32 }, + 'u' => { + if matches!(peekable.peek(), Some('l')) { + chars.next(); + quote::quote! { u64 } + } else { + quote::quote! { u32 } + } + } + 'l' => { + if matches!(peekable.peek(), Some('u' | 'x')) { + chars.next(); + quote::quote! { u64 } + } else { + return None; + } + } + _ => return None, + }) + } + + let mut chars = format_string.chars(); + let mut format_arguments = Vec::new(); + + while let Some(mut ch) = chars.next() { + if ch == '%' { + ch = match chars.next() { + Some('%') => continue, + None => return parsing_error("Unterminated format specifier", span), + Some(ch) => ch, + }; + + let mut has_precision = false; + + while ch.is_ascii_digit() { + ch = match chars.next() { + Some(ch) => ch, + None => { + return parsing_error( + "Unterminated format specifier: missing type after precision", + span, + ); + } + }; + + has_precision = true; + } + + if has_precision && ch == '.' { + ch = match chars.next() { + Some(ch) => ch, + None => { + return parsing_error( + "Unterminated format specifier: missing type after decimal point", + span, + ); + } + }; + + while ch.is_ascii_digit() { + ch = match chars.next() { + Some(ch) => ch, + None => { + return parsing_error( + "Unterminated format specifier: missing type after fraction precision", + span, + ); + } + }; + } + } + + if ch == 'v' { + let width = match chars.next() { + Some('2') => 2, + Some('3') => 3, + Some('4') => 4, + Some(ch) => { + return parsing_error(&format!("Invalid width for vector: {ch}"), span); + } + None => return parsing_error("Missing vector dimensions specifier", span), + }; + + ch = match chars.next() { + Some(ch) => ch, + None => return parsing_error("Missing vector type specifier", span), + }; + + let ty = match map_specifier_to_type(ch, &mut chars) { + Some(ty) => ty, + _ => { + return parsing_error( + &format!("Unrecognised vector type specifier: '{ch}'"), + span, + ); + } + }; + + format_arguments.push(FormatType::Vector { ty, width }); + } else { + let ty = match map_specifier_to_type(ch, &mut chars) { + Some(ty) => ty, + _ => { + return parsing_error( + &format!("Unrecognised format specifier: '{ch}'"), + span, + ); + } + }; + + format_arguments.push(FormatType::Scalar { ty }); + } + } + } + + if format_arguments.len() != variables.len() { + return syn::Error::new( + span, + format!( + "{} % arguments were found, but {} variables were given", + format_arguments.len(), + variables.len() + ), + ) + .to_compile_error() + .into(); + } + + let mut variable_idents = String::new(); + let mut input_registers = Vec::new(); + let mut op_loads = Vec::new(); + + for (i, (variable, format_argument)) in variables.into_iter().zip(format_arguments).enumerate() + { + let ident = quote::format_ident!("_{}", i); + + let _ = write!(variable_idents, "%{ident} "); + + let assert_fn = match format_argument { + FormatType::Scalar { ty } => { + quote::quote! { spirv_std::debug_printf_assert_is_type::<#ty> } + } + FormatType::Vector { ty, width } => { + quote::quote! { spirv_std::debug_printf_assert_is_vector::<#ty, _, #width> } + } + }; + + input_registers.push(quote::quote! { + #ident = in(reg) &#assert_fn(#variable), + }); + + let op_load = format!("%{ident} = OpLoad _ {{{ident}}}"); + + op_loads.push(quote::quote! { + #op_load, + }); + } + + let input_registers = input_registers + .into_iter() + .collect::(); + let op_loads = op_loads.into_iter().collect::(); + // Escapes the '{' and '}' characters in the format string. + // Since the `asm!` macro expects '{' '}' to surround its arguments, we have to use '{{' and '}}' instead. + // The `asm!` macro will then later turn them back into '{' and '}'. + let format_string = format_string.replace('{', "{{").replace('}', "}}"); + + let op_string = format!("%string = OpString {format_string:?}"); + + let output = quote::quote! { + ::core::arch::asm!( + "%void = OpTypeVoid", + #op_string, + "%debug_printf = OpExtInstImport \"NonSemantic.DebugPrintf\"", + #op_loads + concat!("%result = OpExtInst %void %debug_printf 1 %string ", #variable_idents), + #input_registers + ) + }; + + output.into() +} diff --git a/crates/spirv-std/macros/src/lib.rs b/crates/spirv-std/macros/src/lib.rs index 504478b5ae..613e8c8706 100644 --- a/crates/spirv-std/macros/src/lib.rs +++ b/crates/spirv-std/macros/src/lib.rs @@ -71,16 +71,18 @@ // #![allow()] #![doc = include_str!("../README.md")] +mod debug_printf; mod image; +mod sample_param_permutations; +use crate::debug_printf::{DebugPrintfInput, debug_printf_inner}; use proc_macro::TokenStream; -use proc_macro2::{Delimiter, Group, Ident, Span, TokenTree}; - -use syn::{ImplItemFn, visit_mut::VisitMut}; - +use proc_macro2::{Delimiter, Group, Ident, TokenTree}; use quote::{ToTokens, TokenStreamExt, format_ident, quote}; use spirv_std_types::spirv_attr_version::spirv_attr_with_version; -use std::fmt::Write; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{GenericParam, Token}; /// A macro for creating SPIR-V `OpTypeImage` types. Always produces a /// `spirv_std::image::Image<...>` type. @@ -300,438 +302,6 @@ pub fn debug_printfln(input: TokenStream) -> TokenStream { debug_printf_inner(input) } -struct DebugPrintfInput { - span: Span, - format_string: String, - variables: Vec, -} - -impl syn::parse::Parse for DebugPrintfInput { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { - let span = input.span(); - - if input.is_empty() { - return Ok(Self { - span, - format_string: Default::default(), - variables: Default::default(), - }); - } - - let format_string = input.parse::()?; - if !input.is_empty() { - input.parse::()?; - } - let variables = - syn::punctuated::Punctuated::::parse_terminated(input)?; - - Ok(Self { - span, - format_string: format_string.value(), - variables: variables.into_iter().collect(), - }) - } -} - -fn parsing_error(message: &str, span: Span) -> TokenStream { - syn::Error::new(span, message).to_compile_error().into() -} - -enum FormatType { - Scalar { - ty: proc_macro2::TokenStream, - }, - Vector { - ty: proc_macro2::TokenStream, - width: usize, - }, -} - -fn debug_printf_inner(input: DebugPrintfInput) -> TokenStream { - let DebugPrintfInput { - format_string, - variables, - span, - } = input; - - fn map_specifier_to_type( - specifier: char, - chars: &mut std::str::Chars<'_>, - ) -> Option { - let mut peekable = chars.peekable(); - - Some(match specifier { - 'd' | 'i' => quote::quote! { i32 }, - 'o' | 'x' | 'X' => quote::quote! { u32 }, - 'a' | 'A' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' => quote::quote! { f32 }, - 'u' => { - if matches!(peekable.peek(), Some('l')) { - chars.next(); - quote::quote! { u64 } - } else { - quote::quote! { u32 } - } - } - 'l' => { - if matches!(peekable.peek(), Some('u' | 'x')) { - chars.next(); - quote::quote! { u64 } - } else { - return None; - } - } - _ => return None, - }) - } - - let mut chars = format_string.chars(); - let mut format_arguments = Vec::new(); - - while let Some(mut ch) = chars.next() { - if ch == '%' { - ch = match chars.next() { - Some('%') => continue, - None => return parsing_error("Unterminated format specifier", span), - Some(ch) => ch, - }; - - let mut has_precision = false; - - while ch.is_ascii_digit() { - ch = match chars.next() { - Some(ch) => ch, - None => { - return parsing_error( - "Unterminated format specifier: missing type after precision", - span, - ); - } - }; - - has_precision = true; - } - - if has_precision && ch == '.' { - ch = match chars.next() { - Some(ch) => ch, - None => { - return parsing_error( - "Unterminated format specifier: missing type after decimal point", - span, - ); - } - }; - - while ch.is_ascii_digit() { - ch = match chars.next() { - Some(ch) => ch, - None => { - return parsing_error( - "Unterminated format specifier: missing type after fraction precision", - span, - ); - } - }; - } - } - - if ch == 'v' { - let width = match chars.next() { - Some('2') => 2, - Some('3') => 3, - Some('4') => 4, - Some(ch) => { - return parsing_error(&format!("Invalid width for vector: {ch}"), span); - } - None => return parsing_error("Missing vector dimensions specifier", span), - }; - - ch = match chars.next() { - Some(ch) => ch, - None => return parsing_error("Missing vector type specifier", span), - }; - - let ty = match map_specifier_to_type(ch, &mut chars) { - Some(ty) => ty, - _ => { - return parsing_error( - &format!("Unrecognised vector type specifier: '{ch}'"), - span, - ); - } - }; - - format_arguments.push(FormatType::Vector { ty, width }); - } else { - let ty = match map_specifier_to_type(ch, &mut chars) { - Some(ty) => ty, - _ => { - return parsing_error( - &format!("Unrecognised format specifier: '{ch}'"), - span, - ); - } - }; - - format_arguments.push(FormatType::Scalar { ty }); - } - } - } - - if format_arguments.len() != variables.len() { - return syn::Error::new( - span, - format!( - "{} % arguments were found, but {} variables were given", - format_arguments.len(), - variables.len() - ), - ) - .to_compile_error() - .into(); - } - - let mut variable_idents = String::new(); - let mut input_registers = Vec::new(); - let mut op_loads = Vec::new(); - - for (i, (variable, format_argument)) in variables.into_iter().zip(format_arguments).enumerate() - { - let ident = quote::format_ident!("_{}", i); - - let _ = write!(variable_idents, "%{ident} "); - - let assert_fn = match format_argument { - FormatType::Scalar { ty } => { - quote::quote! { spirv_std::debug_printf_assert_is_type::<#ty> } - } - FormatType::Vector { ty, width } => { - quote::quote! { spirv_std::debug_printf_assert_is_vector::<#ty, _, #width> } - } - }; - - input_registers.push(quote::quote! { - #ident = in(reg) &#assert_fn(#variable), - }); - - let op_load = format!("%{ident} = OpLoad _ {{{ident}}}"); - - op_loads.push(quote::quote! { - #op_load, - }); - } - - let input_registers = input_registers - .into_iter() - .collect::(); - let op_loads = op_loads.into_iter().collect::(); - // Escapes the '{' and '}' characters in the format string. - // Since the `asm!` macro expects '{' '}' to surround its arguments, we have to use '{{' and '}}' instead. - // The `asm!` macro will then later turn them back into '{' and '}'. - let format_string = format_string.replace('{', "{{").replace('}', "}}"); - - let op_string = format!("%string = OpString {format_string:?}"); - - let output = quote::quote! { - ::core::arch::asm!( - "%void = OpTypeVoid", - #op_string, - "%debug_printf = OpExtInstImport \"NonSemantic.DebugPrintf\"", - #op_loads - concat!("%result = OpExtInst %void %debug_printf 1 %string ", #variable_idents), - #input_registers - ) - }; - - output.into() -} - -const SAMPLE_PARAM_COUNT: usize = 4; -const SAMPLE_PARAM_GENERICS: [&str; SAMPLE_PARAM_COUNT] = ["B", "L", "G", "S"]; -const SAMPLE_PARAM_TYPES: [&str; SAMPLE_PARAM_COUNT] = ["B", "L", "(G,G)", "S"]; -const SAMPLE_PARAM_OPERANDS: [&str; SAMPLE_PARAM_COUNT] = ["Bias", "Lod", "Grad", "Sample"]; -const SAMPLE_PARAM_NAMES: [&str; SAMPLE_PARAM_COUNT] = ["bias", "lod", "grad", "sample_index"]; -const SAMPLE_PARAM_GRAD_INDEX: usize = 2; // Grad requires some special handling because it uses 2 arguments -const SAMPLE_PARAM_EXPLICIT_LOD_MASK: usize = 0b0110; // which params require the use of ExplicitLod rather than ImplicitLod - -fn is_grad(i: usize) -> bool { - i == SAMPLE_PARAM_GRAD_INDEX -} - -struct SampleImplRewriter(usize, syn::Type); - -impl SampleImplRewriter { - pub fn rewrite(mask: usize, f: &syn::ItemImpl) -> syn::ItemImpl { - let mut new_impl = f.clone(); - let mut ty_str = String::from("SampleParams<"); - - // based on the mask, form a `SampleParams` type string and add the generic parameters to the `impl<>` generics - // example type string: `"SampleParams, NoneTy, NoneTy>"` - for i in 0..SAMPLE_PARAM_COUNT { - if mask & (1 << i) != 0 { - new_impl.generics.params.push(syn::GenericParam::Type( - syn::Ident::new(SAMPLE_PARAM_GENERICS[i], Span::call_site()).into(), - )); - ty_str.push_str("SomeTy<"); - ty_str.push_str(SAMPLE_PARAM_TYPES[i]); - ty_str.push('>'); - } else { - ty_str.push_str("NoneTy"); - } - ty_str.push(','); - } - ty_str.push('>'); - let ty: syn::Type = syn::parse(ty_str.parse().unwrap()).unwrap(); - - // use the type to insert it into the generic argument of the trait we're implementing - // e.g., `ImageWithMethods` becomes `ImageWithMethods, NoneTy, NoneTy>>` - if let Some(t) = &mut new_impl.trait_ - && let syn::PathArguments::AngleBracketed(a) = - &mut t.1.segments.last_mut().unwrap().arguments - && let Some(syn::GenericArgument::Type(t)) = a.args.last_mut() - { - *t = ty.clone(); - } - - // rewrite the implemented functions - SampleImplRewriter(mask, ty).visit_item_impl_mut(&mut new_impl); - new_impl - } - - // generates an operands string for use in the assembly, e.g. "Bias %bias Lod %lod", based on the mask - #[allow(clippy::needless_range_loop)] - fn get_operands(&self) -> String { - let mut op = String::new(); - for i in 0..SAMPLE_PARAM_COUNT { - if self.0 & (1 << i) != 0 { - if is_grad(i) { - op.push_str("Grad %grad_x %grad_y "); - } else { - op.push_str(SAMPLE_PARAM_OPERANDS[i]); - op.push_str(" %"); - op.push_str(SAMPLE_PARAM_NAMES[i]); - op.push(' '); - } - } - } - op - } - - // generates list of assembly loads for the data, e.g. "%bias = OpLoad _ {bias}", etc. - #[allow(clippy::needless_range_loop)] - fn add_loads(&self, t: &mut Vec) { - for i in 0..SAMPLE_PARAM_COUNT { - if self.0 & (1 << i) != 0 { - if is_grad(i) { - t.push(TokenTree::Literal(proc_macro2::Literal::string( - "%grad_x = OpLoad _ {grad_x}", - ))); - t.push(TokenTree::Punct(proc_macro2::Punct::new( - ',', - proc_macro2::Spacing::Alone, - ))); - t.push(TokenTree::Literal(proc_macro2::Literal::string( - "%grad_y = OpLoad _ {grad_y}", - ))); - t.push(TokenTree::Punct(proc_macro2::Punct::new( - ',', - proc_macro2::Spacing::Alone, - ))); - } else { - let s = format!("%{0} = OpLoad _ {{{0}}}", SAMPLE_PARAM_NAMES[i]); - t.push(TokenTree::Literal(proc_macro2::Literal::string(s.as_str()))); - t.push(TokenTree::Punct(proc_macro2::Punct::new( - ',', - proc_macro2::Spacing::Alone, - ))); - } - } - } - } - - // generates list of register specifications, e.g. `bias = in(reg) ¶ms.bias.0, ...` as separate tokens - #[allow(clippy::needless_range_loop)] - fn add_regs(&self, t: &mut Vec) { - for i in 0..SAMPLE_PARAM_COUNT { - if self.0 & (1 << i) != 0 { - // HACK(eddyb) the extra `{...}` force the pointers to be to - // fresh variables holding value copies, instead of the originals, - // allowing `OpLoad _` inference to pick the appropriate type. - let s = if is_grad(i) { - "grad_x=in(reg) &{params.grad.0.0},grad_y=in(reg) &{params.grad.0.1}," - .to_string() - } else { - format!("{0} = in(reg) &{{params.{0}.0}},", SAMPLE_PARAM_NAMES[i]) - }; - let ts: proc_macro2::TokenStream = s.parse().unwrap(); - t.extend(ts); - } - } - } -} - -impl VisitMut for SampleImplRewriter { - fn visit_impl_item_fn_mut(&mut self, item: &mut ImplItemFn) { - // rewrite the last parameter of this method to be of type `SampleParams<...>` we generated earlier - if let Some(syn::FnArg::Typed(p)) = item.sig.inputs.last_mut() { - *p.ty.as_mut() = self.1.clone(); - } - syn::visit_mut::visit_impl_item_fn_mut(self, item); - } - - fn visit_macro_mut(&mut self, m: &mut syn::Macro) { - if m.path.is_ident("asm") { - // this is where the asm! block is manipulated - let t = m.tokens.clone(); - let mut new_t = Vec::new(); - let mut altered = false; - - for tt in t { - match tt { - TokenTree::Literal(l) => { - if let Ok(l) = syn::parse::(l.to_token_stream().into()) { - // found a string literal - let s = l.value(); - if s.contains("$PARAMS") { - altered = true; - // add load instructions before the sampling instruction - self.add_loads(&mut new_t); - // and insert image operands - let s = s.replace("$PARAMS", &self.get_operands()); - let lod_type = if self.0 & SAMPLE_PARAM_EXPLICIT_LOD_MASK != 0 { - "ExplicitLod" - } else { - "ImplicitLod " - }; - let s = s.replace("$LOD", lod_type); - - new_t.push(TokenTree::Literal(proc_macro2::Literal::string( - s.as_str(), - ))); - } else { - new_t.push(TokenTree::Literal(l.token())); - } - } else { - new_t.push(TokenTree::Literal(l)); - } - } - _ => { - new_t.push(tt); - } - } - } - - if altered { - // finally, add register specs - self.add_regs(&mut new_t); - } - - // replace all tokens within the asm! block with our new list - m.tokens = new_t.into_iter().collect(); - } - } -} - /// Generates permutations of an `ImageWithMethods` implementation containing sampling functions /// that have asm instruction ending with a placeholder `$PARAMS` operand. The last parameter /// of each function must be named `params`, its type will be rewritten. Relevant generic @@ -740,14 +310,56 @@ impl VisitMut for SampleImplRewriter { #[proc_macro_attribute] #[doc(hidden)] pub fn gen_sample_param_permutations(_attr: TokenStream, item: TokenStream) -> TokenStream { - let item_impl = syn::parse_macro_input!(item as syn::ItemImpl); - let mut fns = Vec::new(); + sample_param_permutations::gen_sample_param_permutations(item) +} - for m in 1..(1 << SAMPLE_PARAM_COUNT) { - fns.push(SampleImplRewriter::rewrite(m, &item_impl)); - } +#[proc_macro_attribute] +pub fn spirv_vector(attr: TokenStream, item: TokenStream) -> TokenStream { + spirv_vector_impl(attr.into(), item.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +fn spirv_vector_impl( + attr: proc_macro2::TokenStream, + item: proc_macro2::TokenStream, +) -> syn::Result { + // Whenever we'll properly resolve the crate symbol, replace this. + let spirv_std = quote!(spirv_std); + + // Defer all validation to our codegen backend. Rather than erroring here, emit garbage. + let item = syn::parse2::(item)?; + let ident = &item.ident; + let gens = &item.generics.params; + let gen_refs = &item + .generics + .params + .iter() + .map(|p| match p { + GenericParam::Lifetime(p) => p.lifetime.to_token_stream(), + GenericParam::Type(p) => p.ident.to_token_stream(), + GenericParam::Const(p) => p.ident.to_token_stream(), + }) + .collect::>(); + let where_clause = &item.generics.where_clause; + let element = item + .fields + .iter() + .next() + .ok_or_else(|| syn::Error::new(item.span(), "Vector ZST not allowed"))? + .ty + .to_token_stream(); + let count = item.fields.len(); + + Ok(quote! { + #[cfg_attr(target_arch = "spirv", rust_gpu::vector::v1(#attr))] + #item + + unsafe impl<#gens> #spirv_std::vector::VectorOrScalar for #ident<#gen_refs> #where_clause { + type Scalar = #element; + const DIM: core::num::NonZeroUsize = core::num::NonZeroUsize::new(#count).unwrap(); + } - // uncomment to output generated tokenstream to stdout - //println!("{}", quote! { #(#fns)* }.to_string()); - quote! { #(#fns)* }.into() + unsafe impl<#gens> #spirv_std::vector::Vector<#element, #count> for #ident<#gen_refs> #where_clause {} + }) } diff --git a/crates/spirv-std/macros/src/sample_param_permutations.rs b/crates/spirv-std/macros/src/sample_param_permutations.rs new file mode 100644 index 0000000000..40711f940e --- /dev/null +++ b/crates/spirv-std/macros/src/sample_param_permutations.rs @@ -0,0 +1,204 @@ +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenTree}; +use quote::{ToTokens, quote}; +use syn::ImplItemFn; +use syn::visit_mut::VisitMut; + +const SAMPLE_PARAM_COUNT: usize = 4; +const SAMPLE_PARAM_GENERICS: [&str; SAMPLE_PARAM_COUNT] = ["B", "L", "G", "S"]; +const SAMPLE_PARAM_TYPES: [&str; SAMPLE_PARAM_COUNT] = ["B", "L", "(G,G)", "S"]; +const SAMPLE_PARAM_OPERANDS: [&str; SAMPLE_PARAM_COUNT] = ["Bias", "Lod", "Grad", "Sample"]; +const SAMPLE_PARAM_NAMES: [&str; SAMPLE_PARAM_COUNT] = ["bias", "lod", "grad", "sample_index"]; +const SAMPLE_PARAM_GRAD_INDEX: usize = 2; // Grad requires some special handling because it uses 2 arguments +const SAMPLE_PARAM_EXPLICIT_LOD_MASK: usize = 0b0110; // which params require the use of ExplicitLod rather than ImplicitLod + +fn is_grad(i: usize) -> bool { + i == SAMPLE_PARAM_GRAD_INDEX +} + +struct SampleImplRewriter(usize, syn::Type); + +impl SampleImplRewriter { + pub fn rewrite(mask: usize, f: &syn::ItemImpl) -> syn::ItemImpl { + let mut new_impl = f.clone(); + let mut ty_str = String::from("SampleParams<"); + + // based on the mask, form a `SampleParams` type string and add the generic parameters to the `impl<>` generics + // example type string: `"SampleParams, NoneTy, NoneTy>"` + for i in 0..SAMPLE_PARAM_COUNT { + if mask & (1 << i) != 0 { + new_impl.generics.params.push(syn::GenericParam::Type( + syn::Ident::new(SAMPLE_PARAM_GENERICS[i], Span::call_site()).into(), + )); + ty_str.push_str("SomeTy<"); + ty_str.push_str(SAMPLE_PARAM_TYPES[i]); + ty_str.push('>'); + } else { + ty_str.push_str("NoneTy"); + } + ty_str.push(','); + } + ty_str.push('>'); + let ty: syn::Type = syn::parse(ty_str.parse().unwrap()).unwrap(); + + // use the type to insert it into the generic argument of the trait we're implementing + // e.g., `ImageWithMethods` becomes `ImageWithMethods, NoneTy, NoneTy>>` + if let Some(t) = &mut new_impl.trait_ + && let syn::PathArguments::AngleBracketed(a) = + &mut t.1.segments.last_mut().unwrap().arguments + && let Some(syn::GenericArgument::Type(t)) = a.args.last_mut() + { + *t = ty.clone(); + } + + // rewrite the implemented functions + SampleImplRewriter(mask, ty).visit_item_impl_mut(&mut new_impl); + new_impl + } + + // generates an operands string for use in the assembly, e.g. "Bias %bias Lod %lod", based on the mask + #[allow(clippy::needless_range_loop)] + fn get_operands(&self) -> String { + let mut op = String::new(); + for i in 0..SAMPLE_PARAM_COUNT { + if self.0 & (1 << i) != 0 { + if is_grad(i) { + op.push_str("Grad %grad_x %grad_y "); + } else { + op.push_str(SAMPLE_PARAM_OPERANDS[i]); + op.push_str(" %"); + op.push_str(SAMPLE_PARAM_NAMES[i]); + op.push(' '); + } + } + } + op + } + + // generates list of assembly loads for the data, e.g. "%bias = OpLoad _ {bias}", etc. + #[allow(clippy::needless_range_loop)] + fn add_loads(&self, t: &mut Vec) { + for i in 0..SAMPLE_PARAM_COUNT { + if self.0 & (1 << i) != 0 { + if is_grad(i) { + t.push(TokenTree::Literal(proc_macro2::Literal::string( + "%grad_x = OpLoad _ {grad_x}", + ))); + t.push(TokenTree::Punct(proc_macro2::Punct::new( + ',', + proc_macro2::Spacing::Alone, + ))); + t.push(TokenTree::Literal(proc_macro2::Literal::string( + "%grad_y = OpLoad _ {grad_y}", + ))); + t.push(TokenTree::Punct(proc_macro2::Punct::new( + ',', + proc_macro2::Spacing::Alone, + ))); + } else { + let s = format!("%{0} = OpLoad _ {{{0}}}", SAMPLE_PARAM_NAMES[i]); + t.push(TokenTree::Literal(proc_macro2::Literal::string(s.as_str()))); + t.push(TokenTree::Punct(proc_macro2::Punct::new( + ',', + proc_macro2::Spacing::Alone, + ))); + } + } + } + } + + // generates list of register specifications, e.g. `bias = in(reg) ¶ms.bias.0, ...` as separate tokens + #[allow(clippy::needless_range_loop)] + fn add_regs(&self, t: &mut Vec) { + for i in 0..SAMPLE_PARAM_COUNT { + if self.0 & (1 << i) != 0 { + // HACK(eddyb) the extra `{...}` force the pointers to be to + // fresh variables holding value copies, instead of the originals, + // allowing `OpLoad _` inference to pick the appropriate type. + let s = if is_grad(i) { + "grad_x=in(reg) &{params.grad.0.0},grad_y=in(reg) &{params.grad.0.1}," + .to_string() + } else { + format!("{0} = in(reg) &{{params.{0}.0}},", SAMPLE_PARAM_NAMES[i]) + }; + let ts: proc_macro2::TokenStream = s.parse().unwrap(); + t.extend(ts); + } + } + } +} + +impl VisitMut for SampleImplRewriter { + fn visit_impl_item_fn_mut(&mut self, item: &mut ImplItemFn) { + // rewrite the last parameter of this method to be of type `SampleParams<...>` we generated earlier + if let Some(syn::FnArg::Typed(p)) = item.sig.inputs.last_mut() { + *p.ty.as_mut() = self.1.clone(); + } + syn::visit_mut::visit_impl_item_fn_mut(self, item); + } + + fn visit_macro_mut(&mut self, m: &mut syn::Macro) { + if m.path.is_ident("asm") { + // this is where the asm! block is manipulated + let t = m.tokens.clone(); + let mut new_t = Vec::new(); + let mut altered = false; + + for tt in t { + match tt { + TokenTree::Literal(l) => { + if let Ok(l) = syn::parse::(l.to_token_stream().into()) { + // found a string literal + let s = l.value(); + if s.contains("$PARAMS") { + altered = true; + // add load instructions before the sampling instruction + self.add_loads(&mut new_t); + // and insert image operands + let s = s.replace("$PARAMS", &self.get_operands()); + let lod_type = if self.0 & SAMPLE_PARAM_EXPLICIT_LOD_MASK != 0 { + "ExplicitLod" + } else { + "ImplicitLod " + }; + let s = s.replace("$LOD", lod_type); + + new_t.push(TokenTree::Literal(proc_macro2::Literal::string( + s.as_str(), + ))); + } else { + new_t.push(TokenTree::Literal(l.token())); + } + } else { + new_t.push(TokenTree::Literal(l)); + } + } + _ => { + new_t.push(tt); + } + } + } + + if altered { + // finally, add register specs + self.add_regs(&mut new_t); + } + + // replace all tokens within the asm! block with our new list + m.tokens = new_t.into_iter().collect(); + } + } +} + +pub fn gen_sample_param_permutations(item: TokenStream) -> TokenStream { + let item_impl = syn::parse_macro_input!(item as syn::ItemImpl); + let mut fns = Vec::new(); + + for m in 1..(1 << SAMPLE_PARAM_COUNT) { + fns.push(SampleImplRewriter::rewrite(m, &item_impl)); + } + + // uncomment to output generated tokenstream to stdout + //println!("{}", quote! { #(#fns)* }.to_string()); + quote! { #(#fns)* }.into() +} diff --git a/crates/spirv-std/src/lib.rs b/crates/spirv-std/src/lib.rs index 14e887b70f..cbb97442af 100644 --- a/crates/spirv-std/src/lib.rs +++ b/crates/spirv-std/src/lib.rs @@ -87,7 +87,7 @@ /// Public re-export of the `spirv-std-macros` crate. #[macro_use] pub extern crate spirv_std_macros as macros; -pub use macros::spirv; +pub use macros::{spirv, spirv_vector}; pub mod arch; pub mod byte_addressable_buffer; diff --git a/crates/spirv-std/src/scalar.rs b/crates/spirv-std/src/scalar.rs index 520348be3a..6a30add998 100644 --- a/crates/spirv-std/src/scalar.rs +++ b/crates/spirv-std/src/scalar.rs @@ -1,6 +1,6 @@ //! Traits related to scalars. -use crate::vector::{VectorOrScalar, create_dim}; +use crate::vector::VectorOrScalar; use core::num::NonZeroUsize; /// Abstract trait representing a SPIR-V scalar type. @@ -21,7 +21,7 @@ macro_rules! impl_scalar { $( unsafe impl VectorOrScalar for $ty { type Scalar = Self; - const DIM: NonZeroUsize = create_dim(1); + const DIM: NonZeroUsize = NonZeroUsize::new(1).unwrap(); } unsafe impl Scalar for $ty {} )+ diff --git a/crates/spirv-std/src/vector.rs b/crates/spirv-std/src/vector.rs index f3ba4a42bb..7b2cc569fd 100644 --- a/crates/spirv-std/src/vector.rs +++ b/crates/spirv-std/src/vector.rs @@ -16,19 +16,12 @@ pub unsafe trait VectorOrScalar: Copy + Default + Send + Sync + 'static { const DIM: NonZeroUsize; } -/// replace with `NonZeroUsize::new(n).unwrap()` once `unwrap()` is const stabilized -pub(crate) const fn create_dim(n: usize) -> NonZeroUsize { - match NonZeroUsize::new(n) { - None => panic!("dim must not be 0"), - Some(n) => n, - } -} - /// Abstract trait representing a SPIR-V vector type. /// -/// To implement this trait, your struct must be marked with: +/// To derive this trait, mark your struct with: /// ```no_run -/// #[cfg_attr(target_arch = "spirv", rust_gpu::vector::v1)] +/// #[spirv_std::spirv_vector] +/// # #[derive(Copy, Clone, Default)] /// # struct Bla(f32, f32); /// ``` /// @@ -51,8 +44,8 @@ pub(crate) const fn create_dim(n: usize) -> NonZeroUsize { /// /// # Example /// ```no_run +/// #[spirv_std::spirv_vector] /// #[derive(Copy, Clone, Default)] -/// #[cfg_attr(target_arch = "spirv", rust_gpu::vector::v1)] /// struct MyColor { /// r: f32, /// b: f32, @@ -63,7 +56,8 @@ pub(crate) const fn create_dim(n: usize) -> NonZeroUsize { /// /// # Safety /// Must only be implemented on types that the spirv codegen emits as valid `OpTypeVector`. This includes all structs -/// marked with `#[rust_gpu::vector::v1]`, like [`glam`]'s non-SIMD "scalar" vector types. +/// marked with `#[rust_gpu::vector::v1]`, which `#[spirv_std::spirv_vector]` expands into or [`glam`]'s non-SIMD +/// "scalar" vector types use directly. pub unsafe trait Vector: VectorOrScalar {} macro_rules! impl_vector { @@ -71,7 +65,7 @@ macro_rules! impl_vector { $($( unsafe impl VectorOrScalar for $vec { type Scalar = $scalar; - const DIM: NonZeroUsize = create_dim($dim); + const DIM: NonZeroUsize = NonZeroUsize::new($dim).unwrap(); } unsafe impl Vector<$scalar, $dim> for $vec {} )+)+ diff --git a/tests/compiletests/ui/glam/invalid_vector_type_macro.rs b/tests/compiletests/ui/glam/invalid_vector_type_macro.rs new file mode 100644 index 0000000000..6492f90932 --- /dev/null +++ b/tests/compiletests/ui/glam/invalid_vector_type_macro.rs @@ -0,0 +1,33 @@ +// build-fail + +use core::num::NonZeroU32; +use spirv_std::glam::Vec2; +use spirv_std::spirv; + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct FewerFields { + _v: f32, +} + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct TooManyFields { + _x: f32, + _y: f32, + _z: f32, + _w: f32, + _v: f32, +} + +// wrong member types fails too early + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct DifferentTypes { + _x: f32, + _y: u32, +} + +#[spirv(fragment)] +pub fn entry(_: FewerFields, _: TooManyFields, #[spirv(flat)] _: DifferentTypes) {} diff --git a/tests/compiletests/ui/glam/invalid_vector_type_macro.stderr b/tests/compiletests/ui/glam/invalid_vector_type_macro.stderr new file mode 100644 index 0000000000..9e68aeec0e --- /dev/null +++ b/tests/compiletests/ui/glam/invalid_vector_type_macro.stderr @@ -0,0 +1,20 @@ +error: `#[spirv(vector)]` must have 2, 3 or 4 members + --> $DIR/invalid_vector_type_macro.rs:9:1 + | +9 | pub struct FewerFields { + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[spirv(vector)]` must have 2, 3 or 4 members + --> $DIR/invalid_vector_type_macro.rs:15:1 + | +15 | pub struct TooManyFields { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[spirv(vector)]` member types must all be the same + --> $DIR/invalid_vector_type_macro.rs:27:1 + | +27 | pub struct DifferentTypes { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/compiletests/ui/glam/invalid_vector_type_macro2.rs b/tests/compiletests/ui/glam/invalid_vector_type_macro2.rs new file mode 100644 index 0000000000..8a122f040d --- /dev/null +++ b/tests/compiletests/ui/glam/invalid_vector_type_macro2.rs @@ -0,0 +1,42 @@ +// build-fail +// normalize-stderr-test "\S*/crates/spirv-std/src/" -> "$$SPIRV_STD_SRC/" + +use core::num::NonZeroU32; +use spirv_std::glam::Vec2; +use spirv_std::spirv; + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct ZstVector; + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct NotVectorField { + _x: Vec2, + _y: Vec2, +} + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone)] +pub struct NotVectorField2 { + _x: NonZeroU32, + _y: NonZeroU32, +} + +impl Default for NotVectorField2 { + fn default() -> Self { + Self { + _x: NonZeroU32::new(1).unwrap(), + _y: NonZeroU32::new(1).unwrap(), + } + } +} + +#[spirv(fragment)] +pub fn entry( + // workaround to ZST loading + #[spirv(storage_class, descriptor_set = 0, binding = 0)] _: &(ZstVector, i32), + _: NotVectorField, + #[spirv(flat)] _: NotVectorField2, +) { +} diff --git a/tests/compiletests/ui/glam/invalid_vector_type_macro2.stderr b/tests/compiletests/ui/glam/invalid_vector_type_macro2.stderr new file mode 100644 index 0000000000..15c4111e64 --- /dev/null +++ b/tests/compiletests/ui/glam/invalid_vector_type_macro2.stderr @@ -0,0 +1,113 @@ +error: Vector ZST not allowed + --> $DIR/invalid_vector_type_macro2.rs:9:1 + | +9 | / #[derive(Copy, Clone, Default)] +10 | | pub struct ZstVector; + | |_____________________^ + +error[E0412]: cannot find type `ZstVector` in this scope + --> $DIR/invalid_vector_type_macro2.rs:38:67 + | +38 | #[spirv(storage_class, descriptor_set = 0, binding = 0)] _: &(ZstVector, i32), + | ^^^^^^^^^ not found in this scope + +error: unknown argument to spirv attribute + --> $DIR/invalid_vector_type_macro2.rs:38:13 + | +38 | #[spirv(storage_class, descriptor_set = 0, binding = 0)] _: &(ZstVector, i32), + | ^^^^^^^^^^^^^ + +error[E0277]: the trait bound `Vec2: Scalar` is not satisfied + --> $DIR/invalid_vector_type_macro2.rs:15:9 + | +15 | _x: Vec2, + | ^^^^ the trait `Scalar` is not implemented for `Vec2` + | + = help: the following other types implement trait `Scalar`: + bool + f32 + f64 + i16 + i32 + i64 + i8 + u16 + and 3 others +note: required by a bound in `spirv_std::vector::VectorOrScalar::Scalar` + --> $SPIRV_STD_SRC/vector.rs:13:18 + | +13 | type Scalar: Scalar; + | ^^^^^^ required by this bound in `VectorOrScalar::Scalar` + +error[E0277]: the trait bound `Vec2: Scalar` is not satisfied + --> $DIR/invalid_vector_type_macro2.rs:12:1 + | +12 | #[spirv_std::spirv_vector] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Scalar` is not implemented for `Vec2` + | + = help: the following other types implement trait `Scalar`: + bool + f32 + f64 + i16 + i32 + i64 + i8 + u16 + and 3 others +note: required by a bound in `Vector` + --> $SPIRV_STD_SRC/vector.rs:61:28 + | +61 | pub unsafe trait Vector: VectorOrScalar {} + | ^^^^^^ required by this bound in `Vector` + = note: this error originates in the attribute macro `spirv_std::spirv_vector` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `NonZero: Scalar` is not satisfied + --> $DIR/invalid_vector_type_macro2.rs:22:9 + | +22 | _x: NonZeroU32, + | ^^^^^^^^^^ the trait `Scalar` is not implemented for `NonZero` + | + = help: the following other types implement trait `Scalar`: + bool + f32 + f64 + i16 + i32 + i64 + i8 + u16 + and 3 others +note: required by a bound in `spirv_std::vector::VectorOrScalar::Scalar` + --> $SPIRV_STD_SRC/vector.rs:13:18 + | +13 | type Scalar: Scalar; + | ^^^^^^ required by this bound in `VectorOrScalar::Scalar` + +error[E0277]: the trait bound `NonZero: Scalar` is not satisfied + --> $DIR/invalid_vector_type_macro2.rs:19:1 + | +19 | #[spirv_std::spirv_vector] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Scalar` is not implemented for `NonZero` + | + = help: the following other types implement trait `Scalar`: + bool + f32 + f64 + i16 + i32 + i64 + i8 + u16 + and 3 others +note: required by a bound in `Vector` + --> $SPIRV_STD_SRC/vector.rs:61:28 + | +61 | pub unsafe trait Vector: VectorOrScalar {} + | ^^^^^^ required by this bound in `Vector` + = note: this error originates in the attribute macro `spirv_std::spirv_vector` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 7 previous errors + +Some errors have detailed explanations: E0277, E0412. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/compiletests/ui/glam/spirv_vector_macro.rs b/tests/compiletests/ui/glam/spirv_vector_macro.rs new file mode 100644 index 0000000000..90a118b3aa --- /dev/null +++ b/tests/compiletests/ui/glam/spirv_vector_macro.rs @@ -0,0 +1,33 @@ +// build-pass +// only-vulkan1.2 +// compile-flags: -C target-feature=+GroupNonUniform,+GroupNonUniformShuffleRelative,+ext:SPV_KHR_vulkan_memory_model +// compile-flags: -C llvm-args=--disassemble +// normalize-stderr-test "OpSource .*\n" -> "" +// normalize-stderr-test "OpLine .*\n" -> "" +// normalize-stderr-test "%\d+ = OpString .*\n" -> "" + +use spirv_std::arch::subgroup_shuffle_up; +use spirv_std::glam::Vec3; +use spirv_std::spirv; + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct MyColor { + pub r: f32, + pub g: f32, + pub b: f32, +} + +#[spirv(compute(threads(32)))] +pub fn main( + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] input: &Vec3, + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut MyColor, +) { + let color = MyColor { + r: input.x, + g: input.y, + b: input.z, + }; + // any function that accepts a `VectorOrScalar` would do + *output = subgroup_shuffle_up(color, 5); +} diff --git a/tests/compiletests/ui/glam/spirv_vector_macro.stderr b/tests/compiletests/ui/glam/spirv_vector_macro.stderr new file mode 100644 index 0000000000..7bd692745a --- /dev/null +++ b/tests/compiletests/ui/glam/spirv_vector_macro.stderr @@ -0,0 +1,53 @@ +; SPIR-V +; Version: 1.5 +; Generator: rspirv +; Bound: 31 +OpCapability Shader +OpCapability GroupNonUniform +OpCapability GroupNonUniformShuffleRelative +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %1 "main" %2 %3 +OpExecutionMode %1 LocalSize 32 1 1 +OpName %2 "input" +OpName %3 "output" +OpDecorate %6 Block +OpMemberDecorate %6 0 Offset 0 +OpDecorate %2 NonWritable +OpDecorate %2 Binding 0 +OpDecorate %2 DescriptorSet 0 +OpDecorate %3 Binding 1 +OpDecorate %3 DescriptorSet 0 +%7 = OpTypeFloat 32 +%8 = OpTypeVector %7 3 +%6 = OpTypeStruct %8 +%9 = OpTypePointer StorageBuffer %6 +%10 = OpTypeVoid +%11 = OpTypeFunction %10 +%12 = OpTypePointer StorageBuffer %8 +%2 = OpVariable %9 StorageBuffer +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 0 +%3 = OpVariable %9 StorageBuffer +%15 = OpTypePointer StorageBuffer %7 +%16 = OpConstant %13 1 +%17 = OpConstant %13 2 +%18 = OpConstant %13 3 +%19 = OpConstant %13 5 +%1 = OpFunction %10 None %11 +%20 = OpLabel +%21 = OpInBoundsAccessChain %12 %2 %14 +%22 = OpInBoundsAccessChain %12 %3 %14 +%23 = OpInBoundsAccessChain %15 %21 %14 +%24 = OpLoad %7 %23 +%25 = OpInBoundsAccessChain %15 %21 %16 +%26 = OpLoad %7 %25 +%27 = OpInBoundsAccessChain %15 %21 %17 +%28 = OpLoad %7 %27 +%29 = OpCompositeConstruct %8 %24 %26 %28 +%30 = OpGroupNonUniformShuffleUp %8 %18 %29 %19 +OpStore %22 %30 +OpNoLine +OpReturn +OpFunctionEnd diff --git a/tests/compiletests/ui/glam/spirv_vector_macro_generic.rs b/tests/compiletests/ui/glam/spirv_vector_macro_generic.rs new file mode 100644 index 0000000000..1ffad68d17 --- /dev/null +++ b/tests/compiletests/ui/glam/spirv_vector_macro_generic.rs @@ -0,0 +1,34 @@ +// build-pass +// only-vulkan1.2 +// compile-flags: -C target-feature=+GroupNonUniform,+GroupNonUniformShuffleRelative,+ext:SPV_KHR_vulkan_memory_model +// compile-flags: -C llvm-args=--disassemble +// normalize-stderr-test "OpSource .*\n" -> "" +// normalize-stderr-test "OpLine .*\n" -> "" +// normalize-stderr-test "%\d+ = OpString .*\n" -> "" + +use spirv_std::arch::subgroup_shuffle_up; +use spirv_std::glam::Vec3; +use spirv_std::scalar::Scalar; +use spirv_std::spirv; + +#[spirv_std::spirv_vector] +#[derive(Copy, Clone, Default)] +pub struct Vec { + pub x: T, + pub y: T, + pub z: T, +} + +#[spirv(compute(threads(32)))] +pub fn main( + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] input: &Vec, + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut Vec, +) { + let vec = Vec { + x: input.x as f32, + y: input.y as f32, + z: input.z as f32, + }; + // any function that accepts a `VectorOrScalar` would do + *output = subgroup_shuffle_up(vec, 5); +} diff --git a/tests/compiletests/ui/glam/spirv_vector_macro_generic.stderr b/tests/compiletests/ui/glam/spirv_vector_macro_generic.stderr new file mode 100644 index 0000000000..7f3ed8d9f1 --- /dev/null +++ b/tests/compiletests/ui/glam/spirv_vector_macro_generic.stderr @@ -0,0 +1,63 @@ +; SPIR-V +; Version: 1.5 +; Generator: rspirv +; Bound: 39 +OpCapability Shader +OpCapability GroupNonUniform +OpCapability GroupNonUniformShuffleRelative +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %1 "main" %2 %3 +OpExecutionMode %1 LocalSize 32 1 1 +OpName %2 "input" +OpName %3 "output" +OpDecorate %6 Block +OpMemberDecorate %6 0 Offset 0 +OpDecorate %7 Block +OpMemberDecorate %7 0 Offset 0 +OpDecorate %2 NonWritable +OpDecorate %2 Binding 0 +OpDecorate %2 DescriptorSet 0 +OpDecorate %3 Binding 1 +OpDecorate %3 DescriptorSet 0 +%8 = OpTypeInt 32 1 +%9 = OpTypeVector %8 3 +%6 = OpTypeStruct %9 +%10 = OpTypePointer StorageBuffer %6 +%11 = OpTypeFloat 32 +%12 = OpTypeVector %11 3 +%7 = OpTypeStruct %12 +%13 = OpTypePointer StorageBuffer %7 +%14 = OpTypeVoid +%15 = OpTypeFunction %14 +%16 = OpTypePointer StorageBuffer %9 +%2 = OpVariable %10 StorageBuffer +%17 = OpTypeInt 32 0 +%18 = OpConstant %17 0 +%19 = OpTypePointer StorageBuffer %12 +%3 = OpVariable %13 StorageBuffer +%20 = OpTypePointer StorageBuffer %8 +%21 = OpConstant %17 1 +%22 = OpConstant %17 2 +%23 = OpConstant %17 3 +%24 = OpConstant %17 5 +%1 = OpFunction %14 None %15 +%25 = OpLabel +%26 = OpInBoundsAccessChain %16 %2 %18 +%27 = OpInBoundsAccessChain %19 %3 %18 +%28 = OpInBoundsAccessChain %20 %26 %18 +%29 = OpLoad %8 %28 +%30 = OpConvertSToF %11 %29 +%31 = OpInBoundsAccessChain %20 %26 %21 +%32 = OpLoad %8 %31 +%33 = OpConvertSToF %11 %32 +%34 = OpInBoundsAccessChain %20 %26 %22 +%35 = OpLoad %8 %34 +%36 = OpConvertSToF %11 %35 +%37 = OpCompositeConstruct %12 %30 %33 %36 +%38 = OpGroupNonUniformShuffleUp %12 %23 %37 %24 +OpStore %27 %38 +OpNoLine +OpReturn +OpFunctionEnd