Rust GCC backend: Why and how
by Guillaume Gomez
Who am I?
Rust language reviewer and contributor.
Member of:
- rustdoc team (team leader)
- compiler team
- docs.rs team
- dev-tools team
- clippy-contributors team
I am an engineer at Huawei.
## Passes of the Rust compiler (simplified, a lot)
* AST: checks that the syntax is valid
* HIR: checks if types are valid
* MIR: checks lifetimes and runs borrow-checker
* codegen: generate binary code
## Backend and front-end
What's the difference?
## What's the point of having a GCC backend?
* LLVM was created in 2003 and GCC in 1987, lot of old targets will never be supported by LLVM
* GCC modules (allowed to detect a bad lifetime case in `librsvg`)
* In some contexts, one is generating better binaries than the others, so at least you have the choice
* Want to compile games on Dreamcast? You can with the GCC backend
## Difference between gccrs and the GCC backend
* gccrs: front-end
* GCC backend: well, backend
## Calling GCC internal API from Rust compiler
`libgccjit` to the rescue!
## How to implement Rust compiler backend
`rustc_codegen_ssa` provides an abstract interface that backends need to implement through traits.
## Entrypoint
```rust
#[no_mangle]
pub fn _rustc_codegen_backend() -> Box {
// This is the entrypoint.
}
```
## Example of implementing "const strings"
```rust
impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> {
/// Returns the pointer to the string and its length.
fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) {
// Call GCC API to declare this string.
}
}
```
### Example of implementing "const strings" bis
```rust
fn const_str(&self, s: &str) -> (RValue<'gcc>, RValue<'gcc>) {
let mut cache = self.const_str_cache.borrow_mut();
let str_global = cache.get(s).copied().unwrap_or_else(|| {
let st = self.context.new_string_literal(s);
let sym = self.generate_local_symbol_name("str");
let g = self.declare_private_global(&sym, self.val_ty(st));
cache.insert(s.to_owned(), g);
g
});
let cs = self.const_ptrcast(str_global.get_address(None),
self.type_ptr_to(self.layout_of(
self.tcx.types.str_).gcc_type(self)),
);
(cs, self.const_usize(s.len() as _))
}
```
## Optimizations done in backends
Rust compiler has extra information it can communicate to the codegen.
## Example of backend optimization
```rust
fn foo(a: &mut i32, b: &mut i32) {
*a += 1;
*b += 1;
*a += 1;
*b += 1;
}
```
The code the codegen receives looks like this:
```C
void foo(int *a, int *b) {
*a += 1;
*b += 1;
*a += 1;
*b += 1;
}
```
## Example of backend optimization bis
Compiling with -O3:
```x86asm
foo:
addl $1, (%rdi)
addl $1, (%rsi)
addl $1, (%rdi)
addl $1, (%rsi)
ret
```
## Example of backend optimization bis bis
Now since it's a mutable references, we know the pointer can never point to the same memory location so we can add restrict:
```C
void foo(int *restrict a, int *restrict b) {
*a += 1;
*b += 1;
*a += 1;
*b += 1;
}
```
## Example of backend optimization bis bis bis
Again compiled with -O3:
```x86asm
foo:
addl $2, (%rdi)
addl $2, (%rsi)
ret
```
Written version of this blog post
blog.guillaume-gomez.fr/articles/2025-12-15+Rust+GCC+backend%3A+Why+and+how
Reading recommendations
Matt Godbolt (creator of godbolt.org) is writing an "Advent of Compiler Optimizations": https://xania.org/AoCO2025
Thank you for listening!
More Rust things on
< blog.guillaume-gomez.fr >
< guillaume1.gomez@gmail.com >
@GuillaumeGomez
@imperio@toot.cat
@imperioworld.bsky.social