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

loading core…

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

CornerWhat it exercises
Recursive-descent parsingA 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 opAll 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 modesIEEE-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.