Functional programming for C,
at the speed of hand-written assembly.
A small, externally linkable library of reductions, folds, maps, and game math (vec3 / mat4 / quaternion) whose hot paths are hand-tuned x64 AVX2. Pure functions, immutable data, no imperative loops in the inner kernels — wrapped in a thin C API you can drop into a game or graphics engine.
Where SIMD actually wins
Every kernel is timed against a scalar C reference compiled at the same -O3 -march=native — so the baseline is autovectorized by the compiler too. The library pulls ahead exactly where the compiler can't: reductions and dot products, where a scalar float accumulator is a serial dependency chain. Memory-bound element-wise ops sit at parity, and the chart says so.
method · lower is better · time to process 100,000 f32 elements (µs), best of 200 runs · Intel Core i7-4600M (Haswell, AVX2) · gcc 13 · run it yourself: make bench
Eight lanes, no dependency chain
A naive for-loop that sums floats can't be reordered by the compiler — floating-point addition isn't associative, so -O3 keeps it strictly serial: each add waits ~4 cycles for the previous one. The assembly kernels keep several independent SIMD accumulators in flight, so the CPU's execution units never stall.
FP·ASM — parallel accumulators
8× f32 added per instruction, across multiple accumulators — the pipeline stays full.
Scalar — serial chain
Each add depends on the last. One lane, latency-bound — the units sit idle waiting.
Assembly at the core, functions on top
Nothing above the kernels re-implements arithmetic — higher layers compose the assembly primitives.
AVX2 SIMD kernels
Reductions, fused folds (dot / sum-of-squares / SAD), maps (scale / offset / axpy), prefix scans, and vec3 / mat4 / quaternion math — over 10 numeric types.
Functional wrappers
Higher-order map / filter / fold, function composition and pipelines, and Maybe / Either monads for total, allocation-free error handling.
Game & data math
Batched vertex transforms, matrix ops, FFT, radix sort, statistics — composed from the L0 kernels, ready for a renderer or simulation loop.
Drop it into your frame loop
Transform ten thousand vertices and reduce a buffer without writing a single SIMD intrinsic.
// link: cc game.c -lfpasm (or add_subdirectory + fpasm::fpasm in CMake) #include "fp_types.h" extern void fp_mat4_mul_vec3_batch(Vec3f* out, const Mat4* m, const Vec3f* in, int n); extern float fp_fold_dotp_f32(const float* a, const float* b, size_t n); void update(Vec3f* world, const Mat4* mvp, const Vec3f* local, int n){ fp_mat4_mul_vec3_batch(world, mvp, local, n); // AVX2, one pass over n vertices float energy = fp_fold_dotp_f32((float*)world, (float*)world, n*3); // 5× a scalar dot }
Real closures. A real State monad. Real numbers.
Beyond the fast kernels sits a full functional toolkit — map / filter / foldl / foldr, scanl / scanr / mapAccumL, sortBy / groupBy / traverse / foldMap, Maybe / Either, lazy sequences, transducers — and something most C libraries won't claim: first-class escaping closures and a genuine State monad, in portable C11. No nested functions, no executable stack, AddressSanitizer-clean.
Yes, C can do monads — door #1, not wizardry
A closure is just a code pointer plus a heap-allocated environment plus a destructor — the exact thing GHC, Rust and Swift generate for you. Written by hand, it escapes its scope, composes, and frees cleanly. The State monad (get / put / bind / runState) is built on top of it.
…and here is exactly what it costs — same RNG, three ways
All three emit the identical draw sequence. The monad is real but allocates on every bind; mapAccumL is the same stateful computation with zero allocation — its fast path. Run make showcase.
// a pure, reproducible RNG in the State monad — seed IS the state static ST roll_k(int64_t s){ int64_t s2 = lcg(s); return st_then(st_put(s2), st_pure(die_of(s2))); } // put new seed, yield the die static ST roll(void){ return st_bind(st_get(), roll_k); } // do { s <- get; roll_k s } SR r = st_run(roll(), seed); // (die, new_seed) — pure, leak-free, gcc & clang
What it is today — and where it's headed
This is a fast math & FP core, not a game engine. It gives a renderer the pieces that have to be fast; the platform layer is deliberately out of scope — for now.
A tested, cross-platform core
- Linux + Windows x64, one ABI-abstracted source tree
- Static & shared libraries via Make and CMake
- 13 test suites; every kernel checked against a scalar reference
- Non-executable stack, no runtime allocations in the kernels
Toward a graphics library
- A compact, SIMD-first 2D/3D renderer built on the L0 math
- The long-term aim: a leaner, faster alternative to today's go-to frameworks — earned by measurement, not asserted
- macOS / Mach-O target & NEON port under evaluation