I'm planning to write an experimental new backend for the Z80—an 8-bit CPU that was widely used from the 1970s to the 1990s.
As background, I previously created a Rust-to-Z80 asm compiler that uses a multi-step process: Rust → LLVM IR → C → ASM.
(https://github.com/zlfn/rust-gb)
However, this pipeline is very complicated and unstable. I now want to write a more stable and direct backend for this old CPU.
Cranelift seems like a good candidate, but unfortunately, there isn't a clear guide for implementing a new backend, and it has been difficult for me to debug. Currently, I'm trying to write a simple proof-of-concept for Cranelift Z80 codegen. It will be support only basic instructions and contains many TODO placeholders—but I’m not sure what should I do first.
Ultimately, I want to be able to take arbitrary Rust code or a CLIF file and verify that it correctly compiles into Z80 object code.
(It seems that generating object code for a specific architecture would require modifying cranelift-object or cranelift-module, but I'm not entirely sure how.)
Since the Z80 doesn't have a standardized executable format or high level system calls, I want to generate object code in a format that's compatible with C linkers—because I want my code to work together with existing C code and libraries.
I don't have a z80 Cranelift backend yet, but today I tried testing how I would debug things if I made one.
I assumed the compilation flow would be:
Rust → CLIF → Object → Binary.
I know I can emit .clif files from Rust code using the emit=llvm-ir flag.
But how can I turn these CLIF files into object files?
Also, it looks like there is one CLIF file per function, and I don’t see any constant data in them.
There is currently no way to serialize cranelift ir to a text format in a way that you can easily feed it into cranelift-module. The cranelift ir text format only stores information for individual functions, not the names of functions or which symbols are imported by functions. And it indeed doesn't store constant data. cranelift-module is designed for streaming compilation. Basically you feed it one function or data object and then it would immediately compile it and add it to the object file that is being built (in the case of cranelift-object) or write it to memory that will be mapped executable (in the case of cranelift-jit).
Last updated: Dec 06 2025 at 07:03 UTC