Add debug_printf! and debug_printfln! macros that uses the DebugPrintf extension (#768)

This commit is contained in:
Ashley 2021-10-23 01:05:03 -07:00 committed by GitHub
parent 28313a2029
commit e5c2953ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 469 additions and 0 deletions

View File

@ -350,3 +350,266 @@ fn path_from_ident(ident: Ident) -> syn::Type {
path: syn::Path::from(ident), 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 <https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/docs/debug_printf.md#debug-printf-format-string> 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<syn::Expr>,
}
impl syn::parse::Parse for DebugPrintfInput {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> {
let span = input.span();
if input.is_empty() {
return Ok(Self {
span,
format_string: Default::default(),
variables: Default::default(),
});
}
let format_string = input.parse::<syn::LitStr>()?;
if !input.is_empty() {
input.parse::<syn::token::Comma>()?;
}
let variables =
syn::punctuated::Punctuated::<syn::Expr, syn::token::Comma>::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<proc_macro2::TokenStream> {
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::<proc_macro2::TokenStream>();
let op_loads = op_loads.into_iter().collect::<proc_macro2::TokenStream>();
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()
}

View File

@ -130,3 +130,19 @@ extern "C" fn rust_eh_personality() {}
#[doc(hidden)] #[doc(hidden)]
/// [spirv_types] /// [spirv_types]
pub fn workaround_rustdoc_ice_84738() {} pub fn workaround_rustdoc_ice_84738() {}
#[doc(hidden)]
pub fn debug_printf_assert_is_type<T>(ty: T) -> T {
ty
}
#[doc(hidden)]
pub fn debug_printf_assert_is_vector<
TY: crate::scalar::Scalar,
V: crate::vector::Vector<TY, SIZE>,
const SIZE: usize,
>(
vec: V,
) -> V {
vec
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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<f32, 2_usize>` is not satisfied
--> $DIR/debug_printf_type_checking.rs:22:31
|
22 | debug_printf!("%v2f", 11.0);
| ----------------------^^^^--
| | |
| | the trait `Vector<f32, 2_usize>` is not implemented for `{float}`
| required by a bound introduced by this call
|
= help: the following implementations were found:
<BVec2 as Vector<bool, 2_usize>>
<BVec3 as Vector<bool, 3_usize>>
<BVec4 as Vector<bool, 4_usize>>
<DVec2 as Vector<f64, 2_usize>>
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<TY, SIZE>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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`.