VA_ARG Formatter
Motion & Curves
A Super Nintendo draws
Lissajous curves
while formatting the HUD parameters through a variadic
mini_sprintf() — the first demo in the battery to exercise the
va_arg calling convention on the 65816. Six frequency pairs cycle
automatically. The HUD reads "#32 VA ARG FX=N FY=N", formatted live
via va_arg(ap, unsigned int) on every frame. Compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16).
Self-running — 6 Lissajous figures cycle automatically. (Boot holds while the gate CRC computes.)
Click the screen to focus, then watch. Tab away and it pauses.
What it is
Each frame: compute the next Lissajous point, draw it with Bresenham on a
128×128 canvas, then call mini_sprintf(line, "#32 VA ARG FX=%u FY=%u", fx, fy)
to format the HUD. After 256 points (one full trace), the figure holds briefly,
the canvas clears, and the next (fx, fy) pair begins.
// va_arg in the hot path — every frame:
void mini_sprintf(char *buf, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
// ...
case 'u': {
uint16_t v = (uint16_t)va_arg(ap, unsigned int);
// reads 2 bytes on 65816 (16-bit int) — the variadic ABI under test
out += va_fmt_u(out, v);
}
va_end(ap);
} Compiler stress-test #32 — variadic va_arg calling convention
A Round 2 demo targeting the "variadic va_arg" row of the
coverage map — the last uncovered ABI corner after #29a (jump-table) and #26
(aggregate struct ABI). All 31 prior demos use only fixed-parameter calls.
| Corner | What it exercises |
|---|---|
| va_arg on 65816 | On the 65816 with llvm-mos, variadic arguments are passed via the soft-stack imaginary-register ABI. va_start() records the argument pointer; va_arg(ap, unsigned int) reads 2 bytes from the soft-stack on target (16-bit int) vs 4 bytes on host (32-bit int). Values ≤ UINT16_MAX are identical in both widths — the gate CRC is bit-exact host == +mos-a16 == +mos-xy16. |
| mini_sprintf gate | The gate calls mini_sprintf 4 times with 2–3 variadic args each (9 va_arg reads total): "123+456", "-7/3", "beef cafe", "999 -1 abcd". The output characters are folded into a rotate-XOR CRC → 0xE1F3. A miscompile of va_arg (wrong offset, wrong width) changes the output string and diverges the CRC. The disasm gate confirmed jsr=7, rep/sep=45. |
| No prior demo used va_arg | All 31 demos before #32 use explicit fixed-parameter functions. The variadic calling convention is a distinct ABI path: the caller must push variadic args in a specific layout; va_arg must advance the pointer by the correct size. A bug in either side diverges any va_arg call. This demo is the first to exercise that path. |
| Lissajous figure visual | The canvas draws a Lissajous curve: x = R·cos(fx·t), y = R·sin(fy·t). Six (fx, fy) pairs cycle automatically (1:2, 2:3, 3:4, 2:5, 4:5, 3:2). Each pair produces a distinct knot-like figure. The curve uses the same SPIRO_SIN_LUT as the spirograph demo. The HUD parameters (FX, FY, pair index, phase) are formatted via mini_sprintf each frame. |
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to
reproduce the build gate's WRAM assert (gate CRC 0xE1F3) live in this tab.
Disasm gate: jsr=7, rep/sep=45.
No compiler bug found — variadic va_arg codegen correct across all modes.