On this page

GCC and Clang Internals

Coverage: Compilation Pipeline (frontend/middle-end/backend) → IR (GIMPLE/LLVM IR) → LTO → PGO → Sanitizers → Compiler Optimization Overview Applicable to: GCC 12+, Clang 15+, User-space compilation

Compilation Pipeline

flowchart TD
    SRC["📄 C Source Code"]

    SRC --> FRONTEND["🔍 Frontend (Language-specific)<br/>Preprocessing + Lexical Analysis + Syntax Analysis + Semantic Analysis"]

    FRONTEND --> AST["🌳 Abstract Syntax Tree (AST)"]

    AST --> MIDDLE["⚙️ Middle-end (Language-independent, Architecture-independent)<br/>GIMPLE (GCC) or LLVM IR (Clang)<br/>→ Optimization Passes: Dead Code Elimination, Loop Optimization, Vectorization, ..."]

    MIDDLE --> BACKEND["🔧 Backend (Architecture-specific)<br/>Register Allocation + Instruction Selection + Instruction Scheduling"]

    BACKEND --> ASM["📝 Target Assembly (.s)"]

    ASM --> ASSEMBLER["🔗 Assembler"]
    ASSEMBLER --> OBJ["📦 Object File (.o)"]

    classDef src fill:#e3f2fd,stroke:#1565c0
    classDef step fill:#f3e5f5,stroke:#7b1fa2
    classDef ir fill:#fff3e0,stroke:#ef6c00
    classDef out fill:#e8f5e9,stroke:#2e7d32
    class SRC,OBJ src
    class FRONTEND,BACKEND,ASSEMBLER step
    class MIDDLE ir
    class AST,ASM out

GCC GIMPLE vs LLVM IR

GCC GIMPLE (GNU Compiler Collection):
  3-address code form → ~200 optimization passes
  Format: Function → basic blocks → GIMPLE statements
  Use -fdump-tree-all to observe output from each optimization pass

LLVM IR (Clang):
  SSA form → closer to high-level languages
  -emit-llvm → outputs .ll text → human-readable
  opt → standalone optimizer tool (independent of clang)
  llc → standalone code generator

LTO: Link-Time Optimization

# GCC LTO:
gcc -flto -O2 -c a.c b.c
gcc -flto -O2 -o prog a.o b.o  # At link time, sees the entire program's IR → cross-file optimization

# Thin LTO (Clang, faster incremental LTO):
clang -flto=thin -O2 -c a.c b.c

# Typical benefits of LTO:
#   - Inlining of small functions across files (cost model can see thousands of functions)
#   - Dead code elimination (analyzes the entire program → removes un-called functions)
#   - Constant propagation (tracks constants across files)
#   Binary size: ↓5-15%, Performance: ↑2-5%

PGO: Profile-Guided Optimization

# 1. Instrument:
gcc -fprofile-generate -o prog prog.c
./prog  # Run → generates .gcda files (profile data)

# 2. Recompile (using profile to guide):
gcc -fprofile-use -O2 -o prog prog.c

# Typical benefits of PGO:
#   - Branch prediction optimization (rearranging code on hot paths)
#   - More accurate inlining decisions (frequently called functions → inlined)
#   - Better register allocation (hot variables get higher priority)
#   Performance: ↑5-15%

Sanitizers: Runtime Error Detection

# Address Sanitizer (ASAN): heap out-of-bounds, UAF, stack overflow
gcc -fsanitize=address -g -o prog prog.c

# Undefined Behavior Sanitizer (UBSAN):
gcc -fsanitize=undefined -o prog prog.c
# Detects: integer overflow, shift out of bounds, null dereference, type alignment

# Thread Sanitizer (TSAN): data races
gcc -fsanitize=thread -g -o prog prog.c

# Memory Sanitizer (MSAN): uninitialized reads (Clang only)
clang -fsanitize=memory -g -o prog prog.c

# Overhead:
#   ASAN: ~2x slower, ~2x memory usage
#   TSAN: ~5-10x slower, ~5-10x memory usage
#   UBSAN: ~1.2x slower (lightest weight, production-safe)

Common Optimization Flags

-O0: No optimization (for debugging)
-O1: Basic optimization (does not increase code size)
-O2: Standard optimization (-O1 + instruction scheduling + alias analysis + ...) 
-O3: Aggressive optimization (-O2 + vectorization + more aggressive inlining)
-Os: Optimize for size (-O2 but skips optimizations that increase size)

# Specific optimizations:
-fomit-frame-pointer  # Do not keep frame pointer (one extra register, slightly faster, but debugging is harder)
-march=native         # Optimize for the local CPU (uses AVX2/BMI/...)
-ftree-vectorize      # Auto-vectorization (SIMD)
-funroll-loops        # Loop unrolling (may increase size, may be faster)
-fno-semantic-interposition  # Do not generate PLT indirection (faster calls within libraries)

Analyzing Compilation Artifacts

# Observe optimization results:
gcc -O2 -S -o - prog.c | less     # Assembly output
gcc -O2 -fdump-tree-all prog.c    # GCC: All GIMPLE optimization passes
clang -O2 -Rpass=loop-vectorize prog.c  # Clang: Optimization decision report

# Binary analysis:
objdump -d prog | less             # Disassembly
nm prog | grep -v '^_'             # Symbol table
size prog                          # Size of each section

References

  • Documentation: GCC Internals Manual, LLVM Language Reference
  • LWN: "LTO and PGO", "Sanitizers in GCC and Clang"

Keywords: GCC, Clang, GIMPLE, LLVM IR, LTO, ThinLTO, PGO, ASAN, UBSAN, TSAN, optimization flags