diff --git a/crates/spirv-std/macros/src/lib.rs b/crates/spirv-std/macros/src/lib.rs index cf5e21d639..0efba5dfe8 100644 --- a/crates/spirv-std/macros/src/lib.rs +++ b/crates/spirv-std/macros/src/lib.rs @@ -350,3 +350,266 @@ fn path_from_ident(ident: Ident) -> syn::Type { path: syn::Path::from(ident), }) } + +/// Print a formatted string with a newline using the debug printf extension. +/// +/// Examples: +/// +/// ```rust,ignore +/// debug_printfln!("uv: %v2f", uv); +/// debug_printfln!("pos.x: %f, pos.z: %f, int: %i", pos.x, pos.z, int); +/// ``` +/// +/// See for formatting rules. +#[proc_macro] +pub fn debug_printf(input: TokenStream) -> TokenStream { + debug_printf_inner(syn::parse_macro_input!(input as DebugPrintfInput)) +} + +/// Similar to `debug_printf` but appends a newline to the format string. +#[proc_macro] +pub fn debug_printfln(input: TokenStream) -> TokenStream { + let mut input = syn::parse_macro_input!(input as DebugPrintfInput); + input.format_string.push('\n'); + debug_printf_inner(input) +} + +struct DebugPrintfInput { + span: proc_macro2::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: proc_macro2::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 matches!(ch, '0'..='9') { + 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 matches!(ch, '0'..='9') { + 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); + + variable_idents.push_str(&format!("%{} ", 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}}}", ident = ident); + + op_loads.push(quote::quote! { + #op_load, + }); + } + + let input_registers = input_registers + .into_iter() + .collect::(); + let op_loads = op_loads.into_iter().collect::(); + + let op_string = format!("%string = OpString {:?}", format_string); + + let output = quote::quote! { + 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/src/lib.rs b/crates/spirv-std/src/lib.rs index fb65ba3656..ae38c54ef8 100644 --- a/crates/spirv-std/src/lib.rs +++ b/crates/spirv-std/src/lib.rs @@ -130,3 +130,19 @@ extern "C" fn rust_eh_personality() {} #[doc(hidden)] /// [spirv_types] pub fn workaround_rustdoc_ice_84738() {} + +#[doc(hidden)] +pub fn debug_printf_assert_is_type(ty: T) -> T { + ty +} + +#[doc(hidden)] +pub fn debug_printf_assert_is_vector< + TY: crate::scalar::Scalar, + V: crate::vector::Vector, + const SIZE: usize, +>( + vec: V, +) -> V { + vec +} diff --git a/tests/ui/arch/debug_printf.rs b/tests/ui/arch/debug_printf.rs new file mode 100644 index 0000000000..d296f12473 --- /dev/null +++ b/tests/ui/arch/debug_printf.rs @@ -0,0 +1,52 @@ +// build-pass +// compile-flags: -Ctarget-feature=+ext:SPV_KHR_non_semantic_info + +use spirv_std::{ + glam::{IVec2, UVec2, Vec2, Vec3, Vec4}, + macros::{debug_printf, debug_printfln}, +}; + +fn func(a: f32, b: f32) -> f32 { + a * b + 1.0 +} + +struct Struct { + a: f32, +} + +impl Struct { + fn method(&self, b: f32, c: f32) -> f32 { + self.a * b + c + } +} + +#[spirv(fragment)] +pub fn main() { + unsafe { + debug_printf!(); + debug_printfln!(); + debug_printfln!("Hello World"); + debug_printfln!("Hello World",); + debug_printfln!(r#"Hello "World""#); + debug_printfln!( + r#"Hello "World" +"# + ); + debug_printfln!("Hello \"World\"\n\n"); + debug_printfln!("%%r %%f %%%%f %%%%%u", 77); + } + + let vec = Vec2::new(1.52, 25.1); + + unsafe { + debug_printfln!("%v2f", vec); + debug_printfln!("%1v2f", { vec * 2.0 }); + debug_printfln!("%1.2v2f", vec * 3.0); + debug_printfln!("%% %v2f %%", vec * 4.0); + debug_printfln!("%u %i %f 🐉", 11_u32, -11_i32, 11.0_f32); + debug_printfln!("%f", func(33.0, 44.0)); + debug_printfln!("%f", Struct { a: 33.0 }.method(44.0, 55.0)); + debug_printfln!("%v3f %v4f", Vec3::new(1.0, 1.0, 1.0), Vec4::splat(5.0)); + debug_printfln!("%v2u %v2i", UVec2::new(1, 1), IVec2::splat(-5)); + } +} diff --git a/tests/ui/arch/debug_printf_type_checking.rs b/tests/ui/arch/debug_printf_type_checking.rs new file mode 100644 index 0000000000..c41c7924b9 --- /dev/null +++ b/tests/ui/arch/debug_printf_type_checking.rs @@ -0,0 +1,25 @@ +// build-fail +// normalize-stderr-test "\S*/crates/spirv-std/src/" -> "$$SPIRV_STD_SRC/" +// compile-flags: -Ctarget-feature=+ext:SPV_KHR_non_semantic_info + +use spirv_std::{glam::Vec2, macros::debug_printf}; + +#[spirv(fragment)] +pub fn main() { + unsafe { + debug_printf!("%1"); + debug_printf!("%1."); + debug_printf!("%."); + debug_printf!("%.1"); + debug_printf!("%1.1"); + debug_printf!("%1.1v"); + debug_printf!("%1.1v5"); + debug_printf!("%1.1v2"); + debug_printf!("%1.1v2r"); + debug_printf!("%r", 11_i32); + debug_printf!("%f", 11_u32); + debug_printf!("%u", 11.0_f32); + debug_printf!("%v2f", 11.0); + debug_printf!("%f", Vec2::splat(33.3)); + } +} diff --git a/tests/ui/arch/debug_printf_type_checking.stderr b/tests/ui/arch/debug_printf_type_checking.stderr new file mode 100644 index 0000000000..2e21dc300f --- /dev/null +++ b/tests/ui/arch/debug_printf_type_checking.stderr @@ -0,0 +1,113 @@ +error: Unterminated format specifier: missing type after precision + --> $DIR/debug_printf_type_checking.rs:10:23 + | +10 | debug_printf!("%1"); + | ^^^^ + +error: Unterminated format specifier: missing type after decimal point + --> $DIR/debug_printf_type_checking.rs:11:23 + | +11 | debug_printf!("%1."); + | ^^^^^ + +error: Unrecognised format specifier: '.' + --> $DIR/debug_printf_type_checking.rs:12:23 + | +12 | debug_printf!("%."); + | ^^^^ + +error: Unrecognised format specifier: '.' + --> $DIR/debug_printf_type_checking.rs:13:23 + | +13 | debug_printf!("%.1"); + | ^^^^^ + +error: Unterminated format specifier: missing type after fraction precision + --> $DIR/debug_printf_type_checking.rs:14:23 + | +14 | debug_printf!("%1.1"); + | ^^^^^^ + +error: Missing vector dimensions specifier + --> $DIR/debug_printf_type_checking.rs:15:23 + | +15 | debug_printf!("%1.1v"); + | ^^^^^^^ + +error: Invalid width for vector: 5 + --> $DIR/debug_printf_type_checking.rs:16:23 + | +16 | debug_printf!("%1.1v5"); + | ^^^^^^^^ + +error: Missing vector type specifier + --> $DIR/debug_printf_type_checking.rs:17:23 + | +17 | debug_printf!("%1.1v2"); + | ^^^^^^^^ + +error: Unrecognised vector type specifier: 'r' + --> $DIR/debug_printf_type_checking.rs:18:23 + | +18 | debug_printf!("%1.1v2r"); + | ^^^^^^^^^ + +error: Unrecognised format specifier: 'r' + --> $DIR/debug_printf_type_checking.rs:19:23 + | +19 | debug_printf!("%r", 11_i32); + | ^^^^ + +error[E0308]: mismatched types + --> $DIR/debug_printf_type_checking.rs:20:29 + | +20 | debug_printf!("%f", 11_u32); + | ^^^^^^ expected `f32`, found `u32` + | +help: change the type of the numeric literal from `u32` to `f32` + | +20 | debug_printf!("%f", 11_f32); + | ~~~ + +error[E0308]: mismatched types + --> $DIR/debug_printf_type_checking.rs:21:29 + | +21 | debug_printf!("%u", 11.0_f32); + | ^^^^^^^^ expected `u32`, found `f32` + | +help: change the type of the numeric literal from `f32` to `u32` + | +21 | debug_printf!("%u", 11u32); + | ~~~ + +error[E0277]: the trait bound `{float}: Vector` is not satisfied + --> $DIR/debug_printf_type_checking.rs:22:31 + | +22 | debug_printf!("%v2f", 11.0); + | ----------------------^^^^-- + | | | + | | the trait `Vector` is not implemented for `{float}` + | required by a bound introduced by this call + | + = help: the following implementations were found: + > + > + > + > + and 13 others +note: required by a bound in `debug_printf_assert_is_vector` + --> $SPIRV_STD_SRC/lib.rs:142:8 + | +142 | V: crate::vector::Vector, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `debug_printf_assert_is_vector` + +error[E0308]: mismatched types + --> $DIR/debug_printf_type_checking.rs:23:29 + | +23 | debug_printf!("%f", Vec2::splat(33.3)); + | ^^^^^^^^^^^^^^^^^ expected `f32`, found struct `Vec2` + +error: aborting due to 14 previous errors + +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`.