Gotta respect the dedication to a niche interest.
> The primary goal of this is support for old/obscure hardware with no LLVM/GCC support.
I remember reading about the bootstrapping question, how it typically requires a Rust compiler to build the Rust compiler from source. https://bootstrapping.miraheze.org/wiki/Bootstrapping_Specif...
Oh, but I see there's a C++ implementation of the Rust compiler. https://github.com/thepowersgang/mrustc
Anyway, this part sounds useful too, that crustc can compile across network and devices.
> You build a small C server on your Blorbo OS, run rustc on some normal platform like Linux, and let cilly talk over the wire.
More on the Rust bootstrapping process (2018): https://guix.gnu.org/blog/2018/bootstrapping-rust/
Excited to see the compiler implementation when it's out -- a lot to learn from.
The landing page mentions Plan 9 as one of the systems.
[1] https://github.com/tsoding/crust [2] https://github.com/bext-lang/b [3] https://www.nokia.com/bell-labs/about/dennis-m-ritchie/kbman...
this is the wrong direction
(jk i read the readme)
Use crustc to compile the rust source code, producing a new compiler. Then use this new compiler and the official rustc binary, both with deterministic flags, to compile the rust source code again. The two outputs should match bit for bit.
What a shame. I would've read an article about this.
I know some folks within the bootstrappable OS projects community are on Hackernews and I hope that they could take a look at this. I feel as if this project could drastically shrink down the efforts needed to get a working rust compiler in a bootstrappable manner.
There are relatively few people who understand Rust’s compiler internals, LLVM backends, and obscure target support deeply. But there are many engineers who understand C compilers, ABIs, linkers, makefiles, cross-compilation, old operating systems, and weird platform-specific compiler behavior.
If Rust can be lowered into target-specific C, then some problems stop being exclusively “Rust compiler problems” and also become C toolchain problems. That means more people can inspect the generated C, build failures, linker errors, ABI mismatches, and compiler-specific behavior.
C is obviously not a magic portability layer. ABI details, integer widths, alignment, TLS, aliasing, and undefined behavior still matter. But as an ecosystem boundary, C gives many more engineers a way to participate in debugging and porting work.
I think that social/maintenance aspect may be more important than the language translation itself.
> Don't post generated text or AI-edited text. HN is for conversation between humans.
crustc - rustc 1.98.0-nightly (c712ea946 2026-06-16), converted to 46 million lines of C.This is a functional Rust compiler you can build with GCC & make.
# We need to provide a path to LLVM(`libLLVM.so.22.1-rust-1.98.0-nightly`)
# I *could* include pre-built LLVM in the project, but I'd rather not embed random binaries in the project.
make -j20 LLVM_LIB_DIR=~/.rustup/toolchains/nightly-2026-06-16-aarch64-unknown-linux-gnu/lib
It is just C code [1], which, when compiled, gives you a functional Rust compiler.
# It works - (library path to point to libLLVM.so.22.1-rust-1.98.0-nightly - rustc uses llvm)
LD_LIBRARY_PATH=~/.rustup/toolchains/nightly-2026-06-16-aarch64-unknown-linux-gnu/lib:./rustc_driver ./rustc/rustc --version
rustc 1.98.0-nightly (c712ea946 2026-06-16)
That Rust compiler can compile code - build core, alloc, std - you name it!
This is a demo/teaser for my new Rust to C compiler toolchain.
The full cilly toolchain compiles your own Rust to C for arbitrary targets.
This repo just shows the compiler compiling itself, as I believe this is the flashiest showcase I could do.
For the past 3 years, I have been working on compiling Rust to C. I made a few public attempts, like rustc_codegen_clr, and a lot of private ones.
This is, by my count, the 14th attempt: cilly. It is a Rust library for generating C code and a Rust compiler backend (read: plugin) that allows you to compile Rust to C.
The main innovation behind cilly is that it adapts to C compilers.
It can generate "witness" programs, which check what a given compiler and platform support:
/* This compiles if and only if our C compiler supports _Thread_local. */
_Thread_local int KEYWORD_TLS_SUPPORTED;
This means Cilly will generate C code, which will make your specific, weird "Shminky C compiler for Blorbo OS" happy.
/* This will pass in some C compilers. */
assert(sizeof(float) == sizeof(double));
All type layouts, sizes, alignments, character encodings (ASCII), and integer formats (two's complement) are queried for.
With fallbacks, where possible.
I try my very best not to assume anything outside of ANSI C[2] - including workarounds for things in "modern" C standards, like strict aliasing.
Sadly, this means the output of cilly is compiler-specific (i.e., you can't take the cilly C generated for Arm64 and run it on riscv32, but you can generate cilly C specifically for riscv32).
This build of rustc (the generated C) is "targeting" ARM64 Linux because that is the ISA of my workstation.
The primary goal of this is support for old/obscure hardware with no LLVM/GCC support.
There are still some systems out there that don't support Rust but support C.
Whenever some project moves from Rust to C, or a Rust alternative of a C project is made, support for those targets is validly raised as a downside of Rust [3].
The goal of this project is to remove that problem.
cilly wraps rustc and a C compiler and translates the Rust code to C on the fly.
From the user perspective, this is as simple as defining what C compiler to use for a given target.
"triple": [
"sdcc_z180-unknown-none"
],
"tool_def": {
"kind": "local",
"compile": {
"base": {
"executable": "/usr/bin/sdcc",
"base_args": [
"-mz180",
"--std-c89",
"-c"
],
"input_arg_template": [
"{input}"
],
// JSON cut off for brevity.
}
}
}
cilly is network transparent, and can talk to C compilers over TCP (may be extended to weird things like UART if need be).
This is a solution to the bootstrap paradox / platforms without C cross compilers.
You build a small C server on your Blorbo OS, run rustc on some normal platform like Linux, and let cilly talk over the wire.
I have successfully used this to compile small Rust programs for x86 Plan9 VMs, while running rustc on Arm64 linux.
term% echo `{cat /dev/sysname} osversion `{cat /dev/osversion} cputype $cputype
gnot osversion 2000 cputype 386
term% /tmp/hello_plan9
Hello, world!
term% nm /tmp/hello_plan9 | grep rust_begin_unwind
1020 T _RNvCshfEkAwg4zv6_7___rustc17rust_begin_unwind
cilly can optionally embed markers within its object files, and save its IR to a cache directory.
It can then read those markers, split functions / globals by their definition location, and generate
a directory with makefiles - to allow you to build Rust with a C compiler and make.
cilly generated code is mostly ABI compatible with normal rustc compiled code. I say mostly, because on some platforms(... like arm64)
rustc choose an ABI not representable from C[4].
This rust compiler was built on:
uname -a
Linux spark-2773 6.17.0-1021-nvidia #21-Ubuntu SMP PREEMPT_DYNAMIC Wed May 27 19:14:05 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux
This is the C compiler I used:
readelf -p .comment ./rustc/rustc
String dump of section '.comment':
[ 1] GCC: (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
[ 2e] Linker: Ubuntu LLD 18.1.3
In order to build the demo, you will need to provide it with the right LLVM libs. The easies way to do so is to just use the LLVM rustc ships:
rustup install nightly-2026-06-16
With the right GCC(more modern GCC versions should work too, untested), right LLVM version, and GNU make installed, run:
# We need to provide a path to LLVM(`libLLVM.so.22.1-rust-1.98.0-nightly`)
# I *could* include pre-built LLVM in the project, but I'd rather not embed random binaries in the project.
make -j20 LLVM_LIB_DIR=~/.rustup/toolchains/nightly-2026-06-16-aarch64-unknown-linux-gnu/lib
# CFLAGS work(CAVEAT: some flags will slow compilation down)
make -j20 CFLAGS=-g
And... voilà!
I strongly recommend not enabling optimizations: both because the may break stuff(this is just a demo, and it's... ee... rough around the edges[5]) AND because optimizations take time at this scale.
Without opts, my machine builds the project in a few minutes:
make -j20 937.98s user 123.77s system 1352% cpu 1:18.48 total
With opts, expect to choke on some specific larger rust files. You will blitz through most code, and then get stuck on those behemoths.
Run:
LD_LIBRARY_PATH=~/.rustup/toolchains/nightly-2026-06-16-aarch64-unknown-linux-gnu/lib:./rustc_driver ./rustc/rustc --version
You should see the rustc version printed.
For building programs, you will need to build std.
LD_LIBRARY_PATH=~/.rustup/toolchains/nightly-2026-06-16-aarch64-unknown-linux-gnu/lib:./rustc_driver ./rustc/rustc main.rs
error[E0463]: can't find crate for `std`
|
= note: the `aarch64-unknown-linux-gnu` target may not be installed
= help: consider downloading the target with `rustup target add aarch64-unknown-linux-gnu`
= help: consider building the standard library from source with `cargo build -Zbuild-std`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0463`.
Read BUILDING_STD.md for that.
For some weird path-canonicalization-reasons, crustc can crash when run in the directory it was built in(repo root, crustc). Works fine in other places.
...
I am confused too.
cilly out?Nah, not yet. It is not ready for public consumption. I will release it as soon as possible - but
1 - well, C code + some C++ LLVM wrappers. Rust uses a bit of C++ to expose some more LLVM knobs. They are shipped pre-compiled because they are LLVM-version-specific and a pain to build standalone.
2 - rarely, I have to make "reasonable assumptions" which would be goal stoppers. E.g. (void*)(uintptr_t)(ptr) roundtrips. I document those AND add asserts for them (CHAR_BIT = 8) if possible.
3 - the weight of "Rust does not support Plan 9" as an argument against Rust depends on personal preferences, and evaluating that argument is left as an exercise to the reader.
4 - The issue is the struct return pointer(sret). On most platforms, it is passed in the same reg as the first arg, so we can always force to return-by-sret(out pointer) by just having the first arg be the out pointer. Not on Arm64. There, sret pointer is passed in a different register, meaning the native C compiler has to chose to do a return-by-sret for us. Which it does not do for small structs(<16 bytes afaik). Annoying.
5 - I am chasing some optimization related bugs - part of the reason full cilly toolchain is not out.
Would this be useful for this too?
From https://news.ycombinator.com/item?id=46265855 :
> To better port C to Rust: 3C (Checked C), c2rust, Crown ownership analysis, RustMap, c2saferrust (LLM), Laertes
C -> Checked C -> Rust
Because Checked C will annotate the raw and other C pointers first.
Wouldn't it be easier to add old hardware support to LLVM/GCC instead? I adore the project scale and determination, but for this goal extending existing projects seems more logical than building a language translator.
But it doesn't mean rustc generates code for that target, only that you can run it there. You'd still have to teach LLVM about the target. Although that might already have been done.
It's not that useful for retro computers because the Rust compiler needs too much memory for most machines of the 32-bit era.
No, in fact it's much, much harded. You have no idea of the scope. I have no idea of the scope. Nobody does. There are obscure machines we've never heard about and there are C compilers for them. Targeting and supporting them from modern toolchains is a fool's errand.
on the other hand, porting llvm to an infinite number of platforms requires an infinite amount of work
so, it is less work this way
It is said microsoft rust syntax is on the same brain damage complexity level than ISO c++. That, and I don't even talk about the technical cost of its runtime (not far from the toxicity of a jvm?)
- You can compare any two pointers, while in C they must point to the same allocation. This is possible to solve by converting to integers first.
- Signed integer overflow is UB in C, defined to wrap/panic in Rust.
- Type-based alias analysis is a big one, does not exist in Rust.
Edit: On second thought, that's only needed if you want to run rustc itself on the old hardware, which is probably not super useful given the main reason you would need to do this is if LLVM can't target that hardware.
For building code written in Rust for such old hardware, this would be sufficient.
Indeed specifically Rust defines that pointer comparisons are done by address, whereas C doesn't specify what the rules are exactly. This gets sticky if the pointer was invalidated (e.g. you free'd the memory it was pointing at). Rust says - as you might expect - that you can still compare this invalid pointer to another pointer (which may or may not still be valid and indeed might be a valid pointer to the same address!) because we're only comparing the address - but C++ says you mustn't do that and I believe C has the same rule.
> Signed integer overflow is UB in C, defined to wrap/panic in Rust.
This doesn't feel like an interesting difference. C made a weird choice, which was maybe convenient for the usual Worse Is Better reasons 50+ years ago. LLVM doesn't care about that choice, LLVM can cheerfully simulate this behaviour in C if you want that.
> Type-based alias analysis is a big one, does not exist in Rust.
This also doesn't feel relevant but I guess I'm interested in why you think it would make "a big" difference?
Edited: I confirmed C11 has "pointer zap" which is the behaviour I described above, and I believe C23 also still has pointer zap although paulmck and co. are trying to get rid of it or do something on this topic in C2y.