@@ -350,3 +350,266 @@ fn path_from_ident(ident: Ident) -> syn::Type {
350350 path : syn:: Path :: from ( ident) ,
351351 } )
352352}
353+
354+ /// Print a formatted string with a newline using the debug printf extension.
355+ ///
356+ /// Examples:
357+ ///
358+ /// ```rust,ignore
359+ /// debug_printfln!("uv: %v2f", uv);
360+ /// debug_printfln!("pos.x: %f, pos.z: %f, int: %i", pos.x, pos.z, int);
361+ /// ```
362+ ///
363+ /// See <https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/docs/debug_printf.md#debug-printf-format-string> for formatting rules.
364+ #[ proc_macro]
365+ pub fn debug_printf ( input : TokenStream ) -> TokenStream {
366+ debug_printf_inner ( syn:: parse_macro_input!( input as DebugPrintfInput ) )
367+ }
368+
369+ /// Similar to `debug_printf` but appends a newline to the format string.
370+ #[ proc_macro]
371+ pub fn debug_printfln ( input : TokenStream ) -> TokenStream {
372+ let mut input = syn:: parse_macro_input!( input as DebugPrintfInput ) ;
373+ input. format_string . push ( '\n' ) ;
374+ debug_printf_inner ( input)
375+ }
376+
377+ struct DebugPrintfInput {
378+ span : proc_macro2:: Span ,
379+ format_string : String ,
380+ variables : Vec < syn:: Expr > ,
381+ }
382+
383+ impl syn:: parse:: Parse for DebugPrintfInput {
384+ fn parse ( input : syn:: parse:: ParseStream < ' _ > ) -> syn:: parse:: Result < Self > {
385+ let span = input. span ( ) ;
386+
387+ if input. is_empty ( ) {
388+ return Ok ( Self {
389+ span,
390+ format_string : Default :: default ( ) ,
391+ variables : Default :: default ( ) ,
392+ } ) ;
393+ }
394+
395+ let format_string = input. parse :: < syn:: LitStr > ( ) ?;
396+ if !input. is_empty ( ) {
397+ input. parse :: < syn:: token:: Comma > ( ) ?;
398+ }
399+ let variables =
400+ syn:: punctuated:: Punctuated :: < syn:: Expr , syn:: token:: Comma > :: parse_terminated ( input) ?;
401+
402+ Ok ( Self {
403+ span,
404+ format_string : format_string. value ( ) ,
405+ variables : variables. into_iter ( ) . collect ( ) ,
406+ } )
407+ }
408+ }
409+
410+ fn parsing_error ( message : & str , span : proc_macro2:: Span ) -> TokenStream {
411+ syn:: Error :: new ( span, message) . to_compile_error ( ) . into ( )
412+ }
413+
414+ enum FormatType {
415+ Scalar {
416+ ty : proc_macro2:: TokenStream ,
417+ } ,
418+ Vector {
419+ ty : proc_macro2:: TokenStream ,
420+ width : usize ,
421+ } ,
422+ }
423+
424+ fn debug_printf_inner ( input : DebugPrintfInput ) -> TokenStream {
425+ let DebugPrintfInput {
426+ format_string,
427+ variables,
428+ span,
429+ } = input;
430+
431+ fn map_specifier_to_type (
432+ specifier : char ,
433+ chars : & mut std:: str:: Chars < ' _ > ,
434+ ) -> Option < proc_macro2:: TokenStream > {
435+ let mut peekable = chars. peekable ( ) ;
436+
437+ Some ( match specifier {
438+ 'd' | 'i' => quote:: quote! { i32 } ,
439+ 'o' | 'x' | 'X' => quote:: quote! { u32 } ,
440+ 'a' | 'A' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' => quote:: quote! { f32 } ,
441+ 'u' => {
442+ if matches ! ( peekable. peek( ) , Some ( 'l' ) ) {
443+ chars. next ( ) ;
444+ quote:: quote! { u64 }
445+ } else {
446+ quote:: quote! { u32 }
447+ }
448+ }
449+ 'l' => {
450+ if matches ! ( peekable. peek( ) , Some ( 'u' | 'x' ) ) {
451+ chars. next ( ) ;
452+ quote:: quote! { u64 }
453+ } else {
454+ return None ;
455+ }
456+ }
457+ _ => return None ,
458+ } )
459+ }
460+
461+ let mut chars = format_string. chars ( ) ;
462+ let mut format_arguments = Vec :: new ( ) ;
463+
464+ while let Some ( mut ch) = chars. next ( ) {
465+ if ch == '%' {
466+ ch = match chars. next ( ) {
467+ Some ( '%' ) => continue ,
468+ None => return parsing_error ( "Unterminated format specifier" , span) ,
469+ Some ( ch) => ch,
470+ } ;
471+
472+ let mut has_precision = false ;
473+
474+ while matches ! ( ch, '0' ..='9' ) {
475+ ch = match chars. next ( ) {
476+ Some ( ch) => ch,
477+ None => {
478+ return parsing_error (
479+ "Unterminated format specifier: missing type after precision" ,
480+ span,
481+ )
482+ }
483+ } ;
484+
485+ has_precision = true ;
486+ }
487+
488+ if has_precision && ch == '.' {
489+ ch = match chars. next ( ) {
490+ Some ( ch) => ch,
491+ None => {
492+ return parsing_error (
493+ "Unterminated format specifier: missing type after decimal point" ,
494+ span,
495+ )
496+ }
497+ } ;
498+
499+ while matches ! ( ch, '0' ..='9' ) {
500+ ch = match chars. next ( ) {
501+ Some ( ch) => ch,
502+ None => return parsing_error (
503+ "Unterminated format specifier: missing type after fraction precision" ,
504+ span,
505+ ) ,
506+ } ;
507+ }
508+ }
509+
510+ if ch == 'v' {
511+ let width = match chars. next ( ) {
512+ Some ( '2' ) => 2 ,
513+ Some ( '3' ) => 3 ,
514+ Some ( '4' ) => 4 ,
515+ Some ( ch) => {
516+ return parsing_error ( & format ! ( "Invalid width for vector: {}" , ch) , span)
517+ }
518+ None => return parsing_error ( "Missing vector dimensions specifier" , span) ,
519+ } ;
520+
521+ ch = match chars. next ( ) {
522+ Some ( ch) => ch,
523+ None => return parsing_error ( "Missing vector type specifier" , span) ,
524+ } ;
525+
526+ let ty = match map_specifier_to_type ( ch, & mut chars) {
527+ Some ( ty) => ty,
528+ _ => {
529+ return parsing_error (
530+ & format ! ( "Unrecognised vector type specifier: '{}'" , ch) ,
531+ span,
532+ )
533+ }
534+ } ;
535+
536+ format_arguments. push ( FormatType :: Vector { ty, width } ) ;
537+ } else {
538+ let ty = match map_specifier_to_type ( ch, & mut chars) {
539+ Some ( ty) => ty,
540+ _ => {
541+ return parsing_error (
542+ & format ! ( "Unrecognised format specifier: '{}'" , ch) ,
543+ span,
544+ )
545+ }
546+ } ;
547+
548+ format_arguments. push ( FormatType :: Scalar { ty } ) ;
549+ }
550+ }
551+ }
552+
553+ if format_arguments. len ( ) != variables. len ( ) {
554+ return syn:: Error :: new (
555+ span,
556+ & format ! (
557+ "{} % arguments were found, but {} variables were given" ,
558+ format_arguments. len( ) ,
559+ variables. len( )
560+ ) ,
561+ )
562+ . to_compile_error ( )
563+ . into ( ) ;
564+ }
565+
566+ let mut variable_idents = String :: new ( ) ;
567+ let mut input_registers = Vec :: new ( ) ;
568+ let mut op_loads = Vec :: new ( ) ;
569+
570+ for ( i, ( variable, format_argument) ) in variables. into_iter ( ) . zip ( format_arguments) . enumerate ( )
571+ {
572+ let ident = quote:: format_ident!( "_{}" , i) ;
573+
574+ variable_idents. push_str ( & format ! ( "%{} " , ident) ) ;
575+
576+ let assert_fn = match format_argument {
577+ FormatType :: Scalar { ty } => {
578+ quote:: quote! { spirv_std:: debug_printf_assert_is_type:: <#ty> }
579+ }
580+ FormatType :: Vector { ty, width } => {
581+ quote:: quote! { spirv_std:: debug_printf_assert_is_vector:: <#ty, _, #width> }
582+ }
583+ } ;
584+
585+ input_registers. push ( quote:: quote! {
586+ #ident = in( reg) & #assert_fn( #variable) ,
587+ } ) ;
588+
589+ let op_load = format ! ( "%{ident} = OpLoad _ {{{ident}}}" , ident = ident) ;
590+
591+ op_loads. push ( quote:: quote! {
592+ #op_load,
593+ } ) ;
594+ }
595+
596+ let input_registers = input_registers
597+ . into_iter ( )
598+ . collect :: < proc_macro2:: TokenStream > ( ) ;
599+ let op_loads = op_loads. into_iter ( ) . collect :: < proc_macro2:: TokenStream > ( ) ;
600+
601+ let op_string = format ! ( "%string = OpString {:?}" , format_string) ;
602+
603+ let output = quote:: quote! {
604+ asm!(
605+ "%void = OpTypeVoid" ,
606+ #op_string,
607+ "%debug_printf = OpExtInstImport \" NonSemantic.DebugPrintf\" " ,
608+ #op_loads
609+ concat!( "%result = OpExtInst %void %debug_printf 1 %string " , #variable_idents) ,
610+ #input_registers
611+ )
612+ } ;
613+
614+ output. into ( )
615+ }
0 commit comments