/* * bench.c — Benchmarks: interpreted vs compiled vs JIT */ #include "lumbda.h" #include "jit.h" static double bench_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } typedef struct { const char *name; const char *setup; const char *expr; int iterations; } Benchmark; static Benchmark benchmarks[] = { { "fib-iter(30)", "(define (fib-iter n)\n" " (let loop ((a 0) (b 1) (i 0))\n" " (if (= i n) a (loop b (+ a b) (+ i 1)))))\n", "(fib-iter 30)", 10000 }, { "fib-rec(25)", "(define (fib-rec n)\n" " (if (<= n 1) n (+ (fib-rec (- n 1)) (fib-rec (- n 2)))))\n", "(fib-rec 25)", 10 }, { "list-sum(1000)", "(define (list-sum lst)\n" " (let loop ((l lst) (acc 0))\n" " (if (null? l) acc (loop (cdr l) (+ acc (car l))))))\n" "(define big-list (iota 1000))\n", "(list-sum big-list)", 1000 }, { "map-square(1000)", "(define (my-map f lst)\n" " (if (null? lst) '()\n" " (cons (f (car lst)) (my-map f (cdr lst)))))\n" "(define big-list (iota 1000))\n", "(my-map (lambda (x) (* x x)) big-list)", 100 }, { "tak(18,12,6)", "(define (tak x y z)\n" " (if (not (< y x)) z\n" " (tak (tak (- x 1) y z)\n" " (tak (- y 1) z x)\n" " (tak (- z 1) x y))))\n", "(tak 18 12 6)", 10 }, { "ack(3,4)", "(define (ack m n)\n" " (if (= m 0) (+ n 1)\n" " (if (= n 0) (ack (- m 1) 1)\n" " (ack (- m 1) (ack m (- n 1))))))\n", "(ack 3 4)", 100 }, { "ack(3,7)", "(define (ack m n)\n" " (if (= m 0) (+ n 1)\n" " (if (= n 0) (ack (- m 1) 1)\n" " (ack (- m 1) (ack m (- n 1))))))\n", "(ack 3 7)", 10 }, { "eml-compose(1M)", /* EML insight (arXiv:2603.21852v2): eml(x,y) = exp(x) - ln(y) generates\n" * ALL elementary functions from one operator. This benchmark exercises\n" * integer-only EML-style composition: repeated add/sub (exp/ln analog\n" * in the integer domain). Tests whether JIT compiles well the pattern\n" * of composing minimal primitives into higher operations. */ "(define (eml-pow base exp)\n" " (let loop ((e exp) (acc 1))\n" " (if (= e 0) acc (loop (- e 1) (* acc base)))))\n" "(define (eml-log-approx n base)\n" " (let loop ((n n) (count 0))\n" " (if (< n base) count (loop (- n base) (+ count 1)))))\n" "(define (eml-compose x y)\n" " (let ((p (eml-pow x y)))\n" " (eml-log-approx p x)))\n", "(eml-compose 3 7)", 100000 }, { "named-let-sum(100k)", /* Named-let compiles to a native loop with jmp — zero overhead. */ "(define (sum-to n)\n" " (let loop ((i n) (acc 0))\n" " (if (= i 0) acc (loop (- i 1) (+ acc i)))))\n", "(sum-to 100000)", 100 }, { "list-walk(1000)", /* Tests JIT car/cdr/null?/cons pipeline */ "(define (list-reverse lst)\n" " (let loop ((l lst) (acc '()))\n" " (if (null? l) acc (loop (cdr l) (cons (car l) acc)))))\n" "(define big-list (iota 1000))\n", "(list-reverse big-list)", 1000 }, {NULL, NULL, NULL, 0} }; static void run_benchmark(Benchmark *b, const char *mode) { Env *g = make_global_env(); int count; Value *prelude_exprs = read_all(PRELUDE, &count, false); for (int i = 0; i < count; i++) leval(prelude_exprs[i], g); ul_free(prelude_exprs); g_auto_compile = false; g_jit_enabled = false; if (strcmp(mode, "compiled") == 0) { g_auto_compile = true; } else if (strcmp(mode, "jit") == 0) { g_jit_enabled = true; } /* Setup */ Value *setup_exprs = read_all(b->setup, &count, false); for (int i = 0; i < count; i++) leval(setup_exprs[i], g); ul_free(setup_exprs); /* Parse the benchmark expression once */ Value *bench_exprs = read_all(b->expr, &count, false); /* Warmup */ for (int i = 0; i < 3; i++) { for (int j = 0; j < count; j++) leval(bench_exprs[j], g); } /* Timed run */ double start = bench_time(); Value last = VAL_VOID; for (int i = 0; i < b->iterations; i++) { for (int j = 0; j < count; j++) last = leval(bench_exprs[j], g); } double elapsed = bench_time() - start; char *result_str = show(last, false); printf(" %-25s %-10s %8d iters %.3f s (%.1f us/iter) => %s\n", b->name, mode, b->iterations, elapsed, (elapsed / b->iterations) * 1e6, result_str); ul_free(result_str); ul_free(bench_exprs); g_auto_compile = false; g_jit_enabled = false; } int main(void) { int stack_anchor; #ifdef USE_BOEHM_GC GC_INIT(); lumbda_gc_init(); lumbda_gc_set_stack_base(&stack_anchor); #else (void)stack_anchor; #endif init_symbols(); /* Set up root error context for prelude loading */ ErrorContext root_ctx; root_ctx.call_stack_depth = 0; root_ctx.error_obj = VAL_NIL; root_ctx.source_line = 0; g_error_ctx = &root_ctx; if (setjmp(root_ctx.jmp) != 0) { fprintf(stderr, "fatal error: %s\n", root_ctx.message); return 1; } printf("lumbda C benchmarks — interpreted vs compiled vs JIT\n"); printf("═════════════════════════════════════════════════════════════════\n"); const char *modes[] = { "interp", "compiled", "jit", NULL }; for (int i = 0; benchmarks[i].name; i++) { for (int m = 0; modes[m]; m++) { TRY(ctx) { run_benchmark(&benchmarks[i], modes[m]); } CATCH { printf(" %-25s %-10s ERROR: %s\n", benchmarks[i].name, modes[m], ctx.message); } ENDTRY; } printf(" ─────────────────────────────────────────────────────────────\n"); } printf("═════════════════════════════════════════════════════════════════\n"); return 0; }