Repository navigation
fix(cuda-macros): correct gpu_printf default float and unsigned formatting - #1353
Merged
nihalpasham merged 8 commits intoOct 19, 2026
Merged
Conversation
`{:.N}` has no type character, so the default arm packed the argument
as i64 and emitted %.Nlld. C precision on an integer conversion is a
minimum digit count, which turned 3.14159 into 03 instead of 3.14.
Signed-off-by: YeonwooSung <neos960518@gmail.com>
Default `{}` packed every argument as i64 and emitted %lld, so a u64
above i64::MAX printed as a negative number. Visible unsigned arguments
now cast to u64 and use %llu. Bindings whose type the macro cannot see
go through GpuPrintfArg, which also keeps floats off the integer path.
Signed-off-by: YeonwooSung <neos960518@gmail.com>
…printf-default-format Signed-off-by: YeonwooSung <neos960518@gmail.com> # Conflicts: # cuda-oxide/crates/cuda-macros/src/tests/printf.rs
# Conflicts: # cuda-oxide/crates/cuda-macros/src/tests/mod.rs
…ment type
Every placeholder without a type character now takes its C conversion
from the argument's GpuPrintfArg impl, through a generic helper whose
inline const assembles the format string. The token classifier and the
`as f64` precision arm are gone, so the trait is the single policy:
`{}` on a u64 above i64::MAX prints %llu, on a float %f, and `{:.0}` on
u64::MAX keeps all 20 digits.
The helper returns `&'static [u8; N]`. The device importer rejects an
anonymous slice constant, which made every `{}` on a binding fail device
codegen and broke the printf, dynamic_smem, debug-tests and
toolchain_pairing_smoke examples.
The format string is assembled by a const fn in cuda-device from the
literal parts and per-placeholder specs the macro passes as data, and N
is computed from the same data, so the buffer no longer overflows at 54
literal percent signs and is no longer padded by 48 bytes per
placeholder. The builder is unit-tested on the host, and one reachable
expansion test keeps the inline const evaluated at build time.
Every argument, runtime width included, is evaluated into a local in
source order, so evaluation stays left to right. The locals carry
mixed-site spans, so an argument expression cannot see an earlier one.
Sign flags on an unsigned or pointer argument are a const-evaluation
error, and runtime precision `{:.*}` is a macro error: device printf
takes `*` for the width only. The macro doc table states the contract,
including that char, fieldless enums, i128 and u128 are not accepted.
Signed-off-by: nihalpasham <nihalp@nvidia.com>
…ample
Add a kernel with the cases that printed wrong values through %lld:
`{}` on u64::MAX, on f32 and f64 bindings, on a slice element and on a
raw pointer, `{:.2}` on a float binding, `{:08}` on a u64 and one mixed
line. The float kernel's "default" lines now use `{}` and `{:.2}`
instead of `{:.6f}` and `{:.2f}`, so the example exercises the default
route that the smoketest and examples-compile lanes build.
Correct the README's u64 line: the source prints 1311768467463790320
for 0x1234_5678_9ABC_DEF0. Add the expected output of the new kernel.
Signed-off-by: nihalpasham <nihalp@nvidia.com>
The cuda-device README and the API quick reference showed a C-style
call with `%d` and `%f`, two arguments and no placeholder, which the
macro's argument count check rejects. Both now use `{}` placeholders.
The README and the book's debugging page say that a placeholder without
a type character prints its argument by type, and that a type character
casts the argument instead.
Signed-off-by: nihalpasham <nihalp@nvidia.com>
The README still showed the C-style two-argument form that the macro's argument-count check rejects; use placeholders and state the default conversion rule in one sentence. Signed-off-by: nihalpasham <nihalp@nvidia.com>
Collaborator
|
/ok to test a61a7e9 |
Collaborator
|
Thanks for tracking this down; the defaults were wrong and the trait route is the right idea. As submitted, every
At |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
gpu_printf!now chooses the C conversion from the argument's type for every placeholder without a type character. Before,{}and{:.N}packed every argument asi64and printed%lld, so floats printed as truncated integers,{:.2}on a float printed a zero-padded integer, and unsigned values abovei64::MAXprinted negative. The per-type table cuda-device has carried since the first release,GpuPrintfArg, is now the single source of that decision.What we fixed
{},{:8},{:08},{:+}and{:.N}without a type character took the argument throughas i64and%lld, whatever its type. They now resolve the conversion from the argument'sGpuPrintfArgimpl at compile time:%dor%lldfor signed integers,%uor%llufor unsigned,%ffor floats,%dfor bool,%pfor raw pointers.&'static [u8; N]. The importer rejects an anonymous slice constant, so the first version of this change failed device codegen on every{}over a binding and broke the printf, dynamic_smem, debug-tests and toolchain_pairing_smoke examples.%and padded every format global.println!does.{:.*}are compile-time errors; device printf leaves the first undefined and does not support the second.Default conversion contract
GpuPrintfArgtype:i8toi64,isize,u8tou64,usize,f32,f64,bool,*const T,*mut T.i32, an unsuffixed float isf64.+and space flags need a signed integer or float argument.chartakes{:c}; a fieldless enum needs anas i32cast;i128andu128are not accepted.{:x},{:d},{:.2f},{:e}, ...) keep their previous casts and conversions.Implementation
__Ti: GpuPrintfArgof a per-call-site#[inline(always)]helper. Its inline const callscuda_device::debug::__gpu_printf_format::<N>, aconst fnnext to__gpu_assert_c_string, with the literal parts and per-placeholder specs as data; the trait'sFORMAT_CHAR,IS_64BITandIS_FLOATselectlland the type character after monomorphization.GpuPrintfArg::promoteinto a#[repr(C)]struct whose fields are the promoted types at natural alignment, the layoutvprintfreads.Nis computed by the macro: literal parts with%doubled, fixed specifiers, prefix plus four bytes per typed placeholder, one NUL. The builder asserts the fit and NUL-terminates.%%doubling, fit, rejection of+on unsigned); the macro tests check the expansion; the printf example checks the device output.Verification
Checked at
a61a7e965(nightly-2026-10-11, RTX 5090):cargo test -p cuda-macros --all-targets(143 unit tests plus the trybuild cases) andcargo test -p cuda-device --all-targets(124) pass, builder and expansion tests included. Clippy with warnings as errors on both, rustdoc,cargo oxide fmt --check, the 18 guards and the lock check are clean.Unsupported constant type in translate_constant).cargo oxide run printf:=== ALL PRINTF TESTS PASSED ===with the new Test 8 lines matching the README, memcheck 0 errors; the same values through the libNVVM route.03for{:.2}of 3.14159,-1for{}ofu64::MAXand3for{}of an f32 binding.Caveats
as i64path changes:{}oncharand on a fieldless enum no longer compiles (use{:c}oras i32),{}on a raw pointer prints%phex instead of a decimal,{}on a float prints six decimals where it printed a truncated integer.{:.3}on -42 prints-042); Rust ignores precision on integers.