D364 - A va_list is a cursor, so the v-forms render what the register forms cannot

decided - 2026-08-29

vsnprintf is the most imported name nothing here implemented: twenty-two of the twenty-five open-toolchain payloads in the local set ask for it, ahead of every other missing name. It is not decoration - every one of their logging helpers is built out of it, so a payload that cannot render a message cannot say why it stopped.

What a va_list is on this architecture

Not a pointer walking a stack. The System V AMD64 psABI defines a four-field structure, and the arguments live in two places at once: the first six integer arguments were spilled by the caller's own prologue into a register save area, and everything past them is on the stack.

offset  0  gp_offset          u32
offset  4  fp_offset          u32
offset  8  overflow_arg_area  ptr
offset 16  reg_save_area      ptr

The capability this adds

The register-based forms see only what the trampoline caught in registers, so a format with more conversions than that cannot be rendered at all and is refused. Refusing is right for them - they genuinely cannot see the seventh argument. A va_list can, so the v forms are not a convenience wrapper over the others: they are the only ones that can render a long format correctly. The test asserts exactly that, by rendering the same seven-conversion format both ways.

One renderer, two sources

Rather than a second formatter, render_format grew an argument source - a trait with two implementations, one over the captured registers and one over a guest's list. Two formatters would have drifted, and the first divergence would have been a string that differed depending on which spelling of printf the guest happened to call.

Nothing is written back

va_arg advances the caller's list, and a real vsnprintf does too. The standard says ap is indeterminate after the call for exactly that reason, so a conforming caller may not read it again - which makes advancing it unobservable, and makes not advancing it the safer of two permitted behaviours. The cursor lives on our side and no write reaches guest memory.