Function Plotter
Fractals
A recursive-descent parser
evaluates math expressions on a Super Nintendo — using
IEEE-754 soft-float arithmetic
(every +, *, / is a software library call, ~3 000 cycles
each). For each pixel column the parser recurses up to 7 levels deep through
fn_eval_expr → fn_eval_term → fn_eval_factor, folding float results
back up the stack. The curve is plotted left-to-right in real time; four expressions cycle
automatically. Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16).
Self-running demo — curves draw automatically and cycle every ~6 s. (Boot holds a moment while the gate CRC computes.)
Click the screen to focus, then watch. Tab away and it pauses.
What it is
The demo parses four baked expressions with a textbook recursive-descent grammar and plots each as a white pixel curve on a 128×128 canvas. The key stress is that the parser runs per pixel — re-scanning the expression string and recursing through the full grammar for every column:
// Grammar (four functions of x):
// expr = term (('+' | '-') term)*
// term = factor (('*' | '/') factor)*
// factor = '(' expr ')' | '-' factor | 'x' | number
//
// Deepest call chain for x/(x*x+1.0):
// fn_eval_expr → fn_eval_term → fn_eval_factor →
// '(' → fn_eval_expr → fn_eval_term → fn_eval_factor (7 levels)
//
// Every float op is a soft-float libcall on the 65816:
float yi = fn_eval_expr(&p, xi); // recursive descent
int16_t py = (int16_t)((2.0f - yi) * 32.0f); // __subsf3 + __mulsf3 + __fixsfsi Compiler stress-test #24 — recursive parser + soft-float
A Round 2 demo — each targets a codegen corner the first twenty never execute. #24 opens two new corners: the recursive call graph (soft-stack ABI stress in a non-numeric context) and soft-float on a recursive hot path (distinct from #21's iterative Mandelbrot loop).
| Corner | What it exercises |
|---|---|
| Recursive-descent parsing | A real grammar parser runs PER PIXEL: expr = term ((+|-) term)*, term = factor ((*|/) factor)*, factor = (expr) | -factor | x | number. For "x/(x*x+1.0)" that is 7 levels of fn_eval_expr → fn_eval_term → fn_eval_factor → fn_eval_expr recursion. Each recursive call saves a float (4 bytes) and a char** (2 bytes) on the soft stack — thousands of stack frames per second. |
| Soft-float on every op | All arithmetic uses C float — IEEE-754 single precision, implemented in software on the 65816 by the llvm-compiler-rt library. Every x*x is __mulsf3 (~3 000 cycles), every subtraction is __subsf3, and the division in x/(x*x+1.0) is __divsf3. The pixel y-mapping adds __fixsfsi (float→int). The disasm gate confirmed: __mulsf3=5, __divsf3=1, rep/sep=48. |
| Bit-exact across all modes | IEEE-754 single precision is fully specified, so host float and SNES soft-float produce identical bit-patterns — no rounding freedom. The gate evaluates y=x*x-0.5 at 64 x-values and CRCs the lower+upper 16 bits of each result: 0x2EBE. Confirmed host == default == +mos-a16 == +mos-xy16 on bsnes-jg. |
| Four curves cycle automatically | "x*x-0.5" (parabola), "x*x*x-x" (cubic, two turning points), "x/(x*x+1.0)" (rational, S-curve), "x*x*x*x-x*x" (quartic W-shape). Each draws left-to-right at one pixel per frame, pauses 4 s, then clears and advances. The parser re-reads the expression string character by character for every pixel — intentional, so the recursive call graph runs the full workload. |
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to
reproduce the build gate's WRAM assert (gate CRC 0x2EBE) live in this tab. The
disasm gate shape: __mulsf3=5, __divsf3=1,
rep/sep=48 — soft-float recursive evaluation in native-16 mode.
No compiler bug found — soft-float codegen correct across all modes.