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).

loading core…

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.

CornerWhat it exercises
va_arg on 65816On 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 gateThe 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_argAll 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 visualThe 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.