I'm wondering if Richard Feldman or other folks have ever considered transpiling the roc IR to zig as an additional target to all the obvious targets. The reason I ask is that I have been working on a similar language to Roc (not written by me, to be clear) that transpiles its IR to zig. The author and I have been using zig for demonstrating Diverse Double-Compiling (DDC). Apart from DDC being a fun exercise for its own merits, the process of creating a well-enough-working code-generated transpiler that can actually transpile the code that generated it (sorry if that's hard to parse) is great for bug hunting. I ended up finding bugs not only in the transpiler (IR -> zig) but in the core compiler. And, of course, you have the power of zig to make things run faster.
Interestingly, the other language is not itself built from zig--it's a self-hosting language originally seeded from C# of all things. (It transpiles to C# too.)
Here's a typical program:
Chapter: Fib
Section: Math
fib : Integer -> Integer
fib (n) =
if n <= 1 then n
else fib (n - 1) + fib (n - 2)
double : Integer -> Integer
double (n) = n + n
Section: Main
opening : [Console] Nothing = act
print-line-uni (show (fib 20))
end
and after going through the transpiler it becomes:
... (runtime prelude, 774 lines shared by every program, hidden; see full)
fn fib(n: i64) i64 {
return (if ((n <= 1)) n else (fib((n -% 1)) +% fib((n -% 2))));
}
fn opening() void {
return b0: { _ = cx_print_line(cx_show_int(fib(20))); break :b0; };
}
pub fn main() void {
const stack_bytes: usize = 512 * 1024 * 1024;
const t = std.Thread.spawn(.{ .stack_size = stack_bytes }, opening, .{}) catch @panic("spawn");
t.join();
}
in general I don't want to have any compilation targets other than machine instructions or wasm
I don't want to be coupled to any other languages. I also don't want to be coupled to data formats like JSON, which is why the CLI doesn't use JSON for anything. I also don't want to be coupled to lsp long-term, although at the moment we don't have a protocol-agnostic way to get that functionality :smile:
Richard Feldman said:
we don't have a protocol-agnostic way to get that functionality
After 0.1 I would like to focus on the plugins again ... I have learnt a lot about performance and would love to expose more hooks/instrumentation and information to enable app and package authors to really understand what the compiler is doing with their code -- and at the same time enable functionality like the LSP or codegen tools to be written as Roc plugins.
Just to be 100% clear, the angle I'm proposing here is to use zig transpilation as part of building and testing roc. Obviously the pitfall would be that people might use it for real projects. Roc's already coupled to zig, unlike JSON. :)
Steve Howell said:
Diverse Double-Compiling (DDC)
We kind of have been doing this ... from LIR there are multiple backends; wasm, x64 and aarch64 crossed with elf/mach-o/coff, LLVM bitcode, and then also the interpreter. We have thousands of eval tests which compile and execute the same code through every possible combination and assert the identical result (I think that's usually 4-ways on any given target -- and it runs in a matrix on every target in CI). Almost none of the bugs we are seeing now are from the backends because of this :smile:
After type-checking there are two independent methods for lowering to LIR, one with specializations and the simpler "boxy" way. This is pretty recent, but it has also helped a lot to flush out bugs and find places where things can be improved.
If those thousands of tests got transpiled to zig, I wonder if zig would accept the contribution for its own backends? It seems like it could be synergistic.
You could lower LIR to Zig but I'm not sure that really gets you very far. Roc and Zig share the same code for generating LLVM bitcode and the same libraries for that too.
The juicy language specific stuff (unique to Roc) all happens between the type checked CIR and LIR -- that is the semantics of what is essentially Roc. I don't think you can represent the concepts in Zig syntax because it's a different language.
I'm definitely not an expert on this, don't take my word as gospel or anything. This is how I make sense of the idea.
Steve Howell said:
I wonder if zig would accept the contribution for its own backends?
I think our test harness has surfaced bugs in Zig's backends and we've reported them upstream.
Are the eval tests public? I'd like to port them for the project I'm working on.
https://github.com/roc-lang/roc/tree/main/src/eval/test
Luke Boswell said:
So we started with porting https://github.com/roc-lang/roc/blob/main/src/eval/test/eval_closure_recursion_tests.zig from Roc and it took us all of two examples to find a bug in our compiler. Thanks for sharing these! (In fairness to us, we cherry-picked the closure/recursion examples precisely because we thought they'd be especially tricky.)
Thanks Luke Boswell!
Last updated: Sep 03 2026 at 15:16 UTC