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) -> i32 { *a = 123; *b = 67; *a } ``` The code the codegen receives looks like this: ```C int foo(int *a, int *b) { *a = 123; *b = 67; return *a; } ```
## Example of backend optimization bis Compiling with -O3: ```x86asm foo: movl $123, (%rdi) movl $67, (%rsi) movl (%rdi), %eax 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 int foo(int *restrict a, int *restrict b) { *a = 123; *b = 67; return *a; } ```
## Example of backend optimization bis bis bis Again compiled with -O3: ```x86asm foo: movl $123, (%rdi) movl $67, (%rsi) movl $123, %eax ; no need to read registry! 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