手探りでClangを触り始める。アーキテクチャ編
前回の記事で、Windowsから降りるための第一歩としてClangに移ろう、という話を書いた。
戦略は決まった。だが、正直に言うと、Clangそのものについて、私はよく知らなかった。名前と、LLVMというワードと、なんとなくOSS系のコンパイラらしい、という程度の認識。30年以上プログラムを書いてきたのに、この領域は触ったことがなかった。
だから、触り始める前に、まず何者なのかを理解するところから始めることにした。この記事は、その過程の記録になる。
Clangって何者?
最初の質問はこれだった。「Clangって、GCCみたいなコンパイラでしょ?」と思っていたら、半分正解で半分違った。
Clangは、コンパイラの"フロントエンド"だった。
コンパイラは、大きく二段階に分かれている。
フロントエンド: ソースコードを解析して、中間表現に変換する
バックエンド: 中間表現を、各CPU向けの機械語に変換する
GCCやMSVCは、この両方を一体化して持っている。モノリシックな設計。
一方、Clangはフロントエンドだけ。では、バックエンドは誰がやるのか。LLVMというプロジェクトがやる。Clang(フロントエンド) + LLVM(バックエンド)で、初めてGCCやMSVCと並ぶコンパイラになる。

この分離設計が、Clangの一番の特徴だった。
分離設計が何を生むか
最初、「分離してるだけで何が嬉しいの?」と思った。でも調べていくと、ここから派生する利点が大きいことが分かってきた。
フロントエンドとバックエンドが**共通の中間表現(LLVM IR)**で繋がっている。だから、部品の付け替えができる。
他の言語のフロントエンドもLLVMに繋げる → Rust、Swift、Juliaは全部LLVMバックエンドに乗っている
静的解析ツール(clang-tidy) がフロントエンドを共有できる
エディタの補完サーバ(clangd) も同じ基盤で動く
サニタイザ(AddressSanitizer等) もLLVM経由で提供される
つまりClangは「コンパイラ」というより、C/C++のための解析・変換インフラ一式だった。MSVCを使っているだけだと見えない世界が、Clang/LLVMエコシステムには広がっている。
LLVMって、何の略?
ここで素朴な疑問。LLVMって何の略語?
調べると、元々は "Low Level Virtual Machine" だった。ただし今は正式には略語ではない。公式には「LLVMというプロジェクト名であり、もはや略語ではない」という扱い。
歴史的経緯がある。初期のLLVMは「中間表現を解釈実行する仮想マシン」という構想も持っていたらしい。だが実際に育ったのは静的コンパイラ基盤としての方向で、仮想マシンではなくなった。名前の由来と実態がズレたので、略語の解体が進んだ、ということらしい。
IRは Intermediate Representation(中間表現)。これは一般的なコンパイラ用語で、LLVMに限らない。GCCにもGIMPLEというIRがある。
LLVM IRを見てみる
百聞は一見に如かず。LLVM IRが実際にどんなものか、見てみる。
Cで書いた int add(int a, int b) { return a + b; } をLLVM IRにすると、こんな感じらしい:
define i32 @add(i32 %a, i32 %b) {
%sum = add i32 %a, %b
ret i32 %sum
}
アセンブリっぽい。だが、特定のCPUには依存していない。i32という抽象型を使い、レジスタ割り当てもまだ行われていない。ここまでがClangの仕事で、ここから先のCPU特化処理はLLVMバックエンドの仕事。
なるほど、と思った。CPU差を吸収する層があって、そこから先が分かれるわけだ。
誤解1: LLVM IRはプラットフォーム非依存ではない
ここで、私は大きな誤解をしていた。
「LLVM IRって、JavaバイトコードみたいなWrite Once Run Anywhereじゃないの?」
違った。LLVM IRはプラットフォーム非依存ではない。
LLVM IRのファイルを見ると、先頭にこんな記述がある:
target datalayout = "e-m:e-p270:32:32-..."
target triple = "x86_64-pc-windows-msvc"
ターゲットOSとABIが、IRの時点で明記されている。windows-msvc / linux-gnu / linux-musl でIRは変わる。構造体パッキングも、呼び出し規約も、例外処理方式も違う。
さらに致命的なのは、OS APIのシンボル名はそのままIRに載ること。CreateThreadというWin32関数を呼ぶコードは、LLVM IRに落としてもCreateThreadのままだ。LLVMはこれを「OS固有かどうか」を判定しない。単に「外部シンボルを呼ぶ」という情報として記録するだけ。
つまり、LLVM IRが抽象化しているのはCPU差だけ。OS差は何も抽象化しない。
比較表にするとこうなる:

LLVMはネイティブコード生成インフラであって、仮想マシンではない。名前に"VM"が残っているのは歴史的経緯で、実態は違う。
これが分かると、移植に対する期待値が調整される。「LLVMに乗せればOS差が消える」という甘い話ではない。OS API依存は、ソースコードレベルで剥がすしかない。
誤解2: Clangは一つじゃない
Windowsでの運用を調べていて、もう一つ戸惑ったことがある。
「Clangを使う」と言っても、実は2つの顔がある。
clang-cl — MSVC互換ドライバ。/O2 /W4 /Fo: といったMSVCのオプション文法を受け付ける
clang — Unix風ドライバ。-O2 -Wall -o といったGCC的な文法

中身のコンパイラは同じ。違うのは「顔」(ドライバ)と、それに伴うABIやリンカ周りの立ち位置。
clang-clは、MSVCの世界にそのまま乗る。Windows SDK、MSVCのABI、link.exe。既存のVisual Studioプロジェクトのコンパイラパスをcl.exeからclang-cl.exeに差し替えるだけで動く。
clang(素)は、UNIX風の別世界を作る。リンカはlld、ランタイムはMinGW-w64など、MSVC系を避けた構成。GCC/Itanium ABI互換。
この2つ、ABIが違うのでオブジェクトファイルに互換性がない。MSVCでビルドされた既存の.libとリンクしたいならclang-cl、Linux移行を見据えるなら素clang、という使い分けになる。
前回の記事の文脈で言うと、Linux移行を視野に入れるなら素clang一択。clang-clで中間ビルドを作ると、Linux移行時にもう一度ABI剥がしが必要になる。
マングリング、という概念
ここでマングリング(name mangling)という用語に出会った。正直、30年以上プログラムを書いてきて、初めて聞いた。
考えてみれば、これも自然な話だ。Cには原則マングリングがないので、Cを中心にやっていると一生出会わない。C++をやる人、あるいはコンパイラ間の.lib受け渡しで詰まる人が遭遇する概念らしい。
C++にはオーバーロードがある:
int add(int, int);
double add(double, double);
namespace math { int add(int, int); }
ソース上は全部「add」だが、リンカは文字列マッチングしかできない。そこで、コンパイラが型情報・名前空間を全部エンコードして、ユニークな文字列に変換してからリンカに渡す。これがマングリング、ということらしい。
実例を見ると、同じint add(int, int)が、ABIによってこう変わる:
GCC/Clang(Itanium ABI): _Z3addii
MSVC: ?add@@YAHHH@Z
全く別物。これが、clang-clと素clangで.obj/.libが互換性を失う直接の原因だった。
そして、extern "C"でマングリングを止められる、と知った。C++からCライブラリを呼ぶときのあれ。
#ifdef __cplusplus
extern "C" {
#endif
// C関数の宣言
#ifdef __cplusplus
}
#endifFAのベンダー提供ヘッダで、よく見るやつ。これが何のための呪文なのか、ずっと「おまじない」として扱っていた。今回調べて、初めて意味を理解した。「C++から呼ばれてもマングリングしないで」という約束だった。
長年書いてきたコードの中に、意味を理解しないまま書いていた箇所があった、ということになる。ちょっと気恥ずかしい発見だったが、知らないまま通過していた概念に名前が付いたのは、収穫だ。
全体像の整理
ここまで調べて、Clang/LLVMの全体像が頭に入った。概念の地図、という感じ。
Clangはフロントエンド、LLVMがバックエンド。分離設計
LLVM IRはCPU差を吸収するがOS差は吸収しない。移植の期待値を下げる必要がある
Clangには2つの顔(clang-cl / 素clang)がある。ABIが違うので混ぜられない
マングリング方式がABIの境界線。C++の互換性問題の本丸
「Clang = GCCの別実装」ぐらいの認識だったところから、**「LLVMエコシステム全体を背負ったツール群」**という認識に上書きされた。これは、触る前に知っておいてよかった。
次は何をするか
概念は掴めたので、次は実際に触るフェーズに入る。
LLVMをWindowsにインストールする
小さいプロジェクトをClangでビルドしてみる
MSVCとの差を、警告や診断メッセージで体感する
手を動かしながら、「思ってたのと違う」ポイントを拾っていく。たぶん、また誤解が見つかるはず。そのたびに記事にしていこうと思う。
触り始める前に、地図を描く時間を取った。地図を持たずに歩き始めると、どこで詰まっているのか自分でも分からなくなる。今回はそれを避けたかった。
コンパイラの話は深い。深入りすれば、ドラゴンブックやタイガーブックの世界に入っていく。だが、今の目的はプラットフォーム移行であって、コンパイラ研究ではない。必要な分だけ、道具として理解する。そういう距離感で付き合っていこうと思う。
