## how Rust compiles

#### i promise it's actually doing something useful while you wait

the rust compilation model has surprising effects

## introduction to me

Noratrieb (she/her)
contributing to the compiler since 2021
# speed 🚀

## behind cargo build

cargo vs rustc

asciinema of cargo build -v

## what does rustc like, do?

#### a quick overview of the compilation phases

## the frontend and the backend

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph compiler
                      frontend --> backend
                    end
                    source --> frontend
                    backend --> binary

## it all starts at the source

                  pub fn add(a: u8, b: u8) -> u8 {
                    a.wrapping_add(b)
                  }

## it gets processed

                #[attr = MacroUse {arguments: UseAll}]
                extern crate std;
                #[prelude_import]
                use std::prelude::rust_2024::*;
                fn add(a: u8, b: u8) -> u8 { a.wrapping_add(b) }

## until it doesn't even look like Rust anymore

MIR

## further going to LLVM IR ``` ; meow::add define noundef i8 @add(i8 noundef %a, i8 noundef %b) #0 { start: %_0 = add i8 %b, %a ret i8 %_0 } ```
## and thne you're done ``` <add>: lea (%rsi,%rdi,1),%eax ret ```
## ok but why does my program compile so slowly now?
## backend orchestration - the how, what, and when of invoking LLVM

## a crate - the compilation unit

quite big

in C it's just a single file

## a codegen unit

LLVM is single-threaded

rustc: hi LLVM, look we are like a C file, now be fast

~1-256 depending on size and configuration (⚙️)

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    crate

                    crate --> cgu1["Codegen-Unit 1"]
                    crate --> cgu2["Codegen-Unit 2"]
                    crate --> cgu3["Codegen-Unit 3"]
                    crate --> cgu4["Codegen-Unit 4"]

## codegen units

    fn main() {}

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    mainmir["main (MIR)"]

                    subgraph mycgu1[my CGU 1]
                      mainll["main (LLVM IR)"]
                    end

                    mycgu1 --> |LLVM| mycgu1.rcgu.o

                    mainmir --> mainll

                    mycgu1.rcgu.o --> my_binary
                    std["std (and others)"] --> my_binary

## codegen units (but more)

    fn main() {}
    fn foo1() {}
    fn foo2() {}

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    mainmir["main (MIR)"]
                    foo1mir["foo1 (MIR)"]
                    foo2mir["foo2 (MIR)"]

                    subgraph mycgu1[my CGU 1]
                      mainll["main (LLVM IR)"]
                    end
                    subgraph mycgu2[my CGU 2]
                      foo1ll["foo1 (LLVM IR)"]
                      foo2ll["foo2 (LLVM IR)"]
                    end

                    mycgu1 --> mycgu1.rcgu.o
                    mycgu2 --> mycgu2.rcgu.o

                    mainmir --> mainll
                    foo1mir --> foo1ll
                    foo2mir --> foo2ll

                    mycgu1.rcgu.o --> my_binary
                    mycgu2.rcgu.o --> my_binary
                    std["std (and others)"] --> my_binary

## codegen units (cross-crate)

    fn add() {}

    fn main() { math::add() }

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph crate math
                      addmir["add (MIR)"]

                      subgraph mathcgu1[math CGU 1]
                        addll["add (LLVM IR)"]
                      end

                      addmir --> addll

                      mathcgu1 --> mathcgu1.rcgu.o

                      mathcgu1.rcgu.o --> libmath.rlib
                    end

                    subgraph my crate
                      mainmir["main (MIR)"]

                      subgraph mycgu1[my CGU 1]
                        mainll["main (LLVM IR)"]
                      end

                      mycgu1 --> mycgu1.rcgu.o

                      mainmir --> mainll

                      mycgu1.rcgu.o --> my_binary
                      libmath.rlib --> my_binary
                      std["std (and others)"] --> my_binary
                    end

# so compile times just depend on the amount of functions? - no!
## generics ```rust fn add<T: Add>(a: T, b: T) -> T::Output { a + b } add(0_u16, 0_u16); // creates add<u16> function add(0_u32, 0_u32); // creates add<u32> function ```

## instantiating generics

                fn add<T: Add>(a: T, b: T) -> T::Output { a + b }
                fn main() {
                  add(0_u16, 0_u16);
                  add(0_u32, 0_u32);
                }

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    addmir["add (MIR)"]
                    useitmir["main (MIR)"]

                    subgraph mycgu1[my CGU 1]
                      addu16ll["add_u16 (LLVM IR)"]
                      addu32ll["add_u32 (LLVM IR)"]
                      useitll["main (LLVM IR)"]
                    end

                    mycgu1 --> mycgu1.rcgu.o

                    addmir -->|instantiate with T=u16| addu16ll
                    addmir -->|instantiate with T=u32| addu32ll
                    useitmir --> useitll

                    mycgu1.rcgu.o --> my_binary
                    std["std (and others)"] --> my_binary

## generics (cross-crate)

                pub fn add<T: Add>(a: T, b: T) -> T::Output {
                  a + b
                }

                fn main() {
                  math::add(0_u16, 0_u16);
                  math::add(0_u32, 0_u32);
                }

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph crate math
                      addmir["add (MIR)"]
                    end

                    subgraph my crate
                      mainmir["main (MIR)"]
                    end

                    subgraph my crate
                      subgraph mycgu1[my CGU 1]
                        addu16ll["add_u16 (LLVM IR)"]
                        addu32ll["add_u32 (LLVM IR)"]
                        mainll["main (LLVM IR)"]
                      end

                      mainmir --> mainll
                      addmir --> addu16ll
                      addmir --> addu32ll
                      mycgu1 --> mycgu1.rcgu.o
                      mycgu1.rcgu.o --> my_binary
                      std["std (and others)"] --> my_binary
                    end

# generics are slow to compile - spend N times optimizing the function - and there's duplicate instances!
# and the duplicates get worse
## inlining ```rust fn add(a: u8, b: u8) -> u8 { a + b } fn main() { let x = add(1, 4); println!("{x}"); } ```
## let's inline it ```rust fn add(a: u8, b: u8) -> u8 { a + b } fn main() { let x = { let a = 1; let b = 4; a + b }; println!("{x}"); } ```
## cross-crate inlining ```rust pub fn add(a: u8, b: u8) -> u8 { a + b } ``` ```rust fn main() { let x = math::add(1, 4); // what is the body?... println!("{x}"); } ```
## #[inline] to the rescue ```rust #[inline] pub fn add(a: u8, b: u8) -> u8 { a + b } ``` ```rust fn main() { let x = math::add(1, 4); // 💡 it's a + b println!("{x}"); } ```

                #[inline]
                pub fn add(a: u8, b: u8) -> u8 {
                  a + b
                }

                fn main() {
                  let x = math::add(1, 4);
                  println!("{x}");
                }

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph crate math
                      addmir["add (MIR)"]
                    end

                    subgraph my crate
                      mainmir["main (MIR)"]
                    end

                    subgraph my crate
                      subgraph mycgu1[my CGU 1]
                        addll["add (LLVM IR)"]
                        mainll["main (LLVM IR)"]
                      end

                      mainmir --> mainll
                      addmir --> addll
                      mycgu1 --> mycgu1.rcgu.o
                      mycgu1.rcgu.o --> my_binary
                      std["std (and others)"] --> my_binary
                    end

## `#[inline]` (⚙️) - `#[inline]` enables cross-crate inlining of non-generic functions - for very small functions, this happens automatically - for other functions, it doesn't, because it would be slow (try with `-Zcross-crate-inline-threshold=always`) - don't over-apply it in a library, but also don't forget about it - benchmark!
## what if I want to be able to inline everything across crates?
## link-time optimization (LTO) - optimizes everything in your program together at the end - breaks crate boundaries - is awesome - is slow - comes in many forms

## lto = "fat" (monolithic)

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph crate math
                      addmir["add (LLVM IR)"]
                    end
                    subgraph crate math2
                      submir["sub (LLVM IR)"]
                    end

                    subgraph my crate
                      mainmir["main (LLVM IR)"]
                    end

                    subgraph my crate
                      subgraph fatlto[fat LTO]
                        addll["add (LLVM IR)"]
                        subll["sub (LLVM IR)"]
                        mainll["main (LLVM IR)"]
                      end

                      mainmir --> mainll
                      addmir --> addll
                      submir --> subll

                      fatlto --> my_binary
                    end

## lto = "thin" (sharded)

                  %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true, 'fontSize': '25px' }}}%%
                  flowchart LR
                    subgraph crate math
                      addmir["add (LLVM IR)"]
                    end
                    subgraph crate math2
                      submir["sub (LLVM IR)"]
                    end

                    subgraph my crate
                      mainmir["main (LLVM IR)"]
                    end

                    subgraph my crate
                      subgraph thinltosummary[ThinLTO Summary]
                      end

                      subgraph thinlto1[ThinLTO 1]
                        addll["add (LLVM IR)"]
                        subll["sub (LLVM IR)"]
                      end
                      subgraph thinlto2[ThinLTO 2]
                        mainll["main (LLVM IR)"]
                      end

                      mainmir --> thinltosummary
                      addmir --> thinltosummary
                      submir --> thinltosummary

                      thinltosummary --> mainll
                      thinltosummary --> addll
                      thinltosummary --> subll

                      thinlto1 --> my_binary
                      thinlto2 --> my_binary
                    end

## linker-plugin-lto - fat LTO style and thin LTO style - the merging is not done by rustc but by the linker - works across languages (Rust (rustc) + C (clang)) - great for FFI - annoying to set up
## inlining across codegen units in the same crate - ThinLTO across different codegen units by default
## all of this sucks - wasted duplicate work - missing optimizations without LTO
## `Cargo.toml` config ```toml [profile.release] lto = "thin" codegen-units = 1 ```
## and why did `#[inline]` make the error go away? ```rust pub fn error() { // vvvvvvvvvvv post-mono error! let _x: [u8; usize::MAX] = [0; usize::MAX]; } ```
## happy compiling