Whistler: Live eBPF Programming from the Common Lisp REPL

Whistler is a new optimizing compiler for a Common Lisp-based DSL that allows developers to write eBPF programs with less ceremony than C. It bypasses the Clang/LLVM toolchain, enabling live compilation and loading of eBPF bytecode directly from the REPL.
A string of recent experiments around observability and security for agentic AI systems led me down the eBPF rabbit-hole. When I emerged, I came back with a full optimizing compiler for a Common Lisp-based DSL for eBPF called Whistler.
Whistler lets you write shorter code, with less ceremony than eBPF C code, and still produce highly-optimized eBPF output, equivalent or better than clang. And Whistler generates those ELF eBPF files directly, without any of the eBPF clang+llvm toolchain.
In addition to generating object code files directly, and loading them in the traditional way, you can actually inline Whistler code directly in your Common Lisp programs and have them compiled/loaded/unloaded as part of your traditional REPL process, where no object file even lands on disk.
A taste
Here’s a kprobe that counts every execve call on the system:
(with-bpf-session ()
(bpf:map counter :type :hash :key-size 4 :value-size 8 :max-entries 1)
(bpf:prog trace (:type :kprobe
:section "kprobe/__x64_sys_execve"
:license "GPL")
(incf (getmap counter 0))
0)
(bpf:attach trace "__x64_sys_execve")
(loop (sleep 1)
(format t "execve count: ~d~%" (bpf:map-ref counter 0))))
That’s a complete, runnable program. The bpf:prog body compiles to eBPF bytecode during macroexpansion. The bytecode is embedded as a literal in the expansion. At runtime, the map is created, the program is loaded into the kernel, and the probe is attached. The loop at the bottom is plain Common Lisp, polling the map every second.
How it works
The bpf: prefix is the boundary between kernel and userspace. Forms prefixed with bpf: are declarations for the BPF compiler:
bpf:map— declares a BPF map (compiled at macro-expansion time)bpf:prog— declares a BPF program (compiled at macro-expansion time)bpf:attach— generatesperf_event_opencalls (runs at runtime)bpf:map-ref— generatesbpf_map_lookup_elemcalls (runs at runtime)
Everything else is normal Common Lisp. The boundary is syntactic, not semantic — both sides share the same Lisp image. The key insight: the Whistler compiler runs during macroexpansion. By the time SBCL compiles the with-bpf-session form, the eBPF bytecode is already a constant. This allows for compile-time errors with full context.
One struct, both sides
whistler:defstruct generates accessors for both BPF and CL. On the BPF side, you get stack allocation and direct load/store with compile-time offsets. On the CL side, it generates a standard struct with encoders and decoders. One definition serves both kernel and userspace, eliminating manual byte-offset parsing.
The kernel at your fingertips
Whistler can import definitions directly from the running kernel. deftracepoint reads tracepoint format files from tracefs, and import-kernel-struct reads the kernel’s BTF. Offsets resolve from your running kernel at compile time — kernel headers and vmlinux.h are unnecessary.
The loader is pure CL too
whistler/loader is a complete BPF userspace loader written in Common Lisp with zero C dependencies. It uses SBCL’s sb-alien for direct syscall access, handling ELF parsing, map creation, FD relocation, and program loading with verifier error capture.
Polyglot userspace
Not everything has to be Lisp. Whistler can generate matching struct definitions for Go, C, Rust, or Python from the same defstruct declarations used in the BPF program. You compile the BPF with Whistler and write the loader in whatever language your team already uses.
Why this matters
The traditional eBPF workflow is fragmented: write C, compile with clang, then write a loader in another language. With Whistler 1.0, the compiler, loader, and userspace application share a process. You can develop at the REPL — modify a probe, re-eval the form, and see results immediately. The feedback loop is instant.
Source: Hacker News










