← Back to context

Comment by AbanoubRodolf

16 hours ago

What makes eBPF particularly well-suited for REPL-style development is the verifier. Every BPF program gets statically verified by the kernel before it runs -- no unbounded loops, no out-of-bounds memory access, no null dereferences. That safety guarantee is what makes "live" iteration safe: you can't panic the kernel, you just get a rejection and iterate. It's the closest thing to a systems-level sandbox that doesn't require a VM.

The interesting design space here is the feedback loop. bpftrace already gives you ad-hoc one-liners for tracing, but it's a separate language you have to context-switch into. Running eBPF programs directly from a Lisp REPL where you already have your analysis code means the kernel tracing and the result processing live in the same environment. You can define a probe, collect data, and immediately run your existing Lisp functions over it without serialization or context switching between tools.

The catch is that the verifier imposes constraints that don't map cleanly to Lisp's programming model -- no recursion, stack limit of 512 bytes, bounded loops only. Bridging that mismatch at the macro level seems like the harder problem than the basic REPL integration.

The whistler code you inline with your common lisp is an s-expression based DSL. So you can use common lisp macros, but those macros are generating something that will look familiar to CL devs but is restricted based on the eBPF archictecture and validator requirements. eg. it only supports bounded `dotimes`, some basic progn/let/when/if/cond/eq/setf/incf/decf and math, and a simple array iterator. No lists, loops, tagbody/go, conditions, etc, etc. There's a manual in the docs directory.

Common Lisp in particular is multi-paradigm. You can write a ton of code and never use recursion once. I doubt bridging this "gap" was in any way difficult.

> Bridging that mismatch at the macro level seems like the harder problem than the basic REPL integration.

You can (and people do, core.async in Clojure works this way) put entire compilers in macros, macros are just functions that take and return code.