FIXME

文中のFIXME他直すべきものをここにリストにする。

  • だ・である調をです・ます調に変える。

この文書について

この文書はAsciiDocを用いて執筆されています。

この文書はGitによって管理され、 GitHubリポジトリにて 公開されています

この作品は、クリエイティブ・コモンズの 表示 4.0 国際 ライセンスで提供されています。ライセンスの写しをご覧になるには、 http://creativecommons.org/licenses/by/4.0/ をご覧頂くか、Creative Commons, PO Box 1866, Mountain View, CA 94042, USA までお手紙をお送りください。

本文書の内容は筆者が独自に調査したものです。 疑う余地なく誤りが含まれます。誤りに気づかれた方はGitHubリポジトリなどを通じて ご連絡ください。

LLVMバックエンド概略

本書ではRISC-V風味の独自ISAを例にLLVMバックエンドを開発します。

使用するLLVMのバージョンはv9.0.0です。

ところで

一度もコンパイラを書いたことがない人は、この文書を読む前に 『低レイヤを知りたい人のためのCコンパイラ作成入門』[50]などで一度 フルスクラッチからコンパイラを書くことをおすすめします。

また[51]などを参考に、 LLVMではなくGCCにバックエンドを追加することも検討してみてはいかがでしょうか。

参考にすべき文献

LLVMバックエンドを開発する際に参考にできる書籍やWebサイトを以下に一覧します。 なおこの文書では、RISC-Vバックエンド及びそれに関する技術資料を大いに参考しています。

Webページ

  • Writing an LLVM Backend[18]

    • 分かりにくく読みにくい。正直あんまり見ていないが、たまに眺めると有益な情報を見つけたりもする。

  • The LLVM Target-Independent Code Generator[31]

    • [18]よりもよほど参考になる。LLVMバックエンドがどのようにLLVM IRをアセンブリに落とすかが明記されている。必読。

  • TableGenのLLVMのドキュメント[21]

    • 情報量が少ない。これを読むよりも各種バックエンドのTableGenファイルを読むほうが良い。

  • LLVM Language Reference Manual[43]

    • LLVM IRについての言語リファレンス。LLVM IRの仕様などを参照できる。必要に応じて読む。

  • Architecture & Platform Information for Compiler Writers[68]

    • LLVMで公式に実装されているバックエンドに関するISAの情報が集約されている。Lanaiの言語仕様へのリンクが貴重。

  • RISC-V support for LLVM projects[10]

    • どちゃくそに参考になる。以下の開発はこれに基づいて行う。

    • LLVMにRISC-Vサポートを追加するパッチ群。バックエンドを開発するためのチュートリアルも兼ねているらしく docs/ 及びそれと対応したpatchが参考になる。

    • またこれについて、開発者が2018 LLVM Developers' Meetingで登壇したときの動画は[11]より閲覧できる。スライドは[30]より閲覧できる。

    • そのときのCoding Labは[48]より閲覧できる。

  • Create an LLVM Backend for the Cpu0 Architecture[35]

    • Cpu0という独自アーキテクチャのLLVMバックエンドを作成するチュートリアル。多少古いが、内容が網羅的で参考になる。英語が怪しい。

  • FPGA開発日記[44]

    • Cpu0の資料[35]をもとに1からRISC-Vバックエンドを作成する過程がブログエントリとして公開されている。GitHubに実装も公開されている[65]

  • ELVMバックエンド[36]

    • 限られた命令でLLVM IRの機能を達成する例として貴重。でも意外とISAはリッチだったりする。

    • 作成者のスライドも参考になる[37]

  • 2018年度東大CPU実験で開発されたLLVM Backend[40]

    • これについて書かれたAdCのエントリもある[41]

  • Tutorial: Building a backend in 24 hours[45]

    • LLVMバックエンドの大まかな動きについてざっとまとめたあと、 ret だけが定義された最低限のLLVMバックエンド ("stub backend") を構成している。

    • Instruction Selection の説明にある Does bunch of magic and crazy pattern-matching が好き。

  • 2017 LLVM Developers’ Meeting: M. Braun "Welcome to the back-end: The LLVM machine representation"[46]

    • スライドも公開されている[135]

    • 命令選択が終わったあとの中間表現であるLLVM MIR ( MachineFunctionMachineInstr など)や、それに対する操作の解説。 RegStateやframe index・register scavengerなどの説明が貴重。

  • Howto: Implementing LLVM Integrated Assembler[47]

    • LLVM上でアセンブラを書くためのチュートリアル。アセンブラ単体に焦点を絞ったものは珍しい。

  • Building an LLVM Backend[49]

    • 対応するレポジトリが[54]にある。

  • [LLVMdev] backend documentation[116]

    • llvm-devメーリングリストのバックエンドのよいドキュメントは無いかというスレッド。Cpu0とTriCoreが挙げられているが、深くまで記述したものは無いという回答。

  • TriCore Backend[118]

    • TriCoreというアーキテクチャ用のバックエンドを書いたという論文。スライドもある[117]。ソースコードもGitHub上に上がっているが、どれが公式かわからない[1]

  • Life of an instruction in LLVM[136]

    • Cコードからassemblyまでの流れを概観。

  • LLVM Backendの紹介[138]

    • 「コンパイラ勉強会」[2]での、LLVMバックエンドの大きな流れ(特に命令選択)について概観した日本語スライド。

書籍

  • 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』[7]

    • 数少ない日本語資料。Passやバックエンドの各クラスについて説明している。[31]と合わせて大まかな流れを掴むのに良い。

なおLLVMについてGoogleで検索していると"LLVM Cookbook"なる謎の書籍(の電子コピー)が 見つかるが、内容はLLVM公式文書のパクリのようだ[139]

バックエンド

  • RISC-V[5]

    • パッチ群が開発ドキュメントとともに公開されている[10]。以降の開発はこれをベースに行う。

  • Lanai[103]

    • Googleが開発した32bit RISCの謎アーキテクチャ。全く実用されていないが、バックエンドが単純に設計されておりコメントも豊富のためかなり参考になる[3][4]

  • Sparc

    • [18]でも説明に使われており、コメントが豊富。

  • x86

    • みんな大好きx86。貴重なCISCの資料であり、かつ2オペランド方式を採用する場合に実装例を与えてくれる。あと EFLAGS の取り回しなども参考になるが、全体的にコードは読みにくい。ただLLVMの命名規則には従うため、他のバックエンドからある程度推論をして読むのが良い。

ISAの仕様を決める

本書で使用するISAであるCAHPv3について説明します。

cahpv3.pdfを参考のこと。

スケルトンバックエンドを追加する

正常にLLVMのビルドを行うために、何の機能も無いバックエンド(スケルトンバックエンド)を LLVMに追加します。

LLVMをビルドする

LLVMは巨大なプロジェクトで、ビルドするだけでも一苦労です。 以下では継続的な開発のために、高速にLLVMをデバッグビルドする手法を紹介します。

ビルドの際には以下のソフトウェアが必要になります。

  • cmake

  • ninja

  • clang

  • clang++

  • lld

まずLLVMのソースコードをGitを用いて取得します。 前述したように、今回の開発ではLLVM v9.0.0をベースとします。 そこでブランチ llvmorg-9.0.0 から独自実装のためのブランチ cahp を生成し、 以降の開発はこのブランチ上で行うことにします。

$ git clone https://github.com/llvm/llvm-project.git
$ cd llvm-project
$ git switch llvmorg-9.0.0
$ git checkout -b cahp

続いて、ビルドを行うための設定をCMakeを用いて行います。 大量のオプションはビルドを早くするためのものです[96]

$ mkdir build
$ cd build
$ cmake -G Ninja \
    -DLLVM_ENABLE_PROJECTS="clang;lld" \
    -DCMAKE_BUILD_TYPE="Debug" \
    -DBUILD_SHARED_LIBS=True \
    -DLLVM_USE_SPLIT_DWARF=True \
    -DLLVM_OPTIMIZED_TABLEGEN=True \
    -DLLVM_BUILD_TESTS=True \
    -DCMAKE_C_COMPILER=clang \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DLLVM_USE_LINKER=lld \
    -DLLVM_TARGETS_TO_BUILD="" \
    -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHP" \
    ../llvm

Ninjaを用いてビルドを行います。直接Ninjaを実行しても構いません( $ ninja )が、 CMakeを用いて間接的に実行することもできます。

$ cmake --build .

ビルドが終了すると bin/ ディレクトリ以下にコンパイルされたバイナリが生成されます。 例えば次のようにして、CAHPバックエンドが含まれていることを確認できます。

$ bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 9.0.0
  DEBUG build with assertions.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: skylake

  Registered Targets:
    cahp    - CAHP

ここでは開発用にデバッグビルドを行いました。 一方で、他人に配布する場合などはリリースビルドを行います。 その際は次のようにCMakeのオプションを指定します。

$ cmake -G Ninja \
    -DLLVM_ENABLE_PROJECTS="lld;clang" \
    -DCMAKE_BUILD_TYPE="Release" \
    -DLLVM_BUILD_TESTS=True \
    -DCMAKE_C_COMPILER=clang \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DLLVM_USE_LINKER=lld \
    -DLLVM_TARGETS_TO_BUILD="" \
    -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHP" \
    ../llvm

アセンブラを作る

この章ではLLVMバックエンドの一部としてアセンブラを実装します。 具体的にはLLVMのMCLayerを実装し、アセンブリからオブジェクトファイルへの変換を可能にします。

簡易的なアセンブラを実装する

CAHPInstPrinter を実装する

テストを書く

メモリ演算を追加する

属性を指定する

ディスアセンブラを実装する

relocationとfixupに対応する

%hi%lo に対応する

li a0, foo をエラーにする

llvm-objdump の調査

hlt 疑似命令を追加する

コード生成部を作る

コンパイラのスケルトンを作成する

基本的な演算に対応する

定数の実体化に対応する

メモリ演算に対応する

relocationに対応する

条件分岐に対応する

関数呼び出しに対応する

関数プロローグ・エピローグを実装する

frame pointer eliminationを実装する

select に対応する

FrameIndex をlowerする。

大きなスタックフレームに対応する

SETCC に対応する

ExternalSymbol に対応する

jump tableを無効化する

インラインアセンブリに対応する

fastccに対応する

Cコンパイラに仕立てる

LLDにCAHPバックエンドを追加する

ClangをCAHPに対応させる

crt0.ocahp.lds の導入

--nmagic の有効化

libcの有効化

まともなコードを生成する

分岐解析に対応する

branch relaxationに対応する

16bit命令を活用する

jal を活用する

命令スケジューリングを設定する

末尾再帰に対応する

落ち穂拾い

スタックを利用した引数渡し

byval の対応

動的なスタック領域確保に対応する

emergency spillに対応する

可変長引数関数に対応する

単体の sext/zext/trunc に対応する

乗算に対応する

除算・剰余に対応する

frameaddr/returnaddr に対応する

ROTL/ROTR/BSWAP/CTTZ/CTLZ/CTPOP に対応する

32bitのシフトに対応する

間接ジャンプに対応する

BlockAddress のlowerに対応する

参考文献


1. 論文とスライドも怪しいものだが、著者が一致しているので多分正しいだろう。
2. これとは別の発表で「コンパイラ開発してない人生はFAKE」という名言が飛び出した勉強会[114]
3. LLVMバックエンドの開発を円滑にするためのアーキテクチャなのではと思うほどに分かりやすい。
4. 後のSparcについて[116] にて指摘されているように、商業的に成功しなかったバックエンドほどコードが単純で分かりやすい。