このページの目次

GCC と Clang の内部

カバー範囲: コンパイルパイプライン (フロントエンド/ミドルエンド/バックエンド) → IR (GIMPLE/LLVM IR) → LTO → PGO → Sanitizer → コンパイラ最適化一覧 対象: GCC 12+, Clang 15+, ユーザー空間コンパイル

コンパイルパイプライン

flowchart TD
    SRC["📄 C ソースコード"]

    SRC --> FRONTEND["🔍 フロントエンド (言語依存)<br/>プリプロセス + 字句解析 + 構文解析 + 意味解析"]

    FRONTEND --> AST["🌳 抽象構文木 (AST)"]

    AST --> MIDDLE["⚙️ ミドルエンド (言語非依存、アーキテクチャ非依存)<br/>GIMPLE (GCC) または LLVM IR (Clang)<br/>→ 最適化パス: 不要コード削除、ループ最適化、ベクトル化、..."]

    MIDDLE --> BACKEND["🔧 バックエンド (アーキテクチャ依存)<br/>レジスタ割り当て + 命令選択 + 命令スケジューリング"]

    BACKEND --> ASM["📝 対象アセンブリ (.s)"]

    ASM --> ASSEMBLER["🔗 アセンブラ"]
    ASSEMBLER --> OBJ["📦 オブジェクトファイル (.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 と LLVM IR

GCC GIMPLE (GNU Compiler Collection):
  3アドレス形式 → 約200個の最適化パス
  形式: 関数 → ベーシックブロック → GIMPLE文
  -fdump-tree-all を使用して各最適化パスの出力を観察

LLVM IR (Clang):
  SSA形式 → 高級言語に近い
  -emit-llvm → .ll テキストを出力 → 可読性が高い
  opt → 独立した最適化ツール (clangとは独立)
  llc → 独立したコードジェネレータ

LTO: リンク時最適化

# GCC LTO:
gcc -flto -O2 -c a.c b.c
gcc -flto -O2 -o prog a.o b.o  # リンク時にプログラム全体のIRを参照 → ファイル間最適化

# Thin LTO (Clang、高速なインクリメンタル LTO):
clang -flto=thin -O2 -c a.c b.c

# LTO の典型的な効果:
#   - ファイルを跨ぐ小規模関数のインライン化 (コストモデルで数千〜数万の関数を評価可能)
#   - 不要コードの削除 (プログラム全体を解析 → 未呼び出し関数を削除)
#   - 定数伝播 (ファイル間を跨ぐ定数の追跡)
#   バイナリサイズ: ↓5-15%, パフォーマンス: ↑2-5%

PGO: プロファイル誘導型最適化

# 1. インストルメンテーション (計測):
gcc -fprofile-generate -o prog prog.c
./prog  # 実行 → .gcda ファイル (プロファイルデータ) を生成

# 2. 再コンパイル (プロファイルで誘導):
gcc -fprofile-use -O2 -o prog prog.c

# PGO の典型的な効果:
#   - ブランチ予測の最適化 (ホットパス上のコード再配置)
#   - インライン化判断の精度向上 (頻繁に呼び出される関数 → インライン化)
#   - レジスタ割り当ての最適化 (ホット変数の優先度が高い)
#   パフォーマンス: ↑5-15%

Sanitizer: 実行時エラー検出

# Address Sanitizer (ASAN): ヒープ境界外アクセス、UAF、スタックオーバーフロー
gcc -fsanitize=address -g -o prog prog.c

# Undefined Behavior Sanitizer (UBSAN):
gcc -fsanitize=undefined -o prog prog.c
# 検出対象: 整数オーバーフロー、シフト範囲外、null dereference、型アライメント

# Thread Sanitizer (TSAN): データ競合
gcc -fsanitize=thread -g -o prog prog.c

# Memory Sanitizer (MSAN): 未初期化読み込み (Clang のみ)
clang -fsanitize=memory -g -o prog prog.c

# オーバーヘッド:
#   ASAN: 約2倍の低速化、約2倍のメモリ使用
#   TSAN: 約5-10倍の低速化、約5-10倍のメモリ使用
#   UBSAN: 約1.2倍の低速化 (最も軽量、本番環境でも利用可能)

一般的な最適化オプション

-O0: 最適化なし (デバッグ用)
-O1: 基本最適化 (コードサイズを増加させない)
-O2: 標準最適化 (-O1 + 命令スケジューリング + 別名解析 + ...) 
-O3: 積極的最適化 (-O2 + ベクトル化 + より積極的なインライン化)
-Os: サイズ最適化 (-O2 ただし、サイズを増加させる最適化をスキップ)

# 特定の最適化:
-fomit-frame-pointer  # フレームポインタを保持しない (レジスタが1つ増え、若干高速化、ただしデバッグが困難)
-march=native         # 本机CPU向けに最適化 (AVX2/BMI/... を使用)
-ftree-vectorize      # 自動ベクトル化 (SIMD)
-funroll-loops        # ループ展開 (サイズが増加する可能性あり、高速化する可能性あり)
-fno-semantic-interposition  # PLT間接参照を生成しない (ライブラリ内呼び出しが高速化)

コンパイル成果物の分析

# 最適化結果の観察:
gcc -O2 -S -o - prog.c | less     # アセンブリ出力
gcc -O2 -fdump-tree-all prog.c    # GCC: すべての GIMPLE 最適化パス
clang -O2 -Rpass=loop-vectorize prog.c  # Clang: 最適化判断レポート

# バイナリ分析:
objdump -d prog | less             # 逆アセンブル
nm prog | grep -v '^_'             # シンボルテーブル
size prog                          # セグメントごとのサイズ

参考

  • ドキュメント⁠: GCC Internals Manual, LLVM Language Reference
  • LWN: "LTO and PGO", "Sanitizers in GCC and Clang"

キーワード: GCC, Clang, GIMPLE, LLVM IR, LTO, ThinLTO, PGO, ASAN, UBSAN, TSAN, 最適化フラグ