we've been talking about using an interpreter for dev builds, but we haven't really talked about the pros and cons of that when it comes to debugging. So I'd like to discuss!
one thing that comes to mind is that it's easier to write your own debugger in an interpreter
for example, several interpreted languages I've used have keywords like breakpoint that you can just put in the code, and then when execution reaches there, it just halts and gives you an interactive prompt where you can do typical debugger things like print a backtrace, step forward, etc.
I think it will essentially strictly be better
it's strictly easier to make a great experience I'd say
The ones caveat is it will be more likely to hit difference between debug and optimized builds
but then we get into the question of consistency
yeah exactly
With the interpreter we can literally drop into a full repl at any point in the program
like "everything is great but then I add --optimize and suddenly everything is different and worse"
To be fair, if we were gonna make a dev backend anyway, it is really no different in terms of correctness
Both are distinct from llvm in terms of correctness
oh sure I just mean like if we put a repl in there (for example), we should think about whether we want to replicate that ability in non-interpreter backends
Ah, so more a question of how do we debug optimized code?
yeah, so one possible design is:
roc build, we always build a standalone binary and don't use an interpreter for anything, and whether or not there's debuginfo in there (to be used with gdb/lldb/etc) is determined by a roc cli flagroc to run it immediately, we always provide our own debugging experience, and we can make it be the same experience whether or not you used --optimize (give or take info that was lost during optimizations), including things like making a repl availabaleSure. In my mind, the baseline is llvm for everything. Having a dev backend improves compilation speed, but otherwise is equivalent to the llvm backend. Having an interpreter gives debugging super powers in comparison. That said, it is not easily distributable.
So yeah, having roc build always use llvm makes sense.
Though for some cases, always having the interpreter may be really nice (dynamic plugins where perf isn't too important)
totally
a possible variation on that design could be that we have a cli flag for roc build which lets you bundle in our own fancy debugger instead of (or maybe in addition to?) debuginfo
so that you could do a "break into a repl" thing even in a standalone build that you're using for (for example) a plugin that isn't being launched by the roc CLI
This would be interpreter only?
feels like we could do it in llvm too, but it would be (a lot) harder :stuck_out_tongue:
a sketch of how a llvm "drop into a repl right now" build could work:
I'm not saying we should do this soon or anything, just thinking ahead to like "is it possible that we could offer this functionality and have it work both with and without --optimize"
I honestly don't think it would work out well, but it could be attempted.
To be fair, gdb and lldb have plugins that can help a lot with controlling cleaning, but it is nothing near an interpreter really.
Also, the way to offer this the easiest is probably to jit. Then there is no true optimized backend. Instead any code can be run jitted and faster or via the interpreter a step at a time.
do you think it wouldn't work well because of --optimize having stripped away relevant intermediate things?
Think about profiling and how poorly the info often maps back to the original source code. That level of mapping is what you are dealing with when trying to figure out running some form of repl.
that's fair
Even without stripping any debug info all the merging and moving around of code and various optimizations make the mapping very poor
So trying to walk that similar to a repl is just tough
Brendan Hansknecht said:
Also, the way to offer this the easiest is probably to jit. Then there is no true optimized backend. Instead any code can be run jitted and faster or via the interpreter a step at a time.
I definitely think we should always support the current level of "optimize as much as possible"
Yeah, I agree.
if that means some tooling becomes impossible, that's better than having those levels of performance being impossible
I do think it should be theoretically possible to run an optimized build but have a breakpoint with logic to launch into the interpreter at that point. That may help with some optimized debug. Though not exactly sure how you would exit the interpreter and go back to the optimized code. Might have to block function inlining and do this at the function level to make it possible to switch between the two.
Assuming roc has no bugs (few code gen bugs vs interpreter for correctness), theoretically the only disadvantage of debugging in the interpreter is execution speed. Cause optimized builds should never change observable behavior in roc unlike in some other languages.
So there may not be much demand to debug optimized builds anyway.
As a note, I think this message really touches what can be done with traditional executable debugging and tools like lldb:
Andrew Kelley said:
Richard Feldman said:
so the (very serious) debuggability/ergonomics downside we've had of doing SoA in Rust may just be a non-issue in Zig
with the llvm backend it's still a pain in the ass to debug MultiArrayList, however, with our self-hosted backends (currently x86_64 only, aarch64 to follow next) we have custom dwarf + lldb fork that recognizes zig std lib types and makes debugging really nice. same thing goes for hash maps
this project may be interesting to watch long term since jacobly (author of above lldb fork as well as zig's x86_64 backend) has taken an interest in it
Still can be pretty decent, but not nearly as flexible as an interpreter and repl.
Last updated: Jun 16 2026 at 16:19 UTC