はしがき

皆様はじめまして，またはお久しぶりです．本書はこれからLLVMを触ってみたいけれども何をどうすればよいか全くわからない，LLVMを一度触ろうとしたけれどもよくわからないので諦めた，という方々の為の，LLVMの基礎的な内容を纏めた書籍です．

LLVMは10年以上前に開発が始まったコンパイラ基盤ですが，ライセンス上の理由などから最近注目度が急上昇しています．言語処理系に興味がないという人でも“LLVM”や“Clang”という単語を耳にする機会が増えたのではないでしょうか．

現在ではClangをはじめ，LLVMを利用した言語処理系も多く存在していますし，LLVMを前提とした言語処理系は今後も増えていくことでしょう．また，一旦LLVMの中間表現にすることでそこからJavascriptやFlash(ActionScript)など様々な言語へ再変換することもできます．LLVMの発展やClangの性能向上，機能拡張が進めば，その結果GCCにも同様の特徴が取り入れられるといったことも考えられます．このようにLLVMは様々なところでインフラとして利用されており，LLVM自体を普及することにも大きな意味があると言えるでしょう．

ところがLLVMに関する日本語の情報は多くなく，また,LLVMを取り扱った書籍は日本語でなくともおそらく存在しないと思います.少なくともLLVMを解説した日本語の書籍は初心者向け，上級者向け問わず見当たりません．これは，LLVMのバージョンアップの頻度が高いこと，LLVMの公式ドキュメントがある程度充実していること，などなど様々な理由があってのことだと考えられますが，非常に残念なことです．LLVMという有用なインフラがそこにあるにも関わらず，興味を持った人が何から触っていいかよくわからない，何をどうすればわかるのかもわからないという状況に陥る可能性があるのですから．私が初めてLLVMに触れたのはバージョン2.6の頃でしたが，当時もやはり情報が少なく，苦労しながら少しずつ理解していったのを覚えています．

本書はその様な状況を打開し，初心者がLLVMを触っていくための第一歩を応援したい，LLVMをもっと普及させていきたいという思いから生まれました．どんなことにも共通しているかもしれませんが，最初の第一歩が重要かつ大変なことで，そこでつまずき，諦めてしまう人は多くいることでしょう．逆に少し慣れてしまえば後は応用で何とかなるもので，LLVMも最初の第一歩をしっかりと踏み出すことができれば，徐々に歩き方がわかっていくと思います．本書がその助けになれば幸いです．

本書ではフロントエンドの作り方を字句解析から順に説明しています．また，LLVM のコアである最適化などを行うパスの作り方の説明，オブジェクト生成までを説明したバックエンドの作り方の紹介とLLVMを使用してコンパイラを作成するうえで必要になることを一通り網羅しています．LLVMの内部実装の深いところまで紹介しきれているわけではありませんが，多くの人に有用な情報を含んでいると思います．本書を参考にLLVM 関連プロジェクトへのコミット，もしくは新たなプロジェクトを作成されることを期待します．

最後になりましたが，本書は元々は自費出版という形で公開していたものになります．その後たくさんの方から要望を頂き，より多くの方に手にとって頂くために電子書籍化しました．電子書籍化後も多くの反響があり，その結果今回の出版のお話を頂くことになりました．これまでお世話になった方々に感謝申し上げます．無理なお願いが多々あったにもかかわらず快く電子書籍を公開する場を提供してくださった 達人出版会の高橋様，そして難しいスケジュールだったにもかかわらずコメントをくださったレビュアーの @shtaxxxさん，@saten_kanaeさん,自費出版を行っていた時から応援してくださった@xhl_kogitsuneさん，@finalfusionさん，その他すべての読者の方々に改めて感謝申し上げます．

第1章 はじめに

1.1 本書の構成

ここで本書の構成について説明しておきます．

第１章，つまり本章では本書の構成や目的，次章以降の文章表記について説明します．本書のターゲットや位置づけ的なもの，お断り(という名の言い訳)が書いてあるのでとりあえず目を通してもらえると幸いです．

第２章ではLLVMプロジェクトの概要を説明します．LLVMが何かということやLLVMのサブプロジェクトについて説明します．既にご存知の方は読み飛ばして頂いて構いません．

第３章ではLLVMのインストール方法，各種コマンドの説明や動作確認を行います．

第４章ではLLVMの中間表現について説明します．ここでは幾つかのC言語のソースコードをLLVMの中間表現に変換し，どのような構成になっているか確認しながら解説を進めます．

第５章ではLLVMのフロントエンドの作成について説明します．C言語のサブセットである簡易言語を定義し，その言語に対するLLVMフロントエンドの作成過程を順を追って説明します．

第６章ではLLVMの解析や最適化について説明します．この章では、解析/最適化の実装手順を解説した後、簡単な解析/最適化を独自に実装する際の過程を順を追って説明しています

第７章で，LLVMのバックエンドの実装方法について説明します．バックエンドでどのようにデータ形式を変化させていくかを説明し，実際にオリジナルのバックエンドを実装してアセンブリとオブジェクトが生成できるようにします．

なお，最初の本章から第6章までを柏木餅子が，そして第7章を風薬が執筆しています．

1.2 この本で何ができるようになるの？

本書は，LLVMに興味はあるけどよくわからない，遊んでみたいけどドキュメントは英語だし面倒だ！などという人が迷走しつつもLLVMの使い方を覚えてコンパイラを実装できるようになるための本です．少なくとも筆者はそれを目指しています．逆に言うとそれ以上のことは書いていません．例えばLLVM内部で○○はどう実装されているか，といった話は基本的にありません．

1.3 本書の目的とターゲット層

大事なことなので二度言います．本書は，LLVMって単語を聞いたことあるなーという程度の人になんとなくでもLLVMで何か作ってみよう！って思ってもらい，LLVMで遊べるようになってもらうための本です．

1.4 本書を読むにあたって必要となるもの

本書を読むのに必要となる物やスキルを記載します．“必須なもの”と“あった方が良い程度のもの”に分けて記載しますが，基本的には全部揃えておいた方が良いと思います．

1.4.1 必須なもの

C/C++の基本的な知識

本書は説明のためのサンプルコードや，フロントエンドの実装にC/C++を用いています．そのため，本書を読み進める上では最低限C/C++の読み書きが出来ることが求められます．

1.4.2 あった方がよいもの

実行/開発環境

本書のLLVMの説明では，実際にLLVMをマシン上にインストールし，使用しながら学んでいくことを想定しています．本書だけでもある程度理解できるよう心がけてはいますが，本書の内容を理解するためにはLLVMをインストールし，実行/開発するための環境を用意した方が良いでしょう．

コンパイラに関する基本的な知識

本書はLLVMの概要を説明した後，実際にLLVMを用いてコンパイラを実装することでその使用方法を解説していきます．しかしあくまでLLVMの使用方法がメイントピックとなりますので，基本的なコンパイラの説明については極力省いています．ですのでコンパイラの基本的な知識については，予め知っておいた方が良いでしょう．各種最適化のアルゴリズムや実装などを知っている必要はないですが，コンパイラが何をして何を出力してくるのか程度は知っておいて頂きたいです．

折れない心

実は一番大事かもしれないのがこの要素．基本的にLLVMのドキュメントは英語ですし，有用な情報も海外サイトが圧倒的に多いです．*1そして，行き詰ったときに最も参考になるのはDoxygenとソースコードです．本気で何かしようと思った際は少なからずソースコードを読むことになります．割と面倒なことが多いですが，最後まで頑張ってください．

なお，どうしても英語を読む気になれないという場合は，まず@nothingcosmosさんがLLVMについてまとめているページ[1]やIBMによる日本語訳版のLLVM解説ページ[5]を見ることをお勧めします．

読解力

可能な限りわかりやすい文章を心がけてはいますが，筆者の知識や語彙力などの不足もあり，やや表現が難しくなる部分もあります．読者の皆さんの読解力を駆使し，なんとか意図を汲み取っていただけると助かります．

1.5 本書内の記載について

本書内での記載ルールについて説明します．まず，コマンドの入力や出力結果は下記の様に四角い枠で囲って表記します．

# コメントです
$ コマンドです

この枠の中では，$で始まる行は投入するコマンドであり，#で始まる行はコメントとなります．

また，LLVMの各種ライブラリのAPI等は下記のような枠で囲います．

メソッド名等を書きます

APIの定義を書きます

枠の上部分にはメソッド名等を記載し，中には実際の定義を記載します．

1.6 お断り

本書はLLVMの一ユーザが書いた書籍です．極力記載内容に誤りが無いよう努力していますが，誤記，誤認識による誤った情報が含まれる可能性があります．ご了承ください．本書を利用したことにより何かしらの損失があったとしても，筆者は補償できません．もし本書の内容に誤りを発見した場合は，そっと指摘して頂けると幸いです．

[*1] 最近は日本語である程度まとまった情報が得られますが

第2章 コンパイラとLLVM

2.1 一般的なコンパイラのお話

LLVMについて説明する前に一般的なコンパイラのお話から始めます．そんなことはもう知ってるよという人はこの章は読み飛ばしていただいてかまいません．

一般的にコンパイラは，プリプロセッサ，字句解析，構文解析，意味解析，最適化，コード生成といった複数の要素から構成されています．意味解析までをフロントエンドと呼び，ソースコードを解析して中間表現やシンボルテーブルの作成，管理を行います．そしてミドルエンドである最適化部は，フロントエンドから受け取った中間表現に対し，様々な解析や最適化を施してプログラムの高速化を図ります．最後にコード生成部が最適化後の中間表現を目的コード(一般的にはアセンブリ)に変換します．このコード生成部のことをバックエンドと呼びます．あれ？機械語じゃないの？実行ファイルは？と思った方，当然だと思います．たぶん一般的にコンパイラと言ったときに指すものは機械語(実行ファイル)を出力するものです．ではコンパイラじゃなければあれは何なのか？あれはコンパイラドライバだったのさ！はい，というわけで皆さんが普段使っているコンパイラはコンパイラドライバだったのです．

では次の疑問ですね．コンパイラドライバって何だろう？その答えは簡単です．コンパイラ，アセンブラ，リンカなどのプログラムを制御するプログラムです．コンパイラドライバはこれらのプログラムを状況に応じて呼び出します．実行ファイルの生成にはコンパイラが生成したコードをアセンブラで機械語に変換した後，リンカを使用してライブラリ等の外部ファイルをリンクする必要があるわけですが，コンパイラドライバはこれら一連の作業を管理，実行してくれるすごいやつなのです．皆さん良くご存知のgccだってコンパイラドライバだったのです *1．

2.2 LLVMプロジェクト

それではLLVMプロジェクトの概要と構成を説明します．LLVMは2000年にイリノイ大学で開発が開始された，C++で記述されたコンパイラ基盤です．LLVMは現在も開発が進められており，最新版としてLLVM 3.4がリリースされています[2], [3], [4] *6．LLVMプロジェクトは日々成長を続けており，現在のLLVMは多数のサブプロジェクトによって構成されています．LLVMのサブプロジェクトには様々な物が存在しており，サブプロジェクトを組み合わせることでLLVMはコンパイラ基盤としてだけではなく，コンパイラ環境として成立します．

LLVMプロジェクトはBSDライクな自由度の高いライセンスで提供されており，GPLv3の特許関連条項への懸念などから，OSS問わず様々な場面で注目を集めています．

2.2.1 LLVMのサブプロジェクト

LLVMは，LLVM Core，Clang，LLDBなどの複数のサブプロジェクトから構成されています．ここでは，LLVMプロジェクトを構成するLLVMのサブプロジェクトを幾つか紹介します．*2

2.2.2 LLVM Core

LLVM Coreは，LLVMプロジェクトにおいてコンパイラ基盤およびJITコンパイラの機能を提供するプロジェクトであり，その名の通りLLVMのコアとなるサブプロジェクトです．LLVMでは，言語やアーキテクチャから独立した独自の中間表現(以降，LLVM IR)を規定しており，LLVM CoreはLLVM IRに対する最適化機構や様々なアーキテクチャに対するコード生成器を提供します．また，LLVM CoreはLLVM IRに対するJITコンパイラおよびインタプリタの機能も提供しています．これにより，LLVM CoreはLLVM IRに対して静的コンパイラとしてもJITコンパイラとしても使用することが出来ます．本節では，LLVM Coreについて理解を深めるため，次段落でコンパイラ基盤についての説明を行った後，インタプリタ/JITコンパイラおよびコンパイラ基盤としてのLLVM Coreをそれぞれ説明します．

コンパイラ基盤とは何か

コンパイラ基盤は，その名の通りコンパイラを作成するための共通基盤です．もう少し詳しく言うと，コンパイラ基盤はコンパイラに必要となるモジュール群をまとめたもので，新しい言語に対するコンパイラや最適化アルゴリズム，特定のアーキテクチャに対するバックエンドの実装等に使用します．

コンパイラ基盤はコンパイラを作るためのフレームワークです．例えば，あなたが新しい言語を考えたと想定しましょう．その言語で書いたソースコードを手元のマシンで動かすためにはコンパイラが必要となります．この際，普通はフロントエンドからバックエンドまで全て独自に実装する必要があり，相当面倒な作業になります．既に様々なコンパイラが存在しているのですから，出来れば字句解析や構文解析など，自分の言語特有の部分のみを作ってあとは既存のものを流用したいと思いませんか？それを実現するのがコンパイラ基盤です．コンパイラ基盤は一般的に独自の中間表現を規定しており，その中間表現に対する最適化などの操作や中間表現から機械語への変換などのモジュールおよびそれらを作成するためのライブラリが用意してあります．つまりユーザはコンパイラ基盤の中間表現を生成するためのフロントエンドさえ作れば，あとは既存の資産を再利用することでコンパイラを開発できるのです．また，バックエンド側の実装についてもフロントエンドやミドルエンドを再利用することができるので容易に新たなアーキテクチャ用のコンパイラを試作することができます．これら中間表現とコンパイラ開発のためのモジュールのセットがコンパイラ基盤です．

ちなみにLLVM以外には国産のCOINSというコンパイラ基盤もあります．個人的には多少お世話になったのですが，残念ながらこちらはあまり浸透していないのが現状です．

インタプリタ/JITコンパイラとしてのLLVM Core

LLVMは言語非依存の独自の中間表現(以降，LLVM IR)を規定しており，また，LLVM IRに対するインタプリタ/JITコンパイラを有しています．LLVM Coreはこのインタプリタ/JITコンパイラとしてのLLVMの機能を提供しており，Boolean，整数，浮動小数点数，ポインタなどの基本型を格納可能な無限個の仮想レジスタを持つレジスタマシンとして動作します．LLVM Coreは受けとったLLVM IRをインタプリタ，もしくはJITコンパイラとして処理します．JITコンパイラでの動作モードにはEagerとLazyの2つがあり，ユーザは実行方法としてインタプリタ，もしくはEager，Lazyを選択することができます．なお，Eagerは入力されたLLVM IRを最初に全て実行ファイルにコンパイルする方式であり，Lazyは最初に各関数のスタブを作成しておき，最初の関数呼び出しの際に該当関数をコンパイルする方法です．

コンパイラ基盤としてのLLVM Core

LLVM CoreにはLLVM IRを最適化し，任意のマシンの機械語やソースコードに変換するためのモジュールやそれらモジュールを作成するためのライブラリやツールが用意されています．ユーザは特定の言語やアーキテクチャに対するフロントエンドやコード生成部を実装することで様々な言語やマシンに対応したコンパイラを開発できます．また，LLVM Coreに用意されたライブラリを用いて最適化部を容易に作成することも可能です*3．ここで，LLVM Coreの構成を図2.1に示します．

図2.1: LLVMの構成

LLVM Coreはフロントエンドで生成されたLLVM IRに対して，Passと呼ばれるモジュールを用いて解析や最適化，オブジェクトコードの生成を行います*4．また，ユーザはLLVMに用意されている各種クラスを利用して独自のPassを実装し，利用することができます．ここで重要なのは，最適化等の操作はLLVM IRに対して行われるものであり，言語やアーキテクチャから独立しているという点です．つまりユーザはフロントエンドやPassを実装し，LLVMで用意されている解析や最適化Passと組み合わせることで自由にコンパイラを開発することができるのです．なお，Passについては第6章で詳しく説明しますので，とりあえず今はPassという名前のものが存在する，ということだけ覚えておいてください．ちなみに図2.1中でフロントエンドがLLVM Coreの外に配置されていることにお気づきの方もいるかと思いますが，LLVM Coreはあくまでコンパイラ基盤ですので，フロントエンドは含まれていません．つまり特定のプログラミング言語の処理系として動作させるためには，該当言語に対するフロントエンドをそれぞれ別途用意する必要があります．LLVMをバックエンドとして利用するフロントエンド実装としては，次に説明するC/C++/Objective-C/Objective-C++のコンパイラであるClangが有名です．

2.2.3 Clang

ClangはLLVM Coreをバックエンドとして使用するC/C++/Objective-C/Objective-C++のLLVMフロントエンド実装(コンパイラ)です．FreeBSDがデフォルトのコンパイラとして採用するなど，現在注目が高まってきています．*5エンドユーザとして見た時，Clangには以下の様な特徴があります．

高速なコンパイルと省メモリ

わかりやすいメッセージ

GCCとの互換性

なお，Clangは2013年4月のコミットにてC++11に完全対応しました．バージョン3.2ではまだ完全対応とはなっていませんが，この機能はバージョン3.3のリリースにて盛り込まれました．

2.2.4 LLDB

LLDBはClangのパーサやLLVMのディスアセンブラなどのLLVMの既存のライブラリを使用したコマンドラインデバッガです．LLDBは再利用可能なコンポーネント群であり，移植性や拡張性を考慮してプラグインアーキテクチャとなっています．なお，LLDBは現在Mac OS XのXcodeでデフォルトのデバッガです．

2.2.5 libc++

libc++はlibstdc++を置き換えることを目的としたC++の標準ライブラリです．libc++には下記のような特徴があります．

C++11(C++0x)をターゲットにしている

高速な実行

省メモリ

高速なコンパイル

libstdc++とのABI互換性

豊富なユニットテスト

2.2.6 compiler-rt

compiler-rtはlibgccを置き換えることを目的としたライブラリです．compiler-rtには以下の様な特徴があります．

後述するターゲットに対してlibgcc のインターフェイスを完全にサポート

libgccと比較して高速

AppleのBlocksランタイムインターフェースのターゲット非依存の実装

なお，compiler-rtは以下のアーキテクチャをサポートしています．

i386

x86-64

SPARC64

ARM

PowerPC

PowerPC 64

また，これら全てのアーキテクチャについて，以下のOSをサポートしています．

AuroraUX

DragonFlyBSD

FreeBSD

NetBSD

Linux

Darwin

[*1] 偉そうに言ってみたものの，一般的にgccはコンパイラと言いますし，筆者だって普段はコンパイラドライバなんて言葉は使いません

[*2] 紹介するプロジェクトの選択に関しては特に重要度などの指標はなく，筆者の独断と偏見です

[*3] 一から自分で作成することと比較して，という意味で

[*4] つまりバックエンドもPassの一種ということ

[*5] FreeBSD のカーネルはClangでビルドできることが報告されています

[*6] 最新版はLLVM 3.4ですが本書執筆時の最新バージョンがLLVM 3.2であったため，本書の以降の章ではLLVM 3.2を使用します

第3章 環境構築

3.1 本章の概要と構成

本章ではLLVMのインストール方法について説明します．LLVMの公式ページではバイナリで配布されていますし，一部ディストリビューションではパッケージで配布されていたりもしますが，実際にLLVMを利用して開発を進める際には頻繁にLLVMのソースコードを参照しますので，今回はソースコードからインストールしていきます．なお，本書では以降C/C++を例に話を進めていきますので，フロントエンドとしてClangを使用します．

3.2 環境

LLVMをインストールする環境には様々な構成が考えられます．LLVMの公式ドキュメント，Getting Started with the LLVM System*3によると，LLVMは下記の環境で動作確認済みとのことです．*1

表3.1: LLVMの動作環境

OS | アーキテクチャ | コンパイラ
　AuroraUX | x86 | GCC
　Linux | x86 | GCC
　Linux | amd64 | GCC
　Solaris | V9 (UltraSPARC) | GCC
　FreeBSD | x86 | GCC
　FreeBSD | amd64 | GCC
　MacOS X | PowerPC | GCC
　MacOS X | x86 | GCC
　Cygwin/Win32 | x86 | GCC 3.4.X, binutils 2.20

また，表3.2の環境についても部分的にサポートしているとのことです．表3.1，および表3.2の環境における制限事項についてはここでは省略しますが，Getting Started with the LLVM System内に記載されていますので必要に応じて参照してください．

表3.2: LLVMが部分的にサポートする環境

OS | アーキテクチャ | コンパイラ
　Windows | x86 | Visual Studio 2000以上
　AIX | PowerPC | GCC
　Linux | PowerPC | GCC
　Linux | Alpha | GCC
　Linux | Itanium (IA-64) | GCC
　HP-UX | Itanium (IA-64) | HP aCC
　Windows x64 | x86-64 | mingw-w64 の GCC-4.5.x

上記に示したように，LLVMのインストール環境は様々なケースが想像されますが，とりあえず本書では私の手元の環境にあわせ，Windows上のVMwareにUbuntuをインストールし，さらにその上にLLVMをインストールする，という環境を想定して話を進めさせていただきます．今回私の方で使用した環境を表3.3に示しますが，他のディストリビューションや他のバージョンのインストールも基本的には変わらないと思いますので，それぞれの環境に合わせて適宜読みかえてください．なお，本書執筆開始時点の最新バージョンがLLVM 3.2であったため，本書では表3.3の環境にLLVM 3.2をインストールし，本章以降の解説ではLLVM 3.2の仕様をベースに，可能な限りLLVM 3.3，LLVM 3.4での変更点を補足しながら解説を進めていきたいと思います．

表3.3: 検証環境

ホストOS | Windows 7 64bit
ゲストOS | Ubuntu 12.04 32bit
VMware | VMware 4.0.3

3.3 インストール

それではLLVMをインストールしていきましょう.インストールは以下の手順で行います．

ビルドに必要なパッケージのインストール

ビルド/インストール

パスの設定

今回は上記の手順でLLVM 3.2をインストールしますが，他のバージョンでも基本的には同じ手順でインストールできるはずです．なお，インストール先は/usr/local/llvm以下とします．

3.3.1 ビルドに必要なパッケージのインストール

LLVMのインストールを始める前に，インストールに必要となるパッケージをインストールします．LLVMのビルドには表3.4のパッケージが必要となります．

表3.4: ビルドに必要なパッケージ

パッケージ名 | バージョン | 使用目的
　GNU Make | 3.79, 3.79.1 | Makefile(ビルド)の実行
　GCC | 3.4.2 | C/C++ コンパイラとして利用
　Texinfo | 4.5 | Clangのビルドに必要
　SVN | 1.3以上 | LLVMのSubversionへのアクセス
　python | 2.4以上 | テストスイートの実行
　perl | 5.6.0以上 | ユーティリティ
　GNU M4 | 1.4 | マクロプロセッサ
　GNU Autoconf | 2.60 | configureスクリプト生成
　GNU Automake | 1.9.6 | マクロ生成
　libtool | 1.5.22 | 共有ライブラリ管理

LLVM 3.2の公式ドキュメント，Getting Started with the LLVM SystemにはDejaGnu，tcl，expectが必要とされていますが，これらはpythonで置き換えられたため不要の様です．GNU M4，GNU Autoconf，GNU Automakeはconfigureに変更を加える場合に必要になります．なお，今回はホームページからソースコードを取得するためsvnは使用しません．trunkから最新のソースコードを取得したい場合はインストールしてください．また，今回は上記の他に，下記のパッケージもインストールします．

graphviz

xdot

graphviz，xdotはLLVMのビルドには直接必要ないのですが，後でグラフの表示等に使用するのでインストールしておきます．

各パッケージのインストールは以下のコマンドで行います．

# GNU Make
$ sudo apt-get install make
#
# GCC はインストールされていると思いますがされていなければ以下のコマンドを投入
$ sudo apt-get install gcc g++
#
# ライブラリ
$ sudo apt-get install libtool
#
# テスト用のパッケージ
# pythonはUbuntu 12.04の場合デフォルトでインストールされているはず
# インストールされていなければ以下のコマンドを投入
$ sudo apt-get install python
#
# その他ドキュメントやconfigure作成用
$ sudo apt-get install m4 autoconf automake texinfo
#
# グラフ表示等に使用
$ sudo apt-get install graphviz xdot

なお，表3.4のバージョンは“動作確認済み”のバージョンが記載されているとのことなのですが，一部情報が少し古い様に思われます．実際にLLVM/Clang とGCC 等の各種パッケージのバージョンの組み合わせによってはビルド時に問題が発生するのですが，Getting Started with the LLVM System にはLLVM のビルドに問題があるGCC のバージョンなどの情報が記載されていますので，うまくいかない場合は一度確認してみてもよいかもしれません．

3.3.2 LLVMのビルド/インストール

パッケージのインストールが完了したらLLVMのビルド/インストールにはいります．LLVMのインストールは以下の手順で行います．

LLVMのソースを取得

LLVMのソースを展開

LLVM のソースコードを配置するディレクトリを作成し、手順２で展開したディレクトリを移動

ClangのソースをLLVMの所定ディレクトリへ移動

オブジェクトファイルを展開するディレクトリを作成

configure

make及びmake install

ソースコードの取得

まずはLLVMのダウンロードからClangとLLVMのソースコードをダウンロードします．今回はバージョン3.2をソースコードからビルドするので，次の3つのファイルをダウンロードします．

LLVM source code

Clang source code

compiler-rt source code

wgetでさくっと取得してしまいましょう．ダウンロード場所は任意の場所で構いません．

$ wget http://llvm.org/releases/3.2/llvm-3.2.src.tar.gz
$ wget http://llvm.org/releases/3.2/clang-3.2.src.tar.gz
$ wget http://llvm.org/releases/3.2/compiler-rt-3.2.src.tar.gz

ダウンロードが完了すれば，次はアーカイブファイルの展開と移動です．

$ tar xzf llvm-3.2.src.tar.gz
$ tar xzf clang-3.2.src.tar.gz
$ tar xzf compiler-rt-3.2.src.tar.gz
#
# 作業ディレクトリを作成して移動(今回はホームディレクトリ配下のllvmディレクトリ)
$ mkdir ~/llvm
$ mv llvm-3.2.src ~/llvm/
$ mv clang-3.2.src ~/llvm/llvm-3.2.src/tools/clang
$ mv compiler-rt-3.2.src ~/llvm/llvm-3.2.src/projects/compiler-rt

trunkからソースコードを取得する場合

本書では基本的にLLVM 3.2をベースに話を進めますが，常に最新版を使用したいという方はtrunkのソースコードを取得する必要があります．そういった方のために，念のためtrunkのソースコードを取得する際の手順についても記載しておきます．trunkからのソースコード取得手順は次の通りです．

# subversionのインストール
$ sudo apt-get install subversion
#
# 作業ディレクトリへ移動
$ cd ~/llvm/
# LLVMの取得
$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
#
# Clangの取得
$ cd llvm/tools
$ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
#
# compiler-rtの取得
$ cd ../projects
$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt

ビルドとインストール

ファイルの展開，配置が完了したのでビルドとインストールに移ります．実際に投入するコマンドを下記に示すので，皆さんの環境にあわせて適宜置き換えてください*2．

# ビルドディレクトリの作成と移動
$ mkdir ~/llvm/llvm-build/
$ cd ~/llvm/llvm-build/
#
# --enable-optimizedをつけないとDebugでビルドされます
$ ../llvm-3.2.src/configure --prefix=/usr/local/llvm --enable-optimized
#
# -jオプションは後ろに指定した数値分並列で動作します
# 自分の環境に合わせて任意の値を指定してください
$ make -j2
#
# 一応テストをしてみる
$ make check
#
# インストール
$ sudo make install

ビルドにはそれなりの時間がかかりますので，コーヒーでも飲みながら気長に待ちましょう．なお，今回はRelease版でビルドしますが，Debug版でビルドしたい場合はconfigure時のオプションを次のように指定します．DebugビルドにするとDebugシンボルを含めてコンパイルされます．

# デフォルトでDebugですが--disable-optimizedをつけて明示的にDebugビルドを指定
$ ../llvm-3.2.src/configure --prefix=/usr/local/llvm --disable-optimized \
    --enable-assertions --enable-debug-runtime --enable-debug-symbols

Debugビルドに失敗する場合

LLVMのDebugビルドではかなりのメモリを使用します．そのため，メモリが不足しているとOOM Killerによってmakeが異常終了してしまうことがあります．詳細にメモリ使用量を計測したわけではありませんが，最大でおおよそ2GB程度のメモリを使用する様ですので，メモリとスワップを合わせて3GB程度は確保しておいた方が良いと思われます．もしもシグナル9，もしくはシグナル2でビルドが終了してしまうことがある場合はsyslogを確認してみることをおすすめします．メモリ不足が原因であればOut of memory というログメッセージが確認できるはずです．

過去のLLVMをインストールする場合の注意点

普通は最新のLLVMをインストールすれば良いと思いますが，わけあって過去のバージョンをインストールする場合は注意点があります．過去のLLVM(筆者は3.0で確認)ではOCamlがインストールされているとmake install時に，META.llvmでNo rule to make targetというエラーが出ます．その場合，下記のコマンドでMETA.llvmをコピーする必要があります．コピー後再度make installを実行すればインストールは正常に完了します．

$ cp -p bindings/ocaml/llvm/META.llvm bindings/ocaml/llvm/Release/META.llvm

3.3.3 パスの設定

ビルドとインストールが完了したら最後にパスの設定を行います．下記コマンドを投入し，パスを設定します．

$ echo 'export PATH=$PATH:/usr/local/llvm/bin' >> ~/.bashrc
$ source ~/.bashrc

3.4 ツールの種類と確認

インストールが完了しましたので，実際にClangとLLVMのツールを試してみましょう．llvmのコマンドは複数ありますが，本節では代表的な各ツールについて簡単な説明と使用例を紹介します．

3.4.1 Clang

ClangはC/C++/Objective-C/Objective-C++のコンパイラ(コンパイラドライバ)です．Clangは内部で適切なモジュールを選択し，アセンブリや実行ファイルを生成することができます．Clangの代表的なオプションを以下に示します．

表3.5: Clangのオプション

オプション | 概要
–help | ヘルプ情報の出力
-o | 出力ファイル名を指定
-c | プリプロセス，コンパイル，アセンブルのみ実行
-emit-llvm | LLVM IR形式で出力
-S | プリプロセス，コンパイルのみを実行
-cc1 | ドライバを使用せずコンパイラとして動作する

特に-emit-llvmと-Sオプションは頻繁に使用します．-emit-llvmと-Sを併用することで，人間が読める形式のLLVM IR(LLVMアセンブリ)を出力され，-emit-llvmのみを指定した場合は，LLVM IRのビットコード形式(LLVMビットコード)が出力されます．その他にも多くのオプションがありますので，気になる方は–helpで確認してください．

Clangの使用例

# Clangで実行ファイルを生成する場合
$ clang -o hoge.o hoge.c
#
# ClangでLLVMビットコードを出力する場合
$ clang -emit-llvm -c -o hoge.bc hoge.c
#
# ClangでLLVMアセンブリを出力する場合
$ clang -emit-llvm -S -o hoge.ll hoge.c

3.4.2 lli

lliはLLVMのインタプリタ兼JITコンパイラです．lliはデフォルトではJITコンパイラ(Lazy)で動作します．オプションとして-disable-lazyを指定するとEagerで起動できます．また，-force-interpreterを指定するとインタプリタとして実行します．

lliの使用例

# LLVMビットコードを作成してインタプリタで実行する場合
$ clang -emit-llvm -c -o hoge.bc hoge.c
$ lli -force-interpreter hoge.bc

3.4.3 llvm-as

llvm-asはLLVM IRのアセンブラです．llvm-asはLLVMアセンブリを入力として受け取り，LLVMビットコードへ変換します．入力ファイル名が省略された場合や-が指定された場合は，標準入力の情報を読み取ります．

llvm-asの使用例

# ソースコードを一度LLVMアセンブリに変換しVMで実行
$ clang -emit-llvm -S -o hoge.ll hoge.c
$ llvm-as -o hoge.bc hoge.ll
$ lli hoge.bc

3.4.4 llc

llcはLLVM IRを入力として受け取り，指定されたアーキテクチャのアセンブリまたオブジェクトを出力します．アーキテクチャは-marchオプションで指定できますが，指定しない場合は入力ファイルから自動的に選択されます．オブジェクトを出力する場合は-filetype=objを指定します．

llcの使用例

# LLVMアセンブリを経由してアセンブリ出力
$ clang -emit-llvm -S -o hoge.ll hoge.c
$ llvm-as -o hoge.bc hoge.ll
$ llc -o hoge.s hoge.bc
# オブジェクト出力
$ llc -filetype=obj -o hoge.o hoge.ll

3.4.5 opt

optはLLVMのLLVM IRに対して各種最適化や解析を行うモジュール(Pass)を適用するツールです．LLVM IRを入力として受け取り，指定された最適化，もしくは解析を適用して結果を出力します．LLVMでは様々なPassが用意されており，optコマンドを使用することでそれらを自由にLLVM IRに適用することが出来ます．また，LLVMのライブラリを利用すればユーザ独自のPassを作成し，optコマンドで使用することが可能です．なお，LLVMで用意されているPassについてはLLVMの公式ドキュメント，LLVM’s Analysis and Transform Passesにて紹介されています．

optの使用例

# LLVMアセンブリに最適化を施してJITコンパイラで実行
$ clang -emit-llvm -S -o hoge.ll hoge.c
$ opt -O3 -o hoge.bc hoge.ll
$ lli hoge.bc

3.4.6 llvm-link

llvm-linkは複数のLLVM IRを一つのファイルにまとめるためのツールです．-oオプションが指定されなければ標準出力に結果を出力します．

llvm-linkの使用例

# hoge.cとlib.llをLLVMアセンブリにして結合する場合
$ clang -emit-llvm -S -o hoge.ll hoge.c
$ clang -emit-llvm -S -o lib.ll lib.c
$ llvm-link -o a.ll hoge.ll lib.ll

3.4.7 llvm-objdump

llvm-objdumpはオブジェクトファイルをディスアセンブルするためのツールです．-archオプションで任意のアーキテクチャを指定できます．

llvm-objdumpの使用例

# hoge.oをディスアセンブルする場合
$ llvm-objdump -d hoge.o

3.4.8 llvm-config

llvm-configはLLVMを使用したプログラムをコンパイルする際に必要なコンパイルオプションを出力するツールです．LLVMを用いて開発するときに使用します．表3.6に代表的なオプションを示します．

表3.6: llvm-configのオプション

オプション | 概要
–help | ヘルプ情報の出力
–includedir | LLVMのヘッダファイルのパスを表示する
–libdir | LLVMのライブラリファイルのパスを表示する
–cxxflags | LLVMのヘッダを使用するために必要なC++のflagを出力する
–ldflags | LLVMのライブラリをリンクするために必要なC++のflagを出力する
–libs | 指定されたLLVMコンポーネントをリンクするために必要なライブラリを全て表示する

llvm-configの使用方法は実際にコンパイルを行う第5章で説明しますので， とりあえず今は深く考えず“LLVMを使用して何かを開発する時に用いるおまじない”程度に考えておけば良いと思います．

[*1] 何だか情報が古そうですが，筆者はLinux，x86，GCCという環境にしかインストールしたことがないのでこの情報がどこまで正確かわからないのです．

[*2] svnでcheckoutした場合はtopディレクトリでmake happiness を実行するとsvn update，ビルド，testを自動で行ってくれます

[*3] http://llvm.org/releases/3.2/docs/GettingStarted.html

第4章 LLVM IR

4.1 本章の概要と目的

本章ではLLVM IRについて説明します．本章では実際に幾つかのソースコードをLLVM IRに変換し，解説を進めていきます．LLVM IRに現れる基本的な命令群はそれぞれ説明をしますが，LLVM IRに現れる命令は基本的に命令名から概要が想像できるものが多いことや，全ての命令を詳細に解説するのは読者の皆様を混乱させるという判断からこの章では簡単な説明にとどめています．もし各命令の詳細を知りたい場合はLLVMのドキュメント，Language Reference Manualを参照してください．

4.2 LLVM IRの特徴

まず，LLVM IRの特徴について説明します．LLVM IRの命令の多くはアセンブリに似た3番地コードであり，変数はレジスタに保存されます．LLVM IRは無限個の仮想レジスタを持つレジスタマシンをターゲットとした中間表現で，基本的に全てのレジスタ変数がStatic Single Assignment(以降，SSA)形式で表現されます．SSAは日本語で言うと静的単一代入形式のことで，一度代入された変数は変更されることがありません．SSAが何かということは次の段落で説明しますので，今はそういうものがあるということだけ頭に入れておいてください．

LLVMのドキュメント，LLVM Language Reference Manualによると，LLVM IRの形式には以下の3つのパターンがあるそうです．

インメモリcompiler-IR(メモリ上のIR表現)

ディスク上のビットコード表現

人間が読めるアセンブリ形式の表現

2はこれまでLLVMビットコードと呼んでいたもので，3はLLVMアセンブリと呼んでいたものです．1〜3の3つのIR表現は何れも等価ですが，本章では3番のLLVMアセンブリ形式のLLVM IRを説明していきます．

SSAって何？

SSAは静的単一代入形式というその名の通り，各変数への代入は一度しかされず，一度代入された変数は変更されることがありません．どういうことなのか具体例を用いて見てみましょう．

まず，下記のような式を考えます．

リスト4.1: 単純なサンプルコード

 1: x=1;
 2: y=1;
 3: x=y;

何も考えずに中間表現に直すとそのまま代入文を繰り返せばいいわけですが，それでは同じ変数に代入が行われ，SSAにはなりません．SSA形式であらわすと下記の様になります．

リスト4.2: 単純なSSA表現

 1: x1=1;
 2: y1=1;
 3: x2=y1;

SSAではこのように変数にバージョン情報を付加し，同名の変数には一度しか代入が行われないように表現します．

さて，ここで問題となるのは分岐等が存在し，変数のとる値が静的に決定できない場合です．とりあえずまた実例をだして見てみましょう．今度は下記のようなソースコードがあったとします．

リスト4.3: 分岐を含むサンプルコード

 1: x=y-1;
 2: if(x<10){
 3:   x= 0;
 4: }
 5: else{
 6:   x=10;
 7: }
 8: y=x;

このコードではif文のthen節とelse節でそれぞれxの値が定義されており，最終的にyへの代入文でxのとる値としては0と10の2パターンが考えられます．どちらが到達するかは現段階ではわかりませんので，最終的にyにどちらの値が代入されるのか決定できません．これを解決するのがφ関数と呼ばれるものです．リスト4.3のコードをφ関数を利用したSSAで表した場合のイメージを図4.1に示します．φ関数は参照する定義が複数存在する場合に，どの制御フローを通過したかによって適切な値を選択する関数です．つまり上記の例では，φ関数はxが10より小さい場合はthen節のxの値を選択し，xが10以上の場合はelse節のxの値を選択します．なんだかしっくりこないかもしれませんがそういうものです．

図4.1: phi関数の例

SSAの利点としては，変数のdef-useチェインがわかりやすく，最適化/解析が簡略化されることなどがあります．また，SSA形式の中にもpruned-SSAやSemiPruned-SSAといった種類があったりします．

4.3 LLVM IRの構成

ここで，LLVM IRの構成について説明しておきます．説明に先立ち，まずLLVM IRの構成図を図4.2に示します．

図4.2: LLVM IRの構成

一つのモジュール(翻訳単位)はLLVM IRではModuleに対応します．また，Moduleは複数のFunctionやGlobalVariable等によって構成される最上位の構成単位です．さらに，Functionは複数のBasicBlockで構成され，BasicBlockは複数のInstructionで構成されます．LLVMのライブラリを用いてLLVM IRを操作する場合，プログラム中ではFunction，BasicBlock，InstructionはそれぞれFunctionクラス，BasicBlockクラス，Instructionクラスとして管理されます．また，ModuleはModuleクラスとして管理されることになります．

次節以降，幾つかのサンプルソースコードをLLVMアセンブリに変換し，どのような形となるかを解説していきます．なお，サンプルソースコードは全てC言語となっています．

4.4 LLVMの型とLLVMアセンブリの主な命令

実際のコードを確認する前に，本節でLLVMアセンブリに出現する型と主な命令を表形式で示します．ここで紹介する命令は，“最低限これぐらいは知っておいた方が良い”と筆者が考える命令です．また，あまり詳しく説明しても混乱を招くだけだと思うので各命令の説明は簡単にまとめています．次節以降で紹介するLLVMアセンブリのコードに出現するものは一通りここで掲載していますが，LLVMの全命令セットが知りたいかもっと詳しい仕様が知りたいという方はLLVMのドキュメント，LLVM Language Reference Manualを参照してください．なお，命令の掲載順はLLVM Language Reference Manualに従います．

4.4.1 型分類

本節ではLLVM の型を簡単に説明します．まず，LLVM IRに出現する型の分類を表4.1に記載します．

表4.1: LLVM型分類

分類 | 型
primitive(原始型) | label, void, integer, floating point, x86mmx, metadata.
derived(派生型) | array, function, pointer, structure, vector, opaque.
first class(第一級データ型) | integer, floating point, pointer, vector, structure, array, label, metadata.

表4.1において，primitiveは最も基本的な型であり，derivedはprimitiveや他のderivedの型によって構成される派生的な型となっています．また，first classに分類された型はLLVM IRの命令によって生成される型とされています．例えばintegerやfloating pointはprimitiveですが，LLVM IRの命令によって生成されうる型ですのでfirst classでもあります．

primitive(原始型)

primitiveに含まれる型はLLVMの基本的な構成要素になっています．primitiveに分類される型の概要や書式を表4.2に示します． なお，metadataは他のprimitiveの型と違い少々特殊ですので，こちらついては本章の最後で再度説明します．

表4.2: primitiveに含まれる型

型
概要

label
ラベルを表す型．

void
所謂void型．void型は値やサイズを持たない．

integer
整数型．下記の様にiNという構文で記述される．Nはビット幅を表す．
i1, i2, i3, ... i8, ... i16, ... i32, ... i64, ...

floating point
浮動小数点数型．以下の型が存在する．
half, float, double, x86_fp80, fp128, ppc_fp128

x86mmx
x86マシンのMMXレジスタの値を表す型．

metadata
LLVM IR中に埋め込むメタデータを表す型．

derived(派生型)

derivedに分類される型はprimitiveの型，もしくは他のderivedの型を要素に含む派生的な型です．derivedに分類される型の概要や書式を表4.3に示します． なお，表内に示す例では";"より後ろはコメントを表します．

表4.3: derivedに含まれる型

型
概要

array
配列型．以下に示すように，[<要素数> x <型名>]という形式で表す．

例) [10 x i32]　　　　　　　; i32型の要素を10個持つ配列
　[10 x [10 x i32]]　　　　; i32型の要素を10 x 10個持つ二次元配列

function
Functionのシグネチャを表す型．関数の戻り値や引数の型のリストとして表す．

例) i32 (i32)　　　; i32型の引数を一つとり，i32型の値を返すfunction

pointer
ポインタ型．メモリ配置を表すために使用する．
オプションとしてポインタが指すオブジェクトを配置するアドレス空間を指定できる．
以下に示すように<型名>* という形式で表す．

例) i32*　　　　　　　　　 ; i32型の値を指すポインタ
　 i32 addrspace(1)*　　　; アドレス空間1にあるi32型の値を指すポインタ

structure
構造体を表す型．
%名前 = type { <型のリスト> } という形式で表す．
%名前 = type < { <型のリスト> } > という構文で記述された場合はpacked structureと呼ばれ，
この場合は構造体が1バイトアライメントで配置され，構造体の要素間にパディングが
詰められないことを意味する．

例) %struct.mytype = type {i32, float}　　　; i32型とfloat型を持つstructure
　%struct.mytype = type <{i8, i32, i32}>　　　; サイズが9バイトとなるpacked structure

vector
ベクタ型．SIMD命令のオペランドの様に，複数の要素もつ値を表すために使用する．
以下に示すように，< <要素数> x <型名> > という形式で表す．

例) <4 x i32> ; i32型の要素を4個持つベクタ

opaque
opaque構造体．本体が未定義なstructureを表すために使用する．
C言語で言う所の前方宣言に相当．以下の様に使用される．

例) %struct.mytype = type opaque

4.4.2 終端命令

ここからLLVM IRの各種命令を紹介していきます．まずはじめに，代表的な終端命令を表4.4に示します．表4.4のreturn命令はC言語におけるreturn文に相当し，br命令はif-else文等の分岐に相当します．また，switch命令はそのままswitch文にあたります．この他の終端命令としては，indirectbr命令やinvoke命令などがあります．

表4.4: LLVMの終端命令

命令
構文
概要

ret
ret <type> <value>
ret void
return命令．
関数の呼び出し元へ制御をうつす．
valueが指定されていればvalueを返却する

br
br i1 <cond>, label <iftrue>, label <iffalse>
br label <dest>
分岐命令．
condのtrue/falseに応じて分岐先が変わる
condが指定されていなければラベル名destの
BasicBlockへ分岐する

switch
switch <intty> <value>, label <default>
［<intty> <val>, label <dest> …］
複数の分岐先を持つbr命令の様なもの
valueと一致するvalがあれば該当valのdestへ
遷移する．一致しなければdefaultに遷移する

4.4.3 二項演算子

代表的な二項演算子を表4.5に示します．二項演算子には加算や乗算などの二つのオペランドをとる演算命令が分類されます．なお，add命令やsub命令に現れるnswやnuwは，No Signed WrapとNo Unsigned Wrapの略でそれぞれSignedとUnsignedの演算でオーバーフローが発生した時にPoison Valueになるというフラグを意味します．また，udiv命令のexactはop1がop2の倍数で無いときにPoison Valueになるというフラグのようです．LLVMのLLVM Language Reference ManualによるとPoison ValueはUndef Value(未定義の値)と似たもののようです．ただし，Poison Valueは副作用を起こしてはいけない命令や定数式が予期せぬ動作を引き起こすことを検知したという意味を含むとのことです．

表4.5: LLVM の二項演算子

命令
構文
概要

add
result = add <type> <op1>, <op2>
result = add nuw <type> <op1>, <op2>
result = add nsw <type> <op1>, <op2>
result = add nuw nsw <type> <op1>, <op2>
加算命令
op1 とop2 を足して結果を返却．
op1，op2 は整数か整数のベクタ．

fadd
result = fadd <type> <op1>, <op2>
加算命令．
op1 とop2 を足して結果を返却．
op1，op2 は浮動小数点数か浮動小数点数のベクタ．

sub
result = sub <type> <op1>, <op2>
result = sub nuw <type> <op1>, <op2>
result = sub nsw <type> <op1>, <op2>
result = sub nuw nsw <type> <op1>, <op2>
減算命令．
op1 からop2 を引いて結果を返却
op1，op2 は整数か整数のベクタ．

fsub
result = fsub <type> <op1>, <op2>
減算命令
op1 からop2 を引いて結果を返却．
op1，op2 は浮動小数点数か浮動小数点数のベクタ．

mul
result = mul <type> <op1>, <op2>
result = mul nuw <type> <op1>, <op2>
result = mul nsw <type> <op1>, <op2>
result = mul nuw nsw <type> <op1>, <op2>
乗算命令．
op1 とop2 をかけて結果を返却
op1，op2 は整数か整数のベクタ．

fmul
result = fmul <type> <op1>, <op2>
乗算命令．
op1 とop2 をかけて結果を返却
op1，op2 は浮動小数点数か浮動小数点数のベクタ．

udiv
result = udiv <type> <op1>, <op2>
result = udiv exact <type> <op1>, <op2>
除算命令(unsigned)．
op1 をop2 で割って商を返却
op1，op2 は整数か整数のベクタ．

sdiv
result = sdiv <type> <op1>, <op2>
result = sdiv exact <type> <op1>, <op2>
除算命令(signed)．
op1 をop2 で割って商を返却
op1，op2 は整数か整数のベクタ．

fdiv
result = fdiv <type> <op1>, <op2>
除算命令．
op1 をop2 で割って結果を返却
op1，op2 は浮動小数点数か浮動小数点数のベクタ．

urem
result = urem <type> <op1>, <op2>
剰余命令(unsigned)．
op1 をop2 で割って余りを返却
op1，op2 は整数か整数のベクタ．

srem
result = srem <type> <op1>, <op2>
剰余命令(signed)．
op1 をop2 で割って余りを返却
op1，op2 は整数か整数のベクタ．

frem
result = frem <type> <op1>, <op2>
剰余命令．
op1 をop2 で割って余りを返却
op1，op2 は浮動小数点数か浮動小数点数のベクタ．

4.4.4 ビット演算子

代表的なビット演算子を表4.6に示します．ビット演算子にはシフトや論理和，論理積等の命令が含まれます．shl命令に現れるnuwは0でない値がシフトによって外された場合にPoison Valueになるというフラグで，nswはシフトした結果得られる値の符号ビットと一致しない値(0/1)がシフトによってあふれた時にPoison Valueとなることを意味する様です．また，lshr命令やashr命令のexactはシフトによってあふれた何れかのbitが0でないときにPoison Valueになるというフラグです．

表4.6: LLVMのビット演算子

命令
構文
概要

shl
result = shl <type> <op1>, <op2>
result = shl nuw <type> <op1>, <op2>
result = shl nsw <type> <op1>, <op2>
result = shl nuw nsw <type> <op1>, <op2>
左シフト命令．
op1 をop2 で指定したビット数左に算術シフト
op1，op2 は整数か整数のベクタ．

lshr
result = lshr <type> <op1>, <op2>
result = lshr exact <type> <op1>, <op2>
論理右シフト命令．
op1 をop2 で指定したビット数右に論理シフト
op1，op2 は整数か整数のベクタ．

ashr
result = ashr <type> <op1>, <op2>
result = ashr exact <type> <op1>, <op2>
算術右シフト命令．
op1 をop2 で指定したビット数右に算術シフト
op1，op2 は整数か整数のベクタ．

and
result = and <type> <op1>, <op2>
論理積命令．
op1，op2 の論理積をとる．
op1，op2 は整数か整数のベクタ．

or
result = or <type> <op1>, <op2>
論理和命令
op1，op2 の論理和をとる．
op1，op2 は整数か整数のベクタ．

xor
result = xor <type> <op1>, <op2>
排他的論理和命令
op1，op2 の排他的論理和をとる．
op1，op2 は整数か整数のベクタ．

4.4.5 合成演算子

合成演算子を表4.7に示します．合成演算子は合成型の値を操作する命令です．

表4.7: LLVMの合成演算子

命令
構文
概要

extractvalue
result = extractvalue
　　　　<aggreagate type> <val>,
　　　　<index>{, <index>}*
合成型からの値取り出し命令．
valからindexで指定したインデックスの値を取り出す．
第一オペランドは構造体か配列であり，
indexには取り出す要素のインデックスを
定数で指定する．
以下の例では%agg_valからi32型の値を取り出す．
%result = extractvalue {i32, float} %agg_val, 0

insertvalue
result = insertvalue
　　　　<aggreagate type> <val>,
　　　　<type> <elt>, <index>{, <index>}*
合成型への値挿入命令．
val内のindexで指定したインデックスの要素に
値を挿入する．
第一オペランドは構造体か配列であり，
第二オペランドは挿入するfirst-classの値となる．
indexには挿入する要素のインデックスを指定する．
以下の例では%agg_valのi32型要素に0を挿入する．
%result = insertvalue {i32, float} %agg_val, i32 1, 0

4.4.6 メモリアクセス演算子

代表的なメモリアクセス演算子を表4.8に示します．メモリアクセス演算子にはメモリの確保やロード，ストア等の命令が含まれます．SSA形式においてメモリをどのように表現するかというのは重要なポイントですが，LLVMにおいてはメモリ配置はSSA形式にしないことでシンプルにしている様です．少し意図が伝わりにくいかもしれませんが，本節で紹介するstore命令やload命令の役割を見るとメモリアクセス演算子の特殊さがわかると思います．本節で示した以外のメモリアクセス演算子としては，fence命令やcmpxchg命令があります．なお，getelementptr命令にinboundが付与された場合は，getelementptr命令が確保された領域の範囲外へアクセスした場合に結果がPoison Valueとなることを意味します．

表4.8: LLVMのメモリアクセス演算子

命令
構文
概要

alloca
result = alloca <type>
　　　　[, <type> <NumElements>]
　　　　[, align <alignment>]
メモリ確保命令．
type × NumElementsの領域を関数の
スタックフレーム上に確保

load
result = load [volatile]
　　　　<type>* <pointer>
　　　　[, align <alignment>]
　　　　[, !nontemporal !<index>]
　　　　[, !invariant.load !<index>]
result = load atomic [volatile]
　　　　<type>* <pointer>
　　　　<singlethread> <ordering>,
!index = ! i32 1
　　　　align <alignment>
ロード命令．
pointerが指すメモリアドレスから値を読み出す．
volatileが指定されている場合，
最適化はload命令の実行順や実行数を
他のvolatileな命令と変更することが
できない

store
store [volatile]
　　　　<type> <value>,
　　　　<type>* <pointer>
　　　　[, align <alignment>]
　　　　[, !nontemporal !<index>]
store atomic [volatile]
　　　　<type> <value>,
　　　　<type>* <pointer>
　　　　[singlethread] <ordering>,
　　　　align <alignment>

ストア命令．
pointerが指すメモリアドレスに値を格納する
volatileが指定されている場合，
最適化はstore命令の実行順や実行数を
他のvolatileな命令と変更することが
できない

getelementptr
result = getelementptr
　　　　<ptype>* <ptrval>
　　　　{, <type> <idx>}*
result = getelementptr inbounds
　　　　<ptype>* <ptrval>
　　　　{, <type> <idx>}*
result = getelementptr
　　　　<ptr vector> <ptrval>,
　　　　<vector index type> <idx>
要素アドレス取得命令．
合成型内の要素のアドレスを計算する．
prtvalが指すアドレスをベースとしてidx
で指定したインデックスの要素アドレスを
計算する．

4.4.7 型変換演算子

代表的な型変換演算子を表4.9に示します． 型変換演算子にはその名の通り，オペランドとして渡された値の型を変換する命令が分類されます．

表4.9: LLVMの型変換演算子

命令
構文
概要

trunc to
result = trunc <type> <value> to <type2>
切捨て命令．
valueをtypeからtype2へ切り捨てる．
二つの型は整数か整数のベクタ
またtypeはtype2よりビットサイズが大きい．

zext to
result = zext <type> <value> to <type2>
ゼロ拡張命令．valueをtype2へキャストし，
高位ビットを0で埋める．
二つの型は整数か整数のベクタ．
また，type2はtypeよりビットサイズが大きい．

sext to
result = sext <type> <value> to <type2>
符号拡張命令．valueをtype2へキャストし，
高位ビットをvalueの符号ビットで埋める．
二つの型は整数か整数のベクタ．
またtype2はtypeよりビットサイズが大きい．

fptrunc to
result = fptrunc <type> <value> to <type2>
切捨て命令．
valueを浮動小数点数型typeからtype2へ
切捨てる．
typeはtype2よりビットサイズが大きい．

fpext to
result = fpext <type> <value> to <type2>
拡張命令．
valueを浮動小数点数型typeからtype2へ
拡張する．
type2はtypeよりビットサイズが大きい．

fptoui to
result = fptoui <type> <value> to <type2>
変換命令．
浮動小数点数valueを符号無整数型type2へ
変換する．

fptosi to
result = fptosi <type> <value> to <type2>
変換命令．
浮動小数点数valueを符号付整数型type2へ
変換する．

uitofp to
result = uitofp <type> <value> to <type2>
変換命令
符号無整数valueを浮動小数点数型type2へ
変換する．

sitofp to
result = sitofp <type> <value> to <type2>
変換命令．
符号付整数valueを浮動小数点数型type2へ
変換する．

ptrtoint to
result = ptrtoint <type> <value> to <type2>
変換命令．
ポインタvalueを整数型type2へ
変換する．

inttoptr to
result = inttoptr <type> <value> to <type2>
変換命令
整数valueをポインタtype2へ
変換する．

bitcast to
result = bitcast <type> <value> to <type2>
ビット変換命令．
valueを各bitを変更せずにtype2へ
変換する．

4.4.8 その他の命令

その他の命令を表4.10に示します．今回は，その他の命令としてicmp，fcmp，phi，callの四つの命令を紹介しておきます．icmp命令やfcmp命令は値を比較する命令で，phi命令はSSAの説明で紹介したphi関数に相当します．また，call命令は関数呼び出しに使用する命令です．LLVM Language Reference Manualのその他の命令(Other Operations)の項目では，他にselect命令やva_arg命令などが紹介されています．

表4.10: LLVMのその他の命令

命令
構文
概要

icmp
result = icmp <cond> <type> <op1>, <op2>
比較命令
op1，op2をcondで指定した条件で比較し真偽を
返却．
op1，op2は整数かポインタか整数のベクタ

fcmp
result = fcmp <cond> <type> <op1>, <op2>
比較命令
op1，op2をcondで指定した条件で比較し
真偽を返却．
op1，op2は浮動小数点数か浮動小数点数のベクタ

phi
result = phi <type> [<val0>, <label0>], ...
φ命令．SSAのφノードを実現する．
引数として値とBasicBlockのラベルのリストをとる
なお，phi命令はBasicBlockの先頭以外には挿入
できない

call
result = [tail] call [cconv] [ret attrs] <type>
　　　　[<fnty>*] <fnptrval>
　　　　(<function args>) [fn attrs]
関数呼び出し命令．
関数fnptrvalをfunction argsを引数として呼び出す

なお，icmpのcondに指定できる条件を表4.11に，fcmpのcondに指定できる条件を表4.12に示します．

表4.11: icmpのcondの種類

指定条件 | 意味
eq | equal．二つの引数が同値の時にtrue，それ以外の時にfalseとなる
ne | not equal．二つの引数の値が異なる時にtrue，同値の時にfalseとなる
ugt | unsigned greater than．二つの引数を符号無値として扱い，第1引数が第2引数より大きければtrueとなる
uge | unsigned greater or equal．二つの引数を符号無値として扱い，第1引数が第2引数以上であればtrueとなる
ult | unsigned less than．二つの引数を符号無値として扱い，第1引数が第2引数より小さければtrueとなる
ule | unsigned less or equal．二つの引数を符号無値として扱い，第1引数が第2引数以下であればtrueとなる
sgt | signed greater than．二つの引数を符号付値として扱い，第1引数が第2引数より大きければtrueとなる
sge | signed greater or equal．二つの引数を符号付値として扱い，第1引数が第2引数以上であればtrueとなる
slt | signed less than．二つの引数を符号付値として扱い，第1引数が第2引数より小さければtrueとなる
sle | signed less or equal．二つの引数を符号付値として扱い，第1引数が第2引数以下であればtrueとなる

表4.12: fcmpのcondの種類

指定条件 | 意味
false | 引数に関わらず常にfalse
oeq | ordered and equal．二つの引数がQNANでなく，かつ同値の時にtrueとなる
ogt | ordered and greater than．二つの引数がQNANでなく，かつ第1引数が第2引数より大きければtrueとなる
oge | ordered and greater or equal．二つの引数がQNANでなく，かつ第1引数が第2引数以上であればtrueとなる
olt | ordered and less than．二つの引数がQNANでなく，かつ第1引数が第2引数より小さければtrueとなる
ole | ordered and less than or equal．二つの引数がQNANでなく，かつ第1引数が第2引数以下であればtrueとなる
one | ordered and not equal．二つの引数がQNANでなく，かつ二つの引数の値が異なる時にtrueとなる
ord | ordered(not nans)．二つの引数がQNANでなければtrueとなる
ueq | unordered or equal．二つの引数の何れかがQNANか両引数が同値であればtrueとなる
ugt | unordered or greater than．二つの引数の何れかがQNANか第1引数が第2引数より大きければtrueとなる
uge | unordered or greater or equal．二つの引数の何れかがQNANか第1引数が第2引数以上であればtrueとなる
ult | unordered or less than．二つの引数の何れかがQNANか第1引数が第2引数より小さければtrueとなる
ule | unordered or less or equal．二つの引数の何れかがQNANか第1引数が第2引数以下であればtrueとなる
une | unordered or not equal．二つの引数の何れかがQNANか第1引数と第2引数の値が異なればtrueとなる
uno | unordered(either nans)．二つの引数を符号付値として扱い，第1引数が第2引数以上であればtrueとなる
true | 引数に関わらず常にtrue

4.5 基本的なLLVMの中間表現

LLVM IRの構成を理解することを目的として，まず最も簡単な例としてHelloWorldを例としてとりあげます．下記のHelloWorldのソースコードをLLVM IRに変換し，構成を確認していきましょう．

リスト4.4: HelloWorld.c

 1: #include<stdio.h>
 2:
 3: int main(){
 4:   printf("HelloWorld\n");
 5:   return 0;
 6: }

以下のコマンドを投入し，HelloWorld.cをLLVM IRに変換します．

$ clang -emit-llvm -S -o HelloWorld.ll HelloWorld.c

HelloWorld.llというファイルが出力されると思いますので，viやlessで中を確認しましょう．HelloWorld.llの中を見るとリスト4.5のような出力結果が得られていると思います．

リスト4.5: HelloWorld.ll

 1: ; ModuleID = 'HelloWorld.c'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [12 x i8] c"HelloWorld\0A\00", align 1
 6:
 7: define i32 @main() nounwind {
 8: entry:
 9:   %retval = alloca i32, align 4
10:   store i32 0, i32* %retval
11:   %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([12 x i8]* @.str, i32 0, i32 0))
12:   ret i32 0
13: }
14:
15: declare i32 @printf(i8*, ...)

それでは，ここからHelloWorld.llの内容を解説していきます．なお，解説の流れとしてはまず全体の構成を説明した後にコードを幾つかに分割して説明，という形をとります．

全体の構成

まずはざっくりと今回のLLVM IRの構成を説明します．リスト4.5の1〜3行目はソースコード名や環境などの情報が記述されており，5行目でプログラム中に出現する文字列定数の定義を行っています．そして7〜13行目はmain関数の定義，15行目はprintf関数の宣言となっています．

データレイアウトとアーキテクチャ定義

それでは，それぞれの内容を更に詳しく見ていきましょう．まず，1行目の;Moduleから始まる一文はコメントです．LLVMアセンブリでは;をつけると行末までがコメントになります．ちなみに，コメントで書かれている内容は入力ソースコード名です．これは特に問題ないでしょう．

2行目には意味不明の文字列が並んでいますが，実はこれはアラインメント等のデータの配置方法(以降DataLayout)を示しています．この部分に記述される情報を表4.13にまとめましたのでご覧下さい．

表4.13: DataLayoutの書式

項目名 | 意味
E | ターゲットのデータレイアウトがビッグエンディアンであることを規定
e | ターゲットのデータレイアウトがリトルエンディアンであることを規定
Ssize | スタックのアラインメント(単位はビット)を規定
p:size:abi:pref | sizeで指定されたビットサイズのポインタのABIアラインメントと最適なアラインメントを規定(prefは省略可能)
isize:abi:pref | sizeで指定されたビットサイズの整数型のABIアラインメントと最適なアラインメントを規定
vsize:abi:pref | sizeで指定されたビットサイズのベクタ型のABIアラインメントと最適なアラインメントを規定
fsize:abi:pref | sizeで指定されたビットサイズの浮動小数点型のABIアラインメントと最適なアラインメントを規定
asize:abi:pref | sizeで指定されたビットサイズの合成型(配列や構造体や共用体)のABIアラインメントと最適なアラインメントを規定
ssize:abi:pref | sizeで指定されたビットサイズのスタックオブジェクトのアラインメントを規定
nsize1:size2:size3... | ターゲットCPUがサポートする整数データのビット幅を規定

なお，DataLayoutが規定されていない場合は，表4.14の値がデフォルト値として利用されます．

表4.14: DataLayoutのデフォルト値

値 | 説明
E | ターゲットのデータレイアウトがビッグエンディアンであることを規定
p:64:64:64 | 64ビットのポインタを64ビットアラインメントで配置することを規定
i1:8:8 | 1ビットの整数を8ビットアラインメントで配置することを規定
i8:8:8 | 8ビットの整数を8ビットアラインメントで配置することを規定(prefは省略可能)
S0 | スタックオブジェクトのアラインメントは規定しない
i16:16:16 | 16ビットの整数を16ビットアラインメントで配置すること を規定
i32:32:32 | 32ビットの整数を32ビットアラインメントで配置することを規定
i64:32:64 | 64ビットの整数のABIアラインメントは32ビットだが推奨値は64ビットアラインメントであることを規定
f16:16:16 | 16ビットの浮動小数点を16ビットアラインメントで配置することを規定
f32:32:32 | 32ビットの浮動小数点を32ビットアラインメントで配置することを規定
f64:64:64 | 64ビットの浮動小数点を64ビットアラインメントで配置することを規定
v64:64:64 | 64ビットのベクタを64ビットアラインメントで配置することを規定
v128:128:128 | 128ビットのベクタを128ビットアラインメントで配置することを規定
a0:0:64 | 合成型を64ビットアラインメントで配置することを規定

ではここで改めて今回のLLVM IRを見直してみましょう．上記の内容に従うと今回のLLVM IRでは表4.15に示す内容が規定されていることになります．

表4.15: HelloWorld.llのDataLayout

値 | 説明
e | ターゲットのデータレイアウトがリトルエンディアンであることを規定
p:32:32:32 | 32ビットのポインタを32ビットアラインメントで配置することを規定
i1:8:8 | 1ビットの整数を8ビットアラインメントで配置することを規定
i8:8:8 | 8ビットの整数を8ビットアラインメントで配置することを規定(prefは省略可能)
i16:16:16 | 16ビットの整数を16ビットアラインメントで配置することを規定
i32:32:32 | 32ビットの整数を32ビットアラインメントで配置することを規定
i64:32:64 | 64ビットの整数のABIアラインメントは32ビットだが推奨値は64ビットアラインメントであることを規定
f32:32:32 | 32ビットの浮動小数点を32ビットアラインメントで配置することを規定
f64:32:64 | 64ビットの浮動小数点のABIアラインメントは32ビットだが推奨値は64ビットアラインメントであることを規定
v64:64:64 | 64ビットのベクタを64ビットアラインメントで配置することを規定
v128:128:128 | 128ビットのベクタを128ビットアラインメントで配置することを規定
a0:0:64 | 合成型を64ビットアラインメントで配置することを規定
f80:32:32 | 80ビットの浮動小数点を32ビットアラインメントで配置することを規定
n8:16:32 | ターゲットCPUが8ビット，16ビット32ビットの整数データをサポートすることを規定
S128 | スタックのアラインメントが128ビットであることを規定

最後に，3行目のi386〜という記述はtarget-tripleというもので，アーキテクチャやOSなどの環境が記載されています．なお，今回示したDataLayoutやtarget-tripleは筆者が使用している環境での値であり，ここに記載される値は当然環境によって異なります．

グローバル宣言

5行目の@.str〜の一文ではプログラム中に現れる文字列を定義しています．LLVM IRではグローバル変数は@から始まる識別子で表記されます．ここで定義されている@.strというのは後にprintfの引数となる文字列ですね．

関数定義

7行目からmain関数の定義にはいります．8行目のentryというのはBasicBlockのラベル(名前)です．今回はBasicBlockが一つしかありませんが，分岐を含む場合は複数のBasicBlockが現れそれぞれに固有のラベルがつけられます．9行目からはBasicBlock内に含まれる処理，つまり命令が列挙されています．9行目において，まず変数retvalとしてalloca命令でメモリを確保しています．なお，今回のallocaは32×1の領域を4バイトアラインメントで確保しています．10行目はstore命令でretvalに0を代入しています．11行目でcall命令でprintf関数を呼び出し，ret命令で値を返します．ちなみに皆さんお気づきだとは思いますが，retvalの値はstore命令後一切使用されていません．以前のClangで出力では最終的にretvalの値をload命令で取得してret命令で返却する，という処理があったのですが，3.1では無くなっています．*1

さて，話はそれましたが最後にprintfの関数宣言があります．関数宣言はdeclareで行います．今回はライブラリ関数なので宣言のみですが，関数が定義されている場合はmain関数と同様に関数定義，つまりBasicBlockや命令も記述されます．

4.6 条件文

HelloWorldを理解したところで次は条件分岐を含むプログラムを見ていきましょう．今回は下記のif-elseを含むプログラムを使ってみます．

リスト4.6: branch.c

 1: #include <stdio.h>
 2:
 3: int main(int argc, char **argv) {
 4:
 5:   int i;
 6:   if (argc == 0) {
 7:     i = 1;
 8:   } else {
 9:     i = 2;
10:   }
11:
12:   return 0;
13: }

先ほどと同じく上記ソースコードの中間表現を出力します．

$ clang -emit-llvm -S -o branch.ll branch.c

さて，出力された中間表現を確認しましょう．

リスト4.7: branch.ll

 1: ; ModuleID = 'branch.c'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: define i32 @main(i32 %argc, i8** %argv) nounwind {
 6: entry:
 7:   %retval = alloca i32, align 4
 8:   %argc.addr = alloca i32, align 4
 9:   %argv.addr = alloca i8**, align 4
10:   %i = alloca i32, align 4
11:   store i32 0, i32* %retval
12:   store i32 %argc, i32* %argc.addr, align 4
13:   store i8** %argv, i8*** %argv.addr, align 4
14:   %0 = load i32* %argc.addr, align 4
15:   %cmp = icmp eq i32 %0, 0
16:   br i1 %cmp, label %if.then, label %if.else
17:
18: if.then:                                          ; preds = %entry
19:   store i32 1, i32* %i, align 4
20:   br label %if.end
21:
22: if.else:                                          ; preds = %entry
23:   store i32 2, i32* %i, align 4
24:   br label %if.end
25:
26: if.end:                                           ; preds = %if.else, %if.then
27:   ret i32 0
28: }

しかし今回は何やらBasicBlockが複数あって少し長いですね．別に読めなくはないですが，BasicBlockの遷移をグラフにすると読みやすくなりそうです．というわけで，次のコマンドを投入してみてください．

$ opt -S branch.ll -dot-cfg

正常に実行できれば，cfg.main.dotというファイルが出来ていると思います．これはdotという形式で記述されたControl Flow Graph(以降，CFG)です．xdotを入れていればこのファイルを閲覧できると思います．また，以下のようにdotコマンドを用いれば任意の形式へ変換することも可能です．

# Tの直後に指定した形式へ変換．ここではeps
$ dot -Teps cfg.main.dot -o branch.eps

それでは出力したCFGを見てみましょう．

図4.3: LLVMの条件文のCFG

何だかグラフィカルでわかりやすくなりましたよね*2!?さて，それではこのCFGを見ながら先ほどの中間表現を振り返ってみましょう．まずentryブロックからですが，13行目までは特に問題ないでしょう．先ほど説明したallocaやstore命令を使用して，メモリの確保や初期化を行っているだけです．入力ソースコードで言うと5行目までに相当します．

14行目からは新しい命令が登場してきます．今回新しく登場した命令はload，icmp，brの3命令です．順を追って説明していきましょう．

まずload命令はその名の通り値をloadする命令です．今回は，%argc.addrが指すアドレスの値をレジスタ%0に格納しています．%0の値は次のicmp命令の引数として使用されます．icmpは指定した条件で引数で指定した二つの値を比較し，真偽値を返します．今回はeqを指定して%0と0が渡されているため，%0の値が0の時にtrueに，それ以外の時にfalseになります．icmpで得た真偽値は次のbr命令の引数になります．名前でピンと来た方も多いと思いますが，brは所謂ブランチ(分岐)のことです．つまりはここで条件分岐が発生するわけです．icmpの結果がtrueの時にif.thenブロックへ，falseの時にif.elseブロックへ分岐します．あとはもうおわかりだと思いますが分岐後に最終的にif.endブロックに合流し，0をreturnします．

ちなみに今回はif文だったのでicmpとbrで分岐しましたが，switch文の場合はswitch命令が使用されます．

4.7 ループ文

条件文の説明をしたわけですし特別説明する必要もないかとも思いますが，一応念のためにループ文も確認しておきます．今回はリスト4.8に示す様な簡単なループ文をコンパイルしてみます．

リスト4.8: loop.c

 1: #include <stdio.h>
 2:
 3: int loop(int n) {
 4:   int i;
 5:   for (i = 0; i < n; i++)
 6:     printf("%d\n", i);
 7:
 8:   return i;
 9: }

いつも通りClangで中間表現を出力します．

$ clang -emit-llvm -S -o loop.ll loop.c

出力された中間表現はリスト4.9の通りです．

リスト4.9: loop.ll

 1: ; ModuleID = 'loop.c'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
 6:
 7: define i32 @loop(i32 %n) nounwind {
 8: entry:
 9:   %n.addr = alloca i32, align 4
10:   %i = alloca i32, align 4
11:   store i32 %n, i32* %n.addr, align 4
12:   store i32 0, i32* %i, align 4
13:   br label %for.cond
14:
15: for.cond:                                         ; preds = %for.inc, %entry
16:   %0 = load i32* %i, align 4
17:   %1 = load i32* %n.addr, align 4
18:   %cmp = icmp slt i32 %0, %1
19:   br i1 %cmp, label %for.body, label %for.end
20:
21: for.body:                                         ; preds = %for.cond
22:   %2 = load i32* %i, align 4
23:   %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %2)
24:   br label %for.inc
25:
26: for.inc:                                          ; preds = %for.body
27:   %3 = load i32* %i, align 4
28:   %inc = add nsw i32 %3, 1
29:   store i32 %inc, i32* %i, align 4
30:   br label %for.cond
31:
32: for.end:                                          ; preds = %for.cond
33:   %4 = load i32* %i, align 4
34:   ret i32 %4
35: }
36:
37: declare i32 @printf(i8*, ...)
38:

今回もCFGを載せて解説を進めていきます．

$ opt -S loop.ll -dot-cfg
$ dot -Teps cfg.loop.dot -o loop.eps

図4.4: ループ文のCFG

今回はentry，for.cond，for.body，for.inc，for.endの五つのブロックが登場します．まずentryはループのプリヘッダとなるブロックであり，処理内容はこれまで同様allocaによるメモリ確保と初期化，及び次ブロックへのbr命令です．今回のbr命令ではfor.condへ無条件に遷移しています．次に，CFGを確認するとfor.cond，for.body，for.incの3つのブロックでループが発生していることがわかります．つまりここが入力ソースコードの5，6行目のループ文に該当する処理となるわけです．

まず，最初のブロックであるfor.condはループのヘッダと呼ばれる部分であり，ループの繰り返しの条件判定をしています．condをconditionの略だと考えれば意味がわかりやすいと思います．条件分岐はif文でも登場したicmp命令で行っています．今回のicmp命令では条件としてsltを指定し，load命令で読み込んだiとnの値を比較しています．入力ソースコードの5行目，i < nに相当する部分ですね．

for.bodyはその名の通りループの本体です．今回のソースコードでは6行目のprintf関数の呼び出しに相当します．これはHelloWorldと同じ形式なので説明を省略します．

for.incはループラッチと呼ばれるブロックで，今回の例では制御変数iの加算が行われています．for.incは最終的にbr命令でループヘッダ(for.cond)へ遷移しており，CFGでもfor.condへのバックエッジが確認できると思います．

for.condに戻った後は再度条件判定が行われ，繰り返し条件がfalseであればfor.endへ抜けます．for.endはループの出口ブロックであり，変数iの値を返すためにload命令とret命令が記述されています．

なお，今回はfor文で確認しましたが，while文でも基本的には見方は同じです．

最適化した場合

さて，気づいた方もいるかもしれませんが，loop.llではfor.condに到達するブロックの中に変数iへの代入を行う物が複数存在するというのにphi命令が使用されていません．これは変数iをallocaでスタック上に確保しており，storeとloadで読み書きしているためです．一応SSA形式ではあるものの最初に色々と言った手前これでは疑問が残ると思うのでphi命令を使用した形式に変換してみます．

LLVMにはmem2regというPassが用意されておりこのPassはメモリアクセスを可能な限りレジスタへ変更してくれます．次のコマンドでloop.llにmem2regを適用してみましょう．

$ opt -S -mem2reg loop.ll -o loop_reg.ll

次のような結果が得られるはずです．

リスト4.10: loop_reg.ll

 1: ; ModuleID = 'loop.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
 6:
 7: define i32 @loop(i32 %n) nounwind {
 8: entry:
 9:   br label %for.cond
10:
11: for.cond:                                         ; preds = %for.inc, %entry
12:   %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
13:   %cmp = icmp slt i32 %i.0, %n
14:   br i1 %cmp, label %for.body, label %for.end
15:
16: for.body:                                         ; preds = %for.cond
17:   %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %i.0)
18:   br label %for.inc
19:
20: for.inc:                                          ; preds = %for.body
21:   %inc = add nsw i32 %i.0, 1
22:   br label %for.cond
23:
24: for.end:                                          ; preds = %for.cond
25:   ret i32 %i.0
26: }
27:
28: declare i32 @printf(i8*, ...)

allocaやload，storeが消え，全ての変数がレジスタに格納されています．また12行目でphi命令が使用され，iの値選択にφ関数が使用されていることが確認できます．

4.8 合成型へのアクセス

合成型，つまり配列や構造体へのアクセス方法の確認します．実際の例を見て確認していきましょう．

リスト4.11: aggregate.c

 1: #include <stdio.h>
 2:
 3: struct hoge1 {
 4:   float x;
 5:   int y[10][20];
 6: };
 7: struct hoge2 {
 8:   char a;
 9:   double b;
10:   struct hoge1 c;
11: };
12:
13: int *getelem(struct hoge2 *hoge) {
14:   return &hoge[1].c.y[5][13];
15: }

今回もClangでLLVMアセンブリを生成します．

$ clang -emit-llvm -S -o aggregate.ll aggregate.c

出力されたコードを確認します．

リスト4.12: aggregate.ll

 1: ; ModuleID = 'aggregate.c'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: %struct.hoge2 = type { i8, double, %struct.hoge1 }
 6: %struct.hoge1 = type { float, [10 x [20 x i32]] }
 7:
 8: define i32* @getelem(%struct.hoge2* %hoge) nounwind {
 9: entry:
10:   %hoge.addr = alloca %struct.hoge2*, align 4
11:   store %struct.hoge2* %hoge, %struct.hoge2** %hoge.addr, align 4
12:   %0 = load %struct.hoge2** %hoge.addr, align 4
13:   %arrayidx = getelementptr inbounds %struct.hoge2* %0, i32 1
14:   %c = getelementptr inbounds %struct.hoge2* %arrayidx, i32 0, i32 2
15:   %y = getelementptr inbounds %struct.hoge1* %c, i32 0, i32 1
16:   %arrayidx1 = getelementptr inbounds [10 x [20 x i32]]* %y, i32 0, i32 5
17:   %arrayidx2 = getelementptr inbounds [20 x i32]* %arrayidx1, i32 0, i32 13
18:   ret i32* %arrayidx2
19: }

何やら13行目からgetelementptr命令がたくさん並んでいます．直感で何となくわかりそうですが，とりあえず順を追って確認していきましょう．

まずは13行目の命令です．このgetelementptr命令ではstruct.hoge2のポインタ%0と1が引数として与えられています．getelementptr命令は第1引数で与えられたポインタをベースとして，第2引数以降で指定されたインデックスの要素のアドレスを計算します．なお，getelementptr命令の一つ目のインデックスは第1引数で与えたポインタのインデックスづけに使用されます．つまり今回は%0のインデックス1の要素のアドレスを計算しており，入力ソースコードでいうところのhoge[1]に相当します．

次は14行目です．今回は先ほど計算したarrayidxをベースとして0と2という二つのインデックスが渡されています．これにより，このgetelementptr命令ではarrayidx[0]のインデックス2(3つめの要素)のアドレスを計算していると考えることが出来ます．arrayidxはstruct.hoge2のポインタですので，hoge2構造体を確認すると，3つめの要素はstruct hoge1 cとなっています．というわけで，ここまででhoge[1].cのアドレスが計算できました．

ここまでわかれば後は同じことの繰り返しです．15行目でhoge[1].c.y (hoge[1].c.y[0]と言い換えられる)を，16行目でhoge[1].c.y[5]を，そして17行目でhoge[1].c.y[5][13]を計算しています．getelem関数は戻り値がポインタなので最後のarrayidx2の値を返却しますが，計算したアドレスに格納された値を取り出す場合はload命令を使用します．

ちなみに，getelementptrのインデックスは最適化をかけると下記の様に簡略化されます．

リスト4.13: aggregate_opt.ll

 1: ; ModuleID = 'aggregate.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: %struct.hoge2 = type { i8, double, %struct.hoge1 }
 6: %struct.hoge1 = type { float, [10 x [20 x i32]] }
 7:
 8: define i32* @getelem (%struct.hoge2* %hoge) nounwind readnone {
 9: entry:
10:   %arrayidx2 = getelementptr inbounds %struct.hoge2* %hoge, i32 1, i32 2, i32 1, i32 5, i32 13
11:   ret i32* %arrayidx2
12: }

たくさんあったgetelementptr命令が一つになっていますが，意味的には同じです．

4.9 Metadata

4.4.1節で紹介したfirst classにmetadataというものがありました．first class内の他の項目(pointerやvector，structureなど)はおおよそ見当がつくと思いますが，metadataは少し特殊だと思うのでここで説明しておきます．

LLVM IRではソースコードレベルのデバッグ情報など，最適化やコード生成で使用するためのコードに関する情報を命令内に埋め込むことができます．Metadataを命令に付与することでLLVM IRの命令だけでは読み取れない情報をバックエンド側へと渡すことができます．

Metadataの構成要素にはMetadata StringとMetadata Nodesがあるのですが，まずMetadata Stringから説明します．Metadata Stringはダブルクオートで囲まれた任意の文字列で，Metadataであることを示すために前置記号として!をつけて記述します．非表示文字列を含む場合は，“\00”のようにバックスラッシュで16進コードをエスケープします．Metadata Nodesは複数の要素によって構成されるMetadataで，各要素をカンマで区切り，{ }で囲って記述します．なお，Metadata Nodesはオペランドに任意の値をとることができます．

リスト4.14: Metadata Nodesの例

 1: ; Metadata Nodes(0から連番が振られる模様)．
 2: !0 = metadata !{ metadata !"sample\00", i32 0}

さらに，複数のMetadata Nodesの集合としてNamed Metadataを定義することができます．以下にNamed Metadataの例を記載します．

リスト4.15: Named Metadataの例

 1: ; Metadata Nodes
 2: !0 = metadata !{metadata !"sample1"}
 3: !1 = metadata !{metadata !"sample2"}
 4: !2 = metadata !{metadata !"sample3"}
 5: ; 上の三つのMetadata NodesをもつNamed Metadata
 6: !name = !{!0, !1, !2}

なお，Named MetadataのオペランドとなるのはMetadata Nodesのみであり，Metadata Stringをオペランドとして与えることはできません．

LLVM 3.2ではtbaa Metadata，tbaa.struct Metadata，fpmath Metadata，range Metadataなどの特別なMetadataがLLVMのライブラリで用意されています．なお，trunkの情報を見ているとLLVM 3.3ではllvm.loop.parallel Metadataやllvm.mem.loop.parallel_loop_access Metadataが追加される様です．

’tbaa’Metadata

tbaa MetadataはTypeBasedAliasAnalysis(TBAA)で使用します．AliasAnalysisとはある記憶領域が複数の変数から参照されるかを解析するコード解析手法です(ポインタをイメージするとわかりやすいでしょうか)．TBAAはその中でも型に基づいて行う解析のことで，LLVMにおいてはMetadataを用いてLLVMの型情報とは別にプログラミング言語の型情報をLLVM IR中に挿入します．これがtbaa Metadataです．今回は詳細を割愛しますが，LLVMのドキュメント“LLVM Language Reference Manual”にて簡単に紹介されていますので，興味のある方は目を通してみてもいいかもしれません．

’tbaa.struct’Metadata

tbaa.struct Metadataはllvm.memcpyでコピーするメモリ領域のパディング情報や構造体のtbaaタグの情報を表すMetadataです．llvm.memcpyはLLVMの組み込み関数であり，連続するメモリ領域をコピーする関数です．llvm.memcpyは例えば合成型への代入を実装するために使用されますが，連続するメモリ領域をコピーする関数ですので，対象のメモリ領域にパディングが詰められているかどうかは意識しません．また，llvm.memcpyには合成型の各要素のtbaa情報を一切含みません．tbaa.struct Metadataはこれらの情報を表すMetadataです．tbaa.struct Metadataはオフセット，サイズ，tbaaタグの三つの要素を1セットとして，構造体のどの領域がパディングであるかや各要素のtbaa情報を表します．

’fpmath’Metadata

fpmath Metadata は，浮動小数点数に関する命令に付与されるMetadata で，許容可能な誤差をULPで示すのに利用します．ULPとはUnits in the Last Place の略で，演算の精度を表す単位です．ULP は，ULP(x) の値を1ULPとして誤差を示すのに利用します．非常にざっくりとした説明をすると，連続する浮動小数点数aとbの間にある実数xについて，ULP(x)の値は|b - a|になります．基本的に、0.5ULP以上の差がある場合は別の値に近似されるはずですので，ある浮動小数点演算の結果を正確に最も近い値に近似した場合，誤差は0.5ULP以下に収まるはずです．

実際のfpmath Metadataの例を以下に示します．

リスト4.16: fpmath Metadataの例

 1: ; ULP 0.5を示すfpmath Metadata
 2: !0 = metadata !{float 5.000000e-01}
 3: ;上記のMetadataを付与した浮動小数点数の演算命令
 4: %fpmath_sample = fsub float %float_val, 2.000000e+00, !fpmath !0

浮動小数点数を扱う命令はいくつかありますが，今回は例として浮動小数点数同士の減算を行うfsub命令を使用しています．fsubにfpmath Metadataを付与すると，!fpmathというタグともにMetadataがオペランドとして追加されます．上記の例ではULP 0.5を指定したfsub命令となっていますが，これは0.5 ULP以内の誤差を許容することを示しています．

’range’Metadata

range Metadataはload命令に付加できるMetadataです．load命令は値をメモリ上からロードする命令ですが，range Metadataは整数のload命令に対して，ロードする値のとりうる範囲を示すのに使用します．range Metadataの書式は以下のとおりです．

リスト4.17: range Metadataの例

 1: ; 0～100を示すrange Metadata
 2: !0 = metadata !{i32 0, i32 100}
 3: ;上記のrange Metadataを付与したload命令
 4: %tmp = load i32* %i, !range !0

[*1] 当然最適化オプションをつければ削除されます

[*2] まぁ今回程度のコードでは効果がわかりにくいかもしれませんが

第5章 フロントエンドの作成

5.1 本章の概要

本章では，LLVMのフロントエンドの実装を説明します．サンプルとしてC言語のサブセットである簡易言語，DummyCを規定してみました．本章ではDummyCのフロントエンドを作成することで，LLVMのフロントエンドの実装方法を確認していきます．実装に使用した言語はC++です．

本章ではフロントエンドの実装を字句解析，構文解析，意味解析，コード生成の順で解説していきますが，実際のところ字句解析〜意味解析まではLLVMとほとんど関係ありませんので，単純にLLVMの命令をどうやって生成するかという点を知りたい場合はコード生成から読んでいただくのが良いと思います*1．

本章での説明では，フロントエンド実装例として，DummyCフロントエンドのソースコードを適宜載せながら解説をおこないます．しかし，ページ数の削減や本文の可読性を考え，本書に記載する部分は筆者が必要だと考える部分に絞っています．全ソースコードを見たいという方につきましては，巻末の執筆者情報に記載した筆者その1のgithubにてDummyCCompilerリポジトリとして公開していますので各自取得をお願いします．なお，今回のフロントエンド実装は“正しいソースコードを正しく処理できること”ことを目標とします．エラー処理を全くしないわけではありませんが，誤ったソースコードを受け取った時の動作については保証しません．

ちなみにLLVMのドキュメント，LLVM Tutorial*2ではKaleidoScopeという簡単な関数型言語をターゲットにしたフロントエンドの実装を解説しています．興味がある方はそちらにも目を通してみるとよいかもしれません．

5.2 構文規則の定義

最初に今回作成するフロントエンドの対象となる言語の構文規則を規定します．C言語のサブセットですので基本的にC言語が読めればすぐ理解できると思います．なお，今回規定したDummyCがC言語と異なる点は以下の通りです．

変数や関数の型は全てint型

if文やfor文などの分岐/繰り返し無

グローバル変数無

変数の宣言時初期化無

変数のカンマ区切りの宣言無

include等のプリプロセッサディレクティブ無

ビット演算無

単項演算無

配列のサポート無

要約すると，基本的には何も出来ないC言語もどきですが四則演算と関数呼び出しをサポートする，という感じです．また，構文規則に記載されていない制限として，エントリとなる関数名は“main”とし，引数はとらないことにします．

DummyCのEBNFを図5.1に示します*3．

リスト5.1: dummyC-ebnf

 1: (* C言語のサブセット *)
 2: (* 名前はDummyCとしておく *)
 3: (* 機能はかなりミニマル *)
 4: (* 一応EBNFで書いているつもり *)
 5: (* {}は0回以上の繰り返し *)
 6: (* []はオプション *)
 7:
 8:
 9: translation_unit
10:   : { external_declaration }
11:   ;
12:
13: external_declaration
14:   : function_declaration
15:   | function_definition
16:   ;
17:
18: function_declaration
19:   : prototype , ";"
20:   ;
21:
22: function_definition
23:   : prototype , function_statement
24:   ;
25:
26: prototype
27:   : "int" , identifier , "(" , [ parameter , { "," , parameter} ] , ")"
28:   ;
29:
30: parameter
31:   : "int" , identifier
32:   ;
33:
34: function_statement
35:   : "{" , [ variable_declaration_list ] , statement_list , "}"
36:   ;
37:
38: variable_declaration_list
39:   : { variable_declaration }
40:   ;
41:
42: variable_declaration
43:   : "int" , identifier , ";"
44:   ;
45:
46: statement_list
47:   : { statement }
48:   ;
49:
50: statement
51:   : expression_statement
52:   | jump_statement
53:   ;
54:
55: expression_statement
56:   : ";"
57:   | assignment_expression , ";"
58:   ;
59:
60: jump_statement
61:   : "return" , assignment_expression , ";"
62:   ;
63:
64: assignment_expression
65:   : identifier , "=" , additive_expression
66:   | additive_expression
67:   ;
68:
69: additive_expression
70:   : multiplicative_expression , [ { "+" , multiplicative_expression  |  "-" , multiplicative_expression } ]
71:   ;
72:
73: multiplicative_expression
74:   : postfix_expression , [ { "*" , postfix_expression  |  "/" , postfix_expression } ]
75:   ;
76:
77: postfix_expression
78:   : primary_expression
79:   | identifier , "(" , [ assignment_expression , { "," , assignment_expresstion } ] , ")"
80:   ;
81:
82: primary_expression
83:   : identifier
84:   | integer
85:   | "(" , additive_expression , ")"
86:   ;
87:
88: identifier
89:   : character , [ { character | digit } ]
90:   ;
91:
92: integer
93:   : "0" | [ "-" ] digit_excluding_zero , [ { digit } ]
94:   ;
95:
96: character
97:   : "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
98:   | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
99:   | "U" | "V" | "W" | "X" | "Y" | "Z"
100:   | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
101:   | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
102:   | "u" | "v" | "w" | "x" | "y" | "z"
103:   ;
104:
105: digit_excluding_zero
106:   : "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
107:   ;
108:
109: digit
110:   : "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
111:   ;

なお，EBNFだけでは構文がわかりにくいという部分もあるかと思いますので，実際に構文解析クラスを実装を説明する5.5.3節では構文図を記載しています．DummyCの構文についてわからない部分がある場合は5.5.3節の構文図とEBNFを照らし合わせて確認してみてください．

5.3 目標の設定

実装を始める前に，作成するフロントエンドのゴールを決定しておきましょう．本章で作成するDummyCフロントエンドでは，リスト5.2に示すコードをコンパイルし，正常にLLVM IRへと変換できることを目標としたいと思います．

リスト5.2: 目標とするDummyCのサンプルコード

 1: int test(int j) {
 2:   int i;
 3:   i = j * 10;
 4:   return i;
 5: }
 6:
 7: int main() {
 8:   int i;
 9:   i = 10;
10:   test(i);
11:   return 0;
12: }

非常に簡単なソースコードですが，初めの第一歩としてはこのぐらいが適切でしょう．

それでは，目標も決まったことですので次節から早速実装を始めます．

5.4 字句解析

それではまず字句解析の実装から開始しましょう．字句解析の役割は，入力ソースコードをプログラムの最小構成単位であるトークンに分割することです．なお，字句解析には特にLLVMは関係してこないので，ここは自由に実装してしまって問題ありません．今回筆者が作成した字句解析の実装方針は下記の通りです．

最初に受け取った入力ファイルを全て走査し，全てのトークンを切り出す

切り出した個別のトークン情報を格納するTokenクラスを作成する

Tokenを格納しておくTokenStreamクラスを作成する

なお，後述する構文解析ではTokenStreamからトークン情報を取得するものとします．

5.4.1 共通マクロの定義

まずフロントエンドプログラムを構成する各ソースコード内で共通で使用する以下のマクロをAPP.hppというファイルに定義しておきます．内容は簡単で，与えられたポインタの領域をdeleteし，delete後NULLを設定しているだけです．本書で作成するプログラムではメモリ開放時はこのマクロを呼ぶことで二重開放を防ぎます．

リスト5.3: 共通マクロの定義

 1: #ifndef _APP_H_
 2:
 3: #define SAFE_DELETE(x) {delete x;x=NULL;}
 4: #define SAFE_DELETEA(x) {delete[] x;x=NULL;}
 5:
 6: #endif

スマートポインタで管理してしまえば良いという話もあるとは思いますが，とりあえず本書ではこの方法で管理していきます．

5.4.2 Tokenクラスの実装

まず切り出したトークンを格納するTokenクラスを作成します．Tokenクラスでは後々構文解析で必要となる情報を格納しておく必要があります．今回筆者が作成したTokenクラスは以下の情報と各情報へのsetter/getterを持つことにしました．

トークンの種類

トークンの文字列

トークンの数値(トークンの種類が数字だった場合に使用)

トークンが出現したソースコード上の行数

ここで，トークンの種類というのはトークンが識別子，予約語，記号，数値の何れに該当するかという情報です．実際のTokenクラスの定義例をリスト5.4に示します

リスト5.4: Token classの定義

 1: /**
 2:  * トークン種別
 3:  */
 4: enum TokenType {
 5:   TOK_IDENTIFIER, // 識別子
 6:   TOK_DIGIT,      // 数字
 7:   TOK_SYMBOL,     // 記号
 8:   TOK_INT,        // INT
 9:   TOK_RETURN,     // RETURN
10:   TOK_EOF         // EOF
11: };
12:
13: /**
14:  * 個別トークン格納クラス
15:  */
16: class Token {
17: private:
18:   TokenType Type;
19:   std::string TokenString;
20:   int Number;
21:   int Line;
22:
23: public:
24:   Token(std::string string, TokenType type, int line)
25:       : TokenString(string), Type(type), Line(line) {
26:     if (type == TOK_DIGIT)
27:       Number = atoi(string.c_str());
28:     else
29:       Number = 0x7fffffff;
30:   };
31:   ~Token() {};
32:
33:   // トークンの種別を取得
34:   TokenType getTokenType() {
35:     return Type;
36:   };
37:
38:   // トークンの文字列表現を取得
39:   std::string getTokenString() {
40:     return TokenString;
41:   };
42:
43:   // トークンの数値を取得(種別が数字である場合に使用)
44:   int getNumberValue() {
45:     return Number;
46:   };
47:
48:   // トークンの出現した行数を取得
49:   int getLine() { return Line; }
50: };

5.4.3 TokenStreamクラスの実装

先ほどのTokenクラスを格納するTokenStreamクラスを作成します．TokenStreamは切り出した全てのTokenの格納と，格納したTokenの読み出しの機能を提供するものです．TokenStreamクラスはTokenを格納するためのクラスなので，基本的にはTokenを保存するリストや配列を用意してsetter/getterを作成すればよいです．しかし構文解析では“○個分前のトークンまで戻る”という操作や，“○番目のトークンまで戻る”という操作を行うためTokenStreamにはこれらの機能を持たせる必要があります．

このことから，まずTokenStreamクラスでは以下のメンバ変数を持つものとします．

表5.1: TokenStreamクラスのメンバ変数

クラス名 | メンバ変数 | 補足
TokenStream | std::vector<Token*> Tokens | Tokenを格納するベクタ
| int CurIndex | 呼び出し対象のTokenのインデックス

切出したTokenはTokensベクタに格納しておき，Tokenを取得するメソッドが呼び出された際はCurIndex番目のトークンを返却することにします．また，インデックスをインクリメントするメソッド，および指定した値にインデックスの値を設定するメソッドを用意することで次のTokenの読み出しとインデックスの巻き戻しに対応します．

以上をまとめ，TokenStreamクラスには以下のメソッドを実装します．

Tokenの追加

Tokenの取得

インデックスの指定数巻き戻し

指定したインデックスへの巻き戻し

現在のインデックスのトークンの種類の取得

現在のインデックスのトークンの文字列表現の取得

現在のインデックスのトークンの数値の取得(Tokenが数値の場合に使用)

格納されているTokenの出力

5，6，7は構文解析部からのアクセスを容易にするためにTokenクラスのgetterをラップします．また8はデバッグ用です．以上を実装したTokenStreamクラスの宣言と定義をリスト5.5，リスト5.6に示します．

リスト5.5: TokenStream classの宣言

 1: /**
 2:   * 切り出したToken格納用クラス
 3:   */
 4: class TokenStream {
 5: private:
 6:   std::vector<Token *> Tokens;
 7:   int CurIndex;
 8:
 9: public:
10:   TokenStream() : CurIndex(0) {}
11:   ~TokenStream();
12:
13:   bool ungetToken(int Times = 1);
14:   bool nextToken();
15:   bool pushToken(Token *token) {
16:     Tokens.push_back(token);
17:     return true;
18:   }
19:   Token getToken();
20:
21:   // トークンの種類を取得
22:   TokenType getCurType() { return Tokens[CurIndex]->getTokenType(); }
23:
24:   // トークンの文字列表現を取得
25:   std::string getCurString() { return Tokens[CurIndex]->getTokenString(); }
26:
27:   // トークンの数値を取得
28:   int getCurNumVal() { return Tokens[CurIndex]->getNumberValue(); }
29:
30:   // 現在のインデックスを取得
31:   int getCurIndex() { return CurIndex; }
32:
33:   // インデックスを指定した値に設定
34:   bool applyTokenIndex(int index) {
35:     CurIndex = index;
36:     return true;
37:   }
38:
39:   bool printTokens();
40: };

リスト5.6: TokenStream classの定義

 1: /**
 2:   * デストラクタ
 3:   */
 4: TokenStream::~TokenStream() {
 5:   for (int i = 0; i < Tokens.size(); i++) {
 6:     SAFE_DELETE(Tokens[i]);
 7:   }
 8:   Tokens.clear();
 9: }
10:
11: /**
12:   * トークン取得
13:   * @return CureIndex番目のToken
14:   */
15: Token TokenStream::getToken() { return *Tokens[CurIndex]; }
16:
17: /**
18:   * インデックスを一つ増やして次のトークンに進める
19:   * @return 成功時：true　失敗時：false
20:   */
21: bool TokenStream::nextToken() {
22:   int size = Tokens.size();
23:   if (--size <= CurIndex) {
24:     return false;
25:   } else {
26:     CurIndex++;
27:     return true;
28:   }
29: }
30:
31: /**
32:   * インデックスをtimes回戻す
33:   */
34: bool TokenStream::ungetToken(int times) {
35:   for (int i = 0; i < times; i++) {
36:     if (CurIndex == 0)
37:       return false;
38:     else
39:       CurIndex--;
40:   }
41:   return true;
42: }
43:
44: /**
45:   * 格納されたトークン一覧を表示する
46:   */
47: bool TokenStream::printTokens() {
48:   std::vector<Token *>::iterator titer = Tokens.begin();
49:   while (titer != Tokens.end()) {
50:     fprintf(stdout, "%d:", (*titer)->getTokenType());
51:     if ((*titer)->getTokenType() != TOK_EOF)
52:       fprintf(stdout, "%s\n", (*titer)->getTokenString().c_str());
53:     ++titer;
54:   }
55:   return true;
56: }

5.4.4 字句解析の実装

ここまででトークン情報を入れるTokenとTokenを格納するためのTokenStreamを用意しました．しかしTokenとTokenStreamはただの箱ですので，字句解析を実行するにはソースコードからトークンを切り出す解析部が必要です．本節ではこのトークンを切り出す字句解析の実装を行います．

トークン切り出し関数の入出力は以下の通りとします．

入力

入力ソースコード名(std::string型)

戻り値

切り出したTokenStream(TokenStream *型)

また，トークンの切り出しは以下の手順で行います．

std::ifstreamを用いて入力ソースコードを開く

入力ソースコードを一行ずつ読み込んで手順3，4を実行

一文字ずつ読み込みトークンを切り出す

切り出したトークンでTokenクラスのインスタンスを生成してTokenStreamに追加

最終行までトークン取得が完了したらTokenStreamを返却

途中でエラーが発生した場合は読み込んだ全てのTokenを破棄してNULLを返却

なお，DummyCの構文規則より，各トークンは識別子，予約語，数値，その他構文中に表れる記号の何れかになるはずです．

実際のコードをリスト5.7に示します．それほど難しいことはしていないので，詳しい説明は省略します．

リスト5.7: lexer

 1: /**
 2:  * トークン切り出し関数
 3:  * @param 字句解析対象ファイル名
 4:  * @return 切り出したトークンを格納したTokenStream
 5:  */
 6: TokenStream *LexicalAnalysis(std::string input_filename) {
 7:   TokenStream *tokens = new TokenStream();
 8:   std::ifstream ifs;
 9:   std::string cur_line;
10:   std::string token_str;
11:   int line_num = 0;
12:   bool iscomment = false;
13:
14:   ifs.open(input_filename.c_str(), std::ios::in);
15:   if (!ifs)
16:     return NULL;
17:   while (ifs && getline(ifs, cur_line)) {
18:     char next_char;
19:     std::string line;
20:     Token *next_token;
21:     int index = 0;
22:     int length = cur_line.length();
23:
24:     while (index < length) {
25:       next_char = cur_line.at(index++);
26:
27:       // コメントアウト読み飛ばし
28:       if (iscomment) {
29:         if (((length - index) >= 1) && (next_char == '*') &&
30:             (cur_line.at(index) == '/')) {
31:           iscomment = false;
32:           index++;
33:         }
34:         continue;
35:       }
36:
37:       // EOF
38:       if (next_char == EOF) {
39:         token_str = EOF;
40:         next_token = new Token(token_str, TOK_EOF, line_num);
41:
42:       } else if (isspace(next_char)) {
43:         continue;
44:
45:       // IDENTIFIER
46:       } else if (isalpha(next_char)) {
47:         while (isalnum(next_char)) {
48:           token_str += next_char;
49:           if (index == length)
50:             break;
51:           next_char = cur_line.at(index++);
52:         }
53:         if (!isalnum(next_char))
54:           index--;
55:
56:         if (token_str == "int") {
57:           next_token = new Token(token_str, TOK_INT, line_num);
58:         } else if (token_str == "return") {
59:           next_token = new Token(token_str, TOK_RETURN, line_num);
60:         } else {
61:           next_token = new Token(token_str, TOK_IDENTIFIER, line_num);
62:         }
63:
64:       // 数字
65:       } else if (isdigit(next_char)) {
66:         if (next_char == '0') {
67:           token_str += next_char;
68:           next_token = new Token(token_str, TOK_DIGIT, line_num);
69:         } else {
70:           while (isdigit(next_char)) {
71:             token_str += next_char;
72:             if (index == length)
73:               break;
74:             next_char = cur_line.at(index++);
75:           }
76:           next_token = new Token(token_str, TOK_DIGIT, line_num);
77:           if (!isdigit(next_char))
78:             index--;
79:         }
80:
81:       // コメント or '/'
82:       } else if (next_char == '/') {
83:         token_str += next_char;
84:
85:         // コメントの場合
86:         if (((length - index) >= 1) && (cur_line.at(index) == '/')) {
87:           break;
88:
89:         // コメントの場合
90:         } else if (((length - index) >= 1) && (cur_line.at(index) == '*')) {
91:           index++;
92:           iscomment = true;
93:           continue;
94:
95:         // DIVIDER('/')
96:         } else {
97:           next_token = new Token(token_str, TOK_SYMBOL, line_num);
98:         }
99:
100:       // それ以外(記号)
101:       } else {
102:         if (next_char == '*' || next_char == '+' || next_char == '-' ||
103:             next_char == '=' || next_char == ';' || next_char == ',' ||
104:             next_char == '(' || next_char == ')' || next_char == '{' ||
105:             next_char == '}') {
106:           token_str += next_char;
107:           next_token = new Token(token_str, TOK_SYMBOL, line_num);
108:
109:         // 解析不能字句
110:         } else {
111:           fprintf(stderr, "unclear token : %c", next_char);
112:           SAFE_DELETE(tokens);
113:           return NULL;
114:         }
115:       }
116:
117:       // Tokensに追加
118:       tokens->pushToken(next_token);
119:       token_str.clear();
120:     }
121:
122:     token_str.clear();
123:     line_num++;
124:   }
125:
126:   // EOFの確認
127:   if (ifs.eof()) {
128:     tokens->pushToken(new Token(token_str, TOK_EOF, line_num));
129:   }
130:
131:   // クローズ
132:   ifs.close();
133:   return tokens;
134: }

5.5 構文解析

次に構文解析を実装します．ここでは先ほど作成したTokenStreamを読み込んで，Abstract Syntax Tree(以降，AST)を作成します．

5.5.1 ASTを考える

構文解析を実装する前にASTの定義を行います．ASTとは抽象構文木のことで，プログラムの意味的に不要な部分を取り除き，木構造にしたものです．例えばASTでは，木の節に演算子が，葉には変数や数値があてはまります．構文解析の目的は切り出したトークンが構文規則に則っているか確認し，ASTを作成することです．ここではDummyCの構文規則に基づいてASTを考えます．

DummyCの構文規則ではtranslation_unitがモジュールを現し，translation_unitはfunction_declaratinとfunction_definitionによって構成されます．更にfunction_definitionの下にはvariable_declarationとstatementが並び，statementにはexpression_statementとjump_statementに分類されます．expression_statementはさらに“;”もしくはassignment_expressionに分解され，assignment_expressionにはadditive_expressionが含まれ・・・と続きます．これらの構文をASTにすることを考え，構成要素をC++のクラスとして定義していきます．なお，今回記載するASTはあくまで筆者の考えや好みで設計している部分も大いにあることをご了承ください．

ステートメントとエクスプレッション

DummyCでは一つのモジュールには複数の関数が存在し，関数はステートメントの集合として表現されます．ここでいうステートメントとは“;”で終わる一連の処理のことを指します．この段落では，まずステートメントを表すASTをクラスとして定義することを目標とします．ちなみに，DummyCでいうステートメントとは非終端記号のvariable_declarationやexpression_statement，jump_statementを指します．以降本段落では各種ステートメントのASTやそれらASTを構成するASTのクラス定義を解説していきます．

まず最初に方針を決定しておきます．今回のASTクラス定義の方針は下記の通りです．

一連のクラスの基底クラスを作成し，ステートメントを構成する各クラスはこれを継承する

isa<>およびdyn_cast<>を使用可能にするため，継承したクラスにclassofメソッドを実装する

これは実は後々構文解析の実装を簡単にするための対策だったりするのですが，とりあえずは“プログラムのステートメントを構成する要素を共通化するために基底クラスを用意しておく”，ぐらいに捕らえてもらえると幸いです．ちなみに2のclassofメソッドはLLVMのisa<>テンプレートやdyn_cast<>テンプレートを使用するために必要なメソッドです．isa<>やdyn_cast<>はC++のRTTIの代わりとしてLLVMが用意しているテンプレートで，llvm/Support/Casting.hに宣言されています．これらテンプレートにより，クラスの型判別やキャストが可能になります．LLVMではRTTIの使用を控えisa<> ，dyn_cast<>，cast<>といったLLVMのテンプレートを使用することを推奨しています*4．なお，classofの実装方法については後ほど記載するASTのソースコードにて確認をお願いします．話が少しそれましたが，下記のBaseASTクラスを基底クラスとして宣言します．

リスト5.8: ASTの基底クラス

 1: /**
 2:   * ASTの種類
 3:   */
 4: enum AstID {
 5:   BaseID
 6: };
 7:
 8: /**
 9:   * ASTの基底クラス
10:   */
11: class BaseAST {
12:   AstID ID;
13:
14: public:
15:   BaseAST(AstID id) : ID(id) {}
16:   virtual ~BaseAST() {}
17:   AstID getValueID() const { return ID; }
18: };

ASTの種類を表すAstIDはこの時点ではBaseASTをあらわすBaseIDのみですが，今後各種ASTのクラスを定義するごとに追加されていきます．

それでは，引き続きステートメントのASTについて考えましょう．まずは，ステートメントの構成要素から考えていきます．プログラムの最小構成要素はトークンであり，それは予約語(intとreturn)とidentifier，integerおよび記号です．これは同時にステートメントの最小構成要素とも言えます．ちなみにASTとして考えた時には，“;”や“{”，“}”等の記号は取り除くので記号として残るのは演算子のみです．とりあえず予約語はおいておくとして，演算子はASTの節になるのでidentifierとinteger，つまり識別子と数値が葉ノードになると考えられます．識別子は変数名と関数名の二つの可能性が考えられますが，識別子だけでは関数を表現できないのでここではまず変数を考えます．というわけで，まず変数名情報をもつVariableASTクラス，数値情報をもつNumberASTクラスを定義することにしましょう．VariableASTクラスとNumberASTクラスは以下の情報をメンバ変数として持つことにします．

表5.2: VariableASTとNumberASTのメンバ変数

クラス名 | メンバ変数 | 補足
VariableAST | std::string Name | 変数名
NumberAST | int Val | 数値情報

もちろんクラスとして定義する際にはこれらの変数に対するsetter/getter等のメソッドを定義する必要がありますが，ここではとりあえず変数のみを載せています．

変数名と数値，および演算子がそろえば二項演算および関数呼び出しが成立します．DummyCでの二項演算としてはassignment_expression，additive_expression，multiplicative_expressionがあります．これらを別々のクラスとして定義することも出来ると思いますが，あまり意味はなさそうなので全てまとめて一つのクラス，BinaryExprASTで表現することにします．関数呼び出しは構文規則上はpostfix_expressionに記載されていますが，CallExprASTクラスとして個別に定義することにします．BinaryExprASTクラスとCallExprASTに持たせるメンバ変数を表5.3に示します．

表5.3: BinaryExprASTとCallExprASTのメンバ変数

クラス名
メンバ変数
補足

BinaryExprAST
std::string Op
演算子の文字列表現

BaseAST *LHS
二項演算の左辺となるAST

BaseAST *RHS
二項演算の右辺となるAST

CallExprAST
std::string Callee
std::vector<BaseAST*> Args
関数名
関数呼び出しの引数

さて，ここで先ほどおいておいた予約語(intとreturn)の登場です．予約語intと変数名でvariable_declaration(変数宣言)が定義でき，また，jump_statement(return文)は予約語returnと戻り値で構成されます．この二つはVariableDeclASTクラス，JumpStmtASTクラスとし，メンバ変数を表5.4に示します．

表5.4: VariableDeclASTとJumpStmtASTのメンバ変数

クラス名 | メンバ変数 | 補足
VariableDeclAST | std::string Name | 変数名
| enum DeclType Type | 変数宣言の種類
JumpStmtAST | BaseAST *Expr | 戻り値となるAST

表5.4において，VariableDeclASTに変数宣言の種類を示すDeclTypeという列挙型があると思います．これは変数が引数であるか関数内で宣言されたかを区別するために用意しています．どちらもローカル変数なのだから素直に考えると必要は無いのですが，引数の場合は後述するコード生成の際に関数内でalloca命令で領域確保して値を代入という処理をしてやりたいので用意しています．つまりはClangの最適化オプション無しでコード生成した場合と同じようなコードを生成してやりたいのです．また，JumpStmtASTの戻り値はassignment_expressionであり，assignment_expressionは二項演算，変数，数値，関数呼び出しの何れかになるため，戻り値を格納するためのBaseASTをメンバ変数として用意します．

これまで説明した各ASTクラスの宣言を以下に示します．

リスト5.9: 各種ステートメントのAST

 1: /**
 2:   * ASTの種類
 3:   */
 4: enum AstID {
 5:   BaseID,
 6:   VariableDeclID,
 7:   BinaryExprID,
 8:   CallExprID,
 9:   JumpStmtID,
10:   VariableID,
11:   NumberID
12: };
13:
14: /**
15:   * ASTの基底クラス
16:   */
17: class BaseAST {
18:   AstID ID;
19:
20: public:
21:   BaseAST(AstID id) : ID(id) {}
22:   virtual ~BaseAST() {}
23:   AstID getValueID() const { return ID; }
24: };
25:
26: /**
27:   * 変数宣言を表すAST
28:   */
29: class VariableDeclAST : public BaseAST {
30: public:
31:   typedef enum {
32:     param,
33:     local
34:   } DeclType;
35:
36: private:
37:   std::string Name;
38:   DeclType Type;
39:
40: public:
41:   VariableDeclAST(const std::string &name)
42:       : BaseAST(VariableDeclID), Name(name) {}
43:
44:   // VariableDeclASTなのでtrueを返却
45:   static inline bool classof(VariableDeclAST const *) { return true; }
46:
47:   // 渡されたBaseASTクラスがVariableDeclASTか判定する
48:   static inline bool classof(BaseAST const *base) {
49:     return base->getValueID() == VariableDeclID;
50:   }
51:   ~VariableDeclAST() {}
52:
53:   // 変数名を取得する
54:   std::string getName() { return Name; }
55:
56:   // 変数の宣言種別を設定する
57:   bool setDeclType(DeclType type) {
58:     Type = type;
59:     return true;
60:   };
61:
62:   // 変数の宣下種別を設定する
63:   DeclType getType() { return Type; }
64: };
65:
66: /**
67:   * 二項演算を表すAST
68:   */
69: class BinaryExprAST : public BaseAST {
70:   std::string Op;
71:   BaseAST *LHS, *RHS;
72:
73: public:
74:   BinaryExprAST(std::string op, BaseAST *lhs, BaseAST *rhs)
75:       : BaseAST(BinaryExprID), Op(op), LHS(lhs), RHS(rhs) {}
76:   ~BinaryExprAST() {
77:     SAFE_DELETE(LHS);
78:     SAFE_DELETE(RHS);
79:   }
80:
81:   // BinaryExprASTなのでtrueを返却
82:   static inline bool classof(BinaryExprAST const *) { return true; }
83:
84:   // 渡されたBaseASTがBinaryExprASTか判定する
85:   static inline bool classof(BaseAST const *base) {
86:     return base->getValueID() == BinaryExprID;
87:   }
88:
89:   // 演算子を取得する
90:   std::string getOp() { return Op; }
91:
92:   // 左辺値を取得する
93:   BaseAST *getLHS() { return LHS; }
94:
95:   // 右辺値を取得する
96:   BaseAST *getRHS() { return RHS; }
97: };
98:
99: /**
100:   * 関数呼び出しを表すAST
101:   */
102: class CallExprAST : public BaseAST {
103:   std::string Callee;
104:   std::vector<BaseAST *> Args;
105:
106: public:
107:   CallExprAST(const std::string &callee, std::vector<BaseAST *> &args)
108:       : BaseAST(CallExprID), Callee(callee), Args(args) {}
109:   ~CallExprAST();
110:
111:   // CallExprASTなのでtrueを返却
112:   static inline bool classof(CallExprAST const *) { return true; }
113:
114:   // 渡されたBaseASTがCallExprASTか判定する
115:   static inline bool classof(BaseAST const *base) {
116:     return base->getValueID() == CallExprID;
117:   }
118:
119:   // 呼び出す関数名を取得する
120:   std::string getCallee() { return Callee; }
121:
122:   // i番目の引数を取得する
123:   BaseAST *getArgs(int i) {
124:     if (i < Args.size())
125:       return Args.at(i);
126:     else
127:       return NULL;
128:   }
129: };
130:
131: /**
132:   * ジャンプ(今回はreturn)を表すAST
133:   */
134: class JumpStmtAST : public BaseAST {
135:   BaseAST *Expr;
136:
137: public:
138:   JumpStmtAST(BaseAST *expr) : BaseAST(JumpStmtID), Expr(expr) {}
139:   ~JumpStmtAST() { SAFE_DELETE(Expr); }
140:
141:   // JumpStmtASTなのでtrueを返却
142:   static inline bool classof(JumpStmtAST const *) { return true; }
143:
144:   // 渡されたBaseASTがJumpStmtASTか判定する
145:   static inline bool classof(BaseAST const *base) {
146:     return base->getValueID() == JumpStmtID;
147:   }
148:
149:   // returnで返却するExpressionを取得する
150:   BaseAST *getExpr() { return Expr; }
151: };
152:
153: /**
154:   * 変数参照を表すAST
155:   */
156: class VariableAST : public BaseAST {
157:   // Name
158:   std::string Name;
159:
160: public:
161:   VariableAST(const std::string &name) : BaseAST(VariableID), Name(name) {}
162:   virtual ~VariableAST() {}
163:
164:   // VariableASTなのでtrueを返却
165:   static inline bool classof(VariableAST const *) { return true; }
166:
167:   // 渡されたBaseASTがVariableASTか判定する
168:   static inline bool classof(BaseAST const *base) {
169:     return base->getValueID() == VariableID;
170:   }
171:
172:   // 変数名を取得する
173:   std::string getName() { return Name; }
174: };
175:
176: /**
177:   * 整数を表すAST
178:   */
179: class NumberAST : public BaseAST {
180:   int Val;
181:
182: public:
183:   NumberAST(int val) : BaseAST(NumberID), Val(val) {}
184:   ~NumberAST() {}
185:
186:   // NumberASTなのでtrueを返却
187:   static inline bool classof(NumberAST const *) { return true; }
188:
189:   // 渡されたBaseASTがNumberASTか判定する
190:   static inline bool classof(BaseAST const *base) {
191:     return base->getValueID() == NumberID;
192:   }
193:
194:   // このASTが表現する数値を取得する
195:   int getNumberValue() { return Val; }
196: };

関数とモジュール

先ほどまでの内容でDummyCのステートメントであるstatementおよびvariable_declarationの内容は定義しました．本段落ではステートメントの集合である関数，および関数によって構成されるモジュールのASTを定義していきます．

statementおよびvariable_declarationがリストになることでstatement_listおよびvariable_declaration_listになります．この二つのリストでfunction_statementが構成されますので，内部にstatementとvariable_declarationのリストをもつクラスとしてFunctionStmtASTクラスを定義します．なお，function_statementはfunction_definition(関数)のボディにあたる部分です．FunctionStmtASTのメンバ変数は表5.5の通りです．

表5.5: FunctionStmtASTのメンバ変数

クラス名 | メンバ変数 | 補足
FunctionStmtAST | std::vector<VariableDeclAST*> VariableDecls | 関数内の変数宣言
| std::vector<BaseAST*> StmtLists | 関数内のステートメント

ここでfunction_definitionを定義したいわけですが，そのためにはprototype(プロトタイプ宣言)の情報をどう保持するかを先に決定してやりたいですね．prototypeは変数名(引数)リストをと関数を持つクラスとして簡単に定義できるので，変数名のリストと関数名をメンバとして持つPrototypeASTクラスを用意することにします．なお，prototypeはfunction_declarationに相当します．表5.6にPrototypeASTのメンバ変数を記載しておきます．

表5.6: PrototypeASTのメンバ変数

クラス名 | メンバ変数 | 補足
PrototypeAST | std::string Name | 変数名
| std::vector<std::string> Params | 引数の変数名

これでようやくfunction_definitionを定義できます．function_definitionはFunctionASTクラスとし，メンバにPrototypeASTクラスとFunctionStmtASTクラスを持つことにしましょう．translation_unitはfunction_declarationとfunction_definitionの二つの集合ですので，最後にtranslation_unitを表すクラスを定義します．translation_unitは内部にPrototypeASTクラスとFunctionASTクラスのリストをもつTranslationUnitASTクラスとします．FunctionASTクラスとTranslationUnitASTのメンバ変数は表5.7の様になります．

表5.7: FunctionASTクラスとTranslationUnitASTのメンバ変数

クラス名 | メンバ変数 | 補足
FunctionAST | PrototypeAST *Proto | 関数のプロトタイプ宣言
| FunctionStmtAST *Body | 関数のボディ
TranslationUnitAST | std::vector<PrototypeAST*> Prototypes | プロトタイプ宣言のリスト
| std::vector<FunctionAST*> Functions | 関数定義のリスト

以上の内容より，追加するASTのクラス宣言をリスト5.10に示します．各クラスは基本的に既に説明したしたメンバ変数とメンバ変数に対するsetter/getterを持ちます．ただし，TranslationUnitASTにはsetter/getter以外にemptyというメソッドを定義しています．これはPtototypesとFunctionsのサイズが共に0，つまり空のASTの時にtrueを返し，それ以外のときにfalseを返すメソッドで，後にエラー処理に使用するために定義しています．

リスト5.10: モジュールと関数

 1: /**
 2:   * ソースコードを表すAST
 3:   */
 4: class TranslationUnitAST {
 5:   std::vector<PrototypeAST *> Prototypes;
 6:   std::vector<FunctionAST *> Functions;
 7:
 8: public:
 9:   TranslationUnitAST() {}
10:   ~TranslationUnitAST();
11:
12:   // モジュールにプロトタイプ宣言を追加する
13:   bool addPrototype(PrototypeAST *proto);
14:
15:   // モジュールに関数を追加する
16:   bool addFunction(FunctionAST *func);
17:
18:   // モジュールが空か判定する
19:   bool empty();
20:
21:   // i番目のプロトタイプ宣言を取得する
22:   PrototypeAST *getPrototype(int i) {
23:     if (i < Prototypes.size())
24:       return Prototypes.at(i);
25:     else
26:       return NULL;
27:   }
28:
29:   // i番目の関数を取得する
30:   FunctionAST *getFunction(int i) {
31:     if (i < Functions.size())
32:       return Functions.at(i);
33:     else
34:       return NULL;
35:   }
36: };
37:
38: /**
39:   * 関数宣言を表すAST
40:   */
41: class PrototypeAST {
42:   std::string Name;
43:   std::vector<std::string> Params;
44:
45: public:
46:   PrototypeAST(const std::string &name, const std::vector<std::string> &params)
47:       : Name(name), Params(params) {}
48:
49:   // 関数名を取得する
50:   std::string getName() { return Name; }
51:
52:   // i番目の引数名を取得する
53:   std::string getParamName(int i) {
54:     if (i < Params.size())
55:       return Params.at(i);
56:     return NULL;
57:   }
58:
59:   // 引数の数を取得する
60:   int getParamNum() { return Params.size(); }
61: };
62:
63: /**
64:   * 関数定義を表すAST
65:   */
66: class FunctionAST {
67:   PrototypeAST *Proto;
68:   FunctionStmtAST *Body;
69:
70: public:
71:   FunctionAST(PrototypeAST *proto, FunctionStmtAST *body)
72:       : Proto(proto), Body(body) {}
73:   ~FunctionAST();
74:
75:   // 関数名を取得する
76:   std::string getName() { return Proto->getName(); }
77:
78:   // この関数のプロトタイプ宣言を取得する
79:   PrototypeAST *getPrototype() { return Proto; }
80:
81:   // この関数のボディを取得する
82:   FunctionStmtAST *getBody() { return Body; }
83: };
84:
85: /**
86:   * 関数定義(ボディ)を表すAST
87:   */
88: class FunctionStmtAST {
89:   std::vector<VariableDeclAST *> VariableDecls;
90:   std::vector<BaseAST *> StmtLists;
91:
92: public:
93:   FunctionStmtAST() {}
94:   ~FunctionStmtAST();
95:
96:   // 関数に変数を追加する
97:   bool addVariableDeclaration(VariableDeclAST *vdecl);
98:
99:   // 関数にステートメントを追加する
100:   bool addStatement(BaseAST *stmt) { StmtLists.push_back(stmt); }
101:
102:   // i番目の変数を取得する
103:   VariableDeclAST *getVariableDecl(int i) {
104:     if (i < VariableDecls.size())
105:       return VariableDecls.at(i);
106:     else
107:       return NULL;
108:   }
109:
110:   // i番目のステートメントを取得する
111:   BaseAST *getStatement(int i) {
112:     if (i < StmtLists.size())
113:       return StmtLists.at(i);
114:     else
115:       return NULL;
116:   }
117: };

5.5.2 構文解析クラスの実装方針

前節でASTの構造を定義したので，本節では入力ソースコードからASTを作成する構文解析クラスを作成します．構文解析クラスのクラス名はParserとします．

構文解析の実装方法は大きく分けてトップダウン方式とボトムアップ方式がありますし，それぞれの方式にも細かく複数の方式が存在します．どれを採用するかはユーザが自由に考えればいいと思いますが，本書では以下の方針でParserクラスの実装を行います．

コンストラクタで入力ソースコード名を受け取り，LexicalAnalysis関数でTokenStreamのインスタンスを生成する

構文解析では基本的に構文規則の非終端記号一つにつき一つの解析メソッドを作成し，translation_unitからトップダウンに解析を行う

各解析メソッドの命名規則は“visit非終端記号名”とする

各解析メソッドの戻り値は一部の例外を除き，基本的に解析して得られたASTクラス型のポインタとする

構文解析開始のトリガとなるdoParseメソッドを実装する

構文解析は外部からdoParseメソッドを呼び出すことで開始し，解析結果の成否を真偽値で返す

解析結果のASTを外部から取得するために，ASTの頂点であるTranslationUnitASTを返却するgetASTを実装する

以上の内容をまとめたParserクラスの宣言をリスト5.11に示します．

リスト5.11: parserクラス宣言

 1: /**
 2:   * 構文解析・意味解析クラス
 3:   */
 4: class Parser {
 5: private:
 6:   TokenStream *Tokens;
 7:   TranslationUnitAST *TU;
 8:
 9: public:
10:   Parser(std::string filename);
11:   ~Parser() {
12:     SAFE_DELETE(TU);
13:     SAFE_DELETE(Tokens);
14:   }
15:   bool doParse();
16:   TranslationUnitAST &getAST();
17:
18: private:
19:   /**
20:     * 各種構文解析メソッド
21:     */
22:   bool visitTranslationUnit();
23:   bool visitExternalDeclaration(TranslationUnitAST *tunit);
24:   PrototypeAST *visitFunctionDeclaration();
25:   FunctionAST *visitFunctionDefinition();
26:   PrototypeAST *visitPrototype();
27:   FunctionStmtAST *visitFunctionStatement(PrototypeAST *proto);
28:   VariableDeclAST *visitVariableDeclaration();
29:   BaseAST *visitStatement();
30:   BaseAST *visitExpressionStatement();
31:   BaseAST *visitJumpStatement();
32:   BaseAST *visitAssignmentExpression();
33:   BaseAST *visitAdditiveExpression(BaseAST *lhs);
34:   BaseAST *visitMultiplicativeExpression(BaseAST *lhs);
35:   BaseAST *visitPostfixExpression();
36:   BaseAST *visitPrimaryExpression();
37: };

5.5.3 メソッドの実装

先ほど宣言したクラスの各メソッドの実装について説明します．全てのメソッドをのせると冗長な上に読むのも疲れるので幾つかのポイントを記載していきます．

publicなメソッド

まず，外部から参照されるコンストラクタ，doParse，getASTから解説します．最初にコードを載せて解説という流れでいきましょう．リスト5.12にParserクラスのpublicなメソッドの実装例を示します．

リスト5.12: Parserクラスのpublicメソッド

 1: /**
 2:   * コンストラクタ
 3:   */
 4: Parser::Parser(std::string filename) { Tokens = LexicalAnalysis(filename); }
 5:
 6: /**
 7:   * 構文解析実行
 8:   * @return 解析成功：true　解析失敗：false
 9:   */
10: bool Parser::doParse() {
11:   if (!Tokens) {
12:     fprintf(stderr, "error at lexer\n");
13:     return false;
14:   } else
15:     return visitTranslationUnit();
16: }
17:
18: /**
19:   * AST取得
20:   * @return TranslationUnitへの参照
21:   */
22: TranslationUnitAST &Parser::getAST() {
23:   if (TU)
24:     return *TU;
25:   else
26:     return *(new TranslationUnitAST());
27: }

実装方針でも触れましたが，コンストラクタは引数として入力ソースコード名を受け取り，内部でLexicalAnalaysis関数を呼び出します．これによって取得したTokenStreamクラスのインスタンスをメンバ変数Tokensに保存しておき，以降の解析メソッドではこのTokensからソースコードのトークン情報を取得します．doParseは内部でvisitTranslationUnitを呼び出し構文解析を開始し，visitTranslationUnitが返却した解析の成否をそのまま戻り値とします．このコード上からは見えませんがvisitTranslationUnit内では構文解析に成功した場合，TranslationUnitAST ＊型のメンバ変数TUに解析結果のASTの頂点となるTranslationUnitASTが代入されます．なお，doParseはコンストラクタで呼び出したLexicalAnalaysisに失敗し，TokensがNULLの場合には構文解析を行わずにfalseを返して終了します．getASTでは既にdoParseに成功していた時，つまりTUがNULL値でない時にTUへの参照を返します．TUがNULL値の場合は新しく空のTranslationUnitASTを生成して返却します．

visitTranslationUnitとvisitExternalDeclaration

次にdoParseから直接呼び出されるvisitTranslationUnit，およびvisitExternalDeclarationを確認します．visitTranslationUnitとvisitExternalDeclarationはそれぞれtranslation_unitとexternal_declarationを解析するメソッドですが，最初に示したEBNFだけではどのような構文を受け入れるのかわかりにくいかと思いますので，ここでtranslation_unitとexternal_declarationの構文図を図5.1に記載します．なお，図中の四角い箱は非終端記号を示しています．

図5.1: translation_unitとexternal_declarationの構文図

上記の構文を受け入れる構文解析メソッドがvisitTranslationUnitとvisitExternalDeclarationになります．ソースコード例をリスト5.13に示しますので確認してください．なお，以降も各種構文解析メソッドの実装を説明する際には構文図とソースコードをセットで記載していきたいと思います．

リスト5.13: visitTranslationUnitとvisitExternalDeclaration

 1: /**
 2:   * TranslationUnit用構文解析メソッド
 3:   * @return 解析成功：true　解析失敗：false
 4:   */
 5: bool Parser::visitTranslationUnit() {
 6:   TU = new TranslationUnitAST();
 7:
 8:   // ExternalDecl
 9:   while (true) {
10:     if (!visitExternalDeclaration(TU)) {
11:       SAFE_DELETE(TU);
12:       return false;
13:     }
14:     if (Tokens->getCurType() == TOK_EOF)
15:       break;
16:   }
17:   return true;
18: }
19:
20: /**
21:   * ExternalDeclaration用構文解析クラス
22:   * 解析したPrototyupeとFunctionASTをTranslationUitに追加
23:   * @param TranslationUnitAST
24:   * @return true
25:   */
26: bool Parser::visitExternalDeclaration(TranslationUnitAST *tunit) {
27:   // FunctionDeclaration
28:   PrototypeAST *proto = visitFunctionDeclaration();
29:   if (proto) {
30:     tunit->addPrototype(proto);
31:     return true;
32:   }
33:
34:   // FunctionDefinition
35:   FunctionAST *func_def = visitFunctionDefinition();
36:   if (func_def) {
37:     tunit->addFunction(func_def);
38:     return true;
39:   }
40:
41:   return false;
42: }

visitTranslationUnitの内部ではEOFがくるまでvisitExternalDeclarationによるexternal_declarationの解析を繰り返します．visitTranslationUnitメソッドは解析成功の場合はメンバ変数TUに解析結果としてASTの頂点であるTranslationUnitASTのインスタンスを代入し，trueを返却します．失敗時には解析中のASTを破棄しTUにNULL値を設定した後，falseを返却します．なお，visitExternalDeclarationは引数としてTranslationUnitASTのポインタをとり，解析したFunctionDeclarationASTとFunctionDefinitionASTをTranslationUnitASTに追加するようにしました．他のメソッドと整合性をとるためにはTranslationUnitへのASTの追加はvisitTranslationUnit内で行った方が良い気もしますが，今回は楽に実装したかったので上記のようにしています．以降，同様に構文規則に基づいて解析メソッドを実装しますが，ここからは実装上注意すべきポイントを絞って説明を進めます．

関数の再定義の確認はしない

まず関数宣言と関数定義の解析，つまりvisitFunctionDeclarationとvisitFunctionDefinitionでの処理について説明します．今回も先に構文図を図5.2に記載します．(図中に表れるprototypeの解析は後述するvisitPrototypeの部分で記載します．)

図5.2: function_declarationとfunction_definitionの構文図

上記の構文図からもわかると思いますが，function_declarationとfunction_definitionは関数宣言と関数定義を示す非終端記号です．コンパイラとしては関数宣言と定義の解析において，同名の関数が再定義されていないかのチェックを行うべきです．しかし今回はひとまず構文解析までを目的とするので，これらの処理を行いません．これらは後述する意味解析で実装します．ですので構文解析の段階でのvisitFunctionDeclarationとvisitFunctionDefinitionの処理はリスト5.14の様な形になります．

リスト5.14: visitFunctionDeclaration

 1: /**
 2:   * FunctionDclaration用構文解析メソッド
 3:   * @return 解析成功：PrototypeAST　解析失敗：NULL
 4:   */
 5: PrototypeAST *Parser::visitFunctionDeclaration() {
 6:   int bkup = Tokens->getCurIndex();
 7:   PrototypeAST *proto = visitPrototype();
 8:   if (!proto) {
 9:     return NULL;
10:   }
11:
12:   // prototype;
13:   if (Tokens->getCurString() == ";") {
14:
15:     ・・・本来であればここで再定義されていないか確認・・・
16:
17:     Tokens->nextToken();
18:     return proto;
19:   } else {
20:     SAFE_DELETE(proto);
21:     Tokens->applyTokenIndex(bkup);
22:     return NULL;
23:   }
24: }

リスト5.15: visitFunctionDefinition

 1: /**
 2:   * FunctionDefinition用構文解析メソッド
 3:   * @return 解析成功：FunctionAST　解析失敗：NULL
 4:   */
 5: FunctionAST *Parser::visitFunctionDefinition() {
 6:   int bkup = Tokens->getCurIndex();
 7:
 8:   PrototypeAST *proto = visitPrototype();
 9:   if (!proto) {
10:     return NULL;
11:   }
12:
13:   ・・・本来であればここで再定義されていないか確認・・・
14:
15:   FunctionStmtAST *func_stmt = visitFunctionStatement(proto);
16:
17:   ・・・省略・・・
18: }

変数の二重宣言チェックと宣言確認をしない

関数の再定義確認と同様に，変数の二重宣言や変数参照時の変数宣言の確認も構文解析では省きます．上記の処理を含むのはvisitPrototype，visitFunctionStatement，visitAssignmentExpression，visitPostfixExpression，visitPrimaryExpressionですので，構文解析時点でのこれらのコードを載せておきます．

まず，visitPrototypeから説明をはじめます．visitPrototypeはprototypeを受け入れる構文解析メソッドです．今回も先にprototypeの構文図を図5.3に記載します．

図5.3: prototypeの構文図

非終端記号prototypeはその名前の通り関数のプロトタイプ宣言を表します．prototypeを受け入れる構文解析メソッドvisitPrototypeの構文解析時点でのソースコード例をリスト5.16に示します．

リスト5.16: 変数宣言のチェック

 1: /**
 2:   * Prototype用構文解析メソッド
 3:   * @return 解析成功：PrototypeAST　解析失敗：NULL
 4:   */
 5: PrototypeAST *Parser::visitPrototype() {
 6:   // indexを保存
 7:   int bkup = Tokens->getCurIndex();
 8:   　　・・・省略・・・
 9:
10:   // parameter_list
11:   bool is_first_param = true;
12:   std::vector<std::string> param_list;
13:   while (true) {
14:     //,
15:     if (!is_first_param && Tokens->getCurType() == TOK_SYMBOL &&
16:         Tokens->getCurString() == ",") {
17:       Tokens->nextToken();
18:     }
19:     if (Tokens->getCurType() == TOK_INT) {
20:       Tokens->nextToken();
21:     } else {
22:       break;
23:     }
24:
25:     if (Tokens->getCurType() == TOK_IDENTIFIER) {
26:
27:       ・・・本来ならここで引数名に被りがないか確認・・・
28:
29:       param_list.push_back(Tokens->getCurString());
30:       Tokens->nextToken();
31:     } else {
32:       Tokens->applyTokenIndex(bkup);
33:       return NULL;
34:     }
35:   }
36:   　　・・・省略・・・
37: }

visitPrototypeでは非終端記号prototypeの規則に従い，関数のプロトタイプ宣言を解析します．19行目からの処理で，トークンが識別子であることを確認し，引数名としてリストに追加していきます．識別子であることを確認した後，各引数名に被りがないかを確認をしておくところですが，今回は構文解析ですのでこの処理は未実装とします．

次に，visitFunctionStatementメソッドの説明にうつります．visitFunctionStatementで解析するfunction_statementに関連する構文図は図5.4の様になります．

図5.4: function_statementの構文図

function_statementは変数宣言のリストと式のリストを含む関数のブロックを表す非終端記号です．function_statementを受け入れるためのvisitFunctionStatementメソッドの構文解析時点でのコード例をリスト5.17に示します．

リスト5.17: visitFunctionStatement

 1: /**
 2:   * FunctionStatement用構文解析メソッド
 3:   * @param 関数名や引数を格納したPrototypeクラスのインスタンス
 4:   * @return 解析成功：FunctionStmtAST　解析失敗：NULL
 5:   */
 6: FunctionStmtAST *Parser::visitFunctionStatement(PrototypeAST *proto) {
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   if (Tokens->getCurString() == "{") {
10:     Tokens->nextToken();
11:   } else {
12:     return NULL;
13:   }
14:
15:   FunctionStmtAST *func_stmt = new FunctionStmtAST();
16:
17:   // 引数をfunc_stmtの変数宣言リストに追加
18:   for (int i = 0; i < proto->getParamNum(); i++) {
19:     VariableDeclAST *vdecl = new VariableDeclAST(proto->getParamName(i));
20:     vdecl->setDeclType(VariableDeclAST::param);
21:     func_stmt->addVariableDeclaration(vdecl);
22:     VariableTable.push_back(vdecl->getName());
23:   }
24:
25:   ・・・省略・・・
26:
27:   } else if (var_decl = visitVariableDeclaration()) {
28:     while (var_decl) {
29:       var_decl->setDeclType(VariableDeclAST::local);
30:
31:       ・・・本来ならここで変数が二重宣言されていないことを確認・・・
32:
33:       func_stmt->addVariableDeclaration(var_decl);
34:       VariableTable.push_back(var_decl->getName());
35:       var_decl = visitVariableDeclaration();
36:     }
37:
38:     if (stmt = visitStatement()) {
39:       while (stmt) {
40:         last_stmt = stmt;
41:         func_stmt->addStatement(stmt);
42:         stmt = visitStatement();
43:       }
44:     }
45:
46:   // other
47:   } else {
48:     SAFE_DELETE(func_stmt);
49:     Tokens->applyTokenIndex(bkup);
50:     return NULL;
51:   }
52:
53:   ・・・本来ならここで戻り値の確認・・・
54:
55:   if (Tokens->getCurString() == "}") {
56:     Tokens->nextToken();
57:     return func_stmt;
58:   } else {
59:     SAFE_DELETE(func_stmt);
60:     Tokens->applyTokenIndex(bkup);
61:     return NULL;
62:   }
63: }

visitVariableDeclarationで変数宣言を解析した後，本来は変数が二重宣言がされていないことを確認する部分ですが構文解析では行いません．また，Statementがreturn文で終了し，関数が値を返すことを確認すべきですが，これも構文解析では未実装とします．

次は，visitAssignmentExpressionです．visitAssignmentExpressionで解析する非終端記号，assignment_expressionの構文図を図5.5に示します．

図5.5: assignment_expressionの構文図

assignment_expressionは代入文に関する生成規則です．assignment_expressionを受け入れる構文解析メソッド，visitAssignmentExpressionの構文解析時点での実装例をリスト5.18に示します．

リスト5.18: visitAssignmentExpression

 1: /**
 2:   * AssignmentExpression用構文解析メソッド
 3:   * @return 解析成功：AST　解析失敗：NULL
 4:   */
 5: BaseAST *Parser::visitAssignmentExpression() {
 6:   int bkup = Tokens->getCurIndex();
 7:
 8:   BaseAST *lhs;
 9:   if (Tokens->getCurType() == TOK_IDENTIFIER) {
10:     ・・・本来はここで変数の宣言確認・・・
11:     lhs = new VariableAST(Tokens->getCurString());
12:     Tokens->nextToken();
13:     BaseAST *rhs;
14:     if (Tokens->getCurType() == TOK_SYMBOL && Tokens->getCurString() == "=") {
15:       Tokens->nextToken();
16:       if (rhs = visitAdditiveExpression(NULL)) {
17:         return new BinaryExprAST("=", lhs, rhs);
18:       } else {
19:         SAFE_DELETE(lhs);
20:         Tokens->applyTokenIndex(bkup);
21:       }
22:     } else {
23:       SAFE_DELETE(lhs);
24:       Tokens->applyTokenIndex(bkup);
25:     }
26:   }
27:   BaseAST *add_expr = visitAdditiveExpression(NULL);
28:   if (add_expr) {
29:     return add_expr;
30:   }
31:
32:   return NULL;
33: }

assignment_expressionは代入文の解析ですので，左辺値となる識別子(変数名)が現れます．構文解析では意味的な部分はチェックしないので，正しい位置に識別子が出現することの確認のみを行い，それが変数として宣言されているかの確認は行いません．

最後はprimary_expressionを解析するvisitPrimaryExpressionです．primary_expressionの構文図は下記の様になります．

図5.6: primary_expressionの構文図

primary_expressionは式の基本構成要素に関する規則で，識別子や数値，（）で囲まれた式を受け入れます．構文解析時点でのvisitPrimaryExpressionの実装例をリスト5.19に示します．

リスト5.19: visitPrimaryExpression

 1: /**
 2:   * PrimaryExpression用構文解析メソッド
 3:   * @return 解析成功時：AST失敗時：NULL
 4:   */
 5: BaseAST *Parser::visitPrimaryExpression() {
 6:   // indexを保存
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   // VARIABLE_IDENTIFIER
10:   if (Tokens->getCurType() == TOK_IDENTIFIER) {
11:
12:     ・・・本来ならここで変数の宣言確認・・・
13:
14:     std::string var_name = Tokens->getCurString();
15:     Tokens->nextToken();
16:     return new VariableAST(var_name);
17:
18:   // integer
19:   } else if (Tokens->getCurType() == TOK_DIGIT) {
20:     int val = Tokens->getCurNumVal();
21:     Tokens->nextToken();
22:     return new NumberAST(val);
23:
24:   // integer(-)
25:   } else if (Tokens->getCurType() == TOK_SYMBOL &&
26:              Tokens->getCurString() == "-") {
27:
28:     ・・・省略・・・
29:   }
30:
31:   return NULL;
32: }

visitPrimaryExpressionもassignment_expressionと同様で，識別子が正しい場所で出現することの確認のみを行い，定義済みであるかの確認は行いません．

関数の宣言確認もしない

構文解析では関数呼び出し時の関数宣言の確認についても同じく実装しません．関数呼び出しの構文解析が含まれるvisitPostfixExpressionの構文図と構文解析時点でのソースコード例を図5.7とリスト5.20に示します．

図5.7: postfix_expressionの構文図

リスト5.20: visitPostfixExpression

 1: /**
 2:   * PostfixExpression用構文解析メソッド
 3:   * @return 解析成功：AST　解析失敗：NULL
 4:   */
 5: BaseAST *Parser::visitPostfixExpression() {
 6:   // get index
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   ・・・省略・・・
10:
11:   // FUNCTION_IDENTIFIER
12:   if (Tokens->getCurType() == TOK_IDENTIFIER) {
13:
14:     ・・・本来ならここで関数の宣言確認・・・
15:
16:     // 関数名取得
17:     std::string Callee = Tokens->getCurString();
18:     Tokens->nextToken();
19:
20:     // LEFT PAREN
21:     if (Tokens->getCurType() != TOK_SYMBOL || Tokens->getCurString() != "(") {
22:       Tokens->applyTokenIndex(bkup);
23:       return NULL;
24:     }
25:
26:     Tokens->nextToken();
27:     std::vector<BaseAST *> args;
28:     // 引数を解析
29:     BaseAST *assign_expr = visitAssignmentExpression();
30:     if (assign_expr) {
31:       args.push_back(assign_expr);
32:       // ","が続く限り繰り返し
33:       while (Tokens->getCurType() == TOK_SYMBOL &&
34:              Tokens->getCurString() == ",") {
35:         Tokens->nextToken();
36:         assign_expr = visitAssignmentExpression();
37:         if (assign_expr) {
38:           args.push_back(assign_expr);
39:         } else {
40:           break;
41:         }
42:       } // end while
43:     }
44:
45:     ・・・本来ならここで引数の数を確認・・・
46:
47:     // RIGHT PAREN
48:     if (Tokens->getCurType() == TOK_SYMBOL && Tokens->getCurString() == ")") {
49:       Tokens->nextToken();
50:       return new CallExprAST(Callee, args);
51:     } else {
52:       for (int i = 0; i < args.size(); i++)
53:         SAFE_DELETE(args[i]);
54:       Tokens->applyTokenIndex(bkup);
55:       return NULL;
56:     }
57:   } else {
58:     return NULL;
59:   }
60: }

visitPostfixExpressionには関数呼び出しの解析が含まれます．意味的な部分の確認するのであれば，12行目の識別子が宣言，もしくは定義された関数名であるか確認すべきですし，また，関数呼び出しの引数の数が関数の宣言もしくは定義とあっているかどうかの確認も必要です．しかし，ここでもこれまで同様意味解析は実装しないため，これらの処理は未実装とします．

四則演算の解析

次は四則演算の解析を説明します．これは構文規則どおりに実装すればよい部分ではありますが，少々ややこしいのでコードを載せて説明しておきます．なお，四則演算の解析メソッドはvisitAdditiveExpressionとvisitMultiplicativeExpressionにわかれますが，基本的に同様の処理を行うため今回はvisitAdditiveExpressionについてのみ説明します．まず図5.8にadditive_expressionの構文図を記載します．

図5.8: additive_expressionの構文図

additive_expressionは加減算に関する規則で，加算および減算の繰り返しを受け入れます．リスト5.21にvisitAdditiveExpressionの実装を示します．

リスト5.21: 四則演算

 1: /**
 2:   * AdditiveExpression用構文解析メソッド
 3:   * @param lhs(左辺),初回呼び出し時はNULL
 4:   * @return 解析成功：AST　解析失敗：NULL
 5:   */
 6: BaseAST *Parser::visitAdditiveExpression(BaseAST *lhs) {
 7:   // indexを保存
 8:   int bkup = Tokens->getCurIndex();
 9:
10:   if (!lhs)
11:     lhs = visitMultiplicativeExpression(NULL);
12:
13:   if (!lhs)
14:     return NULL;
15:
16:   BaseAST *rhs;
17:   // +
18:   if (Tokens->getCurType() == TOK_SYMBOL && Tokens->getCurString() == "+") {
19:     Tokens->nextToken();
20:     rhs = visitMultiplicativeExpression(NULL);
21:     if (rhs) {
22:       return visitAdditiveExpression(new BinaryExprAST("+", lhs, rhs));
23:     } else {
24:       SAFE_DELETE(lhs);
25:       Tokens->applyTokenIndex(bkup);
26:       return NULL;
27:     }
28:
29:     // -
30:   } else if (Tokens->getCurType() == TOK_SYMBOL &&
31:              Tokens->getCurString() == "-") {
32:     Tokens->nextToken();
33:     rhs = visitMultiplicativeExpression(NULL);
34:     if (rhs) {
35:       return visitAdditiveExpression(new BinaryExprAST("-", lhs, rhs));
36:     } else {
37:       SAFE_DELETE(lhs);
38:       Tokens->applyTokenIndex(bkup);
39:       return NULL;
40:     }
41:   }
42:   return lhs;
43: }

四則演算の解析はvisitAssignmnetExpressionからvisitAdditiveExpressionを呼び出すことで開始します．visitAdditiveExpressionは引数として演算の左辺値(lhs)を受け取りますが，初回呼び出し時はNULLを指定して呼び出されます．lhsがNULLの場合はvisitMultiplicativeExpressionを呼び出して左辺値を解析します．lhsが解析できれば演算子の種類と右辺値の解析を実施します．これで該当演算の解析自体は完了します．しかし，この演算を左辺値とする演算が更に続く可能性があります．そのため，この演算を表すBinaryExprASTを引数として，visitAdditiveExpressionを再帰的に呼び出します．これにより，後続の演算についても解析可能となります．visitMultiplicativeExpressionについても構造は同じですので説明は省略します．

何もしないASTの追加

Parserクラスの各メソッドは基本的にASTを返すようにしています．ここで問題となるのが，expression_statementの解析です．まずexpression_statementの構文図を図5.9に示します．

図5.9: expression_statementの構文図

図で見ると一目瞭然ですが，expression_statementは“;”のみの文を受け入れます．“;”のみの文は何もしない文ですのでNULLを返したいところですが，NULLは今回の仕様上エラーを意味します．そこで，今回は特別に“;”を表すNullExprASTを追加してやることにします．先ほど構文上意味のない記号はASTから除くという話をした手前少々まずい気もしますが，今回は楽にわかりやすく実装を進めたいので少々ごり押しで対応します．そんなわけで，下記の様にAstIDを修正し，NullExprASTを追加します．

リスト5.22: AstIDの修正とNullExprAST

 1: /**
 2:   * ASTの種類
 3:   */
 4: enum AstID {
 5:   BaseID,
 6:   VariableDeclID,
 7:   BinaryExprID,
 8:   NullExprID,
 9:   CallExprID,
10:   JumpStmtID,
11:   VariableID,
12:   NumberID
13: };
14:
15: /**
16:   * ";"を表すAST
17:   */
18: class NullExprAST : public BaseAST {
19: public:
20:   NullExprAST() : BaseAST(NullExprID) {}
21:   static inline bool classof(NullExprAST const *) { return true; }
22:   static inline bool classof(BaseAST const *base) {
23:     return base->getValueID() == NullExprID;
24:   }
25: };

また，expression_statementの解析メソッドvisitExpressionStatementを以下のように定義します．

リスト5.23: visitfExpressionStatement

 1: /**
 2:   * ExpressionStatement用構文解析メソッド
 3:   * @return  解析成功：AST 解析失敗：NULL
 4:   */
 5: BaseAST *Parser::visitExpressionStatement() {
 6:   BaseAST *assign_expr;
 7:
 8:   // NULL Expression
 9:   if (Tokens->getCurString() == ";") {
10:     Tokens->nextToken();
11:     return new NullExprAST();
12:   } else if (assign_expr = visitAssignmentExpression()) {
13:     if (Tokens->getCurString() == ";") {
14:       Tokens->nextToken();
15:       return assign_expr;
16:     }
17:   }
18:   return NULL;
19: }

5.6 意味解析

先ほどは単に構文規則に則っているかのみを見ていましたが，本節では更に意味解析の機能を追加します．

5.6.1 意味解析の実装方法

意味解析の実装方法は下記の二つの方法があります．

AST作成時に同時に意味解析を行う

構文解析終了後にASTを辿り，意味解析を行う

どちらを採用するかは自由ですが，今回は特に複雑な構文でもないので構文解析と同時に実施してしまいます*5．

5.6.2 Parserクラスの修正

意味解析では構文解析で実装しなかった関数の宣言，引数チェックや変数の二重宣言のチェックを実装します．本節で実装する機能を以下に示します．

関数再定義の確認

変数の二重宣言チェック

変数参照時の変数の宣言確認

関数呼び出し時の関数の宣言確認

上記の機能を実装するため，Parserクラスに表5.8のメンバ変数を追加します．

表5.8: Parserクラスに追加するメンバ変数

クラス名 | メンバ変数 | 補足
Parser | std::vector<std::string> VariableTable | 宣言済の変数名を登録する
| std::map<std::string, int> PrototypeTable | 宣言済関数の関数名と引数の数のマップ
| std::map<std::string, int> FunctionTable | 定義済関数の関数名と引数の数のマップ

上記メンバを追加した，修正後のParserクラスの宣言をリスト5.24に示します．

リスト5.24: 修正したparserクラスの宣言

 1: /**
 2:   * 構文解析・意味解析クラス
 3:   */
 4: class Parser {
 5: private:
 6:   TokenStream *Tokens;
 7:   TranslationUnitAST *TU;
 8:
 9:   // 意味解析用各種識別子表
10:   std::vector<std::string> VariableTable;
11:   std::map<std::string, int> PrototypeTable;
12:   std::map<std::string, int> FunctionTable;
13:
14: public:
15:   Parser(std::string filename) { Tokens = LexicalAnalysis(filename); }
16:   ~Parser() {
17:     SAFE_DELETE(TU);
18:     SAFE_DELETE(Tokens);
19:   }
20:   bool doParse();
21:   TranslationUnitAST &getAST();
22:
23:   ・・・省略・・・
24: };

5.6.3 解析メソッドの修正

先ほど用意したテーブルを利用して，各解析メソッドの必要な箇所に意味解析の機能を追加していきます．

関数再定義の確認

最初に，関数の再定義が行われていないかのチェックをvisitFunctionDeclarationとvisitFunctionDefinitionに追加します．まず，visitFuncDeclarationから修正します．visitFuncDeclarationに追加したい処理は以下の通りです．

同名の関数が複数宣言されないことを確認する処理

関数が定義済であった場合，関数定義とプロトタイプ宣言の引数の数が同一であることを確認する処理

上記の処理を追加したテーブルを用いて実装します．修正後のvisitFunctionDeclarationをリスト5.25に示します．

リスト5.25: 意味解析を実装したvisitFunctionDeclaration

 1: /**
 2:   * FunctionDclaration用構文解析メソッド
 3:   * @return 解析成功：PrototypeAST　解析失敗：NULL
 4:   */
 5: PrototypeAST *Parser::visitFunctionDeclaration() {
 6:   int bkup = Tokens->getCurIndex();
 7:   PrototypeAST *proto = visitPrototype();
 8:   if (!proto) {
 9:     return NULL;
10:   }
11:
12:   // prototype;
13:   if (Tokens->getCurString() == ";") {
14:
15:     // ここで再定義されていないか確認
16:     if (PrototypeTable.find(proto->getName()) != PrototypeTable.end() ||
17:         (FunctionTable.find(proto->getName()) != FunctionTable.end() &&
18:          FunctionTable[proto->getName()] != proto->getParamNum())) {
19:
20:       // エラーメッセージを出してNULL返却
21:       fprintf(stderr, "Function：%s is redefined", proto->getName().c_str());
22:       SAFE_DELETE(proto);
23:       return NULL;
24:     }
25:     // (関数名，引数)のペアをプロトタイプ宣言テーブル(Map)に追加
26:     PrototypeTable[proto->getName()] = proto->getParamNum();
27:     Tokens->nextToken();
28:     return proto;
29:   } else {
30:     SAFE_DELETE(proto);
31:     Tokens->applyTokenIndex(bkup);
32:     return NULL;
33:   }
34: }

まずvisitFunctionDeclarationでは，16行目でPrototypeTableを参照し新しく解析した関数が既に宣言されているか確認しています．17，18行目では同様にFunctionTableを参照しており，関数が定義済であるか，定義済の場合は引数の数があっているかを確認しています．この処理によって再定義と判断された場合はエラーメッセージを出力してNULLを返却します．新規に宣言された関数であった場合は，26行目でPrototypeTableに関数名，引数の数のペアを追加します．

次に，visitFunctionDefinitionを修正します．visitFunctionDefinitionに追加したい処理は以下の通りです．

関数がプロトタイプ宣言されていた場合，プロトタイプ宣言と関数定義の引数の数が同一であることを確認する処理

同名の関数が複数定義されていないことを確認する処理

上記の処理を追加したテーブルを用いて実装します．修正後のvisitFunctionDefinitionをリスト5.26に示します．

リスト5.26: 意味解析を実装したvisitFunctionDefinition

 1: /**
 2:   * FunctionDefinition用構文解析メソッド
 3:   * @return 解析成功：FunctionAST　解析失敗：NULL
 4:   */
 5: FunctionAST *Parser::visitFunctionDefinition() {
 6:   int bkup = Tokens->getCurIndex();
 7:
 8:   PrototypeAST *proto = visitPrototype();
 9:   if (!proto) {
10:     return NULL;
11:
12:   // ここでプロトタイプ宣言と違いがないか，
13:   // 既に関数定義が行われていないか確認
14:   } else if ((PrototypeTable.find(proto->getName()) != PrototypeTable.end() &&
15:               PrototypeTable[proto->getName()] != proto->getParamNum()) ||
16:              FunctionTable.find(proto->getName()) != FunctionTable.end()) {
17:
18:     // エラーメッセージを出してNULL返却
19:     fprintf(stderr, "Function：%s is redefined", proto->getName().c_str());
20:     SAFE_DELETE(proto);
21:     return NULL;
22:   }
23:
24:   VariableTable.clear();
25:   FunctionStmtAST *func_stmt = visitFunctionStatement(proto);
26:   if (func_stmt) {
27:     // ここで(関数名，引数の数)のペアを関数テーブル(Map)に追加
28:     FunctionTable[proto->getName()] = proto->getParamNum();
29:
30:     return new FunctionAST(proto, func_stmt);
31:   }
32:   ・・・省略・・・
33: }

visitFunctionDefinitionでは，14行目，15行目でPrototypeTableを参照し，新しく解析した関数が宣言済であるか，宣言されている場合は引数の数があっているかを確認しています．16行目ではFunctionTableを参照し，関数が定義済みでないかを確認しています．14行目〜16行目の条件文によって関数の再定義であると判断された場合，エラーメッセージを出力してNULLを返却します．新規に定義された関数であった場合は，28行目でFunctionTableに関数名，引数の数のペアを追加します．なお，関数の再定義とは関係ありませんが，24行目でVariableTableをクリアしていることに注意してください．VariableTableは関数ごとの宣言済み変数を登録するベクタですので，function_statementの解析に入る前に毎回クリアする必要があります．

変数の二重宣言チェックと宣言確認

変数の二重宣言や変数参照時の変数宣言の確認も構文解析を実装します．上記の処理を含むのはvisitPrototype，visitFunctionStatement，visitAssignmentExpression，visitPostfixExpression，visitPrimaryExpressionです．

まず，意味解析を実装したvisitPrototypeをリスト5.27に示します．

リスト5.27: 意味解析を実装したvisitPrototype

 1: /**
 2:   * Prototype用構文解析メソッド
 3:   * @return 解析成功：PrototypeAST　解析失敗：NULL
 4:   */
 5: PrototypeAST *Parser::visitPrototype() {
 6:   // indexを保存
 7:   int bkup = Tokens->getCurIndex();
 8:   ・・・省略・・・
 9:   std::vector<std::string> param_list;
10:   bool is_first_param = true;
11:   while (true) {
12:     if (!is_first_param && Tokens->getCurType() == TOK_SYMBOL &&
13:         Tokens->getCurString() == ",") {
14:       Tokens->nextToken();
15:     }
16:     if (Tokens->getCurType() == TOK_INT) {
17:       Tokens->nextToken();
18:     } else {
19:       break;
20:     }
21:
22:     if (Tokens->getCurType() == TOK_IDENTIFIER) {
23:       // 引数の変数名に被りがないか確認
24:       if (std::find(param_list.begin(), param_list.end(),
25:                     Tokens->getCurString()) != param_list.end()) {
26:         Tokens->applyTokenIndex(bkup);
27:         return NULL;
28:       }
29:       param_list.push_back(Tokens->getCurString());
30:       Tokens->nextToken();
31:     } else {
32:       Tokens->applyTokenIndex(bkup);
33:       return NULL;
34:     }
35:   }
36:   　　・・・省略・・・
37: }

19行目，20行目のfind関数でparam_list(引数名を格納しているリスト)に新しく読み取った識別子が存在しないか確認しています．同名の識別子が既に存在した場合はNULLを返し，そうでなければリストに識別子を追加します．この処理を行うことで同一の引数名の出現を防いでいます．

次に，意味解析を実装したvisitFunctionStatementは下記の様になります．

リスト5.28: 意味解析を実装したvisitFunctionStatement

 1: /**
 2:   * FunctionStatement用構文解析メソッド
 3:   * @param 関数名，引数を格納したPrototypeクラスのインスタンス
 4:   * @return 解析成功：FunctionStmtAST　解析失敗：NULL
 5:   */
 6: FunctionStmtAST *Parser::visitFunctionStatement(PrototypeAST *proto) {
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   if (Tokens->getCurString() == "{") {
10:     Tokens->nextToken();
11:   } else {
12:     return NULL;
13:   }
14:
15:   FunctionStmtAST *func_stmt = new FunctionStmtAST();
16:
17:   ・・・省略・・・
18:
19:   } else if (var_decl = visitVariableDeclaration()) {
20:     while (var_decl) {
21:       var_decl->setDeclType(VariableDeclAST::local);
22:       // 変数名に被りがないか確認
23:       if (std::find(VariableTable.begin(), VariableTable.end(),
24:                     var_decl->getName()) != VariableTable.end()) {
25:         SAFE_DELETE(var_decl);
26:         SAFE_DELETE(func_stmt);
27:         return NULL;
28:       }
29:       // 変数名テーブルに新しく読み取った変数名を追加
30:       VariableTable.push_back(var_decl->getName());
31:       func_stmt->addVariableDeclaration(var_decl);
32:       var_decl = visitVariableDeclaration();
33:     }
34:
35:     if (stmt = visitStatement()) {
36:       while (stmt) {
37:         last_stmt = stmt;
38:         func_stmt->addStatement(stmt);
39:         stmt = visitStatement();
40:       }
41:     }
42:
43:   // other
44:   } else {
45:     SAFE_DELETE(func_stmt);
46:     Tokens->applyTokenIndex(bkup);
47:     return NULL;
48:   }
49:
50:   // 最後のStatementがjump_statementであるか確認
51:   if (!last_stmt || !isa<JumpStmtAST>(last_stmt)) {
52:     SAFE_DELETE(func_stmt);
53:     Tokens->applyTokenIndex(bkup);
54:     return NULL;
55:   }
56:
57:   if (Tokens->getCurString() == "}") {
58:     Tokens->nextToken();
59:     return func_stmt;
60:   } else {
61:     SAFE_DELETE(func_stmt);
62:     Tokens->applyTokenIndex(bkup);
63:     return NULL;
64:   }
65: }

23行目でのfind関数でVariableTableに新しく読み取った変数宣言の変数名が登録されているか確認しています．VariableTableは解析中の関数の変数名を登録しておくベクタですので，この処理で変数が二重宣言されないようにしています．変数名に被りがあった場合はNULLを返却し，そうでなければ30行目でVariableTableに読み取った変数名を追加します．

次に，意味解析を実装したvisitAssignmentExpressionは下記の様になります．

リスト5.29: 意味解析を実装したvisitAssignmentExpression

 1: /**
 2:   * AssignmentExpression用構文解析メソッド
 3:   * @return 解析成功：AST　解析失敗：NULL
 4:   */
 5: BaseAST *Parser::visitAssignmentExpression() {
 6:   int bkup = Tokens->getCurIndex();
 7:
 8:   BaseAST *lhs;
 9:   if (Tokens->getCurType() == TOK_IDENTIFIER) {
10:     // 変数が宣言されているか確認
11:     if (std::find(VariableTable.begin(), VariableTable.end(),
12:                   Tokens->getCurString()) != VariableTable.end()) {
13:       lhs = new VariableAST(Tokens->getCurString());
14:       Tokens->nextToken();
15:       BaseAST *rhs;
16:       if (Tokens->getCurType() == TOK_SYMBOL && Tokens->getCurString() == "=") {
17:         Tokens->nextToken();
18:         if (rhs = visitAdditiveExpression(NULL)) {
19:           return new BinaryExprAST("=", lhs, rhs);
20:         } else {
21:           SAFE_DELETE(lhs);
22:           Tokens->applyTokenIndex(bkup);
23:         }
24:       } else {
25:         SAFE_DELETE(lhs);
26:         Tokens->applyTokenIndex(bkup);
27:       }
28:     } else {
29:       Tokens->applyTokenIndex(bkup);
30:     }
31:   }
32:
33:   BaseAST *add_expr = visitAdditiveExpression(NULL);
34:   if (add_expr) {
35:     return add_expr;
36:   }
37:   return NULL;
38: }

visitAssignmentExpressionでは代入文の左辺値が宣言済みの変数であることを確認する必要があります．それを確認しているのが11行目，12行目のfind関数を用いた処理であり，VariableTableに変数名が登録されていることを確認しています．

次に，意味解析を実装したvisitPrimaryExpressionは下記の様になります．

リスト5.30: 意味解析を実装したvisitPrimaryExpression

 1: /**
 2:   * PrimaryExpression用構文解析メソッド
 3:   * @return 解析成功時：AST　失敗時：NULL
 4:   */
 5: BaseAST *Parser::visitPrimaryExpression() {
 6:   // recored index
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   // 変数が宣言されていることを確認
10:   // VARIABLE_IDENTIFIER
11:   if (Tokens->getCurType() == TOK_IDENTIFIER &&
12:       (std::find(VariableTable.begin(), VariableTable.end(),
13:                  Tokens->getCurString()) != VariableTable.end())) {
14:     std::string var_name = Tokens->getCurString();
15:     Tokens->nextToken();
16:     return new VariableAST(var_name);
17:
18:   // integer
19:   } else if (Tokens->getCurType() == TOK_DIGIT) {
20:     int val = Tokens->getCurNumVal();
21:     Tokens->nextToken();
22:     return new NumberAST(val);
23:
24:   // integer(-)
25:   } else if (Tokens->getCurType() == TOK_SYMBOL &&
26:              Tokens->getCurString() == "-") {
27:
28:     ・・・省略・・・
29:   }
30:   return NULL;
31: }

visitPrimaryExpressionには変数参照の解析が含まれますので，これまでと同様にfind関数を利用して解析した変数が宣言済みであることを確認します．12行目，13行目のfind関数を用いた条件文が上記の処理に該当します．実行している内容はこれまでと同じで，VariableTableに読み取った変数名が含まれている事を確認しています．

関数の宣言確認

関数呼び出しの解析時には，該当の関数が宣言されていることや引数の数が同じであることを確認する必要があります．意味解析では構文解析では実装しなかったこれらの処理を実装します．以上の処理を実装したvisitPostfixExpressionは下記の様になります．

リスト5.31: 意味解析を実装したvisitPostfixExpression

 1: /**
 2:   * PostfixExpression用構文解析メソッド
 3:   * @return 解析成功：AST　解析失敗：NULL
 4:   */
 5: BaseAST *Parser::visitPostfixExpression() {
 6:   // get index
 7:   int bkup = Tokens->getCurIndex();
 8:
 9:   ・・・省略・・・
10:
11:   // FUNCTION_IDENTIFIER
12:   if (Tokens->getCurType() == TOK_IDENTIFIER) {
13:     int param_num;
14:     // プロトタイプ宣言されているか確認，引数の数をテーブルから取得
15:     if (PrototypeTable.find(Tokens->getCurString()) != PrototypeTable.end()) {
16:       param_num = PrototypeTable[Tokens->getCurString()];
17:
18:     // 関数定義済みであるか確認，引数の数をテーブルから取得
19:     } else if (FunctionTable.find(Tokens->getCurString()) !=
20:                FunctionTable.end()) {
21:       param_num = FunctionTable[Tokens->getCurString()];
22:     } else {
23:       return NULL;
24:     }
25:
26:     // 関数名取得
27:     std::string Callee = Tokens->getCurString();
28:     Tokens->nextToken();
29:
30:     // LEFT PAREN
31:     if (Tokens->getCurType() != TOK_SYMBOL || Tokens->getCurString() != "(") {
32:       Tokens->applyTokenIndex(bkup);
33:       return NULL;
34:     }
35:
36:     Tokens->nextToken();
37:     std::vector<BaseAST *> args;
38:     BaseAST *assign_expr = visitAssignmentExpression();
39:     if (assign_expr) {
40:       args.push_back(assign_expr);
41:       while (Tokens->getCurType() == TOK_SYMBOL &&
42:              Tokens->getCurString() == ",") {
43:         Tokens->nextToken();
44:
45:         // IDENTIFIER
46:         assign_expr = visitAssignmentExpression();
47:         if (assign_expr) {
48:           args.push_back(assign_expr);
49:         } else {
50:           break;
51:         }
52:       } // end while
53:     }
54:
55:     // 引数の数を確認
56:     if (args.size() != param_num) {
57:       for (int i = 0; i < args.size(); i++)
58:         SAFE_DELETE(args[i]);
59:       Tokens->applyTokenIndex(bkup);
60:       return NULL;
61:     }
62:
63:     ・・・省略・・・
64:   }
65: }

まず，15行目〜25行目までの処理でPrototypeTableとFunctionTableを参照し，読み取った関数名が宣言/定義されているか確認しています．既に宣言/定義済であった場合，引数の数を変数param_numに取得しておきます．関数が宣言されていることを確認できれば，visitAssignmentExpressionによる引数の解析を繰り返し，解析に成功した場合はargs(引数を格納するベクタ)に格納しておきます．その後，57行目でargsのサイズと最初に取得したparam_numを比較し，解析中の関数呼び出しが関数定義とあっていることを確認します．

5.7 コード生成

構文解析/意味解析でASTの生成が出来るようになりましたので，次はコード生成を実装します．ここでいうコード生成とはLLVM IRの出力であり，アセンブリやバイナリの出力ではないので注意してください．なお，ここにきてようやくLLVMが絡んできますので気合を入れていきましょう．

5.7.1 LLVM IRの生成手順

コード生成部の実装に先立ち，LLVM IRの生成手順を示しておきます．LLVM IRの生成は以下の手順で行います．

Moduleを生成

Functionを生成しModuleへ追加

BasicBlockを生成しFunctionへ追加

Instructionを生成しBasicBlockへ追加

2-4の処理を必要に応じて繰り返す

生成したLLVM IRをファイルへ出力

LLVM IRではメインコンテナであるModuleが一つのモジュール(翻訳単位)に相当するものなので，Moduleの生成は最初の一回のみです．その後のFunction，BasicBlock，Instructionに関してはソースコード(AST)の内容に応じて適宜生成します．グローバル変数に対応していればGlobalVariable等も挿入する必要がありますが，DummyCでは考慮しないので今回は実装しません．最後のファイルへの出力に関しては次節にて説明しますので，コード生成では手順5までを実装することにします．

なお，LLVMではModule，Function，BasicBlock，Instructionに対応するクラスとしてそれぞれModuleクラス，Functionクラス，BasicBlockクラス，Instructionクラスというクラスが用意されています．また，Instructionに関しては，派生クラスとしてAllocaInstやStoreInstなど，固有の命令を表すクラスが用意されています．最初の手順で示したLLVM IRの生成というのはこれらのクラスのインスタンスを生成するということを意味します．また，LLVMにはこれらFunctionやInstructionを簡単に生成する便利なクラスやメソッドも用意されています．特にInstructionに関してはIRBuilderという一つのクラスで様々なInstructionを生成できるため，非常に容易に実装可能です．本節ではこれらLLVMで用意されているクラスやメソッドの使用方法を中心に，LLVM IRの生成方法を説明します．

5.7.2 コード生成クラスの実装方針

コード生成では構文解析/意味解析で生成したASTを頂点から辿り，必要に応じて関数や命令の生成を行います．これまでと同じようにコード生成用にCodeGenクラスを作成していきます．以下にCodeGenクラスの実装方針を示します．

基本的にASTクラス一つにつき一つのコード生成メソッドを作成し，translation_unitから深さ優先でASTを辿ってコード生成を行う

各コード生成メソッド名は“generate”から始まり，ASTクラス名との対応がわかるものにする

コード生成開始のトリガとなるdoCodeGenメソッドを実装する

コード生成は外部からdoCodeGenメソッドを呼び出すことで開始し，コード生成の成否を真偽値で返す

生成したLLVM IRを外部から取得するために，生成したModuleの参照を返却するgetModuleを実装する

以上をまとめたCodeGenクラスの宣言例をリスト5.32に示します．

リスト5.32: CodeGenクラスの宣言

 1: /**
 2:   * コード生成クラス
 3:   */
 4: class CodeGen {
 5: private:
 6:   Function *CurFunc; // 現在コード生成中のFunction
 7:   Module *Mod;       // 生成したModuleを格納
 8:   IRBuilder<> *Builder; // LLVM-IRを生成するIRBuilderクラス
 9:
10: public:
11:   CodeGen();
12:   ~CodeGen();
13:   bool doCodeGen(TranslationUnitAST &tunit, std::string name);
14:   Module &getModule();
15:
16: private:
17:   bool generateTranslationUnit(TranslationUnitAST &tunit, std::string name);
18:   Function *generateFunctionDefinition(FunctionAST *func, Module *mod);
19:   Function *generatePrototype(PrototypeAST *proto, Module *mod);
20:   Value *generateFunctionStatement(FunctionStmtAST *func_stmt);
21:   Value *generateVariableDeclaration(VariableDeclAST *vdecl);
22:   Value *generateStatement(BaseAST *stmt);
23:   Value *generateBinaryExpression(BinaryExprAST *bin_expr);
24:   Value *generateCallExpression(CallExprAST *call_expr);
25:   Value *generateJumpStatement(JumpStmtAST *jump_stmt);
26:   Value *generateVariable(VariableAST *var);
27:   Value *generateNumber(int value);
28: };

ここで，CodeGenクラス内にIRBuilderという型のメンバ変数が宣言されています．llvm::IRBuilderはLLVMで用意されたクラスでその名の通りLLVM IRの各種Instruction(命令)を生成し，BasicBlockに挿入するAPIを提供するクラスです．LLVM 3.2ではIRBuilderは“llvm/IRBuilder.h”にその宣言が記述されています*6．IRBuilderはコード生成の中核をなす重要なクラスですので，以降のコード生成メソッドの実装ではIRBuilderの使用方法を中心に話を進めます．また，同様にメンバ変数としてModule型のポインタが宣言されていますが，これは生成したLLVM IRを保存する変数です．第4章でも少し触れましたが，Doxygenの言葉を借りるとModuleは内部にグローバル変数や関数等の他の全てのLLVM IRオブジェクトを含む最上位のコンテナです．つまり，Moduleクラスがソースコードを解析/生成したLLVM IR全体の情報を持つと言えます．CodeGenクラスでは，生成したModuleのインスタンスをメンバとして保存しておき，外部からの要求に応じてModuleを返却することにします．

5.7.3 メソッドの実装

前節に記載したCodeGenクラスの各メソッドの実装について説明します．

publicなメソッド

外部から呼び出されるコンストラクタ，doCodeGen，getModuleの説明から開始します．

まずはコンストラクタです．今回のコード生成クラスではコンストラクタでIRBuilderを生成し，各コード生成メソッドで使用します．IRBuilderには複数のコンストラクタが用意されていますが，最も簡単なものを以下に示します．

llvm::IRBuilderのコンストラクタ

IRBuilder(LLVMContext &C, MDNode *FPMathTag = 0)

上記コンストラクタは第1引数としてLLVMContextを受け取ります．LLVMContextの説明は非常に難しいのですが，LLVM Coreのグローバルデータを管理，提供するクラスらしいです．何だか難しそうですが，実はllvm::getGlobalContext()によりLLVMのデフォルトのグローバルコンテキストが得られます．シングルスレッドである限りはgetGlobalContextを指定しておけばよかったりするので，とりあえずはおまじないみたいなものだと思ってgetGlobalContext()を使用していれば良いと思います．第2引数にはMDNodeを指定します．これは4章でお話したfpmath Metadata(ULP)を指定する引数で，デフォルト引数として0が与えられています．しかし今回はすべてint型とするので，今のところは明示的に指定しなくても問題ありません．なお，LLVMContextおよびllvm::getGlobalContextの宣言は“llvm/LLVMContext.h”に記載されています．以上から，IRBuilderの生成を行うCodeGenクラスのコンストラクタをリスト5.33に示します．

リスト5.33: CodeGenクラスのコンストラクタ

 1: /**
 2:   * コンストラクタ
 3:   */
 4: CodeGen::CodeGen(){
 5:   Builder = new IRBuilder<>(getGlobalContext());
 6:   Mod = NULL;
 7: }

次にコード生成のトリガとなるdoCodeGenと生成したModuleを取得するgetModuleのコードを載せます．

リスト5.34: doCodeGen

 1: /**
 2:   * コード生成実行
 3:   * @param  TranslationUnitAST　Module名(入力ファイル名)
 4:   * @return 成功時：true　失敗時:false
 5:   */
 6: bool CodeGen::doCodeGen(TranslationUnitAST &tunit, std::string name) {
 7:   return generateTranslationUnit(tunit, name);
 8: }

リスト5.35: getModule

 1: /**
 2:   * Module取得
 3:   */
 4: Module &CodeGen::getModule(){
 5:   if(Mod)
 6:     return *Mod;
 7:   else
 8:     return *(new Module("null", getGlobalContext()));
 9: }

この二つのメソッドは特にLLVMのライブラリは関係してきませんので，そういう風に実装したんだなー程度に見てもらえれば良いです．doCodeGenは内部でgenerateTranslationUnitを呼び出し，その戻り値をそのまま返却します．getModuleはdoCodeGenにより生成したModule(LLVM IR)への参照を返却します．Moduleの生成に失敗している場合，もしくは未生成の場合は空のModuleを生成して返却します．Moduleクラスの生成については次の段落で説明しますので，ここでの説明は省略します．

generateTranslationUnit

generateTranslationUnitの役割はModuleを生成し，内包する関数のプロトタイプ宣言と関数定義の生成メソッドを呼び出すことです．

Module生成はllvm::Moduleクラスのコンストラクタを呼び出すことで実行します．Moduleクラスの宣言は“llvm/Module.h”に記載されており，コンストラクタは下記の様になっています．

llvm::Moduleのコンストラクタ

Module(StringRef ModuleID, LLVMContext &C)

第一引数のModuleIDにはModuleの名前を指定します．基本的に何を指定してもいいはずですが，とりあえずClangに倣って入力ソースコードのファイル名を入れておくのが良いと思います．第二引数はLLVMのコンテキストで，これはIRBuilderと同じくgetGlobalContext()を使用すれば良いです．Moduleを生成した後はTranslationUnitAST内に存在するPrototypeASTとFunctionASTをループで探索し，それぞれのコード生成メソッドを呼び出すだけです．Module生成を含めたgenerateTranslationUnitのコードを以下に示します．

リスト5.36: generateTranslationUnit

 1: /**
 2:   * Module生成メソッド
 3:   * @param  TranslationUnitAST Module名(入力ファイル名)
 4:   * @return 成功時：true　失敗時：false
 5:   */
 6: bool CodeGen::generateTranslationUnit(TranslationUnitAST &tunit,
 7:                                       std::string name) {
 8:   // Moduleを生成
 9:   Mod = new Module(name, getGlobalContext());
10:
11:   // funtion declaration
12:   for (int i = 0;; i++) {
13:     PrototypeAST *proto = tunit.getPrototype(i);
14:     if (!proto)
15:       break;
16:     else if (!generatePrototype(proto, Mod)) {
17:       SAFE_DELETE(Mod);
18:       return false;
19:     }
20:   }
21:
22:   // function definition
23:   for (int i = 0;; i++) {
24:     FunctionAST *func = tunit.getFunction(i);
25:     if (!func)
26:       break;
27:     else if (!generateFunctionDefinition(func, Mod)) {
28:       SAFE_DELETE(Mod);
29:       return false;
30:     }
31:   }
32:   return true;
33: }

generatePrototype

generatePrototypeではPrototypeASTの情報からFunctionを生成しModuleに追加します．generatePrototypeはFunctionのボディは生成しませんので，空のFunction，つまりプロトタイプ宣言の生成を行います．

Functionの生成には“llvm/Function.h”に宣言されたFunctionクラスのCreateメソッドを使用します．Function::Createのインタフェースは下記の通りです．

llvm::Function::Create

static Function *Create(FunctionType *Ty, LinkageTypes Linkage, const Twine &N = "", Module *M = 0)

第1引数のFunctionTypeはFunctionの戻り値や引数リストを表すクラスです．FunctionTypeの生成については後述しますので，Function::Createの説明を続けます．第2引数はLinkageTypesを指定します．LinkageTypesはGlobalValueクラスに定義されている列挙型で様々な要素を含みますが，ここではExternalLinkageを指定します．第3引数は関数名の指定ですので，PrototypeASTのgetName()メソッドで関数名を取得して与えます．第4引数は生成したFunctionを追加するModuleクラスを指定します．

さて，先ほど後回しにしたFunctionTypeの説明に移ります．FunctionTypeの生成にはFunctionTypeクラスのgetメソッドを使用します．FunctionTypeの宣言は“llvm/DerivedTypes.h”にあり，FunctionType::getのインタフェースは下記の様になっています．

llvm::FunctionType::get

static FunctionType *get(Type *Result, ArrayRef<Type *> Params, bool isVarArg)

第1引数は戻り値の型をTypeで指定します．第2引数には引数の型を示すTypeを格納したベクタを与えます．第3引数は可変長引数であるかをbool型で指定します．なお，Typeの生成には複数の方法があるのですが，本書では以下に示すTypeクラスのメソッドを使用します．

llvm::Type::getInt32Ty

static IntegerType *getInt32Ty(LLVMContext &C)

Typeクラスの宣言は“llvm/Type.h”にあります．なお，DummyCは全てint型という制約を設けているのでTypeの生成は全てgetInt32Ty()メソッドを用いれば良いのですが，void型の場合はgetVoidTy，float型の場合はgetFloatPtrTyなどの他のメソッドを使用しなければなりません．以上をまとめたgeneratePrototypeをリスト5.37に示します．

リスト5.37: generatePrototype

 1: /**
 2:   * 関数宣言生成メソッド
 3:   * @param  PrototypeAST, Module
 4:   * @return 生成したFunctionのポインタ
 5:   */
 6: Function *CodeGen::generatePrototype(PrototypeAST *proto, Module *mod) {
 7:   // already declared?
 8:   Function *func = mod->getFunction(proto->getName());
 9:   if (func) {
10:     if (func->arg_size() == proto->getParamNum() && func->empty()) {
11:       return func;
12:     } else {
13:       fprintf(stderr, "error::function %s is redefined",
14:               proto->getName().c_str());
15:       return NULL;
16:     }
17:   }
18:
19:   // create arg_types
20:   std::vector<Type *> int_types(proto->getParamNum(),
21:                                 Type::getInt32Ty(getGlobalContext()));
22:
23:   // create func type
24:   FunctionType *func_type =
25:       FunctionType::get(Type::getInt32Ty(getGlobalContext()), int_types, false);
26:   // create function
27:   func = Function::Create(func_type, Function::ExternalLinkage,
28:                           proto->getName(), mod);
29:
30:   // set names
31:   Function::arg_iterator arg_iter = func->arg_begin();
32:   for (int i = 0; i < proto->getParamNum(); i++) {
33:     arg_iter->setName(proto->getParamName(i).append("_arg"));
34:     arg_iter++;
35:   }
36:
37:   return func;
38: }

冒頭でModuleクラスのgetFunctionメソッドを呼び出していますが，このメソッドは引数に与えた名前のFunctionがModule内に存在した場合，当該Functionを返却してくれます．このメソッドでFunctionが取得でき，なおかつModuleのemptyメソッドがtrueの時，つまりプロトタイプ宣言のみでまだボディがない時には取得したFunctionを返却するようにしています．それ以外のときは関数の再定義に該当するのでエラーメッセージを出力してNULLを返却します．末尾でFunctionの引数のイテレータarg_iterを辿り，setNameを行っていますが，この処理は各引数に対する変数名の設定です．本書の実装では引数に設定する名前は，PrototypeASTのgetParamnameメソッドで取得した変数名に明示的に“_arg”という接尾語をつけることにしています．

generateFunctionDefinition

generateFunctionDefinitionは関数を生成します．Functionの生成は前述のgeneratePrototypeに任せ，生成されたFunctionに対してBasicBlockの追加，およびgenerateFunctionStatementによるボディの生成を行います．

BasicBlockはBasicBlockクラスのCreateメソッドで生成します．BasicBlockクラスの宣言は“llvm/BasicBlock.h”に記載されており，BasicBlock::Createのインタフェースは以下の様になっています．

llvm::BasicBlock::Create

static BasicBlock *
Create(LLVMContext &Context, const Twine &Name = "", Function *Parent = 0,
       BasicBlock *InsertBefore = 0)

第1引数はLLVMContextです．これまで通りllvm:getGlobalContext()を使用すれば良いでしょう．第2引数はBasicBlockにつけるラベル名です．これは任意の名前をつければよいですが，Functionに最初に挿入するBasicBlockなので“entry”としておくのが良いでしょう．第3引数はBasicBlockを挿入するFunctionを指定します．第4引数はBasicBlockを挿入するFunction内の位置を指定します．デフォルトで0が指定されており，0の場合はFunctionの最後に，特定のBasicBlockが指定された場合はそのBasicBlockの前に挿入されます．

generateFunctionDefinitionでは，BasicBlockを生成した後，IRBuilderクラスのSetInsertPointメソッドで命令の挿入位置を指定しておきます．SetInsertPointはIRBuilderのスーパークラスであるIRBuilderBaseクラスで定義されているメソッドで，引数の異なる複数のSetInsertPointが存在します．今回使用するSetInsertPointメソッドのインタフェースは下記の通りです．

llvm::IRBuilderBase::SetInsertPoint

void SetInsertPoint(BasicBlock *TheBB)

SetInsertPointは引数で指定したBasicBlockの末尾に命令の挿入位置を設定します．以降，IRBuilderで命令を生成した場合ここで指定したBasicBlockの最後に命令が追加されていくことになります*7．

以上をまとめたgenerateFunctionDefinitionをリスト5.38に示します．

リスト5.38: generateFunctionDefinition

 1: /**
 2:   * 関数定義生成メソッド
 3:   * @param  FunctionAST Module
 4:   * @return 生成したFunctionのポインタ
 5:   */
 6: Function *CodeGen::generateFunctionDefinition(FunctionAST *func_ast,
 7:                                               Module *mod) {
 8:   Function *func = generatePrototype(func_ast->getPrototype(), mod);
 9:   if (!func) {
10:     return NULL;
11:   }
12:   CurFunc = func;
13:   BasicBlock *bblock = BasicBlock::Create(getGlobalContext(), "entry", func);
14:   Builder->SetInsertPoint(bblock);
15:   // Functionのボディを生成
16:   generateFunctionStatement(func_ast->getBody());
17:
18:   return func;
19: }

generateFunctionStatement

generateFunctionStatementではFunctionのボディを生成します．とはいえ，実際にLLVM IRを生成するのは内部で呼び出すgenerateVariableDeclarationとgenerateStatementです．generateFunctionStatementで行うことは，引数で与えられたFunctionStmtASTが保持するVariableDeclASTと各種StatementのASTを探索し，上記コード生成メソッドの呼び出すことだけです．ただし，NullExprASTについてはコード生成をする必要がないので何もしないことにします．以上の内容を実装したgenerateFunctionStatementをリスト5.39に示します．

リスト5.39: generateFunctionStatement

 1: /**
 2:   * 関数生成メソッド
 3:   * 変数宣言、ステートメントの順に生成
 4:   * @param  FunctionStmtAST
 5:   * @return 最後に生成したValueのポインタ
 6:   */
 7: Value *CodeGen::generateFunctionStatement(FunctionStmtAST *func_stmt) {
 8:   // insert variable decls
 9:   VariableDeclAST *vdecl;
10:   Value *v＝NULL;
11:   for (int i = 0;; i++) {
12:     // 最後まで見たら終了
13:     if (!func_stmt->getVariableDecl(i))
14:       break;
15:
16:     // create alloca
17:     vdecl = dyn_cast<VariableDeclAST>(func_stmt->getVariableDecl(i));
18:     v = generateVariableDeclaration(vdecl);
19:   }
20:
21:   // insert expr statement
22:   BaseAST *stmt;
23:   for (int i = 0;; i++) {
24:     // 最後まで見たら終了
25:     stmt = func_stmt->getStatement(i);
26:     if (!stmt)
27:       break;
28:     else if (!isa<NullExprAST>(stmt))
29:       v = generateStatement(stmt);
30:   }
31:   return v;
32: }

generateVariableDeclaration

generateVariableDeclarationではBasicBlockに変数宣言を追加します．

変数宣言の生成とはつまりAlloca命令の生成であり，IRBuilderクラスのCreateAllocaを用いて行います．CreateAllocaのインタフェースは下記の通りです．

llvm::IRBuilder::CreateAlloca

AllocaInst *CreateAlloca(Type *Ty, Value *ArraySize = 0, const Twine &Name = "")

第1引数は生成する変数の型を示すTypeです．今回は先ほど同様Type::getInt32Tyを使用します．第2引数は恐らく配列長を示すValueですが，DummyCでは配列をサポートしないので0を指定しておけば問題ありません．第3引数は変数名を指定しますので，VariableADeclASTのgetNameメソッドで変数名を取得して与えます．

なお，ASTの定義でも触れましたが，本書では関数の引数に対して，関数内の他の変数と同様にアクセスするために，引数とは別に関数内に引数を示す変数を宣言します．そのため，generateVariableDeclarationでは，VariableDeclASTの宣言種別(DeclType)がparamであったとき，つまり引数を明示的に関数内部に宣言する場合は，CreateAllocaで確保した変数に該当の引数の値を保存します．Alloca命令で確保した変数に対する値の保存はStore命令を使用します．IRBuilderのStore命令生成メソッドは下記の通りです．

llvm::IRBuilder::CreateStore

StoreInst *CreateStore(Value *Val, Value *Ptr, bool isVolatile = false)

CreateStoreの第1引数は格納する値を示すValueです．また，第2引数は格納先を示すValueです．ValueとはInstructionやGlobalValue，Functionの基底クラスで，LLVMの全てのInstructionは基本的にValueクラスの派生クラスとなっているはずです*8．ですので，ここでは第1引数にFunctionの引数を，第2引数にCreateAllocaで生成したAllocaInstを指定します．第3引数はvolatileかどうかを示すbool値ですので，基本的にはfalseで問題ないと思います．なお，引数を示すValueを取得する際にはFunctionのgetValueSymbolTableメソッド，およびValueSymbolTableのlookupメソッドを使用しています．FunctionのgetValueSymbolTableメソッドはその名の通りValueSymbolTable型の参照として該当Functionの識別子表を返却します．Functionから取得したValueSymbolTableのlookupメソッドは引数として変数名を与えてやることで，Function内の該当するValueを返却してくれます．

以上をまとめたgenerateVariableDeclarationをリスト5.40に示します．

リスト5.40: generateVariableDeclaration

 1: /**
 2:   * 変数宣言(alloca命令)生成メソッド
 3:   * @param VariableDeclAST
 4:   * @return 生成したValueのポインタ
 5:   */
 6: Value *CodeGen::generateVariableDeclaration(VariableDeclAST *vdecl) {
 7:   // create alloca
 8:   AllocaInst *alloca = Builder->CreateAlloca(
 9:       Type::getInt32Ty(getGlobalContext()), 0, vdecl->getName());
10:
11:   // if alloca is used for function arguments
12:   if (vdecl->getType() == VariableDeclAST::param) {
13:     // store args
14:     ValueSymbolTable &vs_table = CurFunc->getValueSymbolTable();
15:     Builder->CreateStore(vs_table.lookup(vdecl->getName().append("_arg")),
16:                          alloca);
17:   }
18:   return alloca;
19: }

generateStatement

generateStatementではステートメントを生成します．generateStatementの処理は引数として与えられたBaseASTの種類を判別し，他のコード生成メソッドを呼び出すだけです．主な処理は外部メソッドなので，コードのみをリスト5.41に示して説明は省略します．

リスト5.41: generateStatement

 1: /**
 2:   * ステートメント生成メソッド
 3:   * 実際にはASTの種類を確認して各種生成メソッドを呼び出し
 4:   * @param  JumpStmtAST
 5:   * @return 生成したValueのポインタ
 6:   */
 7: Value *CodeGen::generateStatement(BaseAST *stmt) {
 8:   if (isa<BinaryExprAST>(stmt)) {
 9:     return generateBinaryExpression(dyn_cast<BinaryExprAST>(stmt));
10:   } else if (isa<CallExprAST>(stmt)) {
11:     return generateCallExpression(dyn_cast<CallExprAST>(stmt));
12:   } else if (isa<JumpStmtAST>(stmt)) {
13:     return generateJumpStatement(dyn_cast<JumpStmtAST>(stmt));
14:   } else {
15:     return NULL;
16:   }
17: }

generateBinaryExpression

generateBinaryExpressionは二項演算のコードを生成します．generateBinaryExpressionはBinaryExprASTを引数として受け取り，左辺値，右辺値のコード生成を順に実施し，最終的に代入文，もしくは四則演算命令を生成します．

まず，IRBuilderでの各種四則演算の生成メソッドを以下に示します．

llvm::IRBuilderの四則演算生成メソッド

Value *CreateAdd(Value *LHS, Value *RHS, const Twine &Name = "", bool HasNUW = false,
                 bool HasNSW = false)
Value *CreateSub(Value *LHS, Value *RHS, const Twine &Name = "", bool HasNUW = false,
                 bool HasNSW = false)
Value *CreateMul(Value *LHS, Value *RHS, const Twine &Name = "", bool HasNUW = false,
                 bool HasNSW = false)
Value *CreateSDiv(Value *LHS, Value *RHS, const Twine &Name= "",
                  bool isExact = false)

上から順に，加算命令，減算命令，乗算命令，除算命令の生成です．第1引数〜第3引数が示すものは全て同一で，それぞれ左辺値，右辺値，名前となっています．ここで指定した名前は命令の結果を格納するレジスタ名となります．加算命令〜乗算命令の第4引数，第5引数はNUW，NSWを有効にするかどうかのフラグで，除算命令の第4引数はExactを有効にするかのフラグです．NUW，NSWは4章で説明した様に，それぞれUnsigned，Signedでオーバーフローした時にPoison Valueになるというフラグで，ExactはLHSがRHSの倍数でないときにPoison Valueになるというフラグだそうです．

generateBinaryExpressionに渡されたBinaryExprASTが代入文であった場合，Store命令により代入文を生成します．Store命令は前述のメソッドで生成できますので，ここでの説明は省略します．なお，generateBinaryExpressionは左辺値，および右辺値を表すBaseASTの種類によって適宜他のコード生成メソッドを呼び出して必要な命令を生成するものとします．

以上の内容を利用したgenerateBinaryExpressionメソッドをリスト5.42に示します．

リスト5.42: generateBinaryExpression

 1: /**
 2:   * 二項演算生成メソッド
 3:   * @param  JumpStmtAST
 4:   * @return 生成したValueのポインタ
 5:   */
 6: Value *CodeGen::generateBinaryExpression(BinaryExprAST *bin_expr) {
 7:   BaseAST *lhs = bin_expr->getLHS();
 8:   BaseAST *rhs = bin_expr->getRHS();
 9:
10:   Value *lhs_v;
11:   Value *rhs_v;
12:
13:   // assignment
14:   if (bin_expr->getOp() == "=") {
15:     printf("genStore\n");
16:     // lhs is variable
17:     VariableAST *lhs_var = dyn_cast<VariableAST>(lhs);
18:     ValueSymbolTable &vs_table = CurFunc->getValueSymbolTable();
19:     lhs_v = vs_table.lookup(lhs_var->getName());
20:
21:   // other operand
22:   } else {
23:     // lhs=?
24:     // Binary?
25:     if (isa<BinaryExprAST>(lhs))
26:       lhs_v = generateBinaryExpression(dyn_cast<BinaryExprAST>(lhs));
27:
28:     // Variable?
29:     else if (isa<VariableAST>(lhs))
30:       lhs_v = generateVariable(dyn_cast<VariableAST>(lhs));
31:
32:     // Number?
33:     else if (isa<NumberAST>(lhs)) {
34:       NumberAST *num = dyn_cast<NumberAST>(lhs);
35:       lhs_v = generateNumber(num->getNumberValue());
36:     }
37:   }
38:
39:   // create rhs value
40:   if (isa<BinaryExprAST>(rhs)) {
41:     rhs_v = generateBinaryExpression(dyn_cast<BinaryExprAST>(rhs));
42:
43:   // CallExpr?
44:   } else if (isa<CallExprAST>(rhs)) {
45:     rhs_v = generateCallExpression(dyn_cast<CallExprAST>(rhs));
46:
47:   // Variable?
48:   } else if (isa<VariableAST>(rhs)) {
49:     rhs_v = generateVariable(dyn_cast<VariableAST>(rhs));
50:
51:   // Number?
52:   } else if (isa<NumberAST>(rhs)) {
53:     NumberAST *num = dyn_cast<NumberAST>(rhs);
54:     rhs_v = generateNumber(num->getNumberValue());
55:   }
56:
57:   if (bin_expr->getOp() == "=") {
58:     // store
59:     return Builder->CreateStore(rhs_v, lhs_v);
60:   } else if (bin_expr->getOp() == "+") {
61:     // add
62:     return Builder->CreateAdd(lhs_v, rhs_v, "add_tmp");
63:   } else if (bin_expr->getOp() == "-") {
64:     // sub
65:     return Builder->CreateSub(lhs_v, rhs_v, "sub_tmp");
66:   } else if (bin_expr->getOp() == "*") {
67:     // mul
68:     return Builder->CreateMul(lhs_v, rhs_v, "mul_tmp");
69:   } else if (bin_expr->getOp() == "/") {
70:     // div
71:     return Builder->CreateSDiv(lhs_v, rhs_v, "div_tmp");
72:   }
73: }

generateCallExpression

generateCallExpressionは関数呼び出し命令を生成するコード生成メソッドです．

関数呼び出し命令の生成はIRBuilderのCreateCallメソッドを使用します．CreateCallメソッドのインタフェースを以下に示します．

llvm::IRBuilder::CreateCall

CallInst *CreateCall(Value *Callee, ArrayRef<Value *> Args, const Twine &Name = "")

第1引数は呼び出し対象のFunctionを指定します．この値はModuleクラスのgetFunctionメソッドに関数名を指定することで取得できます．第2引数は引数として渡すValueです．std::vector型のベクタに引数のValueを詰め込んだものを渡します．第3引数には名前を指定します．ここで指定した名前は関数呼び出しの戻り値を格納するレジスタの名前に使用されます．なお，引数として渡すValueはCallExprASTから取得したBaseASTの種類によって適宜他のコード生成メソッドを呼び出して生成するものとします．

以上の内容を利用したgenerateCallExpressionをリスト5.43に示します．

リスト5.43: generateCallExpression

 1: /**
 2:   * 関数呼び出し(Call命令)生成メソッド
 3:   * @param CallExprAST
 4:   * @return 生成したValueのポインタ
 5:   */
 6: Value *CodeGen::generateCallExpression(CallExprAST *call_expr) {
 7:   std::vector<Value *> arg_vec;
 8:   BaseAST *arg;
 9:   Value *arg_v;
10:   ValueSymbolTable &vs_table = CurFunc->getValueSymbolTable();
11:   for (int i = 0;; i++) {
12:     if (!(arg = call_expr->getArgs(i)))
13:       break;
14:
15:     // isCall
16:     if (isa<CallExprAST>(arg))
17:       arg_v = generateCallExpression(dyn_cast<CallExprAST>(arg));
18:
19:     // isBinaryExpr
20:     else if (isa<BinaryExprAST>(arg)) {
21:       BinaryExprAST *bin_expr = dyn_cast<BinaryExprAST>(arg);
22:       arg_v = generateBinaryExpression(dyn_cast<BinaryExprAST>(arg));
23:       if (bin_expr->getOp() == "=") {
24:         VariableAST *var = dyn_cast<VariableAST>(bin_expr->getLHS());
25:         arg_v = Builder->CreateLoad(vs_table.lookup(var->getName()), "arg_val");
26:       }
27:     }
28:
29:     // isVar
30:     else if (isa<VariableAST>(arg))
31:       arg_v = generateVariable(dyn_cast<VariableAST>(arg));
32:
33:     // isNumber
34:     else if (isa<NumberAST>(arg)) {
35:       NumberAST *num = dyn_cast<NumberAST>(arg);
36:       arg_v = generateNumber(num->getNumberValue());
37:     }
38:     arg_vec.push_back(arg_v);
39:   }
40:   return Builder->CreateCall(Mod->getFunction(call_expr->getCallee()), arg_vec,
41:                              "call_tmp");
42: }

generateJumpStatement

generateJumpStatementではreturn文の生成を行います*9．generateJumpStatementは渡されたJumpStmtASTが含む戻り値を示すASTに応じてreturn文を生成します．

リターン文の生成はIRBuilderクラスのCreateRetメソッドを使用します．CreateRetメソッドのインタフェースは下記の通りです．

IRBuilder::CreateRet

ReturnInst *CreateRet(Value *V)

CreateRetは引数として戻り値を示すValueを与えるだけの簡単なメソッドです．CreateRetに渡すValueはJumpStmtASTが保持する戻り値を示すASTに応じて他のコード生成メソッドを呼び出して生成するものとします．

リスト5.44: generateJumpStatement

 1: /**
 2:   * ジャンプ(今回はreturn命令のみ)生成メソッド
 3:   * @param  JumpStmtAST
 4:   * @return 生成したValueのポインタ
 5:   */
 6: Value *CodeGen::generateJumpStatement(JumpStmtAST *jump_stmt) {
 7:   BaseAST *expr = jump_stmt->getExpr();
 8:   Value *ret_v;
 9:   if (isa<BinaryExprAST>(expr)) {
10:     ret_v = generateBinaryExpression(dyn_cast<BinaryExprAST>(expr));
11:   } else if (isa<VariableAST>(expr)) {
12:     VariableAST *var = dyn_cast<VariableAST>(expr);
13:     ret_v = generateVariable(var);
14:   } else if (isa<NumberAST>(expr)) {
15:     NumberAST *num = dyn_cast<NumberAST>(expr);
16:     ret_v = generateNumber(num->getNumberValue());
17:   }
18:   Builder->CreateRet(ret_v);
19: }

generateVariable

generateVariableは変数の参照に対するコード生成を行います．変数の参照とはすなわちLoad命令の生成です．

IRBuilderのLoad命令生成メソッドのインタフェースを以下に示します．

llvm::IRBuilder::CreateLoad

LoadInst *CreateLoad(Value *Ptr, const Twine &Name = "")

第1引数はLoadの対象となるValueです．ここにはValueSymbolTableからAllocaInstを取得して指定します．第2引数は名前を指定します．ここで指定した名前はLoadした値を格納するレジスタの名前になります．generateVariableは上記メソッドを使用してLoad命令を生成して返却するだけのメソッドです．実際のgenerateVariableの定義をリスト5.45に示します．

リスト5.45: generateVariable

 1: /**
 2:   * 変数参照(load命令)生成メソッド
 3:   * @param VariableAST
 4:   * @return  生成したValueのポインタ
 5:   */
 6: Value *CodeGen::generateVariable(VariableAST *var) {
 7:   ValueSymbolTable &vs_table = CurFunc->getValueSymbolTable();
 8:   return Builder->CreateLoad(vs_table.lookup(var->getName()), "var_tmp");
 9: }

generateNumber

generateNumberは定数を表すValueを生成するメソッドです．引数に与えられた値を示すValueを生成して返却します．

LLVM IRの整数値の生成にはConstantIntクラスのgetメソッドを使用します．ConstantInt::getメソッドのインタフェースを以下に示します．

llvm::ConstantInt::get

static ConstantInt *get(LLVMContext &Context, const APInt &V)

第1引数はLLVMContextですので，今まで通りllvm:getGlobalContextを指定してやれば良いです．第2引数には生成する整数値の値を指定します．APIntという見慣れない型になっていますが，実際のところint型の値を与えてやれば問題ありません．

上記メソッドを使用したgenerateNumberの定義をリスト5.46に示します．

リスト5.46: generateNumber

 1: /**
 2:  * 定数生成メソッド
 3:  * @param 生成する定数の値
 4:  * @return 生成したValueのポインタ
 5:  */
 6: Value *CodeGen::generateNumber(int value) {
 7:   return ConstantInt::get(Type::getInt32Ty(getGlobalContext()), value);
 8: }

5.8 mainの作成

ここまでの内容で字句解析〜コード生成までの各種モジュールを作成しました．しかしこれらを順序だてて呼び出すためのドライバ部分の実装が完了していません．本節では標準入力から入力ソースコード名を受け取り，これまでに作成した各種モジュールを呼び出す機能を実装します．さほど長い処理ではないので，今回はmain関数内に上記処理を記述していくことにします．

5.8.1 入力ソースコード名と出力ソースコード名の受け取り

まず，標準入力から入力ソースコード，および出力ソースコードを受け取る機能を実装します．これはLLVMのコマンドライン ライブラリを使用しても良いのですが，今回は自分で実装することにしました．LLVMのコマンドライン ライブラリについては本章の最後で説明しますが，特に詳しく使用方法が知りたいという方はドキュメントのCommandLine 2.0 Library Manualを読むことをお勧めします．

本書で作成するDummyCコンパイラは以下の方針で入力ソースコード名と出力ソースコード名を受け取ることにします．

入力ソースコード名および出力ソースコード名は実行時の引数として受け取る

入力ソースコード名の指定は必須であり，指定されない場合はエラー終了する

出力ソースソースコード名の指定はオプションであり，-oの後ろに指定するものとする

-hが指定された場合はヘルプメッセージの表示のみを行い終了する

実行時の引数を切り取るOptionParserクラスを作成し，入力ソースコード名および出力ソースコード名はOptionParserクラスから取得する

OptionParserクラスの宣言

OptionParserクラスの宣言を以下に示します．OptionParserクラスのコンストラクタはmain関数のargcとargvをそのまま受け取るものとしています．オプションの切出しは外部からParseOptionメソッドを実行することで開始し，入出力ファイル名をメンバ変数に格納します．また，OptionParserクラスは切出した入出力ファイル名を取得するgetterメソッドを提供します．

リスト5.47: OptionParser

 1: /**
 2:  * オプション切り出し用クラス
 3:  */
 4: class OptionParser {
 5: private:
 6:   std::string InputFilename;
 7:   std::string OutputFilename;
 8:   int Argc;
 9:   char **Argv;
10:
11: public:
12:   OptionParser(int argc, char **argv) : Argc(argc), Argv(argv) {}
13:   void printHelp() {
14:     // ヘルプ表示
15:     fprintf(stdout,
16:             "Compiler for DummyC...\n 試作中なのでバグがあったらご報告を\n");
17:   }
18:   void printHelp();
19:   std::string getInputFileName() { return InputFilename; } //入力ファイル名取得
20:   std::string getOutputFileName() { return OutputFilename; } //出力ファイル名取得
21:   bool parseOption();    //オプション切出しメソッド
22: };

コマンドラインオプション切出しメソッドの実装

OptionParserクラスのオプション切出しメソッドであるParseOptionの実装をリスト5.48に示します．

リスト5.48: ParseOption

 1: /**
 2:  * オプション切り出し
 3:  * @return 成功時：true　失敗時：false
 4:  */
 5: bool OptionParser::parseOption() {
 6:   if (Argc < 2) {
 7:     fprintf(stderr, "引数が足りません\n");
 8:     return false;
 9:   }
10:
11:   for (int i = 1; i < Argc; i++) {
12:     if (Argv[i][0] == '-' && Argv[i][1] == 'o' && Argv[i][2] == '\0') {
13:       // output filename
14:       OutputFilename.assign(Argv[++i]);
15:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'h' && Argv[i][2] == '\0') {
16:       printHelp();
17:       reutrn false;
18:     } else if (Argv[i][0] == '-') {
19:       fprintf(stderr, "%s は不明なオプションです\n", Argv[i]);
20:       return false;
21:     } else {
22:       // inputfilename
23:       InputFilename.assign(Argv[i]);
24:     }
25:   }
26:
27:   // OutputFilename
28:   std::string ifn = InputFilename;
29:   int len = ifn.length();
30:   if (OutputFilename.empty() && (len > 2) && ifn[len - 3] == '.' &&
31:       ((ifn[len - 2] == 'd' && ifn[len - 1] == 'c'))) {
32:     OutputFilename = std::string(ifn.begin(), ifn.end() - 3);
33:     OutputFilename += ".ll";
34:   } else if (OutputFilename.empty()) {
35:     OutputFilename = ifn;
36:     OutputFilename += ".ll";
37:   }
38:
39:   return true;
40: }

特に難しい処理はありませんが，念のために説明しておきます．ParseOptionは冒頭でまず引数の数を確認し，足りなければエラーメッセージを出力してfalseを返却します．次に，argvの値を一つずつ確認し，入力ファイル名，出力ファイル名，ヘルプ表示の何れかに該当すれば該当の処理を行います．最後に，出力ファイル名が指定されていなかった場合は，入力ファイル名から出力ファイル名を生成します．なお，DummyCファイルの拡張子は.dcとなっていると想定しています．末尾が.dcで終わるファイルについては入力ファイルの拡張子を.llに変更したものを出力ファイル名とし，それ以外のファイルは入力ファイル名の末尾に.llを付加したものを出力ファイル名とします．

5.8.2 各種初期化と作成した各クラスの呼び出し

ここでは，OptionParserの呼び出しを含む各種初期化と構文解析やコード生成の各種クラスの生成/メソッド呼び出しによるコンパイル，ファイル出力を実装します．ここでは初期化，作成した各種クラスのメソッド呼び出し，ファイル出力の3段階にわけて説明を進めます．

各種初期化

まず，各種初期化処理からはじめます．リスト5.49にmain関数の初期化部分のコードを記載します．

リスト5.49: 初期化

 1: /**
 2:  * main関数
 3:  */
 4: int main(int argc, char **argv) {
 5:   InitializeNativeTarget();
 6:   sys::PrintStackTraceOnErrorSignal();
 7:   PrettyStackTraceProgram X(argc, argv);
 8:   EnableDebugBuffering = true;
 9:
10:   OptionParser opt(argc, argv);
11:   if (!opt.parseOption())
12:     exit(1);
13:
14:   ・・・省略・・・
15: }

5行目〜8行目は何れもLLVMのライブラリ関数や変数です．InitializeNativeTargetはプログラムを実行したホスト環境に合わせてネイティブターゲットを初期化します．sys::PrintStackTraceOnErrorSignalは致命的なシグナルが発生した時に，スタックトレースを出力します．PrettyStackTraceProgramはプログラムがクラッシュした際に指定された引数をスタックトレースとしてストリームに出力します．EnebleDebugBufferingはtrueにすると，バッファリングされたデバッグ出力をダンプするためにデバッグストリームにシグナルハンドラがインストールされるそうです*10．その後，先ほど作成したOptionParserクラスのインスタンスを生成して引数をパースしています．ちなみに，InitalizeNativeTarget，sys::PrintStackTraceOnErrorSignal，PrettyStackTraceProgram EnableDebugBufferingはそれぞれ，“llvm/Support/TargetSelect.h”，“lvm/Support/Signals.h”，“llvm/Support/PrettyStackTrace.h”，“llvm/Support/Debug.h”に記載されています．

作成した各種クラスの呼び出し

次に，本書で作成した各種クラスを用いたコンパイル実行部分に移ります．リスト5.50にmain関数のコンパイル実行部分のコードを記載します．

リスト5.50: 作成した各種クラスによるコンパイル実行

 1: /**
 2:  * main関数
 3:  */
 4: int main(int argc, char **argv) {
 5:   ・・・初期化処理・・・
 6:
 7:   // check
 8:   if (opt.getInputFileName().length() == 0) {
 9:     fprintf(stderr, "入力ファイル名が指定されていません\n");
10:     exit(1);
11:   }
12:
13:   // lex and parse
14:   Parser *parser = new Parser(opt.getInputFileName());
15:   if (!parser->doParse()) {
16:     fprintf(stderr, "err at parser or lexer\n");
17:     SAFE_DELETE(parser);
18:     exit(1);
19:   }
20:
21:   // get AST
22:   TranslationUnitAST &tunit = parser->getAST();
23:   if (tunit.empty()) {
24:     fprintf(stderr, "TranslationUnit is empty");
25:     SAFE_DELETE(parser);
26:     exit(1);
27:   }
28:
29:   CodeGen *codegen = new CodeGen();
30:   if (!codegen->doCodeGen(tunit, opt.getInputFileName())) {
31:     fprintf(stderr, "err at codegen\n");
32:     SAFE_DELETE(parser);
33:     SAFE_DELETE(codegen);
34:     exit(1);
35:   }
36:
37:   // get Module
38:   Module &mod = codegen->getModule();
39:   if (mod.empty()) {
40:     fprintf(stderr, "Module is empty");
41:     SAFE_DELETE(parser);
42:     SAFE_DELETE(codegen);
43:     exit(1);
44:   }
45:
46:   ・・・ファイル出力と終了処理・・・
47: }

最初にParserクラスのインスタンスを生成し，doParseメソッドで構文解析/意味解析を実行します．doParseが失敗した場合はエラーメッセージを出力して終了します．doParseが成功した場合はgetASTメソッドでASTを取得します．ここで，念のためにemptyメソッドでASTが空でない事を確認しておきます．ASTが取得できればCodeGenクラスのdoCodeGenメソッドでコード生成を実行します．doCodeGenが失敗した場合はエラーメッセージを出力して終了します．成功した場合はCodeGenクラスからgetModuleメソッドでModuleを取得し，Moduleが空でない事を確認します．

ファイル出力と終了処理

最後に，生成したLLVM IRのファイル出力を説明します．ファイル出力にはPrintModulePassクラスを用います．PrintModulePassは“llvm/Assembly/PrintModulePass.h”に宣言されているllvm::createPrintModulePass関数で生成します．llvm::createPrintModulePassのインタフェースは以下の通りです．

llvm::CreatePrintModulePass

ModulePass *llvm::createPrintModulePass(raw_ostream * OS,
                                        bool DeleteStream = false,
                                        const std::string &Banner = "")

まず，第1引数は出力ストリームの指定です．第2引数はこのパスを実行後に第一引数で渡したストリームをdeleteするかを指定するフラグで，trueを指定した場合はPrintModulePassのデストラクタにてストリームがdeleteされます．第3引数のBannerに文字列を指定すると，ストリームの先頭，つまり出力対象のModuleの前にBannerに指定した文字列が出力されます．ただし，第2，第3引数にはデフォルト引数が設定されていますので，通常は第1引数のストリームの指定だけで良いと思います．なお，第1引数に指定できるraw_ostreamの派生クラスは複数あるのですが，今回はファイル出力なのでraw_fd_streamを使用します．raw_fd_streamは“llvm/raw_ostream.h”に宣言されており，コンストラクタは以下の様になっています．

raw_fd_ostream

raw_fd_ostream(const char *Filename, std::string &ErrorInfo, unsigned Flags = 0)

第1引数は出力ファイル名を指定し，第2引数にはエラー情報を格納するstringを渡します．第3引数のFlagsでファイルをどのように開くかのオプションを指定できます．

生成したPrintModulePassはPassManagerのaddメソッドで登録し，runメソッドで適用します．以上の処理を記述したmain関数のファイル出力部分のコードをリスト5.51に記載します．

リスト5.51: ファイル出力と終了処理

 1: /**
 2:  * main関数
 3:  */
 4: int main(int argc, char **argv) {
 5:
 6:   ・・・初期化処理とコンパイル・・・
 7:
 8:   PassManager pm;
 9:
10:   //出力
11:   std::string error;
12:   raw_fd_ostream raw_stream(opt.getOutputFileName().c_str(), error);
13:   pm.add(createPrintModulePass(&raw_stream));
14:   pm.run(mod);
15:   raw_stream.close();
16:
17:   // delete
18:   SAFE_DELETE(parser);
19:   SAFE_DELETE(codegen);
20:
21:   return 0;
22: }

5.9 コンパイルと動作確認

本節ではここまで生成したファイルのコンパイルと動作確認を行います．なお，本節ではここまで説明してきたクラスや関数が表5.9に示すファイルに収められているものとして話を進めます．また，拡張子がcppのファイルはsrcディレクトリ，hppのファイルはincディレクトリに配置されているものとします．

表5.9: DummyCフロントエンドのファイル構成

ファイル名 | 内容
APP.hpp | 全ソースファイルで共通して使用するマクロが記述されたファイル
lexer.hpp | TokenクラスやTokenStreamクラスのクラス宣言等，字句解析関係の宣言が記述されたヘッダファイル　
lexer.cpp | TokenクラスやTokenStreamクラスのメソッド定義およびLexicalAnalysisの定義が記述されたファイル
AST.hpp | 各種ASTクラスのクラス宣言が記述されたヘッダファイル
AST.cpp | 各種ASTクラスのメソッド定義が記述されたファイル
parser.hpp | Parserクラスのクラス宣言が記述されたヘッダファイル
parser.cpp | Parserクラスのメソッド定義が記述されたファイル
codegen.hpp | CodeGenクラスのクラス宣言が記述されたヘッダファイル
codegen.cpp | CodeGenクラスのメソッド定義が記述されたファイル
dcc.cpp | main関数およびOptionParserクラスが記述されたファイル

5.9.1 コンパイル

ここまで作成したソースコードをコンパイルし，実行ファイルを生成します．ここで使用するのが第3章で紹介したllvm-configです．llvm-configは前述の通り，LLVMのライブラリを使用したプログラムのコンパイルに必要なオプションを出力するツールです．llvm-configを利用したコンパイル方法の例として，実際に投入するコンパイルコマンドを次に示します．

# githubからソースコードを取得
$ git clone git@github.com:Kmotiko/DummyCCompiler.git DummyCCompiler
$ cd DummyCCompiler

# チェックアウト
$ git checkout llvm-3.2_ch5.9

# 一時ファイル用ディレクトリを作成
$ mkdir -p ./obj

# dcc.cppを-cオプションでコンパイル
$ g++ -g ./src/dcc.cpp -I./inc `llvm-config --cxxflags --ldflags --libs` \
  -c -o ./obj/dcc.o

# lexer.cppを-cオプションでコンパイル
$ g++ -g ./src/lexer.cpp -I./inc `llvm-config --cxxflags --ldflags --libs` \
  -c -o ./obj/lexer.o

# AST.cppを-cオプションでコンパイル
$ g++ -g ./src/AST.cpp -I./inc `llvm-config --cxxflags --ldflags --libs` \
  -c -o ./obj/AST.o

# parser.cppを-cオプションでコンパイル
$ g++ -g ./src/parser.cpp -I./inc `llvm-config --cxxflags --ldflags --libs` \
  -c -o ./obj/parser.o

# codegen.cppを-cオプションでコンパイル
$ g++ -g ./src/codegen.cpp -I./inc `llvm-config --cxxflags --ldflags --libs` \
  -c -o ./obj/codegen.o

# 実行ファイル用ディレクトリを作成
$ mkdir -p ./bin

# 各オブジェクトファイルをリンクして実行ファイルdccを生成
$ g++ -g ./obj/dcc.o ./obj/lexer.o ./obj/AST.o ./obj/parser.o ./obj/codegen.o \
  `llvm-config --cxxflags --ldflags --libs` -ldl -o ./bin/dcc

最終的に生成されるdccというファイルがDummyCフロントエンドの実行ファイルです．なお，DummyCCompilerをcloneするとMakefileも取得できますので，面倒な方はmakeコマンドを実行していただければコンパイルできるはずです．

5.9.2 動作確認

生成したDummyCフロントエンドの動作確認を行います．以下のサンプルコードに対してコンパイルを実行します．

リスト5.52: DummyCのサンプルコード

 1: int test(int j) {
 2:   int i;
 3:   i = j * 10;
 4:   return i;
 5: }
 6:
 7: int main() {
 8:   int i;
 9:   i = 10;
10:   test(i);
11:   return 0;
12: }

DummyCCompilerディレクトリにて下記コマンドを投入して，DummyCフロントエンドでLLVM IRを生成します．なお，以降サンプルコードはsampleディレクトリ配下に配置されているものとします．

# dccでサンプルコードをコンパイル
$ ./bin/dcc ./sample/test.dc -o ./sample/test.ll

出力としてtest.llにリスト5.53示す様なLLVM IRが得られていれば成功です．

リスト5.53: サンプルコードのLLVM IR表現

 1: ; ModuleID = './sample/test.dc'
 2:
 3: define i32 @test(i32 %j_arg) {
 4: entry:
 5:   %j = alloca i32
 6:   store i32 %j_arg, i32* %j
 7:   %i = alloca i32
 8:   %var_tmp = load i32* %j
 9:   %mul_tmp = mul i32 %var_tmp, 10
10:   store i32 %mul_tmp, i32* %i
11:   %var_tmp1 = load i32* %i
12:   ret i32 %var_tmp1
13: }
14:
15: define i32 @main() {
16: entry:
17:   %i = alloca i32
18:   store i32 10, i32* %i
19:   %var_tmp = load i32* %i
20:   %call_tmp = call i32 @test(i32 %var_tmp)
21:   ret i32 0
22: }

5.10 mem2regの適用と組込み関数の導入

本節では，機能拡張としてmem2reg Passの適用とシステム関数の導入を行います．

5.10.1 mem2regの適用

前節でコード生成の確認は出来ましたが，生成されるLLVM IRはallocaやload，store命令を多用しているため少々汚いです．そこでmem2reg使ってメモリアクセスをレジスタに移動させます．mem2regという名前で指定するPassの実体は実はPromotePassクラスであり，PromotePassはllvm::createPromoteMemoryToRegisterPassで生成します．そこで，main関数内の処理を以下のように変更します．

リスト5.54: PromotePassの追加

 1: /**
 2:  * main関数
 3:  */
 4: int main(int argc, char **argv) {
 5:
 6:   ・・・初期化処理・コード生成・・・
 7:
 8:   PassManager pm;
 9:
10:   // mem2regをPassManagerに登録
11:   pm.add(createPromoteMemoryToRegisterPass());
12:
13:   // 出力
14:   std::string error;
15:   raw_fd_ostream raw_stream(opt.getOutputFileName().c_str(), error);
16:   pm.add(createPrintModulePass(&raw_stream));
17:   pm.run(mod);
18:   raw_stream.close();
19:
20:   ・・・終了処理・・・
21: }

追加した処理は11行目のPromotePassクラスを生成してPassManagerにaddする処理のみです．これでallocaやload，storeなどのメモリアクセス命令を可能な限りレジスタへ移動させることが出来ます．上記修正を施したファイルを再度コンパイルしてサンプルコードのコンパイルを実行してみてください．最終的にリスト5.55に示す様なコードが生成されていれば成功です．

リスト5.55: mem2reg適用後のLLVM

 1: ; ModuleID = './sample/test.dc'
 2:
 3: declare i32 @printnum(i32)
 4:
 5: define i32 @test(i32 %j_arg) {
 6: entry:
 7:   %mul_tmp = mul i32 %j_arg, 10
 8:   ret i32 %mul_tmp
 9: }
10:
11: define i32 @main() {
12: entry:
13:   %call_tmp = call i32 @test(i32 10)
14:   ret i32 0
15: }

なお，createPromoteMemoryToRegisterPassの宣言は“llvm/Transforms/Scalar.h”に記載されています．

5.10.2 組込み関数

前節まででコード生成が出来ることを確認し，メモリアクセスをレジスタへ移動させました．しかし，出力がないので演算結果が正しいのかいまいちわかりません．そこで組込み関数として，数値を出力するprintnumを実装してみます．ここで，printnumはC言語の標準ライブラリ関数であるprintf関数をラップしただけの簡単な関数とします．printnumの実装方法は幾つか考えられましたが，今回は最も簡単に実装できそうな下記の方法を採用しました．

C言語で記述したprintnum関数をClangを用いてLLVM IRに変換する

Parserクラスのメソッドにprintnum関数の宣言を表すASTを生成する処理を追加する

DummyCコンパイラで生成したLLVM IRとprintnumのLLVM IRをllvm-linkでリンクする

それでは，上記方針に従ってprintnumを実装します．

printnumのLLVM IRの用意

まず，printfをラップしたprintnum関数をC言語で記述し，ClangでLLVM IRに変換します．printnum関数の定義をリスト5.56に示します．

リスト5.56: printnumの定義

 1: #include <stdio.h>
 2:
 3: int printnum(int i){
 4:   return printf("%d\n",i);
 5: }

Clang でこれまでと同様にLLVM IRを生成します．折角フロントエンドが生成するLLVM IRにmem2regを適用しているので，今回はClangにも最適化オプションを使用します

$ clang -emit-llvm -S -O -o printnum.ll printnum.c

print.llとしてリスト5.57に示す様なLLVM IRが生成されているはずです．

リスト5.57: printnumのLLVM IR

 1: ; ModuleID = 'lib/src/printnum.c'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
 6:
 7: define i32 @printnum(i32 %i) nounwind {
 8: entry:
 9:   %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %i) nounwind
10:   ret i32 %call
11: }
12:
13: declare i32 @printf(i8* nocapture, ...) nounwind

Parserクラスの修正

printnumの宣言を構文解析/コード生成で参照できるようvisitTranslationUnit内の処理をlist[fixvisitTranslationUnit]の様に修正し，printnumの宣言を表すASTを追加します．これにより，ASTにprintnumの宣言が追加され，最終的にDummyCフロントエンドによって生成されるLLVM IRにprintnumの宣言が追加されます．

リスト5.58: システム関数導入によるvisitTranslationUnitの修正

 1: /**
 2:   * TranslationUnit用構文解析メソッド
 3:   * @return 解析成功：true　解析失敗：false
 4:   */
 5: bool Parser::visitTranslationUnit() {
 6:   // 最初にprintnumの宣言追加
 7:   TU = new TranslationUnitAST();
 8:   std::vector<std::string> param_list;
 9:   param_list.push_back("i");
10:   TU->addPrototype(new PrototypeAST("printnum", param_list));
11:   PrototypeTable["printnum"] = 1;
12:
13:   ・・・省略・・・
14: }

llvm-linkによるリンクと動作確認

Parserクラスの修正が完了したら再度コンパイルしてフロントエンドを生成しなおして下さい．その上で，リスト5.59に示すDummyCのサンプルコードをコンパイルしましょう．

リスト5.59: printnumを含むDummyCのサンプルコード

 1: int test(int j) {
 2:   int i;
 3:   i = j * 10;
 4:   return i;
 5: }
 6:
 7: int main() {
 8:   int i;
 9:   i = 10;
10:   printnum(test(i));
11:   return 0;
12: }

出力としてリスト5.60の様なLLVM IRが生成されると思います．

リスト5.60: printnumを含むDummyCのLLVM IR

 1: ; ModuleID = './sample/test.dc'
 2:
 3: declare i32 @printnum(i32)
 4:
 5: define i32 @test(i32 %j_arg) {
 6: entry:
 7:   %mul_tmp = mul i32 %j_arg, 10
 8:   ret i32 %mul_tmp
 9: }
10:
11: define i32 @main() {
12: entry:
13:   %call_tmp = call i32 @test(i32 10)
14:   %call_tmp1 = call i32 @printnum(i32 %call_tmp)
15:   ret i32 0
16: }

このLLVM IRとprintnumのLLVM IRをllvm-linkでリンクさせ，lliで実行します．投入するコマンドを以下に示します．

$ llvm-link test.ll printnum.ll -S -o  link_test.ll
$ lli link_test.ll
100

100という出力が得られれば成功です．ちなみに，llvm-linkで生成したLLVM IRはリスト5.61の様になっています．

リスト5.61: リンク後のLLVM IR

 1: ; ModuleID = 'test.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
 6:
 7: define i32 @test(i32 %j_arg) {
 8: entry:
 9:   %mul_tmp = mul i32 %j_arg, 10
10:   ret i32 %mul_tmp
11: }
12:
13: define i32 @main() {
14: entry:
15:   %call_tmp = call i32 @test(i32 10)
16:   %call_tmp1 = call i32 @printnum(i32 %call_tmp)
17:   ret i32 0
18: }
19:
20: define i32 @printnum(i32 %i) nounwind {
21: entry:
22:   %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %i) nounwind
23:   ret i32 %call
24: }
25:
26: declare i32 @printf(i8* nocapture, ...) nounwind

なお，下記の様にllcを使用してアセンブリに変換して実行することも可能です．

$ llc -o link_test.s link_test.ll
$ gcc -o link_test link_test.s
$ ./link_test
100

5.11 JITをやってみる

これまでのお話で，ソースコードから中間表現を生成するというフロントエンドとしての最低限の機能を実装しました．ここでは一歩進んでフロントエンドにJITコンパイル機能を導入してみたいと思います．

JITコンパイルとはJust In Timeコンパイルのことで，実行時にコンパイルを行うことを意味します．JITはフロントエンドに実装すべき機能というわけではないですが，折角なのでここでその実装方法を確認しておきたいと思います．

5.11.1 LLVMでのJIT

LLVMでのJITコンパイルにはllvm::ExecutionEngineクラスを使用します．ただし，ExecutionEngineクラスはJITのインターフェースを定義した抽象クラスなので実際にはそのサブクラスであるllvm::Interpreter，llvm::JIT，llvm::MCJITの何れかのインスタンスを生成します．llvm::Interpreterはインタプリタ，llvm::JITはJIT，llvm::MCJITはMC(MachineCode)-JITのクラスです．MC(MachineCode)の話は主にバックエンドに関係するため，詳細は7章にて説明しますが，簡単に言うとMCはLLVM内で使用する中間表現の一種でLLVM IRに比べてよりターゲットアーキテクチャに近い表現になっています．MC-JITの概要についてはLLVM 3.4のドキュメント*11に含まれており，MCについてはLLVMのBlogエントリ*12に記載がありますので，興味のある方は一度読んでみることをお勧めします．なお，MLやBlogエントリの内容からすると，MC-JITはLazyモードをサポートしていないなど，既存のJIT(エントリ内ではOld JITなどと呼ばれています)の全ての機能を提供出来ているわけではない様です．

ExecutionEngineの生成

ExecutionEngineの生成はllvm::EngineBuilderクラスのcreateメソッドを使用すると容易です．EnginBuilder::createメソッドは実行するプラットフォームで利用可能なJITがあればそれを作成し，なければインタプリタを生成します．なお，lli内でもEngineBuilderを利用してExecutionEngineを生成し，コードを実行しています．

まずはEngineBuilderのコンストラクタを以下に示します．

llvm::ExecutionEngineのコンストラクタ

llvm::EngineBuilder::EngineBuilder(Module *m)

コンストラクタの引数はModuleクラスのポインタですので，JITを利用して実行したいModuleのポインタを渡せばよいです．

次に，EnginBuilder::createメソッドの定義を以下に示します．

llvm::EngineBuilderのcreateメソッド

ExecutionEngine *llvm::EngineBuilder::create()

createメソッドは引数をとらず，自動的にJIT，もしくはインタプリタを生成してExecutionEngine型のポインタとして返却します．EngineBuilderにはTargetMachineを引数にとるcreateメソッドも存在しますが，引数がないcreateメソッドは内部で自動的にターゲットを選択して引数ありのcreateメソッドを呼びますので基本的には引数なしのメソッドを使用すればよいでしょう．なお，ExecutionEngineおよびEngineBuilderは“llvm/ExecutionEngine/ExecutionEngine.h”に宣言されています．また，Interpreter/JIT/MCJITを使用する場合は，それぞれ“llvm/ExecutionEngine/Interpreter.h”，“llvm/ExecutionEngine/JIT.h”，“llvm/ExecutionEngine/MCJIT.h”をインクルードしておく必要があります．

ちなみにデフォルトではcreateメソッドは自動的にInterpreter，JITの何れかを生成しようとしますが，createメソッド呼び出し前にEngineBuilderクラスに用意されているsetEngineKindを使用することでExecutionEngineの種類を明示的に指定できます．setEngineKindの定義を以下に示します．

llvm::EngineBuilderのsetEngineKindメソッド

EngineBuilder &setEngineKind(EngineKind::Kind w)

EngineKind::Kindはenumで，要素にはJITとInterpreterがあります．createメソッドはInterpreterが指定されていた場合はInterpreterを，JITが指定されていた場合はJITの生成を試みます．(デフォルトではEitherとなっており，JIT→Interpreterの順で生成を試みます)

さらに，EngineBuilderのsetUseMCJITを使用することでMCJITの利用を明示的に指定できます．

llvm::EngineBuilderのsetUseMCJITメソッド

EngineBuilder &setUseMCJIT(bool Value)

setUseMCJITでtrueが指定され，EngineKindにJIT(もしくはEither)が指定されていた場合，createメソッドはMCJITの生成を試みるようです．

それではDummyCコンパイラへのJIT機能追加の第一歩として，ExecutionEngineの生成処理を追加してみましょう．今回はCodeGenクラスのdoCodeGenメソッドを改良してJITを実装することにします．

リスト5.62: ExecutionEngine生成を追加したdoCodeGen

 1: /**
 2:  * 引数を追加してJITが出来るように変更
 3:  */
 4: bool CodeGen::doCodeGen(TranslationUnitAST &tunit, std::string name,
 5:                         bool with_jit = false) {
 6:   // Module生成に失敗したら終了
 7:   if (!generateTranslationUnit(tunit, name)) {
 8:     return false;
 9:   }
10:
11:   // JITのフラグが立っていたらJIT
12:   if (with_jit) {
13:     // ExecutionEngine生成
14:     ExecutionEngine *EE = EngineBuilder(Mod).create();
15:
16:     ・・・最終的にこのあたりで関数を実行・・・
17:   }
18:
19:   return true;
20: }

これまではdoCodeGen内ではgenerateTranslationUnitを実行してModuleを生成し，その返り値をそのまま返却していました．しかし，今回はJITの実装に伴い，引数とExecutionEngineの生成処理を追加しています．

まず，第3引数にbool型の変数(with_jit)を追加し，JITを行うかのフラグを指定できるようにしました．デフォルトはfalseですが，trueを指定した場合にJITの処理を実行します．ExecutionEngineの生成は非常に簡単で，先ほど紹介したEngineBuilderをModuleを引数に与えて生成し，createメソッドを呼び出すだけです．createメソッドを利用してExecutionEngineを生成してしまえば既にJITの準備はできていますので，ExecutionEngineに用意されているメソッドを利用してプログラムを実行することができます．

JITによるコード実行

ExecutionEngineには様々なメソッドが用意されていますが，今回はgetPointerToFunctionメソッドを使用することにします．ExecutionEngine::getPointerToFucntionによりJITコンパイル済みの関数へのポインタを取得できます．ExecutionEngine::getPointerToFucntionの定義は以下のとおりです．

llvm::ExecutionEngineのgetPointerToFunctionメソッド

void *JIT::getPointerToFunction(Function *F)

getPointerToFunctionの引数はFunction型のポインタです．実行したいFunctionのポインタを引数として渡すと，そのFunction(関数)を(必要があれば)コンパイルしてそのアドレスを返してくれます．帰ってきたポインタを関数ポインタに受けることで，目的のFunctionを実行できるというわけです．getPointerToFunctionメソッドを利用すれば任意の関数へのポインタを取得できるわけですが，今回はmain関数を取得して実行することにしたいと思います．

実際に使用する際のコードを見てみましょう．doCodeGenメソッドに関数ポインタ取得～実行までのコードを追加したものを以下に示します．

リスト5.63: ExecutionEngine生成を追加したdoCodeGen

 1: /**
 2:  * 引数を追加してJITが出来るように変更
 3:  */
 4: bool CodeGen::doCodeGen(TranslationUnitAST &tunit, std::string name,
 5:                         bool with_jit = false) {
 6:   // Module生成に失敗したら終了
 7:   if (!generateTranslationUnit(tunit, name)) {
 8:     return false;
 9:   }
10:
11:   // JITのフラグが立っていたらJIT
12:   if (with_jit) {
13:     llvm::ExecutionEngine *EE = llvm::EngineBuilder(Mod).create();
14:     llvm::Function *F;
15:
16:     // main関数へのポインタを取得
17:     if (!(F = Mod->getFunction("main")))
18:       return false;
19:
20:     // JIT済みmain関数のポインタを取得
21:     int (*fp)() = (int (*)())EE->getPointerToFunction(F);
22:     fprintf(stderr, "%d\n", fp());
23:   }
24:
25:   return true;
26: }

13行目でExecutionEngineを生成した後，17行目でModuleクラスのgetFunctionメソッドを利用してmain関数のFunctionを検索します．getFunctionメソッドは引数としてあたえた文字列に一致する名前のFunctionをModule内から検索して返してくれます．このメソッドによりmain関数が見つかった場合は処理を継続し，取得したFunctionへのポインタを引数としてgetPointerToFunctionを実行します．結果を関数ポインタへ受け取ることでmain関数を実行できます．今回のmain関数は引数なしで返り値はint型なので，“int (*fp)()”としています．後は通常の関数のように実行するだけですが，今回はfprintfを利用してmain関数の返り値を出力しています．

main関数の修正

doCodeGen側の対応は完了していますが，doCodeGenにJITコンパイルを指示するために呼び出し側であるmain関数にも手を加えます．実行時に“-jit”を指定するとJITコンパイルを行うようにしてみたいと思います．

リスト5.64: OptionParserクラスとmain関数の修正（JITの追加)

 1: /**
 2:  * オプション切り出し
 3:  * @return 成功時：true　失敗時：false
 4:  */
 5: bool OptionParser::parseOption() {
 6:   ・・・省略・・・
 7:
 8:   for (int i = 1; i < Argc; i++) {
 9:     // 出力ファイル名取得
10:     if (Argv[i][0] == '-' && Argv[i][1] == 'o' && Argv[i][2] == '\0') {
11:       // output filename
12:       OutputFilename.assign(Argv[++i]);
13:
14:     // ヘルプ
15:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'h' && Argv[i][2] == '\0') {
16:       printHelp();
17:       reutrn false;
18:
19:     // -jitが指定されたときはJITの有効フラグをtrueにする
20:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'j' && Argv[i][2] == 'i' &&
21:                Argv[i][3] == 't' && Argv[i][4] == '\0') {
22:       WithJit = true;
23:
24:     // 不明なオプション
25:     } else if (Argv[i][0] == '-') {
26:       fprintf(stderr, "%s は不明なオプションです\n", Argv[i]);
27:       return false;
28:
29:     // 入力ファイル名取得
30:     } else {
31:       // inputfilename
32:       InputFileName.assign(Argv[i]);
33:     }
34:   }
35:
36:   ・・・省略・・・
37:
38:   return true;
39: }
40:
41: /**
42:  * JIT実行フラグ取得
43:  */
44: bool OptionParser::getWithJit() { return WithJit; }
45:
46: /**
47:  * main関数
48:  */
49: int main(int argc, char **argv) {
50:   ・・・省略・・・
51:
52:   // コード生成実行時の引数を追加
53:   CodeGen *codegen = new CodeGen();
54:   if (!codegen->doCodeGen(tunit, opt.getInputFileName(), opt.getWithJit())) {
55:     fprintf(stderr, "err at codegen\n");
56:     SAFE_DELETE(parser);
57:     SAFE_DELETE(codegen);
58:     exit(1);
59:   }
60:
61:   ・・・省略・・・
62: }

まず，OptionParserクラスのparseOptionメソッドにJITの実行有無を取得する処理を追加しています．“-jit”が指定されたときはWithJitというbool型のメンバ変数にtrueを設定し，WithJitの値はgetWithJitメソッドで取得できるようにしました．main関数の変更点は，doCodeGen呼び出し時にこのメソッドの返り値を渡すようにしている点だけです．

動作確認

追加した機能を確認するため，次のコマンドを投入し、リスト5.65 のコードをJIT コンパイルして実行します。GitHubからソースコードを取得している場合は、事前に“llvm-3.2_all.r2”をチェックアウトし、コンパイルしておいてください。“llvm-3.2_all.r2”のソースコードはJITと後述するLLVM IRのリンクに対応したものになっています。

リスト5.65: DummyCのサンプルコード

 1: int main(){
 2:   return 0;
 3: }

下記コマンドを投入し，リスト5.65のコードをJITコンパイルして実行します．

# dccでサンプルコードをコンパイル
$ ./bin/dcc ./sample/test.dc -o ./sample/test.ll -jit
0

リスト5.65は0を返すだけの何もしないプログラムですので，出力として0が得られれば成功です．

EagerとLazyの指定

第2章にてJITにはEagerとLazyという二つのモードがあるとお話しましたが， ここでExecuttionEgineのEagerとLazyの指定方法を説明しておきます．ExecutionEngineにはDisableLazyCompilationというメソッドが用意されており，このメソッドを使用することでEagerとLazyの切り替えが可能です．DisableLazyCompilationの定義を以下に示します．

llvm::ExecutionEngine::DisableLazyCompilation

void DisableLazyCompilation(bool Disabled = true)

引数にtrueを指定するとEagerとなり，falseを指定するとLazyになります． なお，デフォルトではtrue，つまりEagerとなっています．

5.11.2 組み込み関数のリンクをプログラム中で行う

前節までの内容でJITの機能を実装し，フロントエンドの中で入力ファイルのmain関数を呼び出すことに成功しました．しかし，今の状態ではprintnumのLLVM IRがリンクされていないためprintnumを含むプログラムをJITを利用して実行することができません．そこで，今度はJITコンパイルを行う前に外部ファイルに記述されたLLVM IRを結合してみることにします．

LLVM IRの読み込み

外部ファイルのLLVM IRをプログラム中で利用するには，まずファイルからLLVM IRを読み込む必要があります．LLVM IRの読み込みにはllvm::ParseIRFile関数を使用します．llvm::ParseIRFileは指定したファイルからLLVM IRを読み込む関数です．llvm::ParseIRFileの定義を以下に示します．

llvm::ParseIRFile

Module *ParseIRFile(const std::string &Filename, SMDiagnostic &Err, LLVMContext &Context)

ParseIRFileは第1引数にファイル名，第2引数にSMDiagnostic，第3引数にLLVMContextを渡します．SMDiagnosticクラスはエラー時の診断情報を格納するクラスのようですので，エラーが起きたときにはこのクラスの各種メソッドを利用して情報を取得できます．ParseIRFileは読み込みに成功すると読み込んだModuleへのポインタを返却しますので，自分で生成したModuleと同じようにプログラム中で利用可能になります．

複数のModuleの結合

プログラム中で複数のModuleを合成するにはllvm::LinkerクラスのLinkModulesメソッドを使用します．llvm::Linker::LinkModulesの定義は以下のとおりです．

llvm::Linker::LinkModulesメソッド

static bool LinkModules(Module *Dest, Module *Src, unsigned Mode, std::string *ErrorMsg)

LinkModulesの第1引数と第2引数はModule型のポインタとなっており，第1引数のModule(Dest)で指定されたModuleに第2引数のModule(Src)を結合します．第3引数は結合時のモードです．Linker::DestroySourceとLinker::PreserveSourceが指定でき，モードによってSrcで与えるModuleの扱いが変わるようです．第4引数にはエラーメッセージを格納するstringを渡します．なお，先ほどprintnumのLLVM IRをリンクするのに使用したllvm-linkも内部でllvm::Linkder::LinkModulesを利用しています．

それではDummyCフロントエンド内でLLVM IRを結合する機能を追加してみましょう．方針としては，実行時の引数でリンクしたいファイルを指定し，doCodeGenメソッドで実際にリンクを行うことにします．また，リンク用のメソッドをCodeGenクラスに用意し，doCodeGenメソッド内ではそのメソッドを呼び出すことにします．

それではまずはリンク用のメソッドをリスト5.66に示します．メソッド名はlinkModuleにしてみました．

リスト5.66: linkModule

 1: /**
 2:  * Module結合用メソッド
 3:  */
 4: bool CodeGen::linkModule(llvm::Module *dest, std::string file_name) {
 5:   SMDiagnostic err;
 6:   // Moduleの読み込み
 7:   llvm::Module *link_mod =
 8:       llvm::ParseIRFile(file_name, err, llvm::getGlobalContext());
 9:   if (!link_mod)
10:     return false;
11:
12:   // Moduleの結合
13:   std::string err_msg;
14:   if (llvm::Linker::LinkModules(dest, link_mod, llvm::Linker::DestroySource,
15:                                 &err_msg))
16:     return false;
17:
18:   SAFE_DELETE(link_mod);
19:
20:   return true;
21: }

linkModuleは結合先のModuleと結合対象のLLVM IRが書かれたファイルのパスを引数としてとることにします．linkModuleメソッドではまず結合対象のLLVM IRファイルをllvm::ParseIRFile関数を利用して読み込みます．読み込みに失敗した場合はfalseを返却しますが，成功した場合は8行目でLinkModulesメソッドを利用してModuleの結合を行います．

次にlinkModuleメソッドの呼び出しを追加したdoCodeGenメソッドをリスト5.67に示します．

リスト5.67: ExecutionEngine生成を追加したdoCodeGen

 1: /**
 2:  * 外部Module結合機能を追加したdoCodeGenメソッド
 3:  */
 4: bool CodeGen::doCodeGen(TranslationUnitAST &tunit, std::string name,
 5:                         std::string link_file, bool with_jit = false) {
 6:   if (!generateTranslationUnit(tunit, name)) {
 7:     return false;
 8:   }
 9:
10:   // LinkFileの指定があったらModuleをリンク
11:   if (!link_file.empty() && !linkModule(link_file))
12:     return false;
13:
14:   // JITのフラグが立っていたらJIT
15:   if (with_jit) {
16:     // ExecutionEngine生成
17:     EE = EngineBuilder(Mod).create();
18:     Function *F;
19:
20:     // main関数のFunction取得
21:     if (!(F = Mod->getFunction("main")))
22:       return false;
23:
24:     //コンパイル済みmain関数を取得して実行
25:     int (*fp)() = (int (*)())EE->getPointerToFunction(F);
26:     fprintf(stderr, "%d\n", fp());
27:   }
28:
29:   return true;
30: }

Moduleの結合を行うため，JIT実装後のdoCodeGenメソッドにさらに引数を追加し，結合対象のLLVM IRのファイル名を渡せるようにしています．8行目でファイル名が空でなければlinkModuleメソッドを呼び出してModuleの結合を試みます．結果が失敗だったときはfalseを返却して処理を終了しますが，成功した場合はさらにJITの処理に進むようにしています．

main関数の修正

最後にOptionParserクラスとmain関数に修正を加えてリンクするファイルを実行時に指定できるように変更しましょう．実行時に引数として“-l”が指定された場合は，その次の単語を結合するLLVM IRのファイル名として認識することにします．修正後のソースコードをリスト5.68に示します．

リスト5.68: OptionParserクラスとmain関数の修正(リンク機能の追加)

 1: /**
 2:  * オプション切り出し
 3:  * @return 成功時：true　失敗時：false
 4:  */
 5: bool OptionParser::parseOption() {
 6:   ・・・省略・・・
 7:
 8:   for (int i = 1; i < Argc; i++) {
 9:     // 出力ファイル名取得
10:     if (Argv[i][0] == '-' && Argv[i][1] == 'o' && Argv[i][2] == '\0') {
11:       // output filename
12:       OutputFilename.assign(Argv[++i]);
13:
14:     //ヘルプ
15:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'h' && Argv[i][2] == '\0') {
16:       printHelp();
17:       reutrn false;
18:
19:     // -jitが指定されたときはJITの有効フラグをtrueにする
20:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'j' && Argv[i][2] == 'i' &&
21:                Argv[i][3] == 't' && Argv[i][4] == '\0') {
22:       WithJit = true;
23:
24:     // -lオプションが指定されたときは次のワードをリンクするファイル名として取得
25:     } else if (Argv[i][0] == '-' && Argv[i][1] == 'l' && Argv[i][2] == '\0') {
26:       LinkFileName.assign(Argv[++i]);
27:
28:     // 不明なオプション
29:     } else if (Argv[i][0] == '-') {
30:       fprintf(stderr, "%s は不明なオプションです\n", Argv[i]);
31:       return false;
32:
33:     // 入力ファイル名
34:     } else {
35:       // inputfilename
36:       InputFileName.assign(Argv[i]);
37:     }
38:   }
39:
40:   ・・・省略・・・
41:
42:   return true;
43: }
44:
45: /**
46:  *リンクするファイルを取得
47:  */
48: std::string OptionParser::getLinkFileName() { return LinkFileName; }
49:
50: /**
51:  * main関数
52:  */
53: int main(int argc, char **argv) {
54:   ・・・省略・・・
55:
56:   // コード生成実行時の引数を追加
57:   CodeGen *codegen = new CodeGen();
58:   if (!codegen->doCodeGen(tunit, opt.getInputFileName(), opt.getLinkFileName(),
59:                           opt.getWithJit())) {
60:     fprintf(stderr, "err at codegen\n");
61:     SAFE_DELETE(parser);
62:     SAFE_DELETE(codegen);
63:     exit(1);
64:   }
65:
66:   ・・・省略・・・
67: }

まず，OptionParserクラスのparseOptionメソッドに結合対象のファイル名を取得する処理を追加しています．“-l”が指定されたときは次の単語をLinkFileNameに代入しています．LinkFileNameはstd::string型のメンバ変数で，OptionParser::getLinkFileNmaeメソッドで外部から取得できるようにしています．main関数で変更する箇所はdoCodeGenの引数のみです．第3引数に上記のgetLinkFileNameメソッドの返り値を与えています．これでdoCodeGenメソッドに必要な情報が渡り，LLVM IRの結合をしてくれるはずです．

動作確認

printnumのリンクに成功していることを確認するため，リスト5.69のサンプルコードを使用して動作確認を行います．

リスト5.69: DummyCのサンプルコード

 1: int test(int j){
 2:   printnum(i);
 3:   return 0;
 4: }
 5:
 6: int main() {
 7:   int i;
 8:   i = 10;
 9:   test(i);
10:   return 0;
11: }

下記コマンドを投入し，JITコンパイルを行います．

# dccにprintnumのLLVM IRを結合させてJITの処理をさせる
# -l ./lib/printnum.llでリンクするLLVM IRのパスを指定
$ ./bin/dcc -o ./sample/test.ll -l ./lib/printnum.ll ./sample/test.dc -jit
10
0

今回はtest関数内でprintnumによる出力がありますので，10と0が出力されればリンクとJITコンパイルに成功しています．

5.12 Metadataを埋め込みたい場合

DummyCには特にMetadataを埋め込む予定はありませんが，ここでMetadataの作成と命令への付与方法を紹介しておきます．

5.12.1 Metadataの生成

第4章にてMetadataにはMetadataStringとMetadataNodeがあると説明しましたが，LLVMのAPIではllvm::MDStringとllvm::MDNodeがそれに相当します．これらのMetadataのクラスの生成はllvm::MDBuilderクラスを利用すると簡単です．

llvm::MDBuilderクラスは“llvm/MDBuilder.h”に定義されているクラスで，fpmath Metadataやrange Metadataなど様々なMetadataを生成するメソッドを提供します．まずはMDBuilderクラスのコンストラクタを以下に示します．

llvm::MDBuilderのコンストラクタ

MDBuilder(LLVMContext &context)

MDBuilderクラスのコンストラクタはLLVMContextへの参照を渡すだけの非常に簡単なものです．いつも通りllvm::getGlobalContextを利用すればよいでしょう．

次に，MDBuilderで提供されているMetadata生成用のメソッドを紹介します．LLVM 3.2のMDBuilderクラスでは下記のメソッドが利用可能となっています．

LLVM 3.2のllvm::MDBuilderのMetadata生成メソッド

MDString *createString(StringRef Str)
MDNode *createFPMath(float Accuracy)
MDNode *createRange(const APInt &Lo, const APInt &Hi)
MDNode *createAnonymousTBAARoot()
MDNode *createTBAARoot(StringRef Name)
MDNode *createTBAANode(StringRef Name, MDNode *Parent, bool isConstant = false)
MDNode *createTBAAStructNode(ArrayRef<TBAAStructField> Fields)
MDNode *createBranchWeights(uint32_t TrueWeight, uint32_t FalseWeight)
MDNode *createBranchWeights(ArrayRef<uint32_t> Weights)

createXXという形式ですので，おおよそ何を生成するものかはメソッド名から推測可能だとは思いますが，簡単に概要を記載しておきます．上記のメソッドは上から順に，MetadataString，fpmath Metadata，range Metadata，tbaa MetadataのRootとtbaa MetadataのNode，tbaa.struct Metadata，そしてBranchWeight Metadataの生成用メソッドとなっています(TBAAは本書では説明していませんが，とりあえず紹介程度に記載しています)．なお，Branch Weight Metadata は分岐命令に付与される Metadata で,分岐先の重み付けに使用する Metadata の様です．また，createRangeの引数となっているAPIntというクラスは任意の精度の整数定数を表現するクラスです．

MDBuilderを使用する際のサンプルコード

ここで，MDBuilderクラスのメソッドを使用してMetadataを生成する際の簡単なコードを紹介します．まずは例としてrange Metadataの生成を行います．

リスト5.70: range Metadata生成のサンプルコード

 1: //MDBuilderの生成
 2: MDBuilder *MDB = new llvm::MDBuilder(llvm::getGlobalContext());
 3:
 4: //0～100をあらわすrange Metadataの生成
 5: llvm::MDNode *mdn = MDB->createRange(llvm::APInt(32,0),llvm::APInt(32,100));

まず，2行目でLLVMContextを渡してMDBuilderクラスを生成しています．そして5行目でcreateRangeメソッドを使用してrange Metadataを生成しています．なお，createRangeメソッドの引数となっているAPIntクラスのコンストラクタは下記のようになっています．

llvm::APIntのコンストラクタ

APInt(unsigned numBits, uint64_t val, bool isSigned = false)

第一引数で精度を指定し，第二引数で整数の値を指定します．また第三引数は符号あり/なしを指定する真偽値を指定しますが，デフォルトはfalseとなっています．今回の例では第一引数は32を指定して32bitの整数とし，第二引数には0と100をそれぞれ指定することで32bitの0および100をあらわす整数定数を生成しています．非常に簡単なコードですが，Metadataを生成するだけであれば上記のコードだけで完了です．

次にfpmath Metadata生成のコードをリスト5.71に示します．

リスト5.71: fpmath Metadata生成のサンプルコード

 1: //MDBuilderの生成
 2: MDBuilder *MDB = new llvm::MDBuilder(llvm::getGlobalContext());
 3:
 4: //0.5 ULPのfpmath Metadataの生成
 5: llvm::MDNode *mdn = MDB->createFPMath(0.5);

fpmathの生成ですのでcreateFPMathを使用するのですが，createFPMathの引数はULPの値を示す単精度浮動小数点数のみです．上記の例では0.5 ULPのfpmath Metadataを生成しています．なお，生成したMetadataは別のAPIを利用して命令へと付与するのですが，その詳細は次節以降に記述しています．

5.12.2 setMetadataによるMetadataの付与

生成したMetadataを命令に付与する方法はいくつかありますが，llvm::InstructionクラスのsetMetadataメソッドを利用するのが最も基本的な方法です．LLVMの各種命令はllvm::Instructionクラスのサブクラスですので，任意のInstructionのインスタンスのsetMetadataを呼び出すことでMetadataを付与できます．setMetadataメソッドの定義は以下のとおりです．

llvm::Instruction::setMetadata

void setMetadata(unsigned KindID, MDNode *Node)
void setMetadata(StringRef Kind, MDNode *Node)

llvm::Instructionには第1引数の型が異なる二つのsetMetadataがありますが，まず第1引数がunsignedのメソッドから説明します．このメソッドの第1引数のKindIDにはMetadataの種類を示す数値を指定し，第2引数には付与するMDNodeのポインタを渡します．第1引数のKindIDはLLVMContextクラスのgetMDKindメソッドで取得できます．

llvm::LLVMContext::getMDKind

unsigned LLVMContext::getMDKindID(StringRef Name)

getMDKindメソッドは引数としてMetadataの種類をあらわす文字列を受け取り，該当の種類のMetadataを示すIDを返却します．getMDKindで指定できる文字列は，dbg，tbaa，tbaa.struct，prof，fpmath，rangeの5種類となっています*14．次に第1引数がStringRef型の二つ目のsetMetadataですが，実はこのメソッドは内部でKindに与えられた文字列でgetMDKindメソッドを使用し，一つ目のsetMetadataを呼び出しているだけです．ですので，特に問題がなければこちらを利用するほうが簡単かもしれません．

setMetadataサンプルコード

setMetadataを利用して命令にMetadataを付与する際のサンプルコードをリスト5.72に示します．

リスト5.72: Metadata生成のサンプルコード

 1: //MDBuilderの生成
 2: MDBuilder *MDB = new llvm::MDBuilder(llvm::getGlobalContext());
 3:
 4: //range Metadataの生成
 5: llvm::MDNode *mdn = MDB->createRange(llvm::APInt(32,0),llvm::APInt(32,100));
 6:
 7: //load_valをロードするload命令を生成
 8: llvm::Instruction *inst = Builder->CreateLoad(load_val, "sample_var");
 9:
10: //load命令へMetadataを付与
11: inst->setMetadata(llvm::getGlobalContext().getMDKindID("range"), mdn);

5行目までは前節で紹介したコードと同様ですが，8行目でCreateLoad命令を使用してload_valという変数名で示されるValueをロードするload命令を生成し，11行目で生成したload命令のsetMetadataを呼び出しています．第一引数でLLVMContextクラスのgetMDKindIDメソッドを利用しrange Metadataであることを指定し，第二引数で生成済みのMetadataクラスのインスタンスを渡しています．このコードを実行した場合，リスト5.73のようなLLVM IRが生成されます．

リスト5.73: 生成されるMetadata付きのLLVM IR

 1: ・・・省略・・・
 2: %sample_var = load i32* %i, !range !0
 3: ・・・省略・・・
 4: !0 = metadata !{i32 0, i32 100}

5.12.3 IRBuilderのメソッドによるMetadataの付与

前節ではInstructionクラスのsetMetadataメソッドによるMetadataの付与方法を紹介しましたが，fpmath MetadataについてはIRBuilderのCreateFXX系のメソッドでMetadataを設定することができます．以下にfpmath Metadataを設定可能なIRBuilderクラスのメソッドを記載します．

fpmath Metadataを付与可能なllvm::IRBuilderクラスのメソッド

Value *CreateFAdd(Value *LHS, Value *RHS, const Twine &Name = "", MDNode *FPMathTag = 0)
Value *CreateFSub(Value *LHS, Value *RHS, const Twine &Name = "", MDNode *FPMathTag = 0)
Value *CreateFMul(Value *LHS, Value *RHS, const Twine &Name = "", MDNode *FPMathTag = 0)
Value *CreateFDiv(Value *LHS, Value *RHS, const Twine &Name = "", MDNode *FPMathTag = 0)
Value *CreateFRem(Value *LHS, Value *RHS, const Twine &Name = "", MDNode *FPMathTag = 0)
Value *CreateFNeg(Value *V, const Twine &Name= "" , MDNode *FPMathTag = 0)

fpmath Metadataを付与するため上記はすべて浮動小数点演算に関する命令の生成メソッドで，上から順番に浮動小数点の加算，減算，乗算，除算，剰余，符号反転を行う命令を作成します．CreateFAddからCreateFremまではそれぞれfadd，fsub，fmul，fdiv，frem命令が生成されますが，FNegに関しては実際にはfsubによって符号逆転を行う命令となります．

なお，LLVM 3.2ではBranchWeights Metadataを生成するために，下記のメソッドもIRBuilderに用意されています．

BranchWeights Metadataを付与可能なllvm::IRBuilderクラスのメソッド

BranchInst *CreateCondBr(Value *Cond, BasicBlock *True, BasicBlock *False,
                         MDNode *BranchWeights = 0)
SwitchInst *CreateSwitch(Value *V, BasicBlock *Dest, unsigned NumCases = 10,
                         MDNode *BranchWeights = 0)

createFXXを使用する際のサンプルコード

それでは今度はIRBuilderクラスのcreateFXX系メソッドを利用してMetadataを付与する際のサンプルコードを記載します．今回はIRBuilderクラスのcreateFSubメソッドを利用してfpmath Metadataを付与したfsub命令を生成します．

リスト5.74: createFXXメソッドを使用する際のサンプルコード

 1: // MDBuilderの生成
 2: MDBuilder *MDB = new llvm::MDBuilder(llvm::getGlobalContext());
 3:
 4: // 0.5 ULPのfpmath Metadataの生成
 5: llvm::MDNode *mdn = MDB->createFPMath(0.5);
 6:
 7: // float型のValueをロードするload命令を生成(BuilderはIRBuilderクラスのインスタンス)
 8: llvm::Value *arg_v = Builder->CreateLoad(alloca, "float_tmp");
 9:
10: // Metadataを付与するfsub命令を生成(BuilderはIRBuilderクラスのインスタンス)
11: llvm::Value *tmp = Builder->CreateFSub(
12:     arg_v, llvm::ConstantFP::get(
13:                llvm::Type::getFloatTy(llvm::getGlobalContext()), 1.0),
14:     "fpmath_sample", mdn);

今回も5行目までは前節のコードと同様ですが，8行目から処理が異なります．8行目ではCreateLoadメソッドでfloat型のValueをロードするload命令を生成しています．11行目ではIRBuilder::CreateFSubメソッドを利用して8行目でloadした値から1.0を引くためのfsub命令を生成しています．

最後に，リスト5.74のコードを実行した際に得られるLLVM IRをリスト5.75に示します．

リスト5.75: 生成されるfsub命令

 1: %float = alloca float
 2: %float_tmp = load float* %float
 3: %fpmath_sample = fsub float %float_tmp, 1.000000e+00, !fpmath !0
 4: ・・・省略・・・
 5: !0 = metadata !{float 5.000000e-01}

5.13 CommandLineライブラリ

これまで，DummyCフロントエンドのオプションは自前でOptionParserクラスを生成，実装してきたわけですが，実はLLVMにはコマンドラインオプションを取得するためのCommandLineライブラリという便利なライブラリが存在します．CommandLineライブラリの各種APIは“llvm/Support/CommandLine.h”に定義されており，ここで用意されているAPIを使用することで任意のオプションを非常に簡単にプログラムに追加することができます．

5.13.1 基本的な使い方

まずはCommandLineライブラリを使用したコマンドラインオプション実装の最も基本的な使い方を説明します．CommandLineライブラリでオプションを実装する場合，まずllvm::cl::optテンプレートやllvm::cl::listを利用してプログラムがどの様なオプションを受け付けるのかを宣言する必要があります．本節ではllvm::cl::opt，llvm::cl::listについて説明を行いますが，その使用方法は多岐に渡るため，実際のコード例とその実行結果を軸に紹介していきたいと思います．

出力ファイルオプション

最初に最も基本的な例として，“-o ファイル名”として出力ファイルを指定するオプションをllvm::cl::optで実装する方法を紹介したいと思います．リスト5.76に実装例を示します．

リスト5.76: llvm::cl::optの使用例

 1: #include "llvm/Support/CommandLine.h"
 2: #include <cstdio>
 3:
 4: namespace {
 5:   llvm::cl::opt<std::string> OutputFile(
 6:     "o",
 7:     llvm::cl::desc("出力ファイルを指定します"),
 8:     llvm::cl::value_desc("filename"),
 9:     llvm::cl::init("-")
10:   );
11: }
12:
13: /**
14:  * main関数
15:  */
16: int main(int argc, char **argv) {
17:   // 引数のパース
18:   llvm::cl::ParseCommandLineOptions(argc, argv, "CommandLine Sample\n");
19:
20:   // OutputFileの取得
21:   fprintf(stderr, "output file : %s\n", OutputFile.c_str());
22:
23:   return 0;
24: }

リスト5.76において，OutputFileはcl::optテンプレートにstd::stringを渡しています．これによりOutputFileオプションでパースするデータがstd::string型であることをライブラリに伝えています．ここにはstd::string以外も指定可能で，例えば何らかのフラグの有効無効を指定するようなオプションであれば，ここにbool型を指定することで真偽値として値を受け取ることができます．

次にOutputFileの宣言に記述する引数を確認します．まず第一引数に“o”を指定していますが，これはOuputFileオプションは“-o”で指定することを意味しています．指定する文字列を変更すれば異なる文字列をオプション名に使用することができます．

次に，第二引数としてllvm::cl::desc構造体を渡しています．これはoptで宣言するオプションに指定する属性の一つで，helpを表示した際に出力されるオプションの説明を定義しています．上記の例ではOutputFileは出力ファイルを指定するオプションですのでその旨を記載しています．

第三引数はllvm::cl::value_descとなっています．llvm::cl::value_desc構造体もオプションに指定する属性で，help表示においてこのオプションにどのような値を入力するかを示すのに利用します．今回は“-o”オプションではファイル名を指定するため，“filename”としています．後ほど実行結果を記載しますので，そこでどのような出力になるかを確認するとvalue_descの意味がわかりやすいと思います．最後に第四引数はllvm::cl::init構造体です．これもここまで同様オプションに指定する属性で，オプションの初期値を指定します．仮に実行時にこのオプションの値が指定されなかったときはここで指定した値が使用されることになります．リスト5.76のOutputFileでは“-”が初期値になります．

なお，引数の中で必須なのは第一引数で指定したオプションで使用する文字だけです．第二引数以降の指定は任意ですので，指定がなくても動作します．また，引数の順番についても特に制限はないため，記述する順番を入れ替えても同じように動作します．

llvm::cl::optの宣言によりオプションの定義はしましたが，実際にオプションとして値を受け取るためにはコマンドライン入力をパースしてやる必要があります．コマンドライン入力をパースするにはllvm::cl::ParseCommandLineOptions関数を使用します．llvm::cl::ParseCommandLineOptionsのインターフェースを以下に示します．

llvm::cl::ParseCommandLineOptions

void ParseCommandLineOptions(int argc, const char * const *argv, const char *Overview = 0);

引数名を見ればわかると思いますが，llvm::cl::ParseCommandLineOptionsにはmain関数で受け取るargcとargvを渡します．第三引数にはhelpを表示した際に出力される概要を指定できますが，デフォルト引数として0が設定されています．llvm::cl::ParseCommandLineOptionsを使用すると，パースして得られた値がllvm::cl::optで宣言しておいた各オプションに代入されます．

llvm::cl::ParseCommandLineOptionsで取得した各オプションの値にアクセスする方法は非常に簡単で，llvm::cl::optテンプレートに渡した型の通常の変数と同じようにllvm::cl::optの変数にアクセスするだけです．今回はllvm::cl::optにstd::stringを指定しておいたので，一般的なC++のstringと同じようにOutputFileを使用することができます．実際にリスト5.76の21行目でOutputFileに対してc_strメソッドを呼び，値を出力しています．

実行結果を確認するため，このコードをcltest.cppという名前で保存してコンパイル・実行してみます．

# cltest.cppという名前のソースコードだったとした場合のコンパイル
$ g++ -g cltest.cpp `llvm-config --cxxflags --ldflags --libs` -ldl -pthread \
-o cltest
#
# -oオプションの確認
$ ./cltest -o test
output file : test
#
# helpを表示
$ ./cltest -help
OVERVIEW: CommandLine Sample

USAGE: cltest [options]

OPTIONS:
  -help         - Display available options (-help-hidden for more)
  -o=<filename> - 出力ファイルを指定します
  -version      - Display the version of this program

-oオプションで指定した値が正しく得られていることが確認できます．また，-helpオプションを指定して実行すると-oオプションのヘルプが表示され，OVERVIEWにはParseCommandLineOptionsの第三引数の値を出力されています．なお，helpを出力した際にあらわれるhelp，versionというオプションはライブラリ側で用意されているもので，ユーザプログラム側で特に指定しない場合もオプションとして用意されます．

入力ファイルオプション

出力ファイルのオプションができたので，今度は入力ファイルを受け取ることを考えてみましょう．入力ファイルも出力ファイルと同様入力から文字列を受け取るので基本的にOutputFileと変わりません．しかし入力ファイルは“-o”の様に明示的なオプションを指定するわけではありません．そんな時はllvm::cl::Positionalを使用します．実際の例をリスト5.77に示します．

リスト5.77: llvm::cl::optの使用例(入力ファイルを受け取る場合)

 1: #include "llvm/Support/CommandLine.h"
 2: #include <cstdio>
 3: using namespace llvm;
 4:
 5: namespace {
 6: llvm::cl::opt<std::string> InputFile(llvm::cl::Positional,
 7:                                      llvm::cl::desc("<input file>"),
 8:                                      llvm::cl::init("-"));
 9: }
10:
11: /**
12:  * main関数
13:  */
14: int main(int argc, char **argv) {
15:   // 引数のパース
16:   llvm::cl::ParseCommandLineOptions(argc, argv, "CommandLine Sample\n");
17:
18:   // InputFileの取得
19:   fprintf(stderr, "input file : %s\n", InputFile.c_str());
20:
21:   return 0;
22: }

InputFileの宣言時に第一引数としてllvm::cl::Positionalを指定していることに注目してください．llvm::cl::Positionalは該当オプションが“-o”の様なオプション名をとらず，単独で出現する場合に指定します．次ページに実行結果を記載しますので，llvm::cl::Positionalがどの様に扱われるかを確認しましょう．

# cltest.cppという名前のソースコードだったとした場合のコンパイル
$ g++ -g cltest.cpp `llvm-config --cxxflags --ldflags --libs` -ldl -pthread \
-o cltest
#
# -oオプションの確認
$ ./cltest test
input file : test
#
# helpを表示
$ ./cltest -help
OVERVIEW: CommandLine Sample

USAGE: cltest [options] <input file>

OPTIONS:
  -help    - Display available options (-help-hidden for more)
  -version - Display the version of this program

OutputFileではhelpを表示した際にOPTIONSの欄に概要が出力されていましたが，InputFileではllvm::cl::Positionalを使用したことでUSAGEに記述が追加されています．USAGEは使用方法を説明する項目で，ここではcltestを使用する際の構文はOPTIONS欄に記載されたオプションを指定し，最後にinput fileを指定することを表しています．ちなみに，USAGEに現れる<input file>はllvm::cl::descで指定した文字列です．

なお，コンパイラにおいて入力ファイルは実行に必要不可欠です．そんな時はllvm::cl::initで初期値を指定する代わりにllvm::cl::Requiredを指定することができます．llvm::cl::Requiredを指定するとそのオプションは必須項目として扱われ，必ず一度だけ指定されるオプションとしてライブラリに認識されます．llvm::cl::Requiredを指定したオプションにパラメータが与えられなかった場合はParseCommandLineOption実行時に自動的にエラー終了してくれます．

なお，CommandLineライブラリではllvm::cl::Required以外にもオプションの出現回数がenumで定義されています．指定可能な出現回数のenum値一覧を表5.10に示します．

表5.10: 指定可能なオプション出現回数

名前 | 概要
llvm::cl::Optional | 0回もしくは1回指定される．llvm::cl::optのデフォルト
llvm::cl::ZeroOrMore | 0回以上指定される．後述するllvm::cl::listのデフォルト
llvm::cl::Required | 必ず1度だけ指定される．
llvm::cl::OneOrMore | 1回以上指定される．
llvm::cl::ConsumeAfter | 最後に出現したcl::Positionalなオプションより後ろに出現する．

真偽値でフラグを管理

ここまでで文字列を受け取るオプションについて説明をしましたが，DummyCフロントエンドのJIT有効/無効のフラグの様にオプションで真偽値の管理を行いたい場合があります．

オプションで真偽値を管理するのは文字列を扱うよりも非常に単純に実装できます．例としてJITの有効/無効を指定するオプションの実装例をリスト5.78に示します．

リスト5.78: llvm::cl::optの使用例(真偽値を管理する場合)

 1: #include "llvm/Support/CommandLine.h"
 2: #include <cstdio>
 3:
 4: namespace {
 5: // JIT有効無効を示すオプション
 6: llvm::cl::opt<bool> WithJit("jit",
 7:                             llvm::cl::desc("JIT コンパイルを有効にします"),
 8:                             llvm::cl::init("false"));
 9: }
10:
11: /**
12:  * main関数
13:  */
14: int main(int argc, char **argv) {
15:   // 引数のパース
16:   llvm::cl::ParseCommandLineOptions(argc, argv, "CommandLine Sample\n");
17:
18:   // JITフラグの取得
19:   if (WithJit)
20:     fprintf(stderr, "enabled jit \n");
21:
22:   return 0;
23: }

真偽値を管理する場合はllvm::cl::optテンプレートにbool型を指定します．また，オプションで指定されなかった時のためにllvm::cl::initで初期値を決めておいた方が良いでしょう．あとはこれまでと同様で，オプション名やhelpで表示する説明などを属性として指定するだけです．真偽値へのアクセス方法もこれまで同様で，通常のbool型変数と同じように使用できます．今回はオプション名を“jit”としているのため，“-jit”が指定されているとWithJitの値がtrueとなります．

念のため，実行結果を確認しておきましょう．

# cltest.cppという名前のソースコードだったとした場合のコンパイル
$ g++ -g cltest.cpp `llvm-config --cxxflags --ldflags --libs` -ldl -pthread \
-o cltest
#
# -jitオプションの確認
$ ./cltest -jit
enabled jit
#
# helpの表示
$ ./cltest -help
OVERVIEW: CommandLine Sample

USAGE: cltest [options]

OPTIONS:
  -help    - Display available options (-help-hidden for more)
  -jit     - JITコンパイルを有効にします
  -version - Display the version of this program

OPTIONSに“-jit”オプションが追加されていることが確認できます．また，“-jit”を指定した場合にはWithJitがtrueとなっている様です．

5.13.2 複数のパラメータからの選択

最適化レベルを指定する際など，複数の値からどれか一つを選択してもらう様なオプションをとりたい場合があります．そんな時はllvm::cl::valuesとclEnumVal，clEnumValNマクロを使用します．

複数のパラメータからオプションを選択する場合の例として，最適化レベルオプションの実装例をリスト5.79に示します．

リスト5.79: llvm::cl::optの使用例(複数パラメータから選択する場合)

 1: #include "llvm/Support/CommandLine.h"
 2: #include <cstdio>
 3:
 4: namespace {
 5:
 6: //最適化レベルを示すenum
 7: enum OptLevel {
 8:   NONE,
 9:   O1,
10:   O2,
11:   O3
12: };
13:
14: //オプション定義
15: llvm::cl::opt<OptLevel>
16: OLevel(llvm::cl::values(clEnumValN(NONE, "O0", "最適化しません"),
17:                         clEnumVal(O1, "基本的な最適化"),
18:                         clEnumVal(O2, "積極的な最適化"),
19:                         clEnumVal(O3, "さらに積極的な最適化"), clEnumValEnd),
20:        llvm::cl::desc("最適化レベルの選択"));
21: }
22:
23: /**
24:  * main関数
25:  */
26: int main(int argc, char **argv) {
27:   // 引数のパース
28:   llvm::cl::ParseCommandLineOptions(argc, argv, "CommandLine Sample\n");
29:
30:   // 最適化レベルの取得
31:   if (OLevel == O0)
32:     fprintf(stderr, "disable Optimization\n");
33:
34:   return 0;
35: }

複数のパラメータから一つを選択するようなオプションを実装する場合，まずその選択肢を示すenumを定義しておく必要があります．上記の例ではOptLevelというenumで最適化レベルの種類を定義しています．ここで定義したenumをllvm::cl::optテンプレートで指定することで値の選択が可能になります．

さらにllvm::cl::optに渡す引数もこれまでとは異なり，llvm::cl::valuesを使用します．llvm::cl::valuesは入力文字列を値に結びつけるためのテンプレートです．llvm::cl::valuesの引数はclEnumValマクロ，もしくはclEnumValNマクロで記述します．clEnumValマクロは第一引数にenumを指定し，第二引数にそのオプションの説明をあらわす文字列を渡します．clEnumValでは第一引数で指定したenumの名前がオプション名として使用されます．clEnumValNオプションもclEnumValとよく似たマクロですが，こちらはenumの名前とオプション名が異なる際に使用します．clEnumValNは第一引数にenumをとり，第二引数にオプション名を指定します．第三引数は該当オプションの説明となります．なお，llvm::cl::valuesの引数の最後には必ずclEnumValEndを指定しなければならないことに注意してください．

それでは実行結果の確認をしておきましょう．

# cltest.cppという名前のソースコードだったとした場合のコンパイル
$ g++ -g cltest.cpp `llvm-config --cxxflags --ldflags --libs` -ldl -pthread \
-o cltest
#
# 最適化オプションの確認
$ ./cltest -O0
disable Optimization
#
# helpの表示
$ ./cltest -help
OVERVIEW: CommandLine Sample

USAGE: cltest [options]

OPTIONS:
  最適化レベルの選択
    -O0    - 最適化しません
    -O1    - 基本的な最適化
    -O2    - 積極的な最適化
    -O3    - さらに積極的な最適化
  -help    - Display available options (-help-hidden for more)
  -version - Display the version of this program

llvm::cl::valuesを使用した場合，llvm::cl::optに渡したllvm::cl::descの内容がトップレベルに表示され，その下にllvm::cl::valuesに渡した各オプション名とその説明が出力されます．今回の例では各最適化オプションのオプション名と説明が記載されていることがわかると思います．

5.13.3 一つのオプションで複数の値を受ける

DummyCにおいては入力ファイルは一つのみとしていますが，場合によっては複数のファイルを受け取りたい場合があるでしょう．そのような，一つのオプションに複数の値を受け取りたい場合のCommandLineライブラリの使用方法を紹介します．

一つのオプションに複数の値を受け取るときはllvm::cl::listを使用します．例として入力ファイルを複数受け取る場合の実装例をリスト5.80に示します．

リスト5.80: llvm::cl::optの使用例(複数パラメータから選択する場合)

 1: #include "llvm/Support/CommandLine.h"
 2: #include <cstdio>
 3:
 4: namespace {
 5: //入力ファイルのオプション定義(複数バージョン)
 6: llvm::cl::list<std::string> InputFiles(llvm::cl::Positional,
 7:                                        llvm::cl::desc("<input files>"),
 8:                                        llvm::cl::OneOrMore);
 9: }
10:
11: /**
12:  * main関数
13:  */
14: int main(int argc, char **argv) {
15:   // 引数のパース
16:   llvm::cl::ParseCommandLineOptions(argc, argv, "CommandLine Sample\n");
17:
18:   // 入力ファイル名の取得
19:   for (int i = 0; i < InputFiles.size(); i++)
20:     fprintf(stderr, "input file : %s\n", InputFiles[i].c_str());
21:
22:   return 0;
23: }

llvm::cl::listの使い方は基本的にllvm::cl::optと同様です．異なる点は，llvm::cl::OneOrMoreを使用しているところでしょうか．llvm::cl::OneOrMoreはこのオプションに必ず一つ以上のパラメータが渡されることを意味しています．llvm::cl:listは複数の値を扱うため，llvm::cl::Requiredの代わりにllvm::cl::OneOrMoreを使用しています．

オプションの定義はllvm::cl::optと似ていますが，入力をパースして得られる結果が少し違います．llvm::cl::listはその名の通り複数のパラメータを受け取るため，パースした結果にも複数の値が格納されています．例えば今回の例ではInputFilesには複数の値が格納されているため，“vector<std::string>”のように振舞います．実際に上記のコード例ではInputFilesのサイズを確認し，i番目に格納された値を取得する処理を行っています．

それでは今回も実行結果を確認しておきましょう．

# cltest.cppという名前のソースコードだったとした場合のコンパイル
$ g++ -g cltest.cpp `llvm-config --cxxflags --ldflags --libs` -ldl -pthread \
-o cltest
#
# 入力ファイルオプションの確認
$ ./cltest foo bar
input file : foo
input file : bar
#
# helpの表示
$ ./cltest -help
OVERVIEW: CommandLine Sample

USAGE: cltest [options] <input files>

OPTIONS:
  -help    - Display available options (-help-hidden for more)
  -version - Display the version of this program

helpの出力内容には変化がありませんが，複数の入力ファイルが認識されていることが確認できます．

5.13.4 オプションに指定可能な属性のまとめ

本節では実例をもとにCommandLineライブラリを使用したオプションの実装方法を紹介しました．最後に，CommandLineライブラリのオプションに指定できる属性を表5.11にまとめておきます．本書では紹介していないものもありますが，詳細についてはLLVMの公式ドキュメント，CommandLine 2.0 Library Manual*15をご覧下さい．

表5.11: cl::optに指定可能な属性

属性
概要

llvm::cl::desc
該当オプションにhelpにて出力する説明を付与するために使用する．

llvm::cl::value_desc
該当オプションにhelpにて出力するオプションで指定する値の説明を付与するために使用する．

llvm::cl::init
該当オプションの初期値をあらわす．

llvm::cl::location
入力をパースして得た該当オプションのパラメータを保存する変数を指定するために使用する．

llmv::cl::aliasopt
他のオプションへのエイリアスを設定するために使用する．

llvm::cl::values

複数のパラメータをオプションに受け取る際に使用する．
clEnumValマクロとclEnumValNマクロでオプションを設定する．

llvm::cl::multi_val

一つのオプションに複数の値をとりたい時(-オプション名 値1 値2 値3 の様な形)に使用する．
llvm::cl::listでのみ使用可能．

[*1] とは言えコード生成の前提となる話を意味解析まででやっているので簡単に目を通した方が良いかもしれません

[*2] http://llvm.org/docs/tutorial/index.html

[*3] EBNFの規則守れてなかったらごめんなさい！

[*4] LLVM Tutorialではdynamic_castを使っていたりしますが

[*5] そもそも構文解析の時点でそういう前提で話を進めていましたが

[*6] LLVM 3.1ではllvm/Support/IRBuilder.hになっています

[*7] 他にもInstruction単位で命令の挿入位置を設定するSetInsertPointもあります

[*8] 少々曖昧な表現をしているのは筆者も全てのInstructionの定義を覚えているわけではないからです

[*9] 一応今後の拡張で分岐命令なども考えてはいるのですが

[*10] このあたりはおまじないと思ってもよいでしょう

[*11] http://llvm.org/releases/3.4/docs/MCJITDesignAndImplementation.html

[*12] http://blog.llvm.org/search/label/MC

[*13] 正確には0と1

[*14] BranchWeights Metadataはprofに分類されるようです

[*15] http://llvm.org/docs/CommandLine.html

第6章 Passの概要と実装方法

6.1 Passの概念

ここでLLVMにおけるPassの概念を説明しておきます．LLVM IRはPassによって解析，最適化処理を施されます．要はPassはLLVM IRに何らかの操作をする際に必要になるものと思えば良いと思います．ここで改めてLLVMの構成図を載せておきます．

図6.1: LLVMの構成

LLVMではユーザが任意のPassを適用することができます．PassはPassManagerによって管理され，PassManagerは登録されたPassをスケジューリングしてLLVM IRに適用します．

6.2 Passの種類

一言でPassと言っても，Passには様々な種類があります．本節ではLLVMに用意されているPassの主な基底クラスを紹介します．

ユーザは自分がどのレベルでLLVM IRにアクセスして処理を行いたいかによって適切なPassの基底クラスを選択，継承し，オリジナルのPassを実装します．例えば，LLVM IR内のループに対して何かを行いたいというのであればLoopPassを継承すればいいですし，BasicBlock単位で処理を行いたいというのであればBasicBlockPassを継承すべきです．

6.2.1 ImmutablePass

ImmutablePassはLLVM IRに対して変換を一切行わないPassです．このPassは一般的な変換や解析は行いませんが，ターゲットとなるアーキテクチャ情報等の様々な最適化に有用な静的情報を提供します．Writing LLVM Passによると，ImmutablePassはrunされない特殊なPassとなっています．なお，ImmutablePassを継承したクラスの例としては，DataLayoutクラスがあります．

6.2.2 ModulePass

ModulePassはユーザが使用できる最も一般的な基底クラスです．ModulePassの派生クラスはプログラム全体を必要とし，Functionのボディを参照したりFunctionの追加/削除を行います．

ModulePassの派生クラスは，ModulePassを継承した上でrunOnModuleメソッドをオーバーロードする必要があります．runOnModuleメソッドのインタフェースを以下に示します．

llvm::ModulePass::runOnModule

virtual bool runOnModule(Module &M) = 0;

runOnModuleには処理対象となるModuleが渡され，ユーザはこのメソッドで変換や解析などの自身の実装したい処理を行います．runOnModuleは最適化によってModuleが修正された場合にtrueを返し，そうでなければfalseを返すルールになっています．

以降で紹介するPassも同様で，基本的にrunOnXXというメソッドに処理対象が渡されるため，runOnXXメソッドで目的の処理を行います．例えばそれはFunction単位であったりLoop単位であったりしますが，考え方は同じです．

6.2.3 FunctionPass

FunctionPassの処理はプログラム中の全てのFunctionに対して実行されます．FunctionPassは決まった順序で実行されるわけではなく，また外部関数を変更することはありません．FunctionPassでは以下のことをしてはいけません．

現在処理中のFunction以外のFunctionを操作すること

ModuleにFunctionの追加/削除を行うこと

Moduleにグローバル変数の追加/削除を行うこと

runOnFunctionの実行にまたがって状態を維持すること

FuncitonPassの派生クラスは以下の三つのメソッドをオーバーロードすることができます．これら三つのメソッドは何れもプログラムに変更を行った場合trueを返し，それ以外の場合falseを返却します．

doInitialization

一つ目のメソッドはdoInitializationメソッドです．doInitializationメソッドのインタフェースを以下に示します．

llmv::FunctionPass::doInitialization

virtual bool doInitialization(Module &M);

doInitializationメソッドは，Functionの追加/削除などFunctionPassで許されていない操作のほとんどを実行してよいメソッドです．また，doInitializationメソッドは処理されるFunctionに依存しない要素の初期化を行う場所でもあります．なお，doInitializationメソッドの呼び出しは他のいかなるPassの実行ともオーバーラップしません．

runOnFunction

二つ目のメソッドはrunOnFunctionメソッドです．runOnFunctionメソッドのインタフェースを以下に示します．

llvm::FunctionPass::runOnFunction

virtual bool runOnFunction(Function &F) = 0;

runOnFunctionはFunction毎に呼び出されます．Functionに対する変換や解析を行うためには，全てのFunctionPassの派生クラスはrunOnFunctionを実装する必要があります．通常，Functionが変更された場合にrunOnFuncionメソッドはtrueを返却します．

doFinalization

三つ目のメソッドはdoFinalizationメソッドです．doFinalizationメソッドのインタフェースを以下に示します．

llvm::FunctionPass::doFinalization

virtual bool doFinalization(Module &M)

doFinalizationメソッドはプログラム中の全てのFunctionに対してrunOnFunctionメソッドが呼び出されたあとに呼ばれます．

6.2.4 LoopPass

LoopPassの処理は各Functionの全てのLoopに対して実行されます．なお，LoopPassは多重ループについては内側のループから順に処理します．

LoopPassの派生クラスはLPPassManagerを使用することでLoopのネスト構造を変更することができます．LoopPassの派生クラスは以下の三つの抽象メソッドをオーバーロードすることができます．これら三つのメソッドは何れもプログラムに変更を行った場合trueを返し，それ以外の場合falseを返却します．

doInitialization

一つ目のメソッドはdoInitalizationメソッドです．doInitializationメソッドのインタフェースを以下に示します．

llvm::LoopPass::doInitialization

virtual bool doInitialization(Loop *, LPPassManager &LPM);

doInitializationメソッドは処理されるFunctionに依存しない要素の初期化を行うよう設計されています．また，doInitializationメソッドの呼び出しは他のいかなるPassの実行ともオーバーラップしません．なお，FunctionやModuleレベルの解析情報にアクセスするためにはLPPassManagerインタフェースを使用する必要があります．

runOnLoop

二つ目はrunOnLoopメソッドです．runOnLoopメソッドのインタフェースを以下に示します．

llvm::LoopPass::runOnLoop

virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;

他のPass同様，runOnLoopは各Loopに対する任意の処理を実装するために使用されます．LoopPassの派生クラスは変換や解析を行うためにrunOnLoopメソッドを実装する必要があります．通常，Loopが変更された場合にrunOnLoopメソッドはtrueを返却します．なお，Loopのネスト構造を変更するにはLPPassManagerインタフェースを使用する必要があります．

doFinalization

三つ目はdoFinalizationメソッドです．doFinalizationメソッドのインタフェースを以下に示します．

llvm::LoopPass::doFinalization

virtual bool doFinalization();

doFinalizationメソッドは全てのLoopに対してrunOnLoopが呼ばれた後に呼び出されます．

6.2.5 RegionPass

RegionPassはLoopPassに似ていますが，出入り口を一つずつしか持たない領域に対して処理を実行するという点で異なります．RegionPassはネスト構造に対しては，内側の領域から順に処理を適用します．

RegionPassはRGPassManagerインタフェースを使用することでRegionTreeを変更することが出来ます．RegionPassの派生クラスは以下の三つの抽象メソッドをオーバーロードすることができます．これら三つのメソッドは何れもプログラムに変更を行った場合trueを返し，それ以外の場合falseを返却します．

doInitialization

一つ目はdoInitializationメソッドです．doInitializationメソッドのインタフェースを以下に示します．

llvm::RegionPass::doInitialization

virtual bool doInitialization(Region *, RGPassManager &RGM);

doInitializationメソッドでは処理されるFunctionに依存しない要素の初期化を行います．また，doInitializationメソッドの呼び出しは他のいかなるPassの実行ともオーバーラップしません．なお，FunctionやModuleレベルの解析情報にアクセスするためにはRGPassManagerインタフェースを使用する必要があります．

runOnRegion

二つ目はrunOnRegionメソッドです．runOnRegionメソッドのインタフェースを以下に示します．

llvm::RegionPass::runOnRegion

virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;

RegionPassの派生クラスは変換や解析を行うためにrunOnRegionメソッドを必ず実装する必要があります．通常，Regionが変更された場合にrunOnRegionメソッドはtrueを返却します．なお，RegionTreeを変更するにはRGPassManagerインタフェースを使用する必要があります．

doFinalization

三つ目はdoFinalizationメソッドです．doFinalizationメソッドのインタフェースを以下に示します．

llvm::RegionPass::doFinalization

virtual bool doFinalization();

doFinalizationメソッドは全てのRegionに対してrunOnRegionが呼ばれた後に呼び出されます．

6.2.6 BasicBlockPass

BasicBlockPassは参照や変更の対象が単一のBasicBlockに限られる点以外はFunctionPassに非常によく似ています．BasicBlockPassは以下の処理を行うことが許されません．

処理中のBasicBlock以外のBasicBlockに対する参照や変更

runOnBasicBlockの呼び出しにまたがった状態の保持

CFGの変更

FunctionPassで禁止されている処理

BasicBlockPassはFunctionPassと同様に，doInitialization(Module)やdoFinalization(Module)をオーバーライドすることができますが，そのほかに以下の3つの抽象メソッドを実装することができます．

doInitialization

一つ目はdoInitializationメソッドです．doInitializationメソッドのインタフェースを以下に示します．

llvm::BasicBlockPass::doInitialization

virtual bool doInitialization(Function &F);

doInitializationメソッドはBasicBlockPassで許されていないが，FunctionPassで許可されている処理はほとんど実行することが出来ます．また，doInitializationメソッドでは処理されるBasicBlockに依存しない要素の初期化を行います．なお，doInitializationメソッドの呼び出しは他のいかなるPassの実行ともオーバーラップしません．

runOnBasicBlock

二つ目はrunOnBasicBlockです．runOnBasicBlockメソッドのインタフェースを以下に示します．

llvm::BasicBlockPass::runBasicBlock

virtual bool runOnBasicBlock(BasicBlock &BB) = 0;

BasicBlockPassの処理を行うためにはこのメソッドを実装する必要があります．runOnBasicBlockメソッドは引数として渡された以外のBasicBlockに対して参照や変更を行ってはいけませんし，CFGの変更も許可されません．runOnBasicBlockはBasicBlockが変更された場合にtrueを返却します．

doFinalization

三つ目はdoFinalizationメソッドです．doFinalizationメソッドのインタフェースを以下に示します．

llvm::BasicBlockPass::doFinalization

virtual bool doFinalization(Function &F);

doFinalizationメソッドはプログラム中のコンパイルされた全てのBasicBlockに対するrunOnBasicBlockの呼び出しが完了した後に呼び出されます．

その他のPassの基底クラス

上記以外のPassの基底クラスにはCallGraphSCCPassやMachineFunctionPassがあります．CallGraphSCCPassとはおそらくコールグラフの強連結成分(Strongly Connected Components)に対して処理を行うPassだと思うのですが，筆者はあまりよくわかっていません．保持する抽象メソッドや実装方法は他のPassと大差ないとは思います．MachineFunctionPassはいわゆるバックエンドで使用するPassで，このクラスについては第7章で説明があります．

6.3 その他のPassに実装すべきもの

前節冒頭で少し触れましたが，ユーザは先ほど説明したPassのうち自分の実装したいものに合わせてPassを選択し，継承します．例えばループに対して何か処理したければ，LoopPassを継承することでLLVM IRのLoop毎にrunOnLoopメソッドが呼ばれます．また，BasicBlockPassならBasicBlock毎，FunctionPassならFunction毎にrunOnXXメソッドが呼ばれます．

基本的にはdoInitalizationで初期化処理を行い，runOnメソッドに目的の処理を記述し，doFinalizationで終了処理という形になると思います．しかし，Passの実装には他にもいくつか実装すべき抽象メソッドやテンプレートが存在します．本節ではそれらのPassの実装に必要な事項を説明します．

6.3.1 Pass::getAnalysisUsageメソッド

まず最初にPassクラスのgetAnalysisUsageメソッドを紹介します．getAnalysisUsageメソッドは以下の様に定義されている抽象メソッドです．

lllvm::Pass::getAnalysisUsage

virtual void getAnalysisUsage(AnalysisUsage &Info) const;

getAnalysisUsageメソッドはPassの実行時に他のPassの実行結果(例えば何らかの解析Passの結果など)を利用したい場合に実装します．

実装するPassがLLVM IRに何らかの変換を行う様なPassであった場合，実行時に他のPassの解析結果に影響を与える可能性があるわけですが，getAnalysisUsageメソッドの実装では“このPassの解析結果には影響を与えない”という情報を引数として与えられるAnalysisUsageに設定することができます．また，getAnalysisUsageメソッド内では，何らかのPassの実行結果を利用したい場合などに，どのPassが必要かという情報をAnalaysisUsageに与えることもできます．本節の以降の説明で，この処理を行うためのメソッドを幾つか紹介します．なお，PassがgetAnalysisUsageメソッドを実装しない場合，そのPassは他のPassによる解析結果を必要とせず，また全ての他のPassの解析結果を無効にすることを意味します．

AnalysisUsage::addRequired<>とAnalysisUsage::addRequiredTransitive<>

一つ目のメソッドはAnalysisUsage::addRequiredメソッドです．addRequiredメソッドのインタフェースを以下に示します．

lllvm::AnalysisUsage::addRequired

template<class PassClass>
AnalysisUsage &llvm::AnalysisUsage::addRequired()

addRequiredメソッドは，実装するPassで事前に実行して欲しいPassを登録するために使用します．getAnalysisUsageメソッド内で，必要な情報を提供するPassクラスを指定してaddRequiredメソッドを呼ぶことで事前に実行しておいて欲しいPassを指定することができます．例えば，Pass内でLoopInfoを利用したい場合，getAnalysisUsageメソッド内にリスト6.1の様に記述します．

リスト6.1: addRequiredの使用例

 1: void MyPass::getAnalysisUsage(AnalysisUsage &AU) const {
 2:   AU.addRequired<LoopInfo>();
 3: }

また，Pass内で別のPassに処理を遷移させたい場合があります．その場合addRequiredメソッドの代わりにaddRequiedTransitiveメソッドを使用します．この二つのメソッドの違いは少々理解し難いのですが，何らかのPassの実行結果にアクセスしたい場合は前者を，何らかのPassにPassとしての処理(計算)をさせたい場合は後者を使用する様です．使用方法はほぼ同一のため省略します．

なお，指定したPassの解析情報を自身のPassで利用するには後述するgetAnalysisメソッドを利用します．

AnalysisUsage::addPreserved<>

PassManagerはPassの実行を管理し，必要の無い再計算を回避します．そのために，Passは影響を与えない他のPassを明示的に指定することが出来ます．これを実現するのがAnalysisUsageクラスのaddPreservedメソッドです．addPreservedメソッドのインタフェースを以下に示します．

llvm::AnalysisUsage::addPreserved

template<class PassClass>
AnalysisUsage &llvm::AnalysisUsage::addPreserved()

なお，類似のメソッドにsetPreservesAllメソッドがあります．setPreservesAllは全てのPassに影響を与えないということを意味します．例えば解析系のPassは，setPreservesAllを使用できるはずです．また，プログラム中の命令を変更するがCFGを変更しないような場合，setPreservesCFGメソッドを使用することが出来ます．

6.3.2 Pass::getAnalysis<>

次に紹介するメソッドはPassクラスのgetAnalysisメソッドです．このメソッドを使用することで，事前にaddRequiredメソッドで指定しておいたPassの実行結果を取得できます．getAnalysisメソッドのインタフェースを以下に示します．

llvm::Pass::getAnalysis

template<typename AnalysisType>
AnalysisType &llvm::Pass::getAnalysis()

AnalysisTypeにPassクラスを指定してgetAnalysisメソッドを使用することで，指定したPassの結果を取得することが出来ます．

例えば，runOnFunctionでLoopInfoの解析結果を使用したい場合は以下の様に記述します．

リスト6.2: getAnalysisの使用例

 1: void MyPass::getAnalysisUsage(AnalysisUsage &AU) const {
 2:   AU.addRequired<LoopInfo>();
 3: }
 4:
 5: bool MyPass::runOnFunction(Function &F) {
 6:   LoopInfo &LI = getAnalysis<LoopInfo>();
 7:   ・・・
 8: }

6.3.3 PassのIDとRegisterPassテンプレート

本章で紹介したものを含め，Passの基底クラスのコンストラクタはIDというchar型の参照を引数としてとります．IDはパスを識別するために使用するものだそうで，ユーザが作成するPassについてもchar型の変数をメンバとして宣言し基底クラスのコンストラクタに渡す必要があります．以下にその例を示します．

リスト6.3: IDの初期化と基底クラスへの引渡し

 1: class MyPass:public FunctionPass{
 2:   public:
 3:     static char ID;
 4:     MyPass() : FunctionPass(ID){}
 5:     ・・・
 6: }
 7: char MyPass::ID=0;

なお，LLVMはパスを識別するためにIDのアドレスを使うらしいので，初期化値は重要ではないそうです．

また，作成したPassをopt等から参照するためにはPassをシステムに登録しておく必要があります．Passの登録にはRegisterPassという構造体テンプレートを使用します．ユーザはRegisterPass構造体のインスタンスをソースコード(.cppファイル)のグローバルスコープで宣言することでPassを登録することが出来ます．RegisterPassテンプレートのコンストラクタを以下に示します．

llvm::RegisterPassのコンストラクタ

template<typename passName>
llvm::RegisterPass<passName>::RegisterPass(const char *PassArg,
                                           const char *Name,
                                           bool CFGOnly = false,
                                           bool is_analysis = false)

まず，passNameには登録するPassクラスを指定します．次に，第1引数はopt等でこのPassを指定する際に使用する名前であり，第2引数は-helpオプションが指定された時等に表示する情報です．なお，第3，第4引数はPassの挙動を示します．第3引数のboolは登録するPassがCFGを参照するのみで，変更を行わない場合にtrueを指定します．また，第4引数は解析系のPassの場合にtrueを指定します．RegisterPassの使用例を以下に示します．

リスト6.4: RegisterPassの例

 1: RegisterPasss<MyPass> X("mypass", "My Original Pass");

上記の様にPassを登録した場合，optでは-mypassと指定することでMyPassを使用することが出来ます．

6.4 簡単なPassを実装してみる

それでは実際にPassを実装してみましょう．本来のPassは解析や最適化をするものですが，ここではまず“プログラム中の関数名を出力する”というとても簡単なPassを実装し，Passの実装の雰囲気を掴んでいきたいと思います．

6.4.1 どのPassを継承するか

Passを実装するためには，まずどのPassの基底クラスを継承するかを決定する必要があります．Passの基底クラスは6.2節で紹介したように様々ですが，今回はプログラム中の関数名を出力したいので，FunctionPassを継承すれば良さそうです．FunctionPassを継承し，runOnFunctionにFunctionが渡されるたびにFunction名を出力することにします．

6.4.2 Passクラスの宣言

FunctionPassを使用することを決定したので，実際に独自のPassクラスを実装していきます．今回はEasyPassという名前のクラスにしましょう．EasyPassクラスの宣言をリスト6.5に示します．

リスト6.5: 簡単なPassクラスの宣言

 1: #include <cstdio>
 2: #include <llvm/Pass.h>
 3: #include <llvm/Function.h>
 4: class EasyPass : public FunctionPass {
 5: public:
 6:   static char ID;
 7:   EasyPass() : FunctionPass(ID) {}
 8:   ~EasyPass() {}
 9:
10:   virtual bool runOnFunction(Function &F);
11:   virtual void getAnalysisUsage(llvm::AnalysisUsage &AU) const;
12: };

EasyPassクラスは先ほど説明したとおりFunctionPassを継承します．また，メンバ変数としてIDを持ち，メソッドとしてコンストラクタ，デストラクタ，およびrunOnFuctionを持ちます．メンバ変数のIDは6.2.3節で説明したIDであり，コンストラクタにてFunctionPassのコンストラクタへと渡されます．なお，今回は特に事前処理，後処理がないため，doInitializationとdoFinalizationは実装しないことにします．

6.4.3 Passクラスの定義

次に，宣言したEasyPassクラスのメソッドと登録を実装しましょう．EasyPassクラスの定義をリスト6.6に示します．

リスト6.6: 簡単なPassクラスの定義

 1: bool EasyPass::runOnFunction(Function &F) {
 2:   llvm::errs() << "Function Name : " << F.getName().str() << "\n";
 3:   return false;
 4: }
 5:
 6: void EasyPass::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
 7:   // 変更を加えないためsetPreservesAll
 8:   AU.setPreservesAll();
 9: }
10:
11: char EasyPass::ID = 0;
12: static RegisterPass<EasyPass>
13: X("easypass", "easy pass:only print function name", false, false);

1行目～4行目はrunOnFunctionの定義です．runOnFunctionでは，渡されたFunctionのgetNameメソッドでFunctionの名前を取得し，llvm::errsメソッドにて出力します．llvm::errsメソッドは“llvm/Support/raw_ostream.h”に宣言されているメソッドで，標準エラー出力へのraw_ostreamの参照を取得できます．中間表現に変更を加えるわけではないので，runOnFunctionの戻り値はfalseとします．また，同様に中間表現に一切変更を加えないため，6行からのgetAnalysisUsageメソッドでsetPreservesAllメソッドを呼び出しています．11行目はIDの初期化を行っています．今回は0で初期化していますが，6.2.3節で説明したとおり，必要なのはIDのアドレスであるため，初期値は他の値でも問題ありません．最後に，7行目でRegisterPassにてEasyPassクラスをeasypassという名前で登録しています．

6.4.4 Passのコンパイル/実行

それでは，作成したPassをコンパイルしてLLVM IRへ適用する方法を説明します．作成したPassを使用するには，まず下記のコマンドを投入して共有ライブラリを生成します．

# EasyPassクラスのソースコードはeasypass.cppとする
$ g++ -g easypass.cpp `llvm-config --cflags --ldflags -libs` -c -o easypass.o
# -sharedオプションをつけて共有ライブラリを作成
$ g++ -g easypass.o -shared -o easypass.so

生成した共有ライブラリはoptコマンドを用いることでLLVM IRに適用できます．

# RegisterPassでeasypassという名前で登録している
# -load オプションで生成した共有ライブラリをロード
$ opt -load easypass.so -easypass ./sample/test.ll -S > /dev/null

入力するLLVM IRとしてリスト5.61で示したtest.llを使用した場合，次の様な出力が得られるはずです．

$ opt -load ./bin/easypass.so -easypass ./sample/test.ll -S > /dev/null
Function Name : test
Function Name : main
Function Name : printnum

なお，プログラム中で呼び出す場合は，作成したPassのインスタンスをPassManagerにaddする方法で使用できるはずです．

6.5 無用命令の削除を実装してみる

前節で“関数名を出力する”という非常に簡単なPassを実装しました．これによりPassの実装について大まかなイメージは掴めたかと思いますが，先ほどのPassではLLVM IRに対して特に解析や変換を行いませんし，実際に何かしらの解析/最適化のPassを実装するためにはあまり参考になるとは言えないでしょう．そこで今度は先ほどよりも少し役に立ちそうなPassを実装してみます．とは言え，あまり複雑なものを実装するのは入門書としては適しませんし，無駄にページ数がかさむだけですので簡単な例としてまず，“無用命令の削除”を行うPassを実装することにしましょう．実のところこの機能を持つPassはDCEクラスとしてLLVMで既に用意されているのですが，ここではそれらよりもずっとシンプルな(機能を限定した)Passを実装し，Pass実装の流れを説明したいと思います．

無用命令の削除は，英語でDead Code Eliminationとも呼ばれ，その名の通りプログラム中の死んでいる命令、つまり実行結果に影響を与えない無用な命令を削除する最適化です．本節では最初に最適化処理の前提条件や方針を考え，次に実装方法と結果を解説することにします．

6.5.1 前提条件を考える

まず最初に前提条件を考えてみましょう．無用命令の削除を行うためには，まずどの命令が無用命令となっている(死んでいる命令)かということを判断しなくてはなりません．逆に言うと，プログラム中で無用命令でない，つまり生きている命令を調べることができれば，それ以外のコードが無用命令になるとも言えます．ではLLVM IRにおいて生きている命令がどのような命令かということを考えてみるとどうでしょう？生きている，つまり実行結果に影響を与える命令ですので，次の様な命令が該当すると考えられます．

メモリに対する入出力(load,store)

関数呼び出し(call)

終端命令(return,brなど)

さらに，生きている命令がオペランドとして参照する命令は必要ですので，生きている命令には次の定義も追加できるでしょう．

生きている命令から参照される命令

というわけで，以上の四つの何れかの条件を満たす命令を生きている命令することにします．

6.5.2 実装方針を考える

ではLLVMでどのようにして生きている命令を探すかを考えてみましょう．まずは命令の削除をどの単位で行うか，という問題があります．処理を単純にするのであればBasicBlock単位で命令を一度確認し，不要な命令があれば削除すれば良い気もします．しかし，ある命令が使用するオペランドはBasicBlock間にまたがっている可能性があります．さらに，ある命令を削除することでその命令がオペランドとして参照していた命令が先ほどの四つ目の条件を満たさなくなり，削除対象になる可能性もあります．イメージ図を図6.2に示してみましょう．

図6.2: 無用命令削除の例

図6.2では左側に今回の例として使用するLLVM IRの初期状態を記載しています．初期状態のLLVM IR内の命令を，先頭から順に削除可能であるか確認した場合，if.endというラベルがついたBasicBlockにあるmul命令が唯一削除可能な命令となります．他の命令，例えばphi命令はmul命令から参照されていますし，add命令やsub命令はそのphi命令から参照されています．また，残りの命令はbr命令とbr命令の条件として参照されるicmp命令です．ここでmul命令を削除するとどうなるでしょう？mul命令削除後のLLVM IRを図6.2の右側に記載しているので確認してみましょう．今度はif.endのphi命令が参照されなくなったことで削除可能な命令となっています．更に次のステップを考えた場合，このphi命令を削除すればadd命令やsub命令が削除できるようになります．つまり，削除する命令のオペランドとして参照している命令は，該当命令を削除することによって削除可能になる可能性がある，ということになります．

ここまでの内容を整理し，今回は下記の手順で命令の削除処理を行うことにします．

Function内の全ての命令を調査対象の命令としてリストに登録する

リスト内の命令を一つずつ調べて生きている命令であるか調べる

生きている命令でない(つまり無用な命令である)場合はその命令を削除対象とする

手順3で削除対象とされた場合はその命令のオペランドの値を生成する命令リストに追加する

手順3で削除対象とした命令を削除する

リストが空になるまで手順2から繰り返す

最後に，本節で実装するPassのゴールを決めておきましょう．本節で実装するPassのゴールは図6.2の初期状態のコードから無用な命令を削除することにします．ここで図6.2のLLVM IRを念のため再掲しておきます．

リスト6.7: 無用な命令を含むLLVM IR

 1: ; ModuleID = './sample/sample.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: define i32 @test(i32 %i, i32 %j) nounwind {
 6: entry:
 7:   %cmp = icmp sgt i32 %j, 0
 8:   br i1 %cmp, label %if.then, label %if.else
 9:
10: if.then:                                          ; preds = %entry
11:   %add = add nsw i32 %i, %j
12:   br label %if.end
13:
14: if.else:                                          ; preds = %entry
15:   %sub = sub nsw i32 %i, %j
16:   br label %if.end
17:
18: if.end:                                           ; preds = %if.else, %if.then
19:   %k.0 = phi i32 [ %add, %if.then ], [ %sub, %if.else ]
20:   %mul = mul nsw i32 %k.0, 10
21:   ret i32 0
22: }

なお，このLLVM IRは下記のC言語のソースコードをClangでコンパイルし，mem2regを適用すると得られます．

リスト6.8: C言語での記述

 1: int test(int i, int j) {
 2:   int k;
 3:   if (j > 0) {
 4:     k = i + j;
 5:   } else {
 6:     k = i - j;
 7:   }
 8:   k = k * 10;
 9:   return 0;
10: }

6.5.3 実装

Passクラスの宣言

それでは実装に移ります．まずは継承するPassを決定する必要がありますが，今回はFunction単位で処理を実行するのでFunctionPassを継承しましょう．クラス名をDCEliminatePassとした場合の宣言例を以下に示します．

リスト6.9: DCEliminatePassクラスの宣言

 1: class DCEliminatePass : public llvm::FunctionPass {
 2: public:
 3:   static char ID;
 4:   DCEliminatePass() : llvm::FunctionPass(ID) {}
 5:   ~DCEliminatePass() {}
 6:
 7:   virtual bool runOnFunction(llvm::Function &F);
 8:   virtual void getAnalysisUsage(llvm::AnalysisUsage &AU) const;
 9: };

DCEliminatePassクラスはFunctionPassを継承し，runOnFunctionとgetAnalysisUsageを実装します．

Function内のInstructionの取得

ではここからrunOnFunctionの実装について見ていきましょう．まずは，一つ目の手順として，LLVM IR内のInstructionを全て取得する必要があります.これはllvm/Support/InstIterator.hのinst_begin関数，inst_end関数およびinst_iteratorを利用することで可能です．InstIterator.hのinst_begin関数，inst_end関数の定義は以下の通りです．

llvm::inst_beginとllvm::inst_endメソッド

inst_iterator inst_begin(Function *F)
inst_iterator inst_end(Function *F)

llvm::inst_begin関数ではイテレータの先頭が，llvm::inst_end関数では末尾要素の次を指すイテレータが返却されます．これらの関数を利用してInstructionを取得する際の例は下記の様になります．

リスト6.10: Function内のInstructionの取得

 1: bool DCEliminatePass::runOnFunction(llvm::Function &F) {
 2:   // 対象の命令を格納するlist
 3:   std::list<llvm::Instruction *> works;
 4:
 5:   // 命令を削除したらtrueに変更
 6:   bool change = false;
 7:
 8:   // 最初に全ての命令を調べる
 9:   llvm::inst_iterator inst_it = llvm::inst_begin(F);
10:   llvm::inst_iterator inst_it_end = llvm::inst_end(F);
11:   for (; inst_it != inst_it_end; inst_it++)
12:     works.push_back(&*inst_it);
13:
14:   ・・・命令確認と削除処理・・・
15:
16:   // 命令を削除したらtrueにしておく
17:   return change;
18: }

9行目，10行目でinst_begin関数とinst_end関数に対象のFunctionへの参照を渡し，イテレータを取得しています．inst_beginで取得したイテレータはC++のSTLと同様に++演算子を使用することで次の要素へ移れるため，11，12行目のループ文でinst_endで取得したイテレータと一致するまで処理を繰り返し，Function内の全てのInstructionを取得します．

ちなみに，LLVMでは他にも各種データ構造の要素を辿るために，XXXbegin，XXXendといったメソッドや関数が数多く用意されています．使用方法はC++のSTLと同様で，XXXbeginは先頭を，XXXendは末尾要素の次を指すイテレータを返却するメソッドです．例えば，Function内のInstructionを走査するという目的であれば，以下の様にFunctionクラスやBasicBlockクラスのbeginメソッド，endメソッドで返却されるイテレータを利用することでも可能です．

リスト6.11: Function内のInstructionを取得する際の別例

 1:   /*
 2:    * この方法でもInstructionを辿れる
 3:   */
 4:   llvm::Function::iterator bb_it = F.begin();
 5:   llvm::Function::iterator bb_it_end = F.end();
 6:   for(;bb_it != bb_it_end; bb_it++){
 7:     llvm::BasicBlock::iterator inst_it = bb_it->begin();
 8:     llvm::BasicBlock::iterator inst_it_end = bb_it->end();
 9:     for(;inst_it != inst_it_end; inst_it++)
10:       works.push_back(&*inst_it);
11:   }

生きている命令であるかの確認

命令の取得ができたら，次は取得した各命令が生きているかどうかの判定を行います．ここで，前節に記載した生きている命令の定義を再掲しておきます．

メモリに対する入出力(load,store)

関数呼び出し(call)

終端命令(return,brなど)

生きている命令から参照される命令

この定義の一つ目～三つ目は各Instrucitonの種類を調べるだけで良さそうです．これは第5章でも使用したisa<>テンプレートで簡単に実装できます．残りは4つ目の“生きている命令から参照されているか”という点ですが，命令を削除する場合はオペランドを再度確認するわけですので，“命令を調べる時点で残っている他の命令は全て生きている”と考えれば判定時に他の命令から参照されているかを調べるだけで良いと言えます．ある値が他の命令で使用されているかはInstructionクラスの基底クラスであるllvm::Valueクラスのuse_emptyメソッドによって簡単に調べられます．use_emptyメソッドの定義を以下に示します．

llvm::Value::use_emptyメソッド

bool llvm::Value::use_empty() const

llvm::Value::use_emptyメソッドはこのValueを参照する他のValueが存在する場合にfalseを，存在しない場合はtrueを返却するため，今回はこのメソッドがfalseが返したときは生きている命令であるとすることができます．以上から対象の命令が生きている命令であるかの確認は下記の様になります．

リスト6.12: 生きている命令であるかの確認

 1: bool DCEliminatePass::runOnFunction(llvm::Function &F) {
 2:   // 対象の命令を格納するlist
 3:   std::list<llvm::Instruction *> works;
 4:
 5:   bool change = false;
 6:
 7:   ・・・省略・・・
 8:
 9:   // worksに追加した命令からdead codeがないか調べる
10:   // worksが空になるまで繰り返す
11:   llvm::Instruction *inst = NULL;
12:   while (!works.empty()) {
13:     // 命令をpop
14:     inst = works.front();
15:     works.pop_front();
16:
17:     // この命令の値を使用している命令がある場合は生きているとする
18:     // load/store命令、call命令、終端命令(returnやbr)は生きている命令にする
19:     if (!inst->use_empty() || llvm::isa<llvm::TerminatorInst>(inst) ||
20:         llvm::isa<llvm::CallInst>(inst) || llvm::isa<llvm::LoadInst>(inst) ||
21:         llvm::isa<llvm::StoreInst>(inst))
22:       continue;
23:
24:     ・・・ここで命令の削除を実行・・・
25:   }
26:
27:   return change;
28: }

12行目からのループ内でリストに登録されたInstructionを順番に取り出し，生きている命令であるかを確認しています．実際にその確認を行っているのは19行目～21行目の条件文で，先ほど紹介したuse_emptyメソッドによる参照するValueの有無や，isaテンプレートによる命令の種類の確認を行っています．load命令に関してはvolatile属性がついていなければ削除対象としても良さそうですが，今回は実装を簡単にするためload命令の場合は常に生きている命令としています．なお，br命令を表すllvm::BranchInstクラスや，ret命令を表すReturnInstクラスなど，終端命令に分類される命令のクラスはTerminatorInstクラスのサブクラスですので，終端命令であるかどうかはisaテンプレートを用いてTerminatorInstクラスであるかを調べるだけで問題ありません．何れかの条件に一致すれば生きている命令のためcontinue文で次の命令の確認に移ります．また，全ての条件に一致しなかった場合は無用な命令ですので削除対称となります．

命令のオペランドの取得と命令の削除

次は命令の削除ですが，命令の削除を行う前にその命令のオペランドをリストに再登録する必要があります．そのためには対象のInstructionのオペランドを全て取得する必要があるのですが，これもイテレータを使用することで簡単に行えます．llvm::User::op_beginメソッドでイテレータの先頭が，llvm::User::op_endメソッドで末尾要素の次を指すイテレータが返却されます．ちなみに，UserクラスはInstructionクラスのスーパークラスです．使用方法は先程と同様のため説明は省略します．ここで取得した各オペランドをdyn_castテンプレートでInstructionにキャストすることで，命令が依存する値(オペランド)を生成する命令のInstructionを取得できます．オペランドがInstructionではなく，例えばConstantIntだった場合などにはdyn_castの結果としてNULLが返却されます．Instructionでなければ確認の必要は無いため，NULLが返却された場合はリストへの登録は不要です．

命令の削除についてはllvm::Instruction::eraseFromParentメソッドで実行可能です．このメソッドはBasicBlockからInstructionを除外するとともにそのInstructionをdeleteします．llvm::Instruction::eraseFromParentメソッドの定義は下記の通りです．

llvm::Instruction::eraseFromParentメソッド

void Instruction::eraseFromParent()

オペランドの取得と命令削除を追加したコードは以下の様になります．

リスト6.13: オペランドの取得と命令削除

 1: bool DCEliminatePass::runOnFunction(llvm::Function &F) {
 2:   // 対象の命令を格納するlist
 3:   std::list<llvm::Instruction *> works;
 4:
 5:   // 変更したかどうかを表すフラグ
 6:   bool change = false;
 7:
 8:   ・・・省略・・・
 9:
10:   // worksに追加した命令からdead codeがないか調べる
11:   // worksが空になるまで繰り返す
12:   llvm::Instruction *inst = NULL;
13:   while (!works.empty()) {
14:     // 命令をpop
15:     inst = works.front();
16:     works.pop_front();
17:
18:     ・・・生きている命令か判定・・・
19:
20:     // 削除する命令のオペランドがdead codeになる可能性があるので
21:     // オペランドをworksに追加する
22:     llvm::User::op_iterator op_it = inst->op_begin();
23:     llvm::User::op_iterator op_it_end = inst->op_end();
24:     for (; op_it != op_it_end; op_it++) {
25:       // 既にworksに登録済みなら何もしない
26:       llvm::Instruction *op_inst = llvm::dyn_cast<llvm::Instruction>(*op_it);
27:       if (op_inst)
28:         if (std::find(works.begin(), works.end(), &*op_inst) == works.end())
29:           works.push_back(op_inst);
30:     }
31:
32:     // 削除する
33:     change = true;
34:     inst->eraseFromParent();
35:   }
36:
37:   return change;
38: }

24行からのループ文で，削除対象になった命令のオペランドをリストに再登録しています．先程と同様に，イテレータでオペランドを辿り，worksに登録されていないInstructionのみを登録するようにしています．また，33行目でフラグをtrueに変更し，34行目ではeraseFromParentメソッドで対象のInstructionを削除しています．これでrunOnFunctionの実装は完了です．

getAnalysisUsageとRegisterPassの追加

最後に，getAnalysisUsageの定義とRegisterPassも必要ですので追加しましょう．最終的なコードは以下の様になります．

リスト6.14: DCEliminatePassクラスの定義

 1: bool DCEliminatePass::runOnFunction(llvm::Function &F) {
 2:   // 対象の命令を格納するlist
 3:   std::list<llvm::Instruction *> works;
 4:
 5:   // 最初に全ての命令を調べる
 6:   bool change = false;
 7:
 8:   llvm::inst_iterator inst_it = llvm::inst_begin(F);
 9:   llvm::inst_iterator inst_it_end = llvm::inst_end(F);
10:   for (; inst_it != inst_it_end; inst_it++)
11:     works.push_back(&*inst_it);
12:
13:   // worksに追加した命令からdead codeになった物がないか調べる
14:   // worksが空になるまで繰り返す
15:   llvm::Instruction *inst = NULL;
16:   while (!works.empty()) {
17:     // 命令をpop
18:     inst = works.front();
19:     works.pop_front();
20:
21:     // この命令の値を使用している命令がある場合は生きているとする
22:     // load/store命令、call命令、終端命令(returnやbr)は生きている命令にする
23:     if (!inst->use_empty() || llvm::isa<llvm::TerminatorInst>(inst) ||
24:         llvm::isa<llvm::CallInst>(inst) || llvm::isa<llvm::LoadInst>(inst) ||
25:         llvm::isa<llvm::StoreInst>(inst))
26:       continue;
27:
28:     // 削除する命令のオペランドがdead codeになる可能性があるので
29:     // オペランドをworksに追加する
30:     llvm::User::op_iterator op_it = inst->op_begin();
31:     llvm::User::op_iterator op_it_end = inst->op_end();
32:     for (; op_it != op_it_end; op_it++) {
33:       // 既にworksに登録済みなら何もしない
34:       llvm::Instruction *op_inst = llvm::dyn_cast<llvm::Instruction>(*op_it);
35:       if (op_inst)
36:         if (std::find(works.begin(), works.end(), &*op_inst) == works.end())
37:           works.push_back(op_inst);
38:     }
39:
40:     // 削除する
41:     change = true;
42:     inst->eraseFromParent();
43:   }
44:
45:   return change;
46: }
47:
48: void DCEliminatePass::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
49:   // CFGに変更を加えない
50:   AU.setPreservesCFG();
51: }
52:
53: char DCEliminatePass::ID = 0;
54: static llvm::RegisterPass<DCEliminatePass>
55: X("dceliminate", "eliminate dead code", false, false);

今回のPassは命令の削除は実行しますが，CFGへの変更は加えないため，getAnalysisUsageメソッド内でsetPreservesCFGを呼び出しておきます．また，今回のPassはRegisterPassテンプレートにてdceliminateという名前で登録することにします．

以上で実装は完了です．今回の実装はかなり簡単に済ませてしまっている部分もありますが，今回はこれで良しとしましょう．

6.5.4 コンパイルと実行

最後にコンパイルと実行結果の確認を行いましょう．まずはコンパイルから確認します．下記コマンドにてコンパイルを行います．なお，ソースコードはdcelminate.cpp，dceliminate.hppという名前で，それぞれsrcディレクトリとincディレクトリ配下に保存されていることとします．

# dceliminate.cpp，dceliminate.hpp という名前で保存されているとする
$ g++ -g ./src/dceliminate.cpp -I./inc `llvm-config \
  --cxxflags --ldflags --libs` -ldl -pthread -c -o dceliminate.o
# -shared オプションをつけて共有ライブラリを作成
$ g++ -g -shared -o ./bin/dceliminate.so dceliminate.o

コンパイルが出来たら次は下記コマンドでPassをLLVM IRに適用します．入力するLLVM IRはリスト6.7で示した物とします．

$ opt -load ./bin/dceliminate.so -dceliminate ./sample/sample.ll -S

リスト6.15: 実行結果

 1: ; ModuleID = './sample/sample.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: define i32 @test(i32 %i, i32 %j) nounwind {
 6: entry:
 7:   %cmp = icmp sgt i32 %j, 0
 8:   br i1 %cmp, label %if.then, label %if.else
 9:
10: if.then:                                          ; preds = %entry
11:   br label %if.end
12:
13: if.else:                                          ; preds = %entry
14:   br label %if.end
15:
16: if.end:                                           ; preds = %if.else, %if.then
17:   ret i32 0
18: }

不要だった命令が全て削除され，ret命令やbr命令とそこから参照されるcmp命令だけという非常に寂しいLLVM IRが出力されています．

6.5.5 DCEクラスを使用する

最初に無用命令の削除はLLVMでDCEクラスとして用意されているとお話しましたが，ここでその使用方法を紹介しておきます．DCEはoptコマンドに下記のオプションを与えて実行します．

$ opt -dce ./sample/sample.ll -S

実行結果として下記のLLVM IRが出力されると思います．

リスト6.16: 実行結果

 1: ; ModuleID = './sample/sample.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: define i32 @test(i32 %i, i32 %j) nounwind {
 6: entry:
 7:   %cmp = icmp sgt i32 %j, 0
 8:   br i1 %cmp, label %if.then, label %if.else
 9:
10: if.then:                                          ; preds = %entry
11:   br label %if.end
12:
13: if.else:                                          ; preds = %entry
14:   br label %if.end
15:
16: if.end:                                           ; preds = %if.else, %if.then
17:   ret i32 0
18: }

先程と同じように不要な命令が全て削除されています．

6.6 ループの繰り返し回数のカウント

次の例として，今度はループの繰り返し回数をカウントするPassを実装してみましょう．ループの繰り返し回数は，ループアンローリング等の最適化をかけるための情報として利用できる有用な情報です．しかし一言にループと言ってもその形式(構成)は様々で，全てのループに対応するというのは難しいため，今回は入力されるLLVM IRにある程度条件を設けることで処理を簡単にしたPassを実装したいと思います．この機能もLLVMのScalarEvolutionクラスで既に用意されていますが，今回実装するPassはScalarEvolutionクラスにおける実装よりももっと限定的，かつシンプルなものにします．

6.6.1 前提条件を考える

それではまず前提条件を考えましょう．先ほども軽くふれましたが，今回は入力として想定するLLVM IRをできるだけシンプルにすることで，処理を簡単にしたPassを実装したいと思います．言語レベルで考えた場合，シンプルなループ文の例として“for(int i=定数; i<定数; i=i+定数)”という形式の，つまり下記の条件を満たすようなループがあげられると思います．

ループの制御変数は整数で，かつ定数で初期化されること

ループの繰り返し毎の制御変数の増分値は定数となっていること

ループの繰り返し条件は，“制御変数 < 定数”の形になっていること

今回はこの形式のループ文を解析対象としたいと思います．しかしこれだけでは条件が不十分だと思うので，もう少し条件を追加していきましょう．

ループの繰り返し条件以外にループを抜ける処理がないこと

繰り返し条件以外にループを抜ける分岐が含まれると解析が面倒になります．今回は簡単にしたいのでループはヘッダからのみ抜けることとします．さらに最後にもう一つ条件を追加しておきます．

load/store命令が使用されていない(phi命令が用いられている)こと

これは今までの条件と異なり，言語レベルの条件ではなくLLVM IRとしての条件です．この条件を設けることでdef-useの関係が追いやすく，実装する処理を簡単にすることができます．

解析対象のLLVM IRに対する条件は以上です．この形式のループ文は，例えば下記の様なLLVM IRになります．

リスト6.17: 単純なループ文の例

 1: ; ModuleID = './sample/loop.ll'
 2: target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
 3: target triple = "i386-pc-linux-gnu"
 4:
 5: @.str = private unnamed_addr constant [7 x i8] c"i:%d \0A\00", align 1
 6:
 7: define i32 @main() nounwind {
 8: entry:
 9:   br label %for.cond
10:
11: for.cond:                                         ; preds = %for.inc, %entry
12:   %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
13:   %cmp = icmp slt i32 %i.0, 10
14:   br i1 %cmp, label %for.body, label %for.end
15:
16: for.body:                                         ; preds = %for.cond
17:   %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), i32 %i.0)
18:   br label %for.inc
19:
20: for.inc:                                          ; preds = %for.body
21:   %inc = add nsw i32 %i.0, 1
22:   br label %for.cond
23:
24: for.end:                                          ; preds = %for.cond
25:   ret i32 0
26: }
27:
28: declare i32 @printf(i8*, ...)

リスト6.17は制御変数を0で初期化し，ループの繰り返し毎に制御変数を1ずつ増加させながら制御変数が10未満の間printfの呼び出し処理を繰り返す，という非常に単純なループ文のLLVM IRです．今回のゴールはこのLLVM IRから繰り返し回数を抽出することにしましょう．ちなみにリスト6.17のLLVM IRは下記のソースコードをclangでコンパイルし，mem2regを適用することで生成できます．

リスト6.18: C言語でのループ例

 1: #include <stdio.h>
 2: int main() {
 3:   for (int i = 0; i < 10; i++)
 4:     printf("i:%d \n", i);
 5:
 6:   return 0;
 7: }

6.6.2 実装方針を考える

次に実装方針を考えてみましょう．まずは，先程のループをCFGに変換し，その構成を確認しながら考えていきましょう．

図6.3: リスト6.17のCFG

基本的な構成は第4章で紹介したLLVM IRと同様です．この中で大切なのはfor.condというラベルのついたBasicBlockに含まれる命令とfor.incのadd命令です．CFGからも分かる通り，このLoopにおいて繰り返し条件を含む，つまりLoopの外に抜ける命令を含むBasicBlockはfor.condです．そのBasicBlockの終端命令であるbr命令を確認すると，第一オペランドがicmp命令をさしています．このicmp命令は，“i < 10”に相当する命令ですので，オペランドには制御変数と定数があります．ここで繰り返し条件の右辺の値がわかります．次にicmp命令の第一オペランドを辿るとphi命令にたどり着きます．このphi命令では制御変数%i.0の選択を行っていますが，第一オペランドはpreheaderである%entryブロックから到達する値ですので，ここで制御変数の初期値が取得できます．また，第二オペランドはループの後処理を行う%for.incブロックのadd命令をさしています．add命令は“i++”に相当しますので，add命令の第二オペランドの定数が増分値となります．また，add命令の第一オペランドは先程のphi命令になっていることも確認できます．ループ内で制御変数に対する代入が行われていなければこのようにadd命令の第一オペランドがphi命令になっていると想定できます．

以上の内容から，今回は下記の手順でループの繰り返し回数を取得したいと思います．

Loopの終了条件を含むBasicBlockを取得する

1のBasicBlockの終端命令(br命令)を取得する

2で取得した命令のオペランドからicmp命令を取得する

3のicmp命令のオペランドからphi命令と制御変数の最大値を取得する

3のicmp命令の演算子が“<”であることを確認する

4で取得した命令のオペランドからadd命令を取得する

5で取得したadd命令のオペランドがphi命令になっていることを確認する

7で取得したadd命令のオペランドから制御変数の増分値を取得する

8までで取得した値から繰り返し回数を計算する

なお，各手順において期待する命令や定数が取得できなかった場合は解析不能として終了することにします．

6.6.3 実装

本節ではループの繰り返し回数を解析するPassの実装を説明します．今回も処理毎に順を追って必要なメソッドや使用方法を説明していきます．

Passクラスの宣言

まず最初に継承するPassクラスを決定します．FunctionPassクラスを継承して内包するLoopに対して処理を行う方式でも良いかもしれませんが，今回はLoop毎に繰り返し回数を取得するだけですのでLoopPassを継承することにします．クラス名をLPCountPassとした場合の宣言例は以下の様になります．

リスト6.19: LPCountPassクラスの宣言

 1: class LPCountPass : public llvm::LoopPass {
 2: public:
 3:   static char ID;
 4:   LPCountPass() : llvm::LoopPass(ID) {}
 5:   ~LPCountPass() {}
 6:
 7:   virtual bool runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM);
 8:   virtual void getAnalysisUsage(llvm::AnalysisUsage &AU) const;
 9: };

LoopのExitingBlockと終端命令の取得

ここからrunOnLoopメソッドの実装に移ります．runOnLoopでは最初にループの繰り返し条件を含む，つまりループから抜けるBasicBlockを取得します．これは，LoopクラスのスーパークラスであるLoopBaseクラスに定義されたExitingBlockメソッドで取得できます．llvm::LoopBase::ExitingBlockの定義は下記の通りです．

llvm::LoopBase::ExitingBlock

template<class BlockT , class LoopT>
BlockT *llvm::LoopBase<BlockT, LoopT>::getExitingBlock() const

llvm::LoopBase::getExitingBlockは対象のBasicBlockが複数あった場合はNULLを返却します．NULLが返却された場合は繰り返し条件以外にもループを抜ける処理があるということですので，今回は解析不可とします．

次に先程取得したBasicBlcok内の終端命令を取得します．BasicBlockの終端命令はgetTerminatorメソッドで取得できます．

llvm::BasicBlock::getTerminator

TerminatorInst *BasicBlock::getTerminator()

今回設けた条件により，取得した終端命令はbr命令であるはずですので，返却された命令をdyn_castテンプレートでBranchInstにキャストできるはずです．また4章で示した様にbr命令にはいくつかのフォーマットがありますが，今回のbr命令は条件文の形式になっているはずです．これはBranchInstクラスのisConditionalメソッドで確認できます．

llvm::BranchInst::isConditional

bool llvm::BranchInst::isConditional() const

isConditionalメソッドはそのBranchInstのオペランドが三つであれば，つまり条件文の場合はtrueを，そうでなければfalseを返します．ここまでの処理を実装したrunOnLoopの例は下記の様になります．

リスト6.20: LoopのExitingBlockと終端命令の取得

 1: bool LPCountPass::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:   // 終了ブロックを取得
 3:   llvm::BasicBlock *exiting_block = L->getExitingBlock();
 4:   if (exiting_block == NULL)
 5:     return false;
 6:
 7:   // 終了ブロックのTerminatorInstを取得
 8:   llvm::BranchInst *branch_inst =
 9:       llvm::dyn_cast<llvm::BranchInst>(exiting_block->getTerminator());
10:   if (branch_inst == NULL)
11:     return false;
12:
13:   // conditionalか確認
14:   // operand数が3であるか確認してるだけ
15:   if (!branch_inst->isConditional())
16:     return false;
17:
18:   ・・・その他の情報取得と繰り返し回数の計算処理を実装・・・
19:
20:   return false;
21: }

繰り返し条件の取得

次に，br命令のオペランドからLoopの繰り返し条件となるicmp命令を取得したいと思います．命令のオペランドはllvm::Userクラスに用意されているgetOperandメソッドで取得できます．UserクラスはInstructionクラスのスーパークラスです．

llvm::User::getOperand

Value *llvm::User::getOperand(unsigned i) const

getOperandメソッドは引数として与えたインデックスのオペランドが取得できます．getOperandメソッドは返り値としてValueクラスのポインタを返却するので，これをdyn_castテンプレートでICmpInstにキャストします．

前節で設定した条件より，icmp命令の述語は“<”であり，第二オペランドの片方は最大値を表す定数であるはずですので，この確認をしておく必要があります．まずicmp命令の述語の種類はgetPredicateメソッドで取得できます．

llvm::CmpInst::getPredicate

Predicate llvm::CmpInst::getPredicate() const

getPredicateメソッドは該当のCmpInstの述語の種類を返却します．返り値となっているPredicateはllvm::CmpInstで定義されているenumで，第4章の表4.11，表4.12にて示したものが要素として記述されています．fcmp命令のPredicateは“FCMP_FALSE”のように接頭語として“FCMP_”を，icmp命令のPredicateは“ICMP_EQ”のように接頭語として“ICMP_”をつけて，全て大文字で定義されています．今回は“<”を期待しているので，返却されたPredicateがllvm::CmpInst::ICMP_SLTになっている必要があります．

ちなみに，icmp命令の第一オペランドはPHINodeとなっているはずですので，icmp命令取得と同時にこちらも合わせて取得することにします．ここまでの実装を組み込んだrunOnLoopは下記の様になります．

リスト6.21: runOnLoopの定義

 1: bool LPCountPass::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:
 3:   ・・・ExitingBlockとbr命令の取得・・・
 4:
 5:   // cmp命令取得
 6:   llvm::ICmpInst *icmp =
 7:       llvm::dyn_cast<llvm::ICmpInst>(branch_inst->getOperand(0));
 8:   if (icmp == NULL)
 9:     return false;
10:
11:   // 第二オペランドは終了値を表す定数のはず
12:   // もう片方はPHINodeを想定
13:   llvm::Value *lhs = icmp->getOperand(0);
14:   llvm::Value *rhs = icmp->getOperand(1);
15:   llvm::PHINode *phin = llvm::dyn_cast<llvm::PHINode>(lhs);
16:   llvm::ConstantInt *exit_val = llvm::dyn_cast<llvm::ConstantInt>(rhs);
17:   if (!(phin && exit_val)) {
18:     return false;
19:   }
20:
21:   // PredicateはSLTであるはず
22:   llvm::CmpInst::Predicate pred = icmp->getPredicate();
23:   if (pred != llvm::ICmpInst::ICMP_SLT)
24:     return false;
25:
26:   ・・・その他の情報取得と繰り返し回数の計算処理を実装・・・
27:
28:   return false;
29: }

制御変数の初期値とLoopの後処理の取得

次に制御変数の初期値を取得したいと思います．ここで，今回の入力とするLLVM IRの制限からicmp命令から取得したphi命令で選択するValueの片方は，Loopの前処理を行うBasicBlockから到達する制御変数の初期値のはずです．phi命令が選択するValueは，getIncomingValueで取得できます．

llvm::PHINode::getIncomingValue

Value *llvm::PHINode::getIncomingValue(unsigned i) const

getIncomingValueはPHINodeで選択するValueのうち，引数に渡したインデックスのValueのポインタを返却します．初期値は定数であると仮定しているため，今回は返却された値をdyn_castテンプレートでConstantIntにキャストすれば良いでしょう．なお，今回解析対象とするLLVM IRでは，phi命令で選択するもう片方のValueはLoopの後処理に相当するadd命令となっているはずですので，こちらも合わせてgetIncomingValueメソッドにて取得することにしましょう．add命令であることの確認は，dyn_castテンプレートによるBinaryOperatorへのキャストと，llvm::BinOerator::getOpcodeメソッドによって返却される値によって確認します．

llvm::BinaryOperator::getOpcode

BinaryOps llvm::BinaryOperator::getOpcode() const

戻り値のBinaryOpsは“llvm/Instruction.h”に定義されているenumで，add命令であればllvm::Instruction::Addが返却されているはずです．その他の命令についても“llvm/Instruction.h”内にenumとして定義されているので，詳細を確認したい方は“llvm/Instruction.h”，およびここからインクルードされている“llvm/Instruction.def”(LLVM3.3では“llvm/IR/Instruction.h”になる模様)を確認すると良いでしょう．

ここで紹介したメソッドによる処理を追加したrunOnLoopは以下の様になります．

リスト6.22: runOnLoopの定義

 1: bool LPCountPass::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:
 3:   ・・・ExitingBlock，br命令，icmp命令，PHINodeの取得処理・・・
 4:
 5:   // PHINodeのどちらかが定数であってほしい
 6:   lhs = phin->getIncomingValue(0);
 7:   rhs = phin->getIncomingValue(1);
 8:
 9:   // phinodeのオペランドの片方が初期値
10:   // もう片方がadd命令になるはず
11:   llvm::ConstantInt *init_val = llvm::dyn_cast<llvm::ConstantInt>(lhs);
12:   llvm::BinaryOperator *bin_op = llvm::dyn_cast<llvm::BinaryOperator>(rhs);
13:   if (!(init_val && bin_op)) {
14:     init_val = llvm::dyn_cast<llvm::ConstantInt>(rhs);
15:     bin_op = llvm::dyn_cast<llvm::BinaryOperator>(lhs);
16:   }
17:
18:   // いずれにも一致しなければ終了
19:   if ((!init_val || !exit_val)) {
20:     return false;
21:   }
22:
23:   // add命令であることを確認
24:   if (bin_op == NULL || bin_op->getOpcode() != llvm::Instruction::Add)
25:     return false;
26:
27:   ・・・その他の情報取得と繰り返し回数の計算処理を実装・・・
28:
29:   return false;
30: }

制御変数の増分値の取得と繰り返し回数の計算

次の処理として先程取得したLoopの後処理に相当するadd命令から制御変数の増分値を取得しましょう．今回設けた制限より，add命令のどちらかが定数であり，その定数が増分値となっているはずです．これはgetOperandメソッドでadd命令のオペランドを取得し，dyn_castテンプレートでConstantIntにキャストすればよいでしょう．add命令の形式として，“%val = add %i, const”と“%val = add const, %i”という二つのパターンが考えられますが，今回はadd命令の第二オペランドが定数になっていると仮定して処理を進めてみましょう．この場合，getOperandメソッドを引数として1を指定して呼び出せば定数が取得できるはずです．また，Loop内の処理で制御変数への代入が行われていなければadd命令のgetOperandメソッドに引数0を指定して取得できるValueは，先程のPHINodeを指しているはずですのでこちらも合わせて確認しておくことにします．

増分値が取得できればこれまでに取得した情報と組合わせることで繰り返し回数が計算できます．ConstantIntクラスのgetZExtValueメソッドでConstantIntの値を64bitの符号無整数として取得できますので，この値を使用して計算し，llvm::errs関数で結果を出力することにしましょう．

llvm::ConstantInt::getZExtValue

uint64_t llvm::ConstantInt::getZExtValue() const

ちなみに，ConstantIntクラスのgetValueメソッドを利用すればConstantIntの値を表すAPIntを取得できますので，こちらを利用して計算しても良いでしょう．以上の処理を実装すると以下の様になります．

リスト6.23: runOnLoopの定義

 1: bool LPCountPass::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:
 3:   ・・・ExitingBlockやbr命令の取得・・・
 4:
 5:   // %val = add %i，const の形であると仮定する
 6:   // 制御変数への代入がなければadd命令の第一オペランドは先程取得したPHINodeのはず
 7:   if (bin_op->getOperand(0) != phin)
 8:     return false;
 9:
10:   // 第二オペランドの定数が繰り返しごとの増分値になる
11:   llvm::ConstantInt *step =
12:       llvm::dyn_cast<llvm::ConstantInt>(bin_op->getOperand(1));
13:   if (step == NULL)
14:     return false;
15:
16:   // 初期値と終了値と増分値が得られたので繰り返し回数が得られるはず
17:   if (step->getZExtValue() == 0)
18:     return false;
19:
20:   uint64_t result = (exit_val->getZExtValue() - init_val->getZExtValue()) /
21:                     (step->getZExtValue());
22:   if (((exit_val->getZExtValue() - init_val->getZExtValue()) %
23:        (step->getZExtValue())))
24:     result++;
25:
26:   llvm::errs() << "loop count = " << result << "\n";
27:
28:   return false;
29: }

getAnalysisUsageとRegisterPassの追加

最後にgetAnalysisUsageとRegisterPassを追加します．今回は入力されるLLVM IRに何も変更を加えないため，getAnalysisUsageメソッド内でsetPreservesAllメソッドを呼び出しておくべきでしょう．また，RegisterPassテンプレートでLPCountPassを“lpcount”と言う名前で登録することにします．以上を追加した最終的なソースコードは以下の様になります．

リスト6.24: runOnLoopの定義

 1: bool LPCountPass::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:   // 終了ブロックを取得
 3:   llvm::BasicBlock *exiting_block = L->getExitingBlock();
 4:   if (exiting_block == NULL)
 5:     return false;
 6:
 7:   // 終了ブロックのTerminatorInstを取得
 8:   llvm::BranchInst *branch_inst =
 9:       llvm::dyn_cast<llvm::BranchInst>(exiting_block->getTerminator());
10:   if (branch_inst == NULL)
11:     return false;
12:
13:   // conditionalか確認
14:   // operand数が3であるか確認してるだけ
15:   if (!branch_inst->isConditional())
16:     return false;
17:
18:   // cmp命令取得
19:   llvm::ICmpInst *icmp =
20:       llvm::dyn_cast<llvm::ICmpInst>(branch_inst->getOperand(0));
21:   if (icmp == NULL)
22:     return false;
23:
24:   // 第二オペランドは終了値を表す定数のはず
25:   // もう片方はPHINodeであると仮定
26:   llvm::Value *lhs = icmp->getOperand(0);
27:   llvm::Value *rhs = icmp->getOperand(1);
28:   llvm::PHINode *phin = llvm::dyn_cast<llvm::PHINode>(lhs);
29:   llvm::ConstantInt *exit_val = llvm::dyn_cast<llvm::ConstantInt>(rhs);
30:   if (!(phin && exit_val)) {
31:     return false;
32:   }
33:
34:   // PredicateはSLTであるはず
35:   llvm::CmpInst::Predicate pred = icmp->getPredicate();
36:   if (pred != llvm::ICmpInst::ICMP_SLT)
37:     return false;
38:
39:   // PHINodeのどちらかが定数であってほしい
40:   lhs = phin->getIncomingValue(0);
41:   rhs = phin->getIncomingValue(1);
42:
43:   // phinodeのオペランドの片方が初期値
44:   // もう片方がadd命令になるはず
45:   llvm::ConstantInt *init_val = llvm::dyn_cast<llvm::ConstantInt>(lhs);
46:   llvm::BinaryOperator *bin_op = llvm::dyn_cast<llvm::BinaryOperator>(rhs);
47:   if (!(init_val && bin_op)) {
48:     init_val = llvm::dyn_cast<llvm::ConstantInt>(rhs);
49:     bin_op = llvm::dyn_cast<llvm::BinaryOperator>(lhs);
50:   }
51:
52:   // いずれにも一致しなければ終了
53:   if ((!init_val || !exit_val)) {
54:     return false;
55:   }
56:
57:   // add命令であることを確認
58:   if (bin_op == NULL || bin_op->getOpcode() != llvm::Instruction::Add)
59:     return false;
60:
61:   // %val = add %i，const の形であると仮定する
62:   // 制御変数への代入がなければadd命令の第一オペランドは先程取得したPHINodeのはず
63:   if (bin_op->getOperand(0) != phin)
64:     return false;
65:
66:   // 第二オペランドの定数が繰り返しごとの増分値になる
67:   llvm::ConstantInt *step =
68:       llvm::dyn_cast<llvm::ConstantInt>(bin_op->getOperand(1));
69:   if (!step)
70:     return false;
71:
72:   // 初期値と終了値と増分値が得られたので繰り返し回数が得られるはず
73:   if (step->getZExtValue() == 0)
74:     return false;
75:
76:   uint64_t result = (exit_val->getZExtValue() - init_val->getZExtValue()) /
77:                     (step->getZExtValue());
78:   if (((exit_val->getZExtValue() - init_val->getZExtValue()) %
79:        (step->getZExtValue())))
80:     result++;
81:
82:   llvm::errs() << "loop count = " << result << "\n";
83:
84:   return false;
85: }
86:
87: void LPCountPass::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
88:   // 変更を加えない
89:   AU.setPreservesAll();
90: }
91:
92: char LPCountPass::ID = 0;
93: static llvm::RegisterPass<LPCountPass>
94: X("lpcount", "lpcount pass : count loop iterate time", false, false);

以上で実装は完了です．

6.6.4 コンパイルと実行

それでは実装したPassのコンパイルと実行結果の確認を行いましょう．まず，ここまで実装したソースコードはそれぞれlpcount.cppとlpcount.hppという名前で，srcディレクトリとincディレクトリ配下に配置されていることとします．その場合，下記コマンドでコンパイルするとlibディレクトリ配下に.soファイルが出きるはずです．

# lpcount.cpp，lpcount.hppという名前で保存されているとする
$ g++ -g ./src/lpcount.cpp -I./inc `llvm-config --cxxflags \
  --ldflags --libs` -ldl -pthread -c -o lpcount.o
# -sharedオプションをつけて共有ライブラリを作成
$ g++ -g -shared -o ./bin/lpcount.so lpcount.o

生成したPassのオブジェクトはoptコマンドでLLVM IRに適用できます．冒頭で示したLLVM IRがloop.llという名前であれば，下記のコマンドで実行できます．

# 対象のLLVM IRはloop.llという名前で保存されているとする
$ opt -load ./bin/lpcount.so -lpcount ./sample/loop.ll -S > /dev/null
loop count = 10

6.6.5 ScalarEvolutionクラスを使用する場合

最初に説明した通り，Loopの繰り返し回数の計算はLLVM のScalarEvolutionクラスに既に用意されています．ScalarEvolutionはループの帰納変数の情報などを解析するパスで，ScalarEvolutionに用意されたメソッドの一つを利用するとループの繰り返し回数を取得できます．

Passの宣言

早速ScalarEvolutionクラスを利用する場合の例を確認していきましょう．まずは実装するPassクラスの宣言ですが，これは先程と同様にLoopPassを継承することにしましょう．クラス名をSCEVSampleとした場合，例えば下記の様に宣言すれば良いでしょう．

リスト6.25: SCalaraEvolutionクラスを使用する場合の定義例

 1: class SCEVSample : public llvm::LoopPass {
 2: public:
 3:   static char ID;
 4:   SCEVSample() : llvm::LoopPass(ID) {}
 5:   ~SCEVSample() {}
 6:
 7:   virtual bool runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM);
 8:   virtual void getAnalysisUsage(llvm::AnalysisUsage &AU) const;
 9: };

runOnLoopの実装

次にrunOnLoopの実装を見ていきましょう．今回はScalarEvolutionクラスを利用するため，まず最初にgetAnalysisUsageメソッド内でaddRequiredメソッドを利用してその旨を記述しておく必要があります．また，LLVM IRには変更を加えないため今回もsetPreservesAllメソッドを呼んでおくべきです．ついでですので，ここでRegisterPassテンプレートによるPassの登録も行っておくことにします．今回は“scevsample”という名前で登録しましょう．ここまでの内容については，特に問題ないと思いますので，まずはここまでの実装を確認しましょう．

リスト6.26: SCalaraEvolutionクラスを使用する場合の例

 1: bool SCEVSample::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:
 3:   ・・・後ほど説明・・・
 4:
 5:   return false;
 6: }
 7:
 8: void SCEVSample::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
 9:   // 変更を加えない
10:   AU.setPreservesAll();
11:
12:   // ScalarEvolutionを利用するのでaddRequired
13:   AU.addRequired<llvm::ScalarEvolution>();
14: }
15:
16: char SCEVSample::ID = 0;
17: static llvm::RegisterPass<SCEVSample>
18: X("scevsample", "analyze loop trip count using scalar evolution", false, false);

次はrunOnLoopの実装です．まずはgetAnalysisUsageメソッド内で登録しておいたScalarEvolutionを取得する必要があります．これは6.3.2節で紹介したgetAnalysisメソッドを使用します．使用方法がわからない方は6.3.2節を再度確認してみてください．

次に，繰り返し回数を取得する為のScalarEvolutionクラスのメソッドを紹介します．Loopの繰り返し回数はgetSmallConstantTripCountメソッドを使用することで取得できます．

llvm::ScalarEvolution::getSmallConstantTripCount

unsigned ScalarEvolution::getSmallConstantTripCount(Loop *L,
                                                    BasicBlock *ExitingBlock)

llvm::ScalarEvolution::getSmallConstantTripCountは第一引数に対象のLoopを，第二引数にLoopから抜けるBasicBlockを指定して呼び出すと，対象のLoopの最大繰り返し回数を符号無し整数で返却します．繰り返し回数が解析できない，もしくは定数でない場合には0が返却されます．ここで，返却される繰り返し回数は第二引数で指定したBasicBlockを通って終了するという前提で計算されます．

今回の例では，第一引数にはrunOnLoopの引数で渡されるLoopを，第二引数にはそのLoopのgetExitingBlockメソッドで取得したBasicBlockを渡せば良いでしょう．今回紹介したメソッドを使用すると，runOnLoopは下記の様になります．

リスト6.27: SCalaraEvolutionクラスを使用する場合の例

 1: bool SCEVSample::runOnLoop(llvm::Loop *L, llvm::LPPassManager &LPM) {
 2:   // getAnalysisでScalarEvolutionを取得
 3:   llvm::ScalarEvolution *SE = &(getAnalysis<llvm::ScalarEvolution>());
 4:
 5:   // ExitingBlockを取得
 6:   llvm::BasicBlock *exiting_bb = L->getExitingBlock();
 7:   if (exiting_bb == NULL)
 8:     return false;
 9:   unsigned int trip_count = SE->getSmallConstantTripCount(L, exiting_bb);
10:
11:   llvm::errs() << "loop trip count = " << result << "\n";
12:
13:   return false;
14: }
15:
16: void SCEVSample::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
17:   // 変更を加えない
18:   AU.setPreservesAll();
19:   // ScalarEvolutionを利用するのでaddRequired
20:   AU.addRequired<llvm::ScalarEvolution>();
21: }
22:
23: char SCEVSample::ID = 0;
24: static llvm::RegisterPass<SCEVSample>
25: X("scevsample", "analyze loop trip count using scalar evolution", false, false);

自分で実装した場合に比べてかなりスッキリと楽に実装できてしまいましたね．

コンパイルと実行

念のため，ScalarEvolutionを利用した場合のPassについても動作確認を行っておきましょう．まず，下記コマンドでコンパイルします．これまで同様ソースコードはscevsample.cpp，scevsample.hppという名前でそれぞれsrcディレクトリとincディレクトリに配置されていることとします．

# scevsample.cpp，scevsample.hppという名前で保存されているとする
$ g++ -g ./src/scevsample.cpp -I./inc `llvm-config --cxxflags \
  --ldflags --libs` -ldl -pthread -c -o scevsample.o
# -sharedオプションをつけて共有ライブラリを作成
$ g++ -g -shared -o ./bin/scevsample.so scevsample.o

生成したオブジェクトは，これまで同様optコマンドでLLVM IRに適用できます．

# 対象のLLVM IRはloop.llという名前で保存されているとする
$ opt -load ./bin/scevsample.so -scevsample ./sample/loop.ll -S > /dev/null
loop trip count = 10

先程と同じく繰り返し回数が出力されていることが確認できましたね．

6.7 適用されるPassを確認する

optで何らかのPassを使用する場合，実際にどのようなPassがどういった順番で実行されているか気になることもあるかと思いますが，そんなときに便利なオプションを紹介します．

optコマンドではオプションとして“-debug-pass=Structure”を指定するとパスの情報を表示できます．

# -debug-pass=Structureを指定してoptを実行
$ opt -load ./bin/easypass.so -easypass -debug-pass=Structure ./sample/test.ll -S > /dev/null
Pass Arguments:  -targetlibinfo -targetdata -easypass -preverify -domtree \
-verify -print-module
Target Library Information
Target Data Layout
  ModulePass Manager
    FunctionPass Manager
      easy pass:only print function name
      Preliminary module verification
      Dominator Tree Construction
      Module Verifier
    Print module to stderr
Function Name : test
Function Name : main
Function Name : printnum

ちなみにdebug-passにはStructure以外にも指定可能なパラメータ(デバッグレベル)が存在します．表6.1に指定可能なデバッグレベルと表示内容を示します．

表6.1: debug-passに指定可能なデバッグレベル

指定可能なデバッグレベル | 出力内容
None | デバッグ表示を無効化(デフォルト値)
Arguments | optに渡される引数，つまり実行されるパスを表示
Structure | パス情報をrun()の前に表示
Executions | パスの実行と開放表示
Details | 実行中に詳細を表示

なお，上記表のデバッグレベルは下に行くほどより詳細であり，指定したデバッグレベル以下のレベルの情報はすべて有効になります*1．例えば，Executionsを指定した場合はArgumentsとStructureが有効になります．

[*1] もちろんNoneはのぞく

第7章 バックエンドを作る

7.1 LLVMのバックエンドを学ぶ前に

LLVMのミドルエンドの実装が簡単とは言えませんが，バックエンドは覚えることが多く非常に難しいです．バックエンドは各モジュールが複雑に絡み合って実装されているため，各モジュールがLLVMのバックエンドコアからどのように使用されているかを理解して実装する必要があります．また，LLVMのバックエンドに関する情報はあまり多くありません．公式のドキュメントとしてWriting an LLVM Compiler Backend*4やThe LLVM Target-Independent Code Generator*5などがありますが，これらの情報だけでは全く十分とは言えず，かつ情報が古い部分もあります．ちなみに，LLVM内で命令がどのように変化していくかをまとめた素晴らしいブログエントリ*6がありますのでこちらも参考になるかと思います．公式以外のLLVMに関する情報は最近では多少増えてきましたが，コア部分に関する情報はあまり無いと言えるでしょう．バックエンドに限らず，LLVMについて理解するにはソースコードを読み続けていく必要があります．ソースコード自体は割と丁寧に書かれておりコメントも適度に入っているため，それほどわかりにくいという訳ではありませんが，上述の通り複数のモジュールが複雑に絡み合っているため全体が見えにくいときもあります．

今回この本を執筆するにあたり，試行錯誤しながらバックエンドの実装を行いました．実装したバックエンドは既存のバックエンド(MIPS)を参考にしてはいますが，スクラッチからの実装となっています．機能は最低限しか無く，不完全な部分も多くありますがご了承ください．全て一から実装したことにより，何が実装に必要なのか，どういう仕組みになっているのか，そしてまだ理解が不十分な部分などがよくわかりました．本章では私が理解したことをバックエンドの作成方法を紹介しながらお伝えしたいと思います．

私は一応GCCのバックエンドも少しだけ触ったことがあるため，LLVMとGCCのバックエンドについて比較してみます．実装の容易さのみであればLLVMもGCCもほとんどかわらないと個人的には思います．メインの記述をLLVMではTableGenで行い，GCCではRTLで行っており，それぞれ完全な記述が可能というわけでなく，C/C++での記述で補う必要があることに変わりはありません．しかし，LLVMの方がコード全体が整然としており，特にバックエンドで固有の最適化を行うときにはその実装は容易だと思っています．というわけで，これからコンパイラに関して何かやりたいという場合には断然LLVMをおすすめします．

7.2 LLVMのバックエンドって何するの？

これまではLLVMのフロントエンドでLLVM IRを生成し，ミドルエンドでLLVM IRの最適化を行って来ました．それではLLVMのバックエンドは何をするところなのでしょうか？実はLLVMのバックエンドの目的は次のように多岐に渡ります．

アセンブリ生成

実行オブジェクト生成

LLVM IRの実行(JIT)

コード生成(トランスレータ)

一般的にはコンパイラの出力はアセンブリになりますが，LLVMでは様々な出力を選択できるという事です．

特に注目すべきは実行オブジェクトを直接生成するというところです．LLVMでは一般的なコンパイラと異なり，アセンブリを経由せずに直接実行オブジェクトを生成することができるのです．

LLVM IRの実行というのは更にオブジェクト生成すら経由せずに直接LLVM IRのまま実行できるということです．その際にJIT(Just in Time)コンパイルしながら実行します．

コード生成は他のものよりは特殊で，LLVM IRを(C言語やJavaScriptのような)他の言語に翻訳して出力します．コード生成の目的としては，ある言語からある言語への翻訳することで再利用可能にすることや，逐次処理で記述されたC言語プログラムを並列処理で書かれたC言語プログラムに翻訳し，LLVMとは別のコンパイラに通す前の前処理として利用するなどがあります．

LLVMが流行している理由の1つとして，このJITとコード生成を活用することで，既存の言語や新規言語に対してLLVMの基盤を利用した高度な解析や最適化を容易に適用できることがあげられます．しかし，本書ではJITやトランスレータについては特に触れておらず，アセンブリ生成と実行オブジェクト生成を目的としています．

JITとトランスレータについては以降では紹介しませんのでここで簡単に説明します．

新しいバックエンドをJITに対応させたい場合も一からJITの処理を実装する必要は無く，バックエンド特有の処理を記述するクラスを実装するだけで良いです．JITのコア部分はLLVMのコア実装に含まれているためです．JIT処理自体に興味がある方はLLVMのソースのlib/ExecutionEngine以下を参照すると良いでしょう．

トランスレータについてはアセンブリ生成などと似ているようではありますが，処理の流れとしては全く異なります．最近ではコード生成を行うバックエンドもいくつか出てきたのでそれらの処理を参考にすると良いでしょう．LLVM本体にマージされている実装としてはCppBackend(lib/Target/CppBackend)やNVPTX(lib/Target/NVPTX)があります．NVPTXは非常に大きなプロジェクトで1万行近くあるため，最初に参考にするには難しいかもしれません．よく例として挙げられるのはCBackendですが，LLVM 3.1で削除されてしまいました．3.0までのソースを参考にするのも良いですが，最新版でも動作するようにメンテナンスされているバージョンもあるのでそちらを参考にするのが良さそうです．

7.3 バックエンドの流れ

LLVMのバックエンドはLLVM IRを入力として最終的にはアセンブリやオブジェクトを出力しますが，その過程でフォーマットが何度か変化します．フォーマット変化の流れを図7.1で示しています．フォーマットを変化させることをLoweringと言い，より低レベルの形式へ変化させていることを意味します．

図7.1: フォーマット変化の流れ

バックエンドでもミドルエンドと同様に全ての処理はパスで行われます．バックエンドでは特にMachineFunctionPassを使用します．図で示したSelectionDAGISelやAsmPrinterもMachineFunctionPassの1つです．SelectionDAGISelは(厳密には違いますが)最初に実行されるMachineFunctionPassであり，AsmPrinterが最後に実行されるMachineFunctionPassとなります．中央でMI LayerからMI Layerへ変換しているMachineFunctionPassは，ターゲット依存の最適化などを行うパスになります．

7.3.1 SelectionDAG

バックエンドではまず初めに，LLVM IRからSelectionDAGへフォーマットを変化させます*1．SelectionDAGはLLVM IRをグラフ形式で表したもので，各命令やデータ(レジスタなど)の依存関係を表します．DAGとは有向非巡回グラフ(Directed Acyclic Graph)を意味します．グラフのそれぞれのノードはSDNodeクラスのインスタンスとなります．SDNodeはOpcodeやOperandのリスト，そしてそのSDNodeを使用しているSDNodeのリストなどを持っています．

SelectionDAGへの変形はSelectionDAGISelというMachineFunctionPassの1つで行われます．SelectionDAGISelパスはMachineFunctionPassで最初に実行されるパスで，以降のMachineFunctionPassのために後述するMI(MachineInstr等)を出力します．つまり，SelectionDAGISelパスはLLVM IRを入力としていったんSelectionDAGへ変換した後，MIを出力するパスとなります．

SelectionDAGISelパスは大きく分けて次のフェーズから構成されます．

Lower

Combine

Legalize

Select

Schedule

LowerはLLVM IRからSelectionDAGに変化させるフェーズです．このフェーズでは関数呼び出しなどを除いてLLVM IRから対応するSDNodeへほぼ1対1の変換を行います．この段階ではSelectionDAGが持つ命令やOperandなどはターゲットで利用できないものを含んでいるため，不正(illegal)な状態となります．

Combineはパターンマッチによる置き換えで最適化を行うフェーズで，主な目的は命令をより単純なものに置き換えることです．CombineはLegalizeの前後で何度か実行されます．

Legalizeはターゲットにサポートされていない命令やデータ形式を他のものに置き換えたり削除したりするフェーズです．Legalizeにより不正(illegal)な状態だったSDNodeは全て正当(legal)な状態になります．

SelectはSDNodeからMIの構築に必要な情報を含むSDNodeであるMachineSDNodeに変換するフェーズです．

Scheduleは構築したグラフの依存関係を元に命令をスケジューリングするフェーズです．

これらのフェーズが全て終わったあと，MIを出力します．

上述の通り全ての処理はSelectionDAGISelパスを通して行われますが，ターゲット固有の処理がいくつかあるため，各ターゲットではSelectionDAGISelを継承したクラスを作成し，一部のメソッドをオーバーライドして実装することでターゲット固有の処理が行えるようになります．SelectionDAGISelパスには様々なポイントでフックするためのメソッドがあるので，それらを実装することでより細かい動作を定義することも可能です．Lower, Legalize, Selectの処理を行うメソッドはターゲット毎に必ず実装しなければなりません．

7.3.2 MI Layer

MIはバックエンドでは最も一般的なフォーマットで，通常はこのフォーマットで最適化を行います．LLVM IRが機械語と比較すると非常に抽象的な表現になっているのに対し，MIは機械語に近い表現になっているため，ターゲット依存の最適化を行うのに適しています．MIとは後述するMachineInstrなどのクラスの総称であり，MI LayerとはデータのフォーマットとしてMIを扱うフェーズのことを指します*2．

MIはMachineFunction，MachineBasicBlock，MachineInstr，そしてMachineOperandから構成されます．この構成はLLVM IRとほとんど同じで，使い方も同じようになっています．LLVM IRではIRBuilderでInstructionを生成するように，MachineInstrおよびMachineOperandの生成はMachineInstrBuilderクラスを利用します．BuildMI関数でMachineInstrを引数に指定するとMachineInstrBuilderクラスのインスタンスが生成されるので，MachineInstrBuilderクラスのメソッドを利用してMachineOperandを追加していく形になります．

MIはSelectionDAGISelパスにより生成された直後はまだ，無限個のレジスタを仮定した仮想レジスタやPHI命令を含むSSA形式で表現されています．SSA形式でいくつかの最適化を行ったあと，アセンブリやオブジェクトを生成するAsmPrinterパスまでにレジスタ割り当てやPHI命令の削除を行って非SSA形式へ変換する必要があります．レジスタ割り当てはTargetPassConfig::addOptimizedRegAllocで追加される複数パスからなるようです．MI Layerでの最適化はTargetPassConfig::addMachinePassesで追加されるパスで行われるので興味がある場合にはこれらの処理を追ってみると良いでしょう．ここだけで30個以上のパスが実行されています．

7.3.3 MC Layer

MC Layerは機械語(バイナリ列)レベルの形式を表現したり扱うために利用されます．この段階ではラベル名，マシン命令，セクション，オブジェクトファイルと言った単位で扱います．このMC Layerが何のためにあるかというと，アセンブリ出力やオブジェクト出力，JITなどの処理を共通して扱うための抽象化レイヤーを用意して無駄な処理を無くすためです．

MC Layer(MC)で扱うクラスとしてはMCContext，MCSymbol，MCSection，MCInstなどがありますが，ターゲット実装時に実際に扱うことになるMCInstのみを紹介します*3．MCInstはMC Layerで命令を表現するための唯一のクラスです．MIとMCの違いは，MCInstにはFunctionのような構造が無く，オブジェクトファイルのように全ての命令がフラットに並ぶことです．

MCStreamer APIは最終的なファイル(アセンブリやオブジェクトファイルなど)を出力するための抽象APIです．ファイル毎にMCStreamerクラスを継承したクラスがあるため，それらを利用することで目的の結果を得られます．例えばアセンブリであればMCAsmStreamerクラスであったり，オブジェクトであればMCObjectStreamerクラスであったりします．オブジェクトの場合でもELFオブジェクトの場合は，更に継承したMCELFStreamerクラスになります．MCStreamerにはディレクティブ出力のためのメソッドや命令出力のためのメソッドなどがあります．

MCStreamer APIはMC Layerを入力とするMCStreamerクラスのただのメソッドに過ぎません．MIをMCに変換したり，MCStreamer APIを操作するのはAsmPrinterパスになります．例えば，AsmPrinterパスのアセンブリなどを出力するメソッドであるEmitInstructionでは命令毎にMI(MachineInstr)が引数として渡されるため，MC(MCInst)に変換してからMCStreamer APIに渡すという処理を実装する必要があります．AsmPrinterパスについての詳しい説明は後述します．

7.4 目標を決める

実際にターゲットの仕様を考える前に今回どこまで実装するかを考えましょう．本書ではバックエンド全体が見える程度の機能を実装したいと思います．目標としてはいくつかの命令をアセンブリ出力できて，オブジェクトファイル(ELF)を出力でき，その結果をディスアセンブルできるところまでいきます．本来ならオブジェクトファイルを実行する簡単な処理系を作るところですが，そこまでは時間が足りなかったため，ディスアセンブルにより正しくオブジェクトが生成されているか確かめる程度とします．

コンパイルの対象とするコードも決めました．リスト7.1およびリスト7.2のようなソースがコンパイル出来れば完成としましょう．リスト7.1はsample_addという32bitの整数2つを引数に取り，加算した後，32bit整数として返す関数です．これにより最も単純なコードがコンパイル可能かどうかを確認します．リスト7.2は関数呼び出しを含むコードです．こちらもコードとしてはシンプルですが，関数呼び出しがあることにより呼び出し規約や関数のアドレス解決などが必要になるため，難易度は高くなります．

リスト7.1: 目標のソースコード(sample_add.ll)

 1: define i32 @sample_add(i32 %a, i32 %b) nounwind readnone {
 2: entry:
 3:   %add = add nsw i32 %b, %a
 4:   ret i32 %add
 5: }

リスト7.2: 目標のソースコード(sample_call.ll)

 1: define i32 @add(i32 %a, i32 %b) nounwind readnone {
 2: entry:
 3:   %add = add nsw i32 %b, %a
 4:   ret i32 %add
 5: }
 6:
 7: define i32 @sample_call() nounwind readnone {
 8: entry:
 9:   %call = tail call i32 @add(i32 1, i32 2)
10:   ret i32 %call
11: }

7.5 ターゲットの仕様を決める

さて，目標は決まりましたがターゲットの仕様が決まっていません．今回この本ではオリジナルのターゲットを作成して，その上で目標となるコードをコンパイルできるようにします．ターゲットの名前はSampleとしましょう．ターゲットの実装をする前に，どういうアーキテクチャなのかというのを決める必要があります．私が一番知ってるアーキテクチャがMIPSなのでMIPSに似た仕様で決めていきます．　

まず何を決める必要があるかを考えましょう．大きく分けると命令と呼び出し規約(Calling Convention)があります．それぞれ更に細かく分けると以下のように分類できるでしょう．以降でそれぞれについて詳しく見ていきます．

命令

オプコード

オペランド

レジスタ

フォーマット

呼び出し規約

引数および戻り値の渡し方

スタックフレームの管理

レジスタの扱い

7.5.1 命令

命令を決める，と言った時に考えることはいろいろあります．どんな命令があるのか，各命令のオペランドは何をいくつ取るのか，機械語にするときのエンコーディング，レジスタって何があるの？などなど．x86は少し複雑で私自身あまり知らないので，MIPSを例に説明しましょう．MIPSでは命令の種類は6bitのopcode(命令コード)で定義します(細かく言えばfunctもありますが)．オペランドのとり方には3種類あり，3つレジスタを取るRフォーマットと2レジスタと1つの即値を取るIフォーマット，そして1つの即値を取るJフォーマットがあります．これらの組み合わせを32bit固定で表現しており，各bitのどこからどこまでがopcodeかレジスタかなどが定義されてます．レジスタは計32個あり，一時レジスタ8個，保存レジスタ8個，引数レジスタ4個，戻り値レジスタ2個，あとはスタックレジスタ，フレームレジスタ，戻りアドレスレジスタなどがあります．

今回作成するターゲットでは32bitマシンを前提にして，32bitの固定命令長で考えましょう．命令はロード(load)，ストア(store)，即値ロード(move)，関数呼び出し(call)，呼び出し元ジャンプ(ret)，加算(add)，減算(sub)の7つのみとします．レジスタ数は4bitで表せる16個とします．

レジスタの一覧は表7.1で示しています．各レジスタの概要は後述する呼び出し規約との関連が大きいです．ZEROレジスタはどのような値を入れても値が0で固定されるレジスタです．Vレジスタは関数から戻るときに戻り値を入れるレジスタです．Aレジスタは関数呼び出し時に引数を入れるレジスタです．Tレジスタはなんでも使えるレジスタですが，関数呼び出しをすると戻った時に上書きされている可能性があるレジスタです．SレジスタはTレジスタと同様に何でも使えますが，関数呼び出し後でも値が変化しないことが保証されているレジスタです．SPレジスタはスタックの現在位置アドレスを格納するレジスタです．RAレジスタは関数から戻る先のアドレスを格納するレジスタです．

表7.1: レジスタ一覧

レジスタ番号 | レジスタ名 | 概要
0 | ZERO | 常にゼロを格納
1 | V0 | 戻り値を格納
2 | A0 | 1つ目の引数を格納
3 | A1 | 2つ目の引数を格納
4 | A2 | 3つ目の引数を格納
5 | A3 | 4つ目の引数を格納
6 | T0 | 一時レジスタ
7 | T1 | 一時レジスタ
8 | T2 | 一時レジスタ
9 | T3 | 一時レジスタ
10 | S0 | 保存レジスタ
11 | S1 | 保存レジスタ
12 | S2 | 保存レジスタ
13 | S3 | 保存レジスタ
14 | SP | スタックレジスタ
15 | RA | 戻りアドレスレジスタ

これでレジスタと命令が決まったのでフォーマットを決めましょう．命令の種類はopcodeで表しMSB(Most Significant Bit)から8bit使うことにします．レジスタは4bitで表します．フォーマットの種類としては3種類有り，レジスタを使う数で分けられています．フォーマットを図7.2に示します．

図7.2: 命令フォーマット

まず，レジスタを使わない0レジスタフォーマットはcall命令で使われます．上位8bitがopcodeで残り24bitはアドレスに使われます．1レジスタフォーマットはレジスタ1つと1つの即値を取り，load命令，store命令，ret命令，move命令に使われます．opcodeとレジスタ1で上位12bitを使用し，残りの20bitを即値とします*8．最後の3レジスタフォーマットは3つのレジスタを取る形式で，add命令とsub命令で使われます．opcodeとレジスタ3つで20bitしか使わないため，残りの12bitがあまりますが未使用とします．

7.5.2 呼び出し規約

呼び出し規約はCalling Conventionと言います．関数を呼び出すときや関数から戻るときの決まりごとを定義しています．

引数および戻り値の渡し方

まず関数呼び出し時に引数をどうやって渡すかを定義します．定義したレジスタにある通り引数専用のレジスタが4つあるため，引数4つまではA0から順に割り当てていくということにします．では，5つ以上あった場合はどうしましょうか．方法としてはレジスタに割り当てられない部分をスタックを利用して渡すということが可能です．しかし，今回は実装を簡単にするために引数が5つ以上ある場合はエラーとします．

戻り値の渡し方も同様にVレジスタを使います．Vレジスタは1つしかないため，2つ以上の戻り値がある場合は引数と同様にエラーとします．

スタックフレームの管理

スタックについての詳細は本書では説明しませんが，簡単に説明しておきます．関数呼び出し毎にスタック上に領域を確保します．確保した領域のことをフレームと呼びます．SPレジスタが現在の関数のフレームの先頭を示しています．関数呼び出し時には新しい関数フレームの先頭を示すようにSPレジスタを更新する必要があります．同様に関数から戻るときにも，呼び出し元の関数フレームの先頭を示すようにSPレジスタを戻す必要があります．呼び出し元のフレームの先頭位置を知る方法は2つあります．各フレームサイズが事前に計算可能，もしくは位置をどこかに保存しておくかになります．実装を簡単にするために，フレームサイズは必ず固定長として呼び出し元の関数フレームの先頭位置を現在の位置から単純に求められるようにします．

レジスタの扱い

関数呼び出し時にレジスタをどう扱うかということも規約として決めておかなければいけません．引数のAレジスタ，戻り値のVレジスタ，スタックの位置を管理するSPレジスタについては既に述べました．それ以外のレジスタについても考えましょう．

まずは関数の呼び出し元アドレスを格納するRAレジスタについてです．RAレジスタには関数呼び出し時にアドレスを格納する必要がありますが，これはcall命令が実行された時にハードウェアが自動的に格納するとします*9．呼び出し元アドレスが自動的に保存されるからと言って安心してはいけません．呼び出し先の関数で再度関数呼び出しを行った場合にはRAレジスタが上書きされるため，最初の関数へ戻るためのアドレスが消滅してしまいます．ネストした関数呼び出しにも対応できるようにRAレジスタの値はスタックの最初に保存することにします．

次に一時レジスタであるTレジスタについてです．Tレジスタは関数の呼び出し先で変更される可能性があるレジスタです．関数呼び出しを跨いでTレジスタの値を使う場合には値をスタックに保存しておく必要があります．

保存レジスタであるSレジスタは，Tレジスタと似ていますが，Tレジスタとは逆に呼び出し先で変更してはいけないレジスタです．ただし呼び出し元の関数に戻る時に元の値に戻ってさえいれば良いです．関数内でSレジスタを使う場合には，最初に値をスタックに保存しておき最後に値を戻してあげる必要があります．

それぞれのレジスタは必要に応じてスタックに保存していくことになりますが，これは結局全てのレジスタがスタックに保存される可能性があるということです．今回の実装ではレジスタがどこに格納されているかという管理を簡単にするため，およびスタックフレームサイズを固定長にするために，スタック上に全てのレジスタ分の領域を確保します．

7.6 TableGen

実装するターゲットの仕様が決まったところで実装をすすめたいですが，その前にターゲットの詳細を記述するためのDSL(Domain Specific Language)としてTableGenというものがあります．このTableGenの使い方について説明します*10．

7.6.1 TableGenの目的

TableGenでは命令やレジスタの定義など，大量に記述する必要があるものを簡単に，統一した方法で記述できます．TableGenで記述されたものはLLVMのソースコードがコンパイルされる前に変換が行われて，C++のソースコードになります．生成されたコードはターゲット個別のC++ソースコードからincludeして利用します．TableGenで定義すべきものを表7.2に示します．この他にも記述できるものはありますが最低限これだけで良いです．

表7.2: TableGenで定義すべきクラス一覧

クラス名 | 詳細
Register | レジスタを定義
RegisterClass | レジスタの種類を定義
FuncUnit | 機能ユニットを定義
InstrItinClass | 命令の種類を定義
ProcessorItineraries | IstrItinClassとFuncUnitの関係を定義して命令パイプラインを定義
Operand | オペランドを定義
Instruction | 命令を定義
CallingConv | 呼び出し規約を定義
CalleeSavedRegs | 呼び出し元保存レジスタを定義
InstrInfo | 命令セットを定義
Processor | ハードウェア定義.ProcessorItinerariesを持つ
Target | 命令セットやアセンブリパーサやライタの有無を定義
Subtarget | サブターゲットの定義
AsmWriter | アセンブリ出力の定義　

7.6.2 TableGenの記述方法

TableGenでの記述には主にclassとdefという専用の構文を使います．既に述べた通りTableGenでは専用の文法を持っています．C++と非常に似た文法なのでわかりやすいでしょう．

classは新たに型を定義します．リスト7.3で示すようにclassにはクラス名と複数の値を持つことができます．クラスAでは値としてVという名前をもつbit型を定義してしており，その初期値は0です．クラスBはクラスAを継承しているため，クラスBも同様にbit型のVを持っています．クラスCはクラスAを継承し，更に値としてstrというstring型を定義しており，初期値は“hello”です．もちろんクラスCはクラスAの持っている値Vも持っています．クラスDはクラスAを継承していますが，継承時にAの持つ値Vの初期値を上書きするために，let式を使っています．既に定義された値を再定義する場合にはlet式を使う必要があります．

リスト7.3: TableGenのクラス定義

 1: class A { bit V = 0; }
 2: class B : A;
 3: class C : A {
 4:   string str = "hello";
 5: }
 6: class D : A {
 7:   let V = 1;
 8: }

classでクラス定義しただけでは実体化されていません．リスト7.4で示すように，定義したクラスをdefで実体化する必要があります．一番シンプルなdefの使い方としてはクラスをそのまま実体化させることです．リスト7.4の1行目ではクラスAをXとしてそのまま実体化しています．2-4行目では，クラスCをYとして実体化するときにstring型の値str2を定義しており，その値は“world”です．実体化するときにクラスで定義された値を変更する場合にはlet式を使う必要があります．5-6行目ではlet式を使って，クラスCをZとして実体化するときに値Vと値strを変更しています．let式は複数行に適用したり，ネストさせることもできます．7-11行目では値Vを3で上書きするlet式を複数行に適用させています．let式を複数行に適用させる場合には{}で囲む必要があります．9-10行目ではlet式の中で更にlet式を使っており，ZZZは値strが“Hello World!”で上書きされるだけでなく値Vも3で上書きされます．

リスト7.4: TableGenのクラスの実体化

 1: def X : A;
 2: def Y : C {
 3:   string str2 = "world";
 4: }
 5: let V = 2, str = "Hello World" in
 6:   def Z : C;
 7: let V = 3 in {
 8:   def ZZ : C;
 9:   let str = "Hello World!" in
10:     def ZZZ : C;
11: }

classやdefをまとめて定義する方法としてmulticlassとdefmがあります．これらはマクロのようなもので一定のパターンの定義を一度に実体化させることができます．使える場面も多くあると思いますが，classとdefだけでできないことではないためここでは紹介しません．

他にも制御構造としてforeachを使ったループや!ifを使った条件分岐などが記述可能です．これらの細かい説明はここでは行いませんので，公式のドキュメントやソースコードなどを参考にして下さい．利用できる式などは以降で列挙しています．

7.6.3 TableGenの記述形式

TableGenを記述するために専用の型や構文が用意されています．TableGenで利用できる型の一覧を表7.3に，値の一覧を表7.4に，構文を表7.5に示します．

表7.3: TableGenで利用できる型の種類

型 | 詳細
bit | boolean型0か1を保持できる
int | 32bit整数型
string | 文字列型
bits<n> | 固定長の整数型
list<ty> | 他の型のリスト
Class type | クラス型
dag | 有向グラフ
code | ソースコード

表7.4: TableGenで利用できる値の種類

値 | 詳細
? | 未初期化値
0b101 | 2進数値
0707 | 8進数値
190 | 10進数値
0x7f | 16進数値
"foo" | 文字列
[{ ... }] | ソースコード
[X,Y,Z]<type> | type型のリスト
{a, b, c} | bits<3>の初期化
value | 値の参照
value{17} | 値の特定bitにアクセス
value{15-17} | 値の複数bitにアクセス
DEF | 定義の参照
CLASS<val list> | 引数付きの匿名クラスの参照
X.Y | 値のサブフィールドの参照
list[4-7,17,2-3] | リストスライス

表7.5: TableGenで利用できる構文

構文 | 詳細
foreach<var> = <list> in {<body>} | listの値をvarとしてbodyを実行する
foreach<var> = <list> in <def> | listの値をvarとしてdefを実行する
foreach<var> = 0-15 in ... | 整数値の範囲をvarとして...を実行
foreach<var> = {0-15,32-37} in ... | 整数値の範囲をvarとして...を実行
(DEF a,b) | DAG. 最初のDEFは定義したもの.残りは他の値.
!strconcat(a,b) | 文字列aとbの連結
str1#str2 | str1とstr2を連結して文字列とする.
!cast<type>(a) | シンボルテーブルから文字列aをルックアップしてtype型のシンボルを取得
!subst(a,b,c) | aとbが文字列ならcの中のaをbに置き換える
!foreach(a,b,c) | DAGもしくはlistであるaをbとして操作cを適用
!head(a) | list aの先頭要素を取得
!tail(a) | list aの先頭要素以外の残りの要素を取得
!empty(a) | list aが空かどうかを0か1の整数で示す
!if(a,b,c) | 操作aの結果がゼロでなければbを返し，それ以外ならcを返す
!eq(a,b) | aとbが同じならbit1を返し，それ以外ならbit0を返す(文字列，数値，bitのみ)

7.7 ターゲットのクラス構成

ここから本格的にターゲットを記述していきます．ターゲットに関する仕様はこれまで説明してきた通りなので，具体的にソースコードに落としていくところになります．

都合上全てのソースコードを載せていません．今回使用するソースコードは私のGitHub*11のllvm-sample-targetリポジトリにおいてありますので，そちらを参考にして下さい．

7.7.1 ファイル一覧

はじめにどれくらいのファイルを作成する必要があるのかというのを列挙し，各ファイルについての簡単な説明を付随しておきます．ここで列挙したファイルは全て作成しなければ動作しないと思って良いでしょう．実際には一部のクラス(サブターゲットなど)は無くても動きますが，中身が空っぽでも作っておいた方が良いです．

補足ですが，この定義はこのファイルでしなければいけないということはありません．他のターゲットではほとんどがこの構造で統一されています．他のターゲットの実装を調べるときに参考にしやすいという意味でも，従っておいた方が良いでしょう．

Sample.td TableGenでの定義．

Sample.h ターゲットで利用するパスを宣言．

SampleTargetMachine.{cpp,h} ターゲットを定義する他のクラスのインスタンスを持っている中心となるクラスを定義．他のクラスはこのクラスの(スーパークラスの)参照を持っていることが多い．

SampleSubtarget.{cpp,h} サブターゲットの定義．TableGenで自動生成されるSampleGenSubtargetInfo.incを利用．

SampleInstrInfo.cpp 機械命令に対する動作の詳細を記述する．TableGenで自動生成されるSampleGenInstrInfo.incを利用．

SampleRegisterInfo.{cpp,h} レジスタの詳細を記述する．TableGenで自動生成されるSampleGenRegisterInfo.incを利用．

SampleISelDAGtoDAG.cpp LLVM IRからSelectionDAGを経由してMI Layerに落とすパス(SelectionDAGIsel)の定義．TableGenで自動生成されるSampleGenDAGISel.incを利用．

SampleISelLowering.{cpp,h} LLVM IRからSelectionDAGに落とす定義．SelectionDagISelパスから利用される．TableGenで自動生成されるSampleGenCallingConv.incを利用．

SampleSelectionDAGInfo.{cpp,h} SelectionDagISelパスでターゲット固有の情報を保存・参照するためのクラス．

SampleMachineFunction.{cpp,h} MachineFunction毎にターゲット固有の情報を保存・参照するためのクラス．

SampleMCInstLower.{cpp,h} MachineInstr(MI Layer)をMCInst(MC Layer)に落とす定義

SampleFrameLowering.{cpp,h} フレームの処理を定義．

SampleAsmPrinter.cpp アセンブリ出力するためのパス(AsmPrinter)の定義

TargetInfo/

SampleTargetInfo.cpp ターゲットを外部プログラムから利用できるように登録する関数を定義．

InstPrinter/

SampleInstPrinter.{cpp,h} MCInst(MC Layer)からアセンブリ出力するための定義．TableGenで自動生成されるSampleGenAsmWriter.incを利用．

MCTargetDesc/

SampleAsmBackend.cpp オブジェクトのリロケーション処理を定義．

SampleBaseInfo.h レジスタ番号の対応表．

SampleELFObjectWriter.cpp オブジェクトファイル出力のためのクラス定義．

SampleFixupKinds.h リロケーションのenumを定義．

SampleMCAsmInfo.{cpp,h} ターゲットのアセンブリの詳細を定義．

SampleMCCodeEmitter.cpp コード(機械語)出力のためのクラス定義．TableGenで自動生成されるSampleGenMCCodeEmitter.incを利用．

SampleMCTargetDesc.{cpp,h} ターゲットを外部プログラムから利用できるように登録する関数を定義．TableGenで自動生成されるSampleGenInstrInfo.inc，SampleGenSubtargetInfo.inc，SampleGenRegisterInfo.incを利用．

Disassembler/

SampleDisassembler.cpp ディスアセンブラのクラス定義．TableGenで自動生成されるSampleGenEDInfo.inc，SampleGenDisassemblerTables.incを利用．

7.7.2 クラス一覧

バックエンドで定義するクラスの一覧も見ておきましょう．本書ではクラス単位で説明していくため，こちらの方が重要です．図7.3ではバックエンドで定義するクラスとそれに関連するクラスを用途毎に分けて示しています．下線がついているものがターゲット独自に定義するクラスで，中でも斜体で表記しているもの(SampleGenXXX)はTableGenにより自動生成されるクラスです．実線で指しているものが親クラスで，点線がそのクラスのインスタンスを持っていることを示しています．17個もクラスを定義しなければなりませんが，ほとんど中身が空っぽのクラスもあったりします．図では親クラスまで示していますが，実装時には親クラスの方で扱うことが多いので親クラスがどうなっているか知っておくことは重要です．

図7.3: バックエンドのクラス一覧

用途毎にクラスを分けていますが，それぞれのグループについて簡単に説明しておきます．

・共通クラス

ターゲットの仕様を定義したクラスなどで他の全てのクラスから参照される．

・SelectionDAGISelパス

SelectionDAGISelパスで使われるクラス．

・フレーム処理(PEIパス)

スタックフレームの処理に関するクラス．

・AsmPrinterパス

AsmPrinterパスで使われるクラス．アセンブリやオブジェクトの出力に関わるクラスも含まれる．

・Disassembler

ディスアセンブラの処理に関わるクラス．AsmPrinterパスで使われるクラスとの関わりもありますが，処理としては独立しています．

7.8 TableGenの記述(Sample.td)

既に説明した通り，TableGenで定義すべきものは決まっています．定義を始める前に必要なこととして，ベースとなる定義ファイルをインクルードすることです．ここに全ての定義が書かれているため，迷った時やベースのクラスについて知りたい時はこのファイルを調べましょう．

include "llvm/Target/Target.td"

TableGenの定義は後述するSelectionDAGISelパスやPEIパス(フレーム処理)，AsmPrinterパスなどの様々な場所で利用されます．全ての定義をまとめて記述しているためその説明はかなり長くなりますが，重要なものですので一つ一つ確認していきましょう．

7.8.1 レジスタ

ではまず最初にレジスタを定義しましょう．レジスタはRegisterクラスで定義されており，これをdefで実体化するのみです．ただし，1つ1つdefで実体化すると面倒なので，ある程度共通部分をまとめてから実体化しましょう．

Registerクラスを継承したSampleRegクラスを定義します．NamespaceはTableGenからC++のコードを生成した時のnamespaceになります．ターゲット名と同じ"Sample"を指定します．

その後，SampleRegクラスを各レジスタ毎に実体化していきます(リスト7.5)．DwarfRegNumクラスはおそらく必要無い(デバッグで使うDwarfだと思うので)ですが，一応定義しておきます．

リスト7.5: TableGenでのレジスタ定義

 1: class SampleReg<bits<4> num, string n> : Register<n> {
 2:   field bits<4> num;
 3:   let Namespace = "Sample";
 4: }
 5:
 6: // 汎用レジスタ
 7: def ZERO : SampleReg< 0, "ZERO">, DwarfRegNum<[0]>;
 8: def V0   : SampleReg< 1, "V0">,   DwarfRegNum<[1]>;
 9: def A0   : SampleReg< 2, "A0">,   DwarfRegNum<[2]>;
10: def A1   : SampleReg< 3, "A1">,   DwarfRegNum<[3]>;
11: def A2   : SampleReg< 4, "A2">,   DwarfRegNum<[4]>;
12: def A3   : SampleReg< 5, "A3">,   DwarfRegNum<[5]>;
13: def T0   : SampleReg< 6, "T0">,   DwarfRegNum<[6]>;
14: def T1   : SampleReg< 7, "T1">,   DwarfRegNum<[7]>;
15: def T2   : SampleReg< 8, "T2">,   DwarfRegNum<[8]>;
16: def T3   : SampleReg< 9, "T3">,   DwarfRegNum<[9]>;
17: def S0   : SampleReg< 10, "S0">,  DwarfRegNum<[10]>;
18: def S1   : SampleReg< 11, "S1">,  DwarfRegNum<[11]>;
19: def S2   : SampleReg< 12, "S2">,  DwarfRegNum<[12]>;
20: def S3   : SampleReg< 13, "S3">,  DwarfRegNum<[13]>;
21: def SP   : SampleReg< 14, "SP">,  DwarfRegNum<[14]>;
22: def RA   : SampleReg< 15, "RA">,  DwarfRegNum<[15]>;

Registerの定義が終わったのでRegisterClassを定義しましょう(リスト7.6)．RegisterClassはRegisterの集合を定義します．サブターゲットで特殊なレジスタが存在するなど複数のレジスタの集合がある場合には，その数だけRegisterClassを定義する必要があります．今回のターゲットでは全てのレジスタを1つの集合としているので，1つのRegisterClassに全てのレジスタを登録します．RegisterClass名はCPURegsとしましょう．

リスト7.6: TableGenのレジスタクラス定義

 1: def CPURegs : RegisterClass<"Sample", [i32], 32, (add
 2:   // 戻り値と引数用レジスタ (Return values and Arguments registers)
 3:   V0, A0, A1, A2, A3,
 4:   // 呼び出し元待避レジスタ (Caller saved registers)
 5:   T0, T1, T2, T3,
 6:   // 呼び出し先待避レジスタ (Callee saved registers)
 7:   S0, S1, S2, S3,
 8:   // 予約レジスタ (Reserved registers)
 9:   ZERO, SP, RA)>;

7.8.2 機能ユニット

次に加算や減算などの機能単位での詳細を記述していきます(リスト7.7)．ここで機能間での実行時の制約や命令パイプラインの定義，各機能の実行サイクル数など，細かいレベルの定義が可能です．今回は細かい定義はせずとりあえず使えるようにするため程度で定義しています．

リスト7.7: TableGenでの機能ユニット定義

 1: // 機能ユニット
 2: def ALU : FuncUnit;
 3:
 4: // 命令スケジュール(Instruction Itinerary)
 5: def IICAlu    : InstrItinClass;
 6: def IICLoad   : InstrItinClass;
 7: def IICStore  : InstrItinClass;
 8: def IICBranch : InstrItinClass;
 9: def IICPseudo : InstrItinClass;
10:
11: // Sampleターゲットのプロセッサスケジュール
12: def SampleGenericItineraries : ProcessorItineraries<[ALU], [], [
13:     InstrItinData<IICAlu    , [InstrStage<1,  [ALU]>]>,
14:     InstrItinData<IICLoad   , [InstrStage<1,  [ALU]>]>,
15:     InstrItinData<IICStore  , [InstrStage<1,  [ALU]>]>,
16:     InstrItinData<IICBranch , [InstrStage<1,  [ALU]>]>,
17:     InstrItinData<IICPseudo , [InstrStage<1,  [ALU]>]>
18: ]>;

InstrItinClassは命令を機能ごとにグルーピングしたものです．FuncUnitは複数の機能をまとめたもので，InstrItinClassをグルーピングしたものと考えてよいでしょう．最後にProcessorItinerariesでプロセッサがどんな機能を持っているかの詳細を定義します．InstrItinDataを使って各機能がどの機能ユニットを使って何サイクルで実行できるか定義しています．

全ての命令が1つの機能ユニットに属しており，命令の種類毎にInstrItinClassを定義しています．そして全ての機能が1サイクルで実行可能であるプロセッサを定義しています．

7.8.3 呼び出し規約

次に呼び出し規約について定義します(リスト7.8)．呼び出し規約で定義するのは呼び出し時のレジスタの使い方，関数から戻るときのレジスタの使い方，呼び出し時に保存するレジスタの定義の3つです．

リスト7.8: TableGenでの呼び出し規約定義

 1: // Sample Calling Convention
 2: def CC_Sample : CallingConv<[
 3:   // i8/i16型の引数はi32型に昇格する
 4:   CCIfType<[i8, i16], CCPromoteToType<i32>>,
 5:
 6:   // 整数型はAレジスタに渡す
 7:   CCIfType<[i32], CCAssignToReg<[A0, A1, A2, A3]>>,
 8: ]>;
 9:
10: def RetCC_Sample : CallingConv<[
11:   // i32型はV0レジスタに渡す
12:   CCIfType<[i32], CCAssignToReg<[V0]>>
13: ]>;
14:
15: // 呼び出し先待避レジスタ(Callee-saved register)
16: def CSR_SingleFloatOnly : CalleeSavedRegs<(add (sequence "S%u", 3, 0), RA)>;

CallingConvクラスを使って呼び出し時のレジスタの使い方を定義します．使い方としては，パターンマッチで引数がこの型ならこのレジスタに割り当てますという記述になります．整数型の8bit，16bitなら32bitに拡張し，32bitならA0, A1, A2, A3レジスタに割り当てるようにしています．

次に戻るときの定義ですが，これもCallingConvを使います．こちらは32bitの場合はV0レジスタを使うとだけ定義しています．

最後に関数呼び出し時に保存しなければいけないレジスタを定義します．ターゲットの仕様からS0, S1, S2, S3レジスタを保存する必要があるので， (add S0, S1, S2, S3, RA)と書けばよいですがsequenceを使って記述しています．

7.8.4 ターゲット

まだ命令定義を残していますが，先にターゲットの定義をします(リスト7.9)．ターゲット定義ではProcessorクラスとTargetクラスの実体化が必要です．

リスト7.9: TableGenでのターゲット定義

 1: def SampleInstrInfo : InstrInfo;
 2:
 3: def : Processor<"sample32", SampleGenericItineraries, []>;
 4:
 5: def SampleAsmWriter : AsmWriter {
 6:   string AsmWriterClassName  = "InstPrinter";
 7:   bit isMCAsmWriter = 1;
 8: }
 9:
10: def Sample : Target {
11:   let InstructionSet = SampleInstrInfo;
12:   let AssemblyWriters = [SampleAsmWriter];
13: }

Processorクラスはターゲット持っている機能ユニットと特徴(サブターゲット)を引数で指定して実体化します．例えばターゲットの種類によっては浮動小数点数の機能があったりなかったりする場合には，特徴の異なるProcessorとして実体化します．今回は特にサブターゲットを定義していないので指定せずに実体化します．

Targetクラスでは命令セットとアセンブリのパーサやライタがあるかどうかなどを定義します．今回のターゲットではアセンブリを出力するためにアセンブリライタを定義しています．AsmWriterClassNameで指定した文字列の前にターゲット名を付けたもの，つまりSampleInstPrinterをアセンブリライタとして指定しています．SampleInstPrinterクラスについての詳細はAsmPrinterパスで説明します．

7.8.5 命令

さて最後に命令を定義します．なぜ命令を最後にしたかというと命令の定義は非常に複雑だからです．即値を使うload/storeや関数呼び出しのcallは特に複雑になっています．

命令はInstructionクラスを実体化させるだけで良いのですが，必要なパラメータがいっぱいあるので1つ1つで実体化させるのは面倒です．まず命令のフォーマット毎にテンプレートを作成して実体化させます．

共通フォーマット定義

まずは共通のフォーマットを定義します．リスト7.10に定義を示します．命令長は32bitでOpcodeがMSBの8bitなどのターゲットの仕様はここで定義します．

リスト7.10: TableGenでの命令のフォーマットテンプレート定義

 1: class Format<bits<3> val> {
 2:   bits<3> Value = val;
 3: }
 4:
 5: def Pseudo    : Format<0>;
 6: def FormReg0  : Format<1>;
 7: def FormReg1  : Format<2>;
 8: def FormReg2  : Format<3>;
 9: def FormReg3  : Format<4>;
10:
11: // 共通命令フォーマット
12: class SampleInst<dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin, Format f>
13:       : Instruction {
14:   field bits<32> Inst;
15:   Format Form = f;
16:
17:   bits<8> Opcode = 0;
18:
19:   let Namespace = "Sample";
20:   let Size = 4;
21:   let Inst{31-24} = Opcode;
22:   let OutOperandList = outs;
23:   let InOperandList  = ins;
24:   let AsmString   = asmstr;
25:   let Pattern     = pattern;
26:   let Itinerary   = itin;
27:
28:   bits<3> FormBits = Form.Value;
29:
30:   let DecoderNamespace = "Sample";
31:
32:   field bits<32> SoftFail = 0;
33: }

Instructionクラスで重要な値は，Inst，OutOperandList，InOperandList，AsmString，Pattern，Itineraryです．Instはオブジェクト出力等，バイナリで出力するときにこの値を使います．OutOperandListとInOperandListはその命令の依存関係を示すSDNodeのリストで，それぞれ命令の出力と命令の入力に対応します．AsmStringはこの命令をアセンブリ出力するときの文字列になります．PatternはSelectionDAGでパターンマッチする時のSDNodeです．Itineraryは命令がどのグループに属しているかを示すもので先程InstrItinClassで定義したものです．

次にフォーマット毎の定義を記述します．リスト7.11に各フォーマットの定義を示しています．ターゲットの仕様として決めた通り，フォーマットの種類としては0レジスタフォーマット，1レジスタフォーマット，2レジスタフォーマットの3種類あります．

リスト7.11: レジスタ数毎のフォーマット定義

 1: // 0レジスタフォーマットの定義
 2: class SampleInstFormReg0<bits<8> op, dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin>
 3:       : SampleInst<outs, ins, asmstr, pattern, itin, FormReg0> {
 4:   bits<24> other;
 5:
 6:   let Opcode = op;
 7:
 8:   let Inst{23-0} = other;
 9: }
10:
11: // 1レジスタフォーマットの定義
12: class SampleInstFormReg1<bits<8> op, dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin>
13:       : SampleInst<outs, ins, asmstr, pattern, itin, FormReg1> {
14:   bits<4> rc;
15:   bits<20> other;
16:
17:   let Opcode = op;
18:
19:   let Inst{23-20} = rc;
20:   let Inst{19-0}  = other;
21: }
22:
23: // 3レジスタフォーマットの定義
24: class SampleInstFormReg3<bits<8> op, dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin>
25:       : SampleInst<outs, ins, asmstr, pattern, itin, FormReg3> {
26:   bits<4> rc;
27:   bits<4> ra;
28:   bits<4> rb;
29:   bits<12> other;
30:
31:   let Opcode = op;
32:
33:   let Inst{23-20} = rc;
34:   let Inst{19-16} = ra;
35:   let Inst{15-12} = rb;
36:   let Inst{11-0}  = other;
37: }
38:
39: // 疑似命令の定義
40: class SamplePseudo<dag outs, dag ins, string asmstr, list<dag> pattern>:
41:   SampleInst<outs, ins, asmstr, pattern, IICPseudo, Pseudo> {
42:   let isCodeGenOnly = 1;
43:   let isPseudo = 1;
44: }

図7.2の通りで特に難しい点は無いと思いますが，例としてSampleInstFormReg3で説明します．MSBから8bitはOpcodeとして使っているのでその次のbitから4bitで1レジスタを表すとして3レジスタ分確保します．rc = ra (op) rbとなるように，順番にrc, ra, rbの順に割り当てるとします．残りのbitはとりあえずotherとしておきます．

仕様で定義した命令フォーマットとは別に，実際の命令にはならない疑似命令を定義しておきます．疑似命令ではisCodeGenOnlyとisPseudoを1にしておきます．

add,sub命令

add命令とsub命令を定義する前にSampleInstFormReg3を継承して算術演算用のテンプレートとしてArithLogicInstを定義します．リスト7.12に定義を示します．

リスト7.12: TableGenでのadd，sub命令定義

 1: // 3つのオペランドを取る算術命令用のフォーマット定義
 2: class ArithLogicInst<bits<8> op, string asmstr, SDNode OpNode, InstrItinClass itin, RegisterClass RC>
 3:   : SampleInstFormReg3<op, (outs RC:$rc), (ins RC:$ra, RC:$rb),
 4:                       !strconcat(asmstr, "\t$rc, $ra, $rb"),
 5:                       [(set RC:$rc, (OpNode RC:$ra, RC:$rb))], itin> {
 6: }
 7:
 8: def ADD : ArithLogicInst<0x05, "add", add, IICAlu, CPURegs>;
 9: def SUB : ArithLogicInst<0x06, "sub", sub, IICAlu, CPURegs>;

まずSampleInstFormgReg3に第4引数で実行している!strconcatは第1引数と第2引数の文字列をそのまま連結するものです．つまりasmstrが“add”だった場合“add $rc, $ra, $rb”が生成されます．次に第5引数ではマッチパターンのリストを渡しています．[]はリストで，()で囲まれたものはDAGのパターンを示しています．これは入力として受け取った$raと$rbに対してOpNodeを適用した結果を$rcにセットするというパターンになります．OpNodeは引数で指定されているSDNodeになりますが，今回の例ではaddやsubになります．RCは使用するレジスタの集合を示しています．

最後にArithLogicInstを使ってADDとSUBを定義します．ADDはopcodeが0x05でSUBは0x06としており，InstrItinClassはともにIICAluでRegisterClassはCPURegsです．既に述べたようにこの命令のSDNodeとしてaddとsubをそれぞれ指定しています．これはデフォルト定義されている命令のため，そのまま使うことができます．

move命令

次にmove命令を定義します．リスト7.13でmove命令の定義を示します．move命令はadd命令やsub命令よりも少し複雑になります．

リスト7.13: TableGenでのmove命令定義

 1: // 符号付き20bit整数
 2: def immSExt20 : PatLeaf<(imm), [{ return isInt<20>(N->getSExtValue()); }]>;
 3:
 4: // 即値ロード用のオペランド. 20bit
 5: // 19-0: 符号付き20bit整数
 6: // EncoderMethod: bit列から符号付き20bit整数を取得
 7: def movetarget : Operand<i32> {
 8:   let EncoderMethod = "getMoveTargetOpValue";
 9: }
10:
11: // 即値ロード命令(Load Immediate)
12: class LoadI<bits<8> op, string asmstr>:
13:   SampleInstFormReg1<op, (outs CPURegs:$rc), (ins movetarget:$other),
14:      !strconcat(asmstr, "\t$rc, $other"), [(set CPURegs:$rc, immSExt20:$other)], IICLoad> {
15:   let DecoderMethod = "DecodeMoveTarget";
16: }
17:
18: def MOVE : LoadI<0x02, "move">;

move命令の即値には20bitの符号付き整数を利用するため，専用のSDNodeが必要になります．PatLeafを使って20bitの符号付き整数を定義しています．immは整数値を表すSDNodeで，その中でも20bitで表現可能な整数を符号拡張しています．

次にmove命令の即値用のOperandとしてmovetargetを定義します．move命令の即値は20bitしか使っていないため，32bitのオペランドの中の20bitしか使わないということを明示的に指定する必要があります．この処理を行う関数としてgetMoveTargetOpValueをEncoderMethodで指定します．EncoderMethodを設定するとSampleMCCodeEmitterクラスでオペランドをエンコードするために設定した関数が呼び出されるようになるため，その関数で任意の処理を実装することができます．

次に即値ロード命令用のフォーマットを定義しておきます．注目すべきところはSampleInstFormReg1の第三引数(入力のOperand)にmovetargetを指定しておくことと，第四引数にimmSExt20というPatternを指定しておくことです．DecoderMethodが指定されていますが，これはEncoderMethodとは逆にディスアセンブルするときに命令の何bit目から何bit目がこのOperandである，という処理を行う関数を指定できます．通常はEncoderMethodを指定した場合には対になるDecoderMethodも指定する必要があります．DecoderMethodで指定した関数はSampleDisassemblerで実装する必要があります．

これでmove命令を定義する準備ができたので，後はLoadIを使って実体化するだけです．move命令にはOpcodeとして0x02を割り当てます．

call命令

次にcall命令の定義をリスト7.14に示します．move命令よりもさらに複雑で，SDNodeなど定義する必要があるものが多いです．

リスト7.14: TableGenでのcall命令定義

 1: // iPTR型のオペランドを1つ取るという制約条件
 2: def SDT_SampleCall : SDTypeProfile<0, 1, [SDTCisVT<0, iPTR>]>;
 3:
 4: // Sampleターゲットの関数呼び出し命令のSDNode
 5: def SampleCall : SDNode<"SampleISD::Call", SDT_SampleCall,
 6:                          [SDNPHasChain, SDNPOutGlue, SDNPOptInGlue,
 7:                           SDNPVariadic]>;
 8:
 9: def : Pat<(SampleCall (i32 tglobaladdr:$dst)),
10:           (CALL tglobaladdr:$dst)>;
11: def : Pat<(SampleCall (i32 texternalsym:$dst)),
12:           (CALL texternalsym:$dst)>;
13:
14: def calltarget : Operand<iPTR> {
15:   let EncoderMethod = "getCallTargetOpValue";
16: }
17:
18: class Call<bits<8> op, string asmstr>:
19:   SampleInstFormReg0<op, (outs), (ins calltarget:$other, variable_ops),
20:      !strconcat(asmstr, "\t$other"), [(SampleCall imm:$other)],
21:      IICBranch> {
22:   let isCall=1;
23:   let DecoderMethod = "DecodeCallTarget";
24: }
25:
26: def CALL : Call<0x03, "call">;

SDTypeProfileクラスはSDNodeの条件を定義します．第一引数は出力SDNode数で，第二引数がオペランドの数，第三引数がオペランドの制約条件になります．SDT_SampleCallは1つのオペランドを取り，ポインタ型であるという条件になります．

SDNodeクラスは新しいSDNodeを定義するもので，第一引数にopcode，第二引数に制約条件(SDTypeProfile)，第三引数に特徴(SDNodeProperty)を指定します．特徴には例えば，オペランドが可換であるSDNPCommutativeや可変引数のSDNPVariadicなどがあります．SampleCallはSampleISD::Callをopcodeとして，先ほど定義したSD_SampleCallを条件としたSDNodeになります．

Patクラスは第一引数にマッチしたパターンを第二引数に変換するものです．SelectionDAGISelパスで関数呼び出しがSDNodeとしてSampleCallに変換されるため，それをCALLに対応付けしています．

calltargetは24bitのアドレスを取るためにmove命令と同様に独自のOperandを定義しています．オペランドとしてiPTR型を指定しているのが特徴です．EncoderMethodとしてgetCallTargetOpValueを指定し，この関数で24bitのアドレスを取得します．

関数呼び出し用のフォーマットとしてCallを定義します．isCallを1にしてDecoderMethodを設定します．variable_opsは可変長引数であることを示しています．

最後にCallを使ってcall命令を実体化して完了です．call命令のopcodeは0x03です．

ret命令

ret命令の定義をリスト7.15に示します．call命令と非常に似ているので特に説明することは無いと思います．ret命令のopcodeは0x04です．

リスト7.15: TableGenでのret命令定義

 1: def SDT_SampleRet : SDTypeProfile<0, 1, [SDTCisInt<0>]>;
 2: def SampleRet : SDNode<"SampleISD::Ret", SDT_SampleRet,
 3:                        [SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;
 4:
 5: class RetInst<bits<8> op, string asmstr>:
 6:   SampleInstFormReg1<op, (outs), (ins CPURegs:$rc),
 7:      !strconcat(asmstr, "\t$rc"), [(SampleRet CPURegs:$rc)], IICBranch> {
 8:   let isBranch=1;
 9:   let isTerminator=1;
10:   let isBarrier=1;
11:   let isReturn=1;
12: }
13:
14: def RET : RetInst<0x04, "ret">;

load,store命令

最後にload/store命令の定義をリスト7.16に示します．

リスト7.16: TableGenでの1レジスタ命令定義

 1: // アドレスオペランド
 2: def mem : Operand<i32> {
 3:   let PrintMethod = "printMemOperand";
 4:   let MIOperandInfo = (ops CPURegs, i16imm);
 5:   let EncoderMethod = "getMemEncoding";
 6: }
 7:
 8: def addr : ComplexPattern<iPTR, 2, "SelectAddr", [], []>;
 9:
10: // Load/Store命令共通フォーマット
11: class FMem<bits<8> op, dag outs, dag ins, string asmstr, list<dag> pattern, InstrItinClass itin>
12:       : SampleInstFormReg1<op, outs, ins, asmstr, pattern, itin> {
13:   bits<20> addr;
14:   let Inst{19-0}  = addr;
15:   let DecoderMethod = "DecodeMem";
16: }
17:
18: // Load命令
19: let canFoldAsLoad = 1 in
20: class LoadM<bits<8> op, string asmstr, RegisterClass RC>:
21:   FMem<op, (outs RC:$rc), (ins mem:$addr),
22:      !strconcat(asmstr, "\t$rc, $addr"),
23:      [(set RC:$rc, (load addr:$addr))], IICLoad>;
24:
25: // Store命令
26: class StoreM<bits<8> op, string asmstr, RegisterClass RC>:
27:   FMem<op, (outs), (ins RC:$rc, mem:$addr),
28:      !strconcat(asmstr, "\t$rc, $addr"),
29:      [(store RC:$rc, addr:$addr)], IICStore>;
30:
31: def LOAD  : LoadM<0x00, "load", CPURegs>;
32: def STORE : StoreM<0x01, "store", CPURegs>;

まずメモリのアドレスとして使うOperandを定義します．このOperandは少し特殊で1つのOperandでレジスタ1つと20bitのアドレスを取ります．このような特殊な形式となっているため，アセンブリなどで出力するためにPrintMethodで独自の出力関数を指定しています．PrintMethodで指定する関数はSampleInstPrinterで実装する必要があります．EncoderMethodも指定する必要があります．

ComplexPatternはプログラムで明示的にパターンマッチを記述する必要のある複雑なパターンを定義するために利用します．load/store命令で使うアドレスで利用します．第三引数で指定したSelectAddrをSampleDAGToDAGISelクラスで実装する必要があります．

LoadMとStoreMでアドレスを引数に取るパターンを定義し，実体化させてload/store命令の完成です．

7.8.6 includeファイルの生成

ここまででTableGenでの定義は完了です．実際に利用するときにはC++のコードとして変換しなければいけません．C++への変換はllvm-tblgenコマンドを利用するのですが，LLVMをビルドするときにはMakefileに記述しておくだけで自動生成されます．

lib/Target/Sample/Makefileをリスト7.17のように記述すれば良いです．BUILT_SOURCESでTableGenで生成する種類を指定します．今回使っていないものも指定していますが，特に問題ないので良いでしょう．

リスト7.17: ターゲットのMakefile

 1: LEVEL = ../../..
 2: LIBRARYNAME = LLVMSampleCodeGen
 3: TARGET = Sample
 4:
 5: # Make sure that tblgen is run, first thing.
 6: BUILT_SOURCES = SampleGenRegisterInfo.inc SampleGenInstrInfo.inc \
 7:                 SampleGenAsmWriter.inc SampleGenCodeEmitter.inc \
 8:                 SampleGenDAGISel.inc SampleGenCallingConv.inc \
 9:                 SampleGenSubtargetInfo.inc SampleGenMCCodeEmitter.inc \
10:                 SampleGenEDInfo.inc SampleGenDisassemblerTables.inc
11:
12: DIRS = InstPrinter Disassembler TargetInfo MCTargetDesc
13:
14: include $(LEVEL)/Makefile.common

7.9 共通クラスの実装

共通クラスは特定のパスで使われるという訳ではなく，全てのパスから使われる可能性があるクラスです．最も中心となるのがSampleTargetMachineクラスです．バックエンドで実行されるパスであるMachineFunctionPassはパスマネージャによりrunOnMachineFunctionが実行されますが，その際に引数としてMachineFunctionクラスが渡されます．MachineFunctionクラスからSampleTargetMachineクラスの親クラスであるTargetMachineクラスが得られます．そこからSubtargetクラス，MCInstrInfoクラス，MCRegisterInfoクラスなど全てが取得可能です．

各クラスでどのような関数を実装しなければいけないかは親クラスを見るしかありません．わかりやすい場合には純粋仮想関数になっていますが，必ず実行されるわけではない関数などはllvm_unreachableを呼び出して実行時エラーになるようになっています．必要に応じて子クラスでoverrideしましょう．

7.9.1 SampleTargetMachineクラス

SampleTargetMachineクラスは最も中心となるクラスですが，それ自体では特に重要な実装はもっておらず，他のクラスのインスタンスを持っているくらいです．リスト7.18でクラス定義を示します．ほとんどのメソッドはメンバ変数を取得するだけです．

リスト7.18: SampleTargetMachineのクラス定義

 1: class SampleTargetMachine : public LLVMTargetMachine {
 2:   const DataLayout DL;
 3:   SampleSubtarget Subtarget;
 4:   SampleInstrInfo InstrInfo;
 5:   SampleFrameLowering FrameLowering;
 6:   SampleTargetLowering TLInfo;
 7:   SampleSelectionDAGInfo TSInfo;
 8:
 9:  public:
10:   SampleTargetMachine(const Target &T, StringRef TT,
11:                       StringRef CPU, StringRef FS, const TargetOptions &Options,
12:                       Reloc::Model RM, CodeModel::Model CM,
13:                       CodeGenOpt::Level OL);
14:
15:   virtual const SampleInstrInfo *getInstrInfo() const {
16:     return &InstrInfo;
17:   }
18:   virtual const SampleSubtarget *getSubtargetImpl() const {
19:     return &Subtarget;
20:   }
21:   virtual const SampleRegisterInfo *getRegisterInfo() const {
22:     return &InstrInfo.getRegisterInfo();
23:   }
24:   virtual const DataLayout *getDataLayout() const {
25:     return &DL;
26:   }
27:   virtual const SampleTargetLowering *getTargetLowering() const {
28:     return &TLInfo;
29:   }
30:   virtual const SampleFrameLowering *getFrameLowering() const{
31:     return &FrameLowering;
32:   }
33:   virtual const SampleSelectionDAGInfo* getSelectionDAGInfo() const {
34:     return &TSInfo;
35:   }
36:
37:   virtual TargetPassConfig *createPassConfig(PassManagerBase &PM);
38: };

SampleTargetMachine::createPassConfig

SampleTargetMachineクラスの中で唯一少し特殊なメソッドがcreatePassConfigです．リスト7.19に示している通り，後述するSamplePassConfigクラスを生成して返すだけとなっています．

リスト7.19: SampleTargetMachine::createPassConfig

 1: TargetPassConfig *SampleTargetMachine::createPassConfig(PassManagerBase &PM) {
 2:   return new SamplePassConfig(this, PM);
 3: }

7.9.2 SamplePassConfigクラス

SamplePassConfigクラスはSampleTargetMachineクラスでしか使われません．SamplePassConfigクラスの実装はリスト7.20で示しているのが全てです．このクラスで行うことはaddInstSelectorでこのターゲットで使うSelectionDAGISelパスを登録することです．createSampleISelDAGはSampleターゲットのSelectionDAGISelパスであるSampleDAGToDAGISelクラスを生成するファクトリ関数となります．

リスト7.20: SamplePassConfigクラス

 1: class SamplePassConfig : public TargetPassConfig {
 2:  public:
 3:   SamplePassConfig(SampleTargetMachine *TM, PassManagerBase &PM)
 4:     : TargetPassConfig(TM, PM) {}
 5:
 6:   SampleTargetMachine &getSampleTargetMachine() const {
 7:     return getTM<SampleTargetMachine>();
 8:   }
 9:
10:   virtual bool addInstSelector();
11: };
12:
13: bool SamplePassConfig::addInstSelector() {
14:   addPass(createSampleISelDag(getSampleTargetMachine()));
15:   return false;
16: }

7.9.3 SampleSubtargetクラス

SampleSubtargetクラスは全く利用していないので特に詳しい説明はしません．クラス定義はリスト7.21のようになっています．SampleSubtargetクラスはTableGenによって生成されたSampleGenSubtargetInfo.incをインクルードしており，そこで定義されたSampleGenSubtargetInfoクラスを継承したものです．また，自動生成されたParseSubtargetFeaturesメソッドを使用しています．

リスト7.21: SampleSubtargetのクラス定義

 1: class SampleSubtarget : public SampleGenSubtargetInfo {
 2:   virtual void anchor() {};
 3:   bool ExtendedInsts;
 4:  public:
 5:
 6:   SampleSubtarget(const std::string &TT, const std::string &CPU,
 7:                   const std::string &FS);
 8:
 9:   // TableGenにより自動生成
10:   void ParseSubtargetFeatures(StringRef CPU, StringRef FS);
11: };

7.9.4 SampleInstrInfoクラス

SampleInstrInfoクラスはTableGenで生成されたSampleGenInstrInfoクラスを継承していますが，更にその親クラスはTargetInstrInfoImplクラスです．様々な命令の動作を定義しています．その関数でどのような実装をすればよいかはTargetInstrInfoクラス(TargetInstrInfoImplの親クラス)のヘッダを見ると書いています．

リスト7.22: SampleInstrInfoのクラス定義

 1: class SampleInstrInfo : public SampleGenInstrInfo {
 2:   SampleTargetMachine &TM;
 3:   const SampleRegisterInfo RI;
 4:  public:
 5:   explicit SampleInstrInfo(SampleTargetMachine &TM);
 6:
 7:   virtual const SampleRegisterInfo &getRegisterInfo() const;
 8:
 9:   virtual unsigned isLoadFromStackSlot(const MachineInstr *MI,
10:                                        int &FrameIndex) const;
11:
12:   virtual unsigned isStoreToStackSlot(const MachineInstr *MI,
13:                                       int &FrameIndex) const;
14:
15:   virtual void copyPhysReg(MachineBasicBlock &MBB,
16:                            MachineBasicBlock::iterator MI, DebugLoc DL,
17:                            unsigned DestReg, unsigned SrcReg,
18:                            bool KillSrc) const;
19:   virtual void storeRegToStackSlot(MachineBasicBlock &MBB,
20:                                    MachineBasicBlock::iterator MBBI,
21:                                    unsigned SrcReg, bool isKill, int FrameIndex,
22:                                    const TargetRegisterClass *RC,
23:                                    const TargetRegisterInfo *TRI) const;
24:
25:   virtual void loadRegFromStackSlot(MachineBasicBlock &MBB,
26:                                     MachineBasicBlock::iterator MBBI,
27:                                     unsigned DestReg, int FrameIndex,
28:                                     const TargetRegisterClass *RC,
29:                                     const TargetRegisterInfo *TRI) const;
30:
31:   // 分岐解析
32:   virtual bool AnalyzeBranch(MachineBasicBlock &MBB, MachineBasicBlock *&TBB,
33:                              MachineBasicBlock *&FBB,
34:                              SmallVectorImpl<MachineOperand> &Cond,
35:                              bool AllowModify) const;
36:
37:   virtual unsigned RemoveBranch(MachineBasicBlock &MBB) const;
38:
39:   virtual unsigned InsertBranch(MachineBasicBlock &MBB, MachineBasicBlock *TBB,
40:                                 MachineBasicBlock *FBB,
41:                                 const SmallVectorImpl<MachineOperand> &Cond,
42:                                 DebugLoc DL) const;
43: };

SampleInstrInfo::isLoadFromStackSlot

load命令がスタックスロットから直接ロードする場合にレジスタ番号を返し，それ以外の命令なら0を返す関数を定義します．

SampleInstrInfo::isStoreToStackSlot

store命令がスタックスロットに直接ストアする場合にレジスタ番号を返し，それ以外の命令なら0を返す関数を定義します．

SampleInstrInfo::copyPhysReg

レジスタをコピーするための命令を生成します．実装をリスト7.23に示します．add命令を使ってZEROレジスタとの加算で実装しています．BuildMI関数で命令とレジスタを追加していっています．

リスト7.23: SampleInstrInfo::copyPhysReg

 1: void SampleInstrInfo::
 2: copyPhysReg(MachineBasicBlock &MBB,
 3:             MachineBasicBlock::iterator I, DebugLoc DL,
 4:             unsigned DestReg, unsigned SrcReg,
 5:             bool KillSrc) const {
 6:   unsigned Opc = Sample::ADD;
 7:   unsigned ZeroReg = Sample::ZERO;
 8:   MachineInstrBuilder MIB = BuildMI(MBB, I, DL, get(Opc));
 9:
10:   if (DestReg)
11:     MIB.addReg(DestReg, RegState::Define);
12:
13:   if (ZeroReg)
14:     MIB.addReg(ZeroReg);
15:
16:   if (SrcReg)
17:     MIB.addReg(SrcReg, getKillRegState(KillSrc));
18: }

SampleInstrInfo::storeRegToStackSlot

レジスタを指定のフレームインデックスにstoreする関数です．実装をリスト7.24に示します．引数に保存するレジスタ，保存する位置などが与えられるので，それらから命令を生成します．BuildMI関数を使ってstore命令を生成します．

リスト7.24: SampleInstrInfo::storeRegToStackSlot

 1: void SampleInstrInfo::
 2: storeRegToStackSlot(MachineBasicBlock &MBB, MachineBasicBlock::iterator I,
 3:                     unsigned SrcReg, bool isKill, int FI,
 4:                     const TargetRegisterClass *RC,
 5:                     const TargetRegisterInfo *TRI) const {
 6:   DebugLoc DL;
 7:   if (I != MBB.end()) DL = I->getDebugLoc();
 8:   MachineFunction &MF = *MBB.getParent();
 9:   MachineFrameInfo &MFI = *MF.getFrameInfo();
10:
11:   MachineMemOperand *MMO =
12:     MF.getMachineMemOperand(MachinePointerInfo::getFixedStack(FI),
13:                             MachineMemOperand::MOStore,
14:                             MFI.getObjectSize(FI),
15:                             MFI.getObjectAlignment(FI));
16:
17:   BuildMI(MBB, I, DL, get(Sample::STORE))
18:       .addReg(SrcReg, getKillRegState(isKill))
19:       .addFrameIndex(FI).addImm(0).addMemOperand(MMO);
20: }

SampleInstrInfo::loadRegFromStackSlot

storeRegToStackSlotの逆で指定のフレームインデックスからレジスタにloadする関数です．実装をリスト7.25に示しています．BuildMI関数を使ってload命令を生成します．

リスト7.25: SampleInstrInfo::loadRegFromStackSlot

 1: void SampleInstrInfo::
 2: loadRegFromStackSlot(MachineBasicBlock &MBB,
 3:                      MachineBasicBlock::iterator MI,
 4:                      unsigned DestReg, int FI,
 5:                      const TargetRegisterClass *RC,
 6:                      const TargetRegisterInfo *TRI) const {
 7:   DebugLoc DL;
 8:   if (MI != MBB.end()) DL = MI->getDebugLoc();
 9:   MachineFunction &MF = *MBB.getParent();
10:   MachineFrameInfo &MFI = *MF.getFrameInfo();
11:
12:   MachineMemOperand *MMO =
13:     MF.getMachineMemOperand(MachinePointerInfo::getFixedStack(FI),
14:                             MachineMemOperand::MOLoad,
15:                             MFI.getObjectSize(FI),
16:                             MFI.getObjectAlignment(FI));
17:
18:   BuildMI(MBB, MI, DL, get(Sample::LOAD))
19:       .addReg(DestReg).addFrameIndex(FI).addImm(0).addMemOperand(MMO);
20: }

SampleInstrInfo::AnalyzeBranch/RemoveBranch/InsertBranch

今のところ分岐命令を使っていないため実装していません．

7.9.5 SampleRegisterInfoクラス

SampleRegisterInfoクラスはレジスタ関連の実装になります．クラス定義をリスト7.26に示します．TableGenで生成されたSampleGenRegisterInfoクラスを継承したクラスです．基本的にはそれほど複雑なものはないです．

リスト7.26: SampleRegisterInfoのクラス定義

 1: struct SampleRegisterInfo : public SampleGenRegisterInfo {
 2:   const TargetInstrInfo &TII;
 3:
 4:   SampleRegisterInfo(const TargetInstrInfo &tii);
 5:
 6:   const uint16_t *getCalleeSavedRegs(const MachineFunction* MF = 0) const;
 7:   const uint32_t *getCallPreservedMask(CallingConv::ID) const;
 8:
 9:   BitVector getReservedRegs(const MachineFunction &MF) const;
10:
11:   void eliminateCallFramePseudoInstr(MachineFunction &MF,
12:                                      MachineBasicBlock &MBB,
13:                                      MachineBasicBlock::iterator I) const;
14:
15:   void eliminateFrameIndex(MachineBasicBlock::iterator II,
16:                            int SPAdj, RegScavenger *RS = NULL) const;
17:
18:   unsigned getFrameRegister(const MachineFunction &MF) const;
19: };

SampleRegisterInfo::getCalleeSavedRegs

この関数は関数呼び出し時に保存しなければいけないレジスタのリストを返します．TableGenで生成されたSampleGenRegisterInfo.incに定義されているCSR_SingleFloatOnly_SaveListを返すだけで良いです．

SampleRegisterInfo::getCallPreservedMask

関数呼び出し時に保存しなければいけないレジスタリストのマスクを返します．SampleGenRegisterInfo.incで定義されているCSR_SingleFloatOnly_RegMaskを返すだけで良いです．

SampleRegisterInfo::getReservedRegs

この関数はコンパイラが一時的な値として使ってはいけないレジスタのセット(BitVector)を返す必要があります．今回の仕様ではZERO, SP, RAレジスタは特殊用途で使うため，これらを指定します．

SampleRegisterInfo::eliminateCallFramePseudoInstr

この関数は擬似命令を削除するためのものです．単純に命令を削除するだけで良いです．

SampleRegisterInfo::eliminateFrameIndex

このメソッドではフレームインデックスが擬似的なものになっているのを本来のレジスタに置き換えます．実装をリスト7.27に示します．

リスト7.27: SampleRegisterInfo::eliminateFrameIndex

 1: void SampleRegisterInfo::
 2: eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj,
 3:                     RegScavenger *RS) const {
 4:   MachineInstr &MI = *II;
 5:   const MachineFunction &MF = *MI.getParent()->getParent();
 6:
 7:   // フレームインデックスを探す
 8:   unsigned opIndex;
 9:   for (opIndex = 0; opIndex < MI.getNumOperands(); opIndex++) {
10:     if (MI.getOperand(opIndex).isFI()) break;
11:   }
12:   assert(opIndex < MI.getNumOperands() && "Instr doesn't have FrameIndex operand!");
13:
14:   int FrameIndex = MI.getOperand(opIndex).getIndex();
15:   uint64_t stackSize = MF.getFrameInfo()->getStackSize();
16:   int64_t spOffset = MF.getFrameInfo()->getObjectOffset(FrameIndex);
17:   int64_t Offset = spOffset + stackSize + MI.getOperand(opIndex+1).getImm();
18:   unsigned FrameReg = Sample::SP;
19:
20:   // レジスタと即値を変更する
21:   MI.getOperand(opIndex).ChangeToRegister(FrameReg, false);
22:   MI.getOperand(opIndex+1).ChangeToImmediate(Offset);
23: }

実装としてはまず，与えられたMachineBasicBlockを走査してフレームインデックスがあるMachineInstrを探します．この時点でのMachineInstrをdumpすると以下のようになってます．

LOAD %RA, <fi#0>, 0;

これをChangeToRegisterを使ってオペランドを変えます．ついでに必要があれば即値を変えてもよいでしょう．変更した後は以下のようになるでしょう．この例ではスタックに保存したRAレジスタを取り出そうとしているので，相対位置が60となります．

LOAD %RA, %SP, 60;

SampleRegisterInfo::getFrameRegister

フレームレジスタを返す関数です．仕様でSPレジスタを使うことにしているのでSPレジスタを返すだけです．

7.9.6 SampleMachineFunctionInfoクラス

SampleMachineFunctionInfoクラスはMachineFunctionInfoを継承するクラスです．今の実装では特に使っていないため空っぽになっています．このクラスに任意の値を格納しておくことでMachineFunction単位でターゲット固有の情報を格納することができます．

リスト7.28: SampleMachineFunctionInfoのクラス定義

 1: class SampleMachineFunctionInfo : public MachineFunctionInfo {
 2:   virtual void anchor();
 3:
 4:  public:
 5:   SampleMachineFunctionInfo(MachineFunction& MF) {}
 6: };

7.10 SelectionDAGISelパスの実装

SelectionDAGISelパスはバックエンドで最も最初に実行されるパスです．このパスの最終的な目的はLLVM IRからMIへ変換することです．その間にSelectionDAGと呼ばれるグラフ構造に一度変換し，ターゲット非依存/依存両方の最適化を行います．SelectionDAGISelパスでは常にSelectionDAGを操作していきます．すでに述べたとおりSelectionDAGISelパスはLower，Combine，Legalize，Select，Scheduleの5つのフェーズから成ります．最後のSchedule以外はターゲット毎に実装すべきものがあります．

Lowerフェーズの具体的な処理はSelectionDAGBuilder::visitで行われ，基本的にはLLVM IRからSelectionDAGへの1対1への変換が行われます．関数呼び出しに関してはSampleTargetLoweringクラスのLowerCallとLowerReturnにより別途実装しなければいけません．

CombineフェーズではDAGCombinerクラスにより命令の置き換え処理が行われます．このフェーズで冗長な命令はよりシンプルな命令へと置き換えられます．ターゲット固有の処理を実装する場合にはSampleTargetLowering::PerformDAGCombineをoverrideすることで任意の処理を追加できます．

LegalizeフェーズではSampleTargetLoweringのコンストラクタで指定した内容に従ってターゲットで対応していない命令を対応している命令へと変換します．Legalizeは処理内容により担当するクラスが異なりDAGTypeLegalizerクラス，VectorLegalizerクラス，SelectionDAGLegalizeクラスなどがあります．

SelectフェーズではSelectionDAGの各命令とMIを対応付けするためにSampleDAGToDAGISel::Selectが呼び出されます．Selectフェーズの処理はSelectionDAGISel::DoInstructionSelectionで行われます．

7.10.1 SampleDAGToDAGISelクラス

SampleDAGToDAGISelクラスはSelectionDAGISelクラスを継承したもので，MachineFunctionPassを継承したものでもあります．クラス定義をリスト7.29に示します．特に重要なものはSelectとSelectAddrになります．これらについては個別に詳細を説明していきます．

リスト7.29: SampleDAGToDAGISelのクラス定義

 1: class SampleDAGToDAGISel : public SelectionDAGISel {
 2:   SampleTargetMachine &TM;
 3:   const SampleSubtarget &Subtarget;
 4:
 5:  public:
 6:   explicit SampleDAGToDAGISel(SampleTargetMachine &tm) :
 7:   SelectionDAGISel(tm),
 8:   TM(tm),
 9:   Subtarget(tm.getSubtarget<SampleSubtarget>()) {}
10:
11:   // パス名
12:   virtual const char *getPassName() const {
13:     return "Sample DAG->DAG Pattern Instruction Selection";
14:   }
15:
16: private:
17:   // TableGenで自動生成されたメソッドをインクルード
18:   #include "SampleGenDAGISel.inc"
19:
20:   const SampleTargetMachine &getTargetMachine() {
21:     return static_cast<const SampleTargetMachine &>(TM);
22:   }
23:
24:   const SampleInstrInfo *getInstrInfo() {
25:     return getTargetMachine().getInstrInfo();
26:   }
27:
28:   SDNode *Select(SDNode *N);
29:
30:   // Complex Pattern.
31:   bool SelectAddr(SDValue N, SDValue &Base, SDValue &Offset);
32: };

SampleDAGToDAGISel::Select

Selectは必ず実装しなければならないメソッドです．実装をリスト7.30に示します．

リスト7.30: SampleDAGToDAGISel::Select

 1: SDNode* SampleDAGToDAGISel::Select(SDNode *Node) {
 2:   // TableGenにより自動生成された関数を呼び出す
 3:   SDNode *ResNode = SelectCode(Node);
 4:   return ResNode;
 5: }

メインの処理はTableGenで生成されたSampleGenDAGISel.incの中で共通処理であるSelectCode関数が定義されてます．Select関数ではこのSelectCode関数を使います．これは名前の通りSelectionDAGISelパスのSelectフェーズで実行されるものです．命令(SDNode)に対応するMIをTableGenにより自動生成されたテーブルから引いて，MIの情報を持ったSDNodeを返します．この対応関係はほとんど全てがTableGenにより生成されますが，ComplexPatternなど特殊なパターンマッチを必要とするものは対応する関数がコールバックされます．

SampleDAGToDAGISel::SelectAddr

これはTableGenで定義したComplexPatternでSelectAddrを指定しているため，必ず実装する必要があります．実装をリスト7.31に示します．Selectフェーズでload/store命令を扱うときにSelectCodeの内部から呼び出されます．

リスト7.31: SampleDAGToDAGISel::SelectAddr

 1: bool SampleDAGToDAGISel::
 2: SelectAddr(SDValue N, SDValue &Base, SDValue &Offset) {
 3:   EVT ValTy = N.getValueType();
 4:
 5:   if (FrameIndexSDNode *FIN = dyn_cast<FrameIndexSDNode>(N)) {
 6:     Base   = CurDAG->getTargetFrameIndex(FIN->getIndex(), ValTy);
 7:     Offset = CurDAG->getTargetConstant(0, ValTy);
 8:     return true;
 9:   }
10:
11:   llvm_unreachable("Unknown pattern");
12:   return true;
13: }

SelectAddrでは第一引数で受け取った値をレジスタ部分と即値部分に分けて返します．

7.10.2 SampleTargetLoweringクラス

SampleTargetLoweringクラスはSelectionDAGISelパスにおいて，LLVM IRからSelectionDAGに落とす(Lowering)ときにターゲット固有の処理を記述するクラスです．クラス定義をリスト7.32に示します．

リスト7.32: SampleTargetLoweringのクラス定義

 1: class SampleTargetLowering : public TargetLowering {
 2:   const SampleSubtarget &Subtarget;
 3:
 4:  public:
 5:   explicit SampleTargetLowering(SampleTargetMachine &TM);
 6:
 7:   // Customで指定した操作のLowering
 8:   virtual SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const;
 9:
10:   // 引数のLowering
11:   virtual SDValue
12:   LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv,
13:                        bool isVarArg,
14:                        const SmallVectorImpl<ISD::InputArg> &Ins,
15:                        DebugLoc dl, SelectionDAG &DAG,
16:                        SmallVectorImpl<SDValue> &InVals) const;
17:
18:   // 関数呼び出しのLowering
19:   virtual SDValue
20:   LowerCall(CallLoweringInfo &CLI,
21:             SmallVectorImpl<SDValue> &InVals) const;
22:
23:   // 戻り値のLowering
24:   virtual SDValue
25:   LowerCallResult(SDValue Chain, SDValue InFlag,
26:                   CallingConv::ID CallConv, bool isVarArg,
27:                   const SmallVectorImpl<ISD::InputArg> &Ins,
28:                   DebugLoc dl, SelectionDAG &DAG,
29:                   SmallVectorImpl<SDValue> &InVals) const;
30:
31:   // returnのLowering
32:   virtual SDValue
33:   LowerReturn(SDValue Chain,
34:               CallingConv::ID CallConv, bool isVarArg,
35:               const SmallVectorImpl<ISD::OutputArg> &Outs,
36:               const SmallVectorImpl<SDValue> &OutVals,
37:               DebugLoc dl, SelectionDAG &DAG) const;
38: };

SampleTargetLoweringクラスのメソッド名を見るとわかりますが，LowerOperationを除くと呼び出し規約に関わる部分を独自に実装しなければなりません．基本的にはSelectionDAGへのLoweringは1対1で行われるため特別な記述は必要ありませんが，呼び出し規約はターゲット毎に仕様が異なるため，Loweringする際に対応するメソッドがコールバックされる仕組みになっています．

SampleTargetLoweringのコンストラクタ

コンストラクタではターゲットがサポートしている仕様について設定する必要があります．コンストラクタの実装をリスト7.33に示します．

リスト7.33: SampleTargetLoweringのコンストラクタ

 1: SampleTargetLowering::
 2: SampleTargetLowering(SampleTargetMachine &TM)
 3:   : TargetLowering(TM, new TargetLoweringObjectFileELF()),
 4:     Subtarget(*TM.getSubtargetImpl()) {
 5:
 6:   // booleanをどう表すかを定義
 7:   setBooleanContents(ZeroOrOneBooleanContent);
 8:   setBooleanVectorContents(ZeroOrOneBooleanContent);
 9:
10:   // ターゲットで利用できるレジスタを登録
11:   addRegisterClass(MVT::i32, Sample::CPURegsRegisterClass);
12:
13:   // (符号)拡張ロード命令が対応していない型の操作方法を登録
14:   setLoadExtAction(ISD::EXTLOAD,  MVT::i1,  Promote);
15:   setLoadExtAction(ISD::EXTLOAD,  MVT::i8,  Promote);
16:   setLoadExtAction(ISD::EXTLOAD,  MVT::i16, Promote);
17:   setLoadExtAction(ISD::ZEXTLOAD, MVT::i1,  Promote);
18:   setLoadExtAction(ISD::ZEXTLOAD, MVT::i8,  Promote);
19:   setLoadExtAction(ISD::ZEXTLOAD, MVT::i16, Promote);
20:   setLoadExtAction(ISD::SEXTLOAD, MVT::i1,  Promote);
21:   setLoadExtAction(ISD::SEXTLOAD, MVT::i8,  Promote);
22:   setLoadExtAction(ISD::SEXTLOAD, MVT::i16, Promote);
23:
24:   // 関数のアラインメント
25:   setMinFunctionAlignment(2);
26:
27:   // スタックポインタのレジスタを指定
28:   setStackPointerRegisterToSaveRestore(Sample::SP);
29:
30:   // レジスタの操作方法を計算
31:   computeRegisterProperties();
32: }

まず，addRegisterClassでどの型(32bit整数型とか)がどのレジスタクラスで対応しているかを対応付けします．

次に，ターゲットでサポートしてない命令(SelectionDAG)を処理するためにコールバックに登録します．コールバックの種類はPromote, Expand, Custom, Legalの4つがあります．

Promoteはターゲットでサポートされてない型をより大きな型へ拡張して対応させるものです．例えば，i1型に対する操作をターゲットで対応していなくても，i8型なら対応している場合はPromoteすれば良いです．Expandは他の命令(もしくは命令郡)で対応させるようにするものです．Customは特別な操作が必要な命令のために独自でその操作を実装するためのものです．例えば，特定のレジスタが必要であったりスタック操作が必要であったりする命令などです．Customで指定した命令はLowerOperationでその命令に対する操作を実装する必要があります．Legalはその命令がターゲットで対応されていることを示すものです．Legalはデフォルトで指定されているため改めて使用することはほとんどないですが，一旦Expandで指定したものを(サブターゲットで)後からLegalにする場合などで使います．

とりあえず今回の実装では整数型のPromoteのみを一通りしています．最後にcomputeRegisterPropertiesを呼び出して設定完了です．

SampleTargetLowering::LowerOperation

LowerOperationはsetOperationActionでCustomに指定したDAGノードに対する操作を実装する関数です．第一引数のOpのgetOpcodeメソッドで操作すべきopcodeが得られるので，switchで分岐して個別の操作を実装します．

Customに指定しているノードがない場合には特に実装する必要はありません．

SampleTargetLowering::LowerFormalArguments

LowerFormalArgumentsでは関数呼び出し時の引数の処理をします．実装をリスト7.34に示します．

リスト7.34: SampleTargetLowering::LowerFormalArguments

 1: SDValue SampleTargetLowering::
 2: LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv,
 3:                      bool isVarArg,
 4:                      const SmallVectorImpl<ISD::InputArg> &Ins,
 5:                      DebugLoc dl, SelectionDAG &DAG,
 6:                      SmallVectorImpl<SDValue> &InVals) const {
 7:   MachineFunction &MF = DAG.getMachineFunction();
 8:   MachineFrameInfo *MFI = MF.getFrameInfo();
 9:   MachineRegisterInfo &RegInfo = MF.getRegInfo();
10:
11:   // 引数を格納する
12:   SmallVector<CCValAssign, 16> ArgLocs;
13:   CCState CCInfo(CallConv, isVarArg, DAG.getMachineFunction(),
14:                  getTargetMachine(), ArgLocs, *DAG.getContext());
15:   CCInfo.AnalyzeFormalArguments(Ins, CC_Sample);
16:
17:   for (unsigned i = 0, e = ArgLocs.size(); i != e; ++i) {
18:     CCValAssign &VA = ArgLocs[i];
19:     if (VA.isRegLoc()) {
20:       // 引数がレジスタ経由で渡された場合
21:       EVT RegVT = VA.getLocVT();
22:       // 使用するレジスタクラスを指定
23:       const TargetRegisterClass *RC = Sample::CPURegsRegisterClass;
24:
25:       if (VA.getLocInfo() != CCValAssign::Full) {
26:         llvm_unreachable("not supported yet");
27:       }
28:
29:       // 仮想レジスタを作成
30:       unsigned VReg = RegInfo.createVirtualRegister(RC);
31:       RegInfo.addLiveIn(VA.getLocReg(), VReg);
32:       SDValue ArgValue = DAG.getCopyFromReg(Chain, dl, VReg, RegVT);
33:       InVals.push_back(ArgValue);
34:     } else { // VA.isRegLoc()
35:       // 引数がスタック経由で渡された場合
36:       assert(VA.isMemLoc());
37:
38:       // フレームインデックスを作成する
39:       unsigned ObjSize = VA.getLocVT().getSizeInBits()/8;
40:       int FI = MFI->CreateFixedObject(ObjSize, VA.getLocMemOffset(), true);
41:
42:       // スタックから引数を取得するためにloadノードを作成する
43:       SDValue FIN = DAG.getFrameIndex(FI, MVT::i32);
44:       InVals.push_back(DAG.getLoad(VA.getLocVT(), dl, Chain, FIN,
45:                                    MachinePointerInfo::getFixedStack(FI),
46:                                    false, false, false, 0));
47:     }
48:   }
49:
50:   return Chain;
51: }

全ての引数を走査して引数がレジスタで渡される場合とスタックで渡される場合で処理を分けます．実際には今回の仕様では引数はレジスタ経由でのみ渡すことにしているため，スタック経由の方は実行されることはありません．レジスタで渡される場合は使用するレジスタクラスを指定して仮想レジスタを作成します．処理が終わったらそれぞれをInValsに追加して終わりです．

SampleTargetLowering::LowerCall

LowerCallでは関数呼び出し時の処理を行います．実装をリスト7.35に示します．

リスト7.35: SampleTargetLowering::LowerCall

 1: SDValue SampleTargetLowering::
 2: LowerCall(CallLoweringInfo &CLI,
 3:           SmallVectorImpl<SDValue> &InVals) const {
 4:   SelectionDAG &DAG                     = CLI.DAG;
 5:   DebugLoc &dl                          = CLI.DL;
 6:   SmallVector<ISD::OutputArg, 32> &Outs = CLI.Outs;
 7:   SmallVector<SDValue, 32> &OutVals     = CLI.OutVals;
 8:   SmallVector<ISD::InputArg, 32> &Ins   = CLI.Ins;
 9:   SDValue InChain                       = CLI.Chain;
10:   SDValue Callee                        = CLI.Callee;
11:   bool &isTailCall                      = CLI.IsTailCall;
12:   CallingConv::ID CallConv              = CLI.CallConv;
13:   bool isVarArg                         = CLI.IsVarArg;
14:
15:   // 末尾呼び出しは未対応
16:   isTailCall = false;
17:
18:   // 関数のオペランドを解析してオペランドをレジスタに割り当てる
19:   SmallVector<CCValAssign, 16> ArgLocs;
20:   CCState CCInfo(CallConv, isVarArg, DAG.getMachineFunction(),
21:              getTargetMachine(), ArgLocs, *DAG.getContext());
22:
23:   CCInfo.AnalyzeCallOperands(Outs, CC_Sample);
24:
25:   // スタックを何Byte使っているか取得
26:   unsigned NumBytes = CCInfo.getNextStackOffset();
27:
28:   // 関数呼び出し開始のNode
29:   InChain = DAG.getCALLSEQ_START(InChain,
30:                                  DAG.getConstant(NumBytes, getPointerTy(), true));
31:
32:   SmallVector<std::pair<unsigned, SDValue>, 4> RegsToPass;
33:   SDValue StackPtr;
34:
35:   // 引数をRegsToPassに追加していく
36:   for (unsigned i = 0, e = ArgLocs.size(); i != e; ++i) {
37:     SDValue Arg = OutVals[i];
38:     CCValAssign &VA = ArgLocs[i];
39:     ISD::ArgFlagsTy Flags = Outs[i].Flags;
40:
41:     // 引数が数値
42:     if (Flags.isByVal()) {
43:       assert(Flags.getByValSize() &&
44:              "ByVal args of size 0 should have been ignored by front-end.");
45:       llvm_unreachable("ByVal arguments are not supported");
46:       continue;
47:     }
48:
49:     // 必要に応じてPromoteする
50:     switch (VA.getLocInfo()) {
51:       default: llvm_unreachable("Unknown loc info!");
52:       case CCValAssign::Full: break;
53:       case CCValAssign::SExt:
54:         Arg = DAG.getNode(ISD::SIGN_EXTEND, dl, VA.getLocVT(), Arg);
55:         break;
56:       case CCValAssign::ZExt:
57:         Arg = DAG.getNode(ISD::ZERO_EXTEND, dl, VA.getLocVT(), Arg);
58:         break;
59:       case CCValAssign::AExt:
60:         Arg = DAG.getNode(ISD::ANY_EXTEND, dl, VA.getLocVT(), Arg);
61:         break;
62:     }
63:
64:     // レジスタ経由の引数はRegsToPassに追加
65:     if (VA.isRegLoc()) {
66:       RegsToPass.push_back(std::make_pair(VA.getLocReg(), Arg));
67:     } else {
68:       assert(VA.isMemLoc());
69:       llvm_unreachable("MemLoc arguments are not supported");
70:     }
71:   }
72:
73:   // レジスタをコピーするノードを作成
74:   SDValue InFlag;
75:   for (unsigned i = 0, e = RegsToPass.size(); i != e; ++i) {
76:     InChain = DAG.getCopyToReg(InChain, dl, RegsToPass[i].first,
77:                              RegsToPass[i].second, InFlag);
78:     InFlag = InChain.getValue(1);
79:   }
80:
81:   if (GlobalAddressSDNode *G = dyn_cast<GlobalAddressSDNode>(Callee)) {
82:     Callee = DAG.getTargetGlobalAddress(G->getGlobal(), dl, MVT::i32);
83:   } else if (ExternalSymbolSDNode *E = dyn_cast<ExternalSymbolSDNode>(Callee)) {
84:     Callee = DAG.getTargetExternalSymbol(E->getSymbol(), MVT::i32);
85:   }
86:
87:   SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue);
88:   SmallVector<SDValue, 8> Ops;
89:   Ops.push_back(InChain);
90:   Ops.push_back(Callee);
91:
92:   // 引数のレジスタをリストに追加
93:   for (unsigned i = 0, e = RegsToPass.size(); i != e; ++i) {
94:     Ops.push_back(DAG.getRegister(RegsToPass[i].first,
95:                                   RegsToPass[i].second.getValueType()));
96:   }
97:
98:   if (InFlag.getNode())
99:     Ops.push_back(InFlag);
100:
101:   InChain = DAG.getNode(SampleISD::Call, dl, NodeTys, &Ops[0], Ops.size());
102:   InFlag = InChain.getValue(1);
103:
104:   // 関数呼び出し終了のNode
105:   InChain = DAG.getCALLSEQ_END(InChain,
106:                                DAG.getConstant(NumBytes, getPointerTy(), true),
107:                                DAG.getConstant(0, getPointerTy(), true),
108:                                InFlag);
109:   InFlag = InChain.getValue(1);
110:
111:   // 戻り値の処理
112:   return LowerCallResult(InChain, InFlag, CallConv, isVarArg,
113:                          Ins, dl, DAG, InVals);
114: }

このメソッドでもSelectionDAGの操作が行われるため処理が複雑になっています．おおまかな処理の流れとしては以下のようになります．

引数のチェック

DAGの構築

LowerCallResultの呼び出し

まず最初に引数が呼び出し規約通りになっているか確認するための解析を行います．この処理はTableGenによって生成されたCC_Sample関数で行われます．その後，引数のリストを配列として格納します． 今回はレジスタでしか引数を渡さないため単純に配列に追加していくだけになっています．

次にDAGを構築していきます．まず引数の配列をそれぞれ仮想レジスタにコピーするためのノードを作成します．そして，それらを引数に取る関数呼び出しのノードを作成します．関数呼び出しの前後にCALLSEQ_STARTとCALLSEQ_ENDというノードを作成していますが，これは関数呼び出しの開始と終了を意味する擬似命令で，後のパスで削除されます．

ここまでで関数呼び出し処理は終わりになりますが，そのまま戻り値の処理をするためのLowerCallResultを呼び出します．

SampleTargetLowering::LowerCallResult

LowerCallResultは関数呼び出し後に戻り値をどう扱うかの処理を行います．実装をリスト7.36に示します．

リスト7.36: SampleTargetLowering::LowerCallResult

 1: SDValue SampleTargetLowering::
 2: LowerCallResult(SDValue Chain, SDValue InFlag,
 3:                 CallingConv::ID CallConv, bool isVarArg,
 4:                 const SmallVectorImpl<ISD::InputArg> &Ins,
 5:                 DebugLoc dl, SelectionDAG &DAG,
 6:                 SmallVectorImpl<SDValue> &InVals) const {
 7:   SmallVector<CCValAssign, 16> RVLocs;
 8:   CCState CCInfo(CallConv, isVarArg, DAG.getMachineFunction(),
 9:              getTargetMachine(), RVLocs, *DAG.getContext());
10:
11:   CCInfo.AnalyzeCallResult(Ins, RetCC_Sample);
12:
13:   // 結果レジスタをコピー
14:   for (unsigned i = 0; i != RVLocs.size(); ++i) {
15:     Chain = DAG.getCopyFromReg(Chain, dl, RVLocs[i].getLocReg(),
16:                                RVLocs[i].getValVT(), InFlag).getValue(1);
17:     InFlag = Chain.getValue(2);
18:     InVals.push_back(Chain.getValue(0));
19:   }
20:
21:   return Chain;
22: }

処理としては他のターゲットと似たような処理になるようです．AnalyzeCallResult関数を呼び出して，その結果を引数で与えられた結果を格納する変数に入れていくだけです．

SampleTargetLowering::LowerReturn

LowerReturnはLowerFormalArgumentsの逆で関数呼び出しから戻るときの戻り値の処理をします．実装をリスト7.37に示します．

リスト7.37: SampleTargetLowering::LowerReturn

 1: SDValue SampleTargetLowering::
 2: LowerReturn(SDValue Chain,
 3:             CallingConv::ID CallConv, bool isVarArg,
 4:             const SmallVectorImpl<ISD::OutputArg> &Outs,
 5:             const SmallVectorImpl<SDValue> &OutVals,
 6:             DebugLoc dl, SelectionDAG &DAG) const {
 7:
 8:   SmallVector<CCValAssign, 16> RVLocs;
 9:   CCState CCInfo(CallConv, isVarArg, DAG.getMachineFunction(),
10:              getTargetMachine(), RVLocs, *DAG.getContext());
11:
12:   // 戻り値を解析する
13:   CCInfo.AnalyzeReturn(Outs, RetCC_Sample);
14:
15:   // この関数で最初の戻り値の場合はレジスタをliveoutに追加
16:   if (DAG.getMachineFunction().getRegInfo().liveout_empty()) {
17:     for (unsigned i = 0; i != RVLocs.size(); ++i)
18:       if (RVLocs[i].isRegLoc())
19:         DAG.getMachineFunction().getRegInfo().addLiveOut(RVLocs[i].getLocReg());
20:   }
21:
22:   SDValue Flag;
23:
24:   // 戻り値をレジスタにコピーするノードを作成
25:   for (unsigned i = 0; i != RVLocs.size(); ++i) {
26:     CCValAssign &VA = RVLocs[i];
27:     assert(VA.isRegLoc() && "Can only return in registers!");
28:
29:     Chain = DAG.getCopyToReg(Chain, dl, VA.getLocReg(),
30:                              OutVals[i], Flag);
31:
32:     Flag = Chain.getValue(1);
33:   }
34:
35:   // 常に "ret $ra" を生成
36:   if (Flag.getNode())
37:     return DAG.getNode(SampleISD::Ret, dl, MVT::Other,
38:                        Chain, DAG.getRegister(Sample::RA, MVT::i32), Flag);
39:   else // Return Void
40:     return DAG.getNode(SampleISD::Ret, dl, MVT::Other,
41:                        Chain, DAG.getRegister(Sample::RA, MVT::i32));
42: }

戻り値が呼び出し規約とあっているかどうかを解析します．戻り値をレジスタにコピーするノードを作成し，呼び出し元に戻るためのノードとしてret命令を作成します．

7.10.3 SampleSelectionDAGInfoクラス

SampleSelectionDAGInfoクラスはTargetSelectionDAGInfoクラスを継承したものです．SampleMachineFunctionInfoクラスと同様にSelectionDAGISelパスでターゲット固有の情報を格納するために使用します．今回は特に使っていないので空っぽのクラスを作成したのみです．クラス定義をリスト7.38を示します．

リスト7.38: SampleSelectionDAGInfoのクラス定義

 1: class SampleSelectionDAGInfo : public TargetSelectionDAGInfo {
 2: public:
 3:   explicit SampleSelectionDAGInfo(const SampleTargetMachine &TM);
 4:   ~SampleSelectionDAGInfo();
 5: };

7.11 フレーム処理(PEIパス)の実装

SelectionDAGISelが終わった後はMIの状態で様々なパスにより最適化が行われます．その中の一つがフレーム処理を行うPEI(PrologEpilogInserter)パスです．PEI以外のパスは新規ターゲットを作成時にターゲット固有の処理を実装する必要はありませんが，PEIパスはターゲット毎に実装しなければいけません．

PEIパスで行うことは関数呼び出し時またはリターン時のスタックフレームサイズの処理です．なぜこの処理をSelectionDAGISelパスなどではなくこのタイミングで行う必要があるかというと，SelectionDAGISelパスやSelectionDAGISelパスが終了直後のMIがまだ仮想レジスタを使っているため，必要になるスタックサイズがわからないからです．PEIパスの前にレジスタ割り当てを行うパスが実行されているため，この段階で必要となるスタックサイズが判明します．

PEIパスではTargetFrameLoweringクラスのemitPrologueとemitEpilogueを呼び出し，関数(MachineFunction)の最初と最後に行う処理を実行します．ターゲット固有の処理を実装するために，TargetFrameLoweringクラスを継承したSampleFrameLoweringクラスを記述していきます．

7.11.1 SampleFrameLoweringクラス

SampleFrameLoweringクラスはTargetFrameLoweringクラスを継承したクラスです．クラス定義をリスト7.39に示します．重要なメソッドはemitPrologueとemitEpilogueで，これらがPEIパスから呼び出されます．

リスト7.39: SampleFrameLoweringのクラス定義

 1: class SampleFrameLowering : public TargetFrameLowering {
 2:  protected:
 3:   const SampleSubtarget &STI;
 4:
 5:  public:
 6:   explicit SampleFrameLowering(const SampleSubtarget &sti)
 7:       : TargetFrameLowering(StackGrowsDown, 8, 0), STI(sti) {
 8:   }
 9:
10:   void emitPrologue(MachineFunction &MF) const;
11:   void emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const;
12:   bool hasFP(const MachineFunction &MF) const;
13: };

SampleFrameLowering::emitPrologue

emitPrologueは名前通りMachineFunction毎に最初に処理すべきものを定義する関数です．MachineFunctionで必要なスタックサイズを計算し，そのサイズ分のスタックを確保する必要があります．

emitPrologueの実装をリスト7.40に示します．

リスト7.40: SampleFrameLowering::emitPrologue

 1: void SampleFrameLowering::
 2: emitPrologue(MachineFunction &MF) const {
 3:   MachineBasicBlock &MBB   = MF.front();
 4:   MachineFrameInfo *MFI = MF.getFrameInfo();
 5:
 6:   const SampleInstrInfo &TII =
 7:     *static_cast<const SampleInstrInfo*>(MF.getTarget().getInstrInfo());
 8:
 9:   MachineBasicBlock::iterator MBBI = MBB.begin();
10:   DebugLoc dl = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();
11:
12:   // 実装を簡単にするため固定サイズのスタック割り当て(4byte x レジスタ数)
13:   uint64_t StackSize = 4 * 16;
14:
15:   // スタックサイズを保存
16:   MFI->setStackSize(StackSize);
17:
18:   // スタックを伸ばす命令を生成
19:   BuildMI(MBB, MBBI, dl, TII.get(Sample::MOVE), Sample::T0)
20:       .addImm(-StackSize);
21:   BuildMI(MBB, MBBI, dl, TII.get(Sample::ADD), Sample::SP)
22:       .addReg(Sample::SP)
23:       .addReg(Sample::T0);
24: }

今回の実装では実装を簡単にするために，固定サイズのスタック割り当てにしています．本来であれば必要最小限のサイズを計算して割り当てる必要があります．

スタックサイズが決まれば，最後に命令を生成して完了です．即値のロードはmove命令を，レジスタの加算にはadd命令を生成しています．

SampleFrameLowering::emitEpilogue

emitEpilogueはMachineFunctionの最後に処理すべきものを定義します．主にemitPrologueで行なったことの逆のことをすると思います．emitEpilogueの実装をリスト7.41に示します．

リスト7.41: SampleFrameLowering::emitEpilogue

 1: void SampleFrameLowering::
 2: emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const {
 3:   MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr();
 4:   MachineFrameInfo *MFI            = MF.getFrameInfo();
 5:   const SampleInstrInfo &TII =
 6:     *static_cast<const SampleInstrInfo*>(MF.getTarget().getInstrInfo());
 7:   DebugLoc dl = MBBI->getDebugLoc();
 8:
 9:   // FrameInfoからスタックサイズを取得
10:   uint64_t StackSize = MFI->getStackSize();
11:
12:   // スタックを戻す命令を生成
13:   BuildMI(MBB, MBBI, dl, TII.get(Sample::MOVE), Sample::T0)
14:       .addImm(StackSize);
15:   BuildMI(MBB, MBBI, dl, TII.get(Sample::ADD), Sample::SP)
16:       .addReg(Sample::SP)
17:       .addReg(Sample::T0);
18: }

emitPrologueで設定したスタックサイズを取得して，SPレジスタを戻すだけとなります．

SampleFrameLowering::hasFP

ターゲットがフレームポインタを持っているかどうかをbooleanで返します．今回のターゲットの仕様ではフレームレジスタを定義しなかったのでfalseを返しています．

7.12 AsmPrinterの実装

AsmPrinterパスはクラス構成がSelectionDAGISelパスに比べると複雑になっています．AsmPrinterパスでMI LayerからMC Layerへの変換を通して最終的なコード生成，つまりアセンブリやオブジェクトの出力を行います．その過程でどのようなクラスを利用しているかを知っておくとわかりやすいと思います．

AsmPrinterパス(クラス)に関連するクラスを図7.4に示します．AsmPrinterクラスはMachineFunctionPassを継承したパスとなります．SampleAsmPrinterクラスはAsmPrinterクラスを継承して，必要に応じてメソッドをoverrideして実装することによりターゲット固有の処理を実現しています．

図7.4: AsmPrinterのクラス図

AsmPrinterクラスの内部ではMCStreamerクラスを持っており，アセンブリ出力やオブジェクト出力はMCStreamerクラスが担当します．MCStreamerが扱うのはMC Layerとなるため，AsmPrinter側でMI LayerからMC LayerへのLoweringをする必要があります．Loweringの処理はSampleMCInstLowerクラスで行います．

MCStreamerの種類(子クラス)として2種類あり，アセンブリ生成の場合はMCAsmStreamerクラス，オブジェクト生成の場合はMCObjectStreamerクラスがあります．オブジェクト生成の場合でもELFファイル生成の場合はMCObjectStreamerの子クラスであるMCELFStreamerクラスを使います．

MCAsmStreamerクラスはMCInstPrinterクラスを操作してアセンブリ生成を行います．また，MCCodeEmitterクラスとMCAsmBackendクラスが実装されている場合は，これらのクラスを利用してアセンブリ生成時に機械語も出力します．

MCAssemblerクラスはMCObjectWriterクラスを操作してオブジェクト生成を行います．実際にはELFファイルを生成する場合にはその子クラスであるMCELFObjectTargetWriterクラスとなります．オブジェクト生成の場合にはMCCodeEmitterとMCAsmBackendの実装は必須となります．これらのクラスを組み合わせてオブジェクト生成します．

7.12.1 SampleAsmPrinterクラス

SampleAsmPrinterクラスはAsmPrinterクラスを継承したクラスでMachineFunctionPassとなります．SampleAsmPrinterクラスのクラス定義をリスト7.42に示します．

リスト7.42: SampleAsmPrinterのクラス定義

 1: class SampleAsmPrinter : public AsmPrinter {
 2:  public:
 3:   SampleAsmPrinter(TargetMachine &TM, MCStreamer &Streamer)
 4:       : AsmPrinter(TM, Streamer) {}
 5:
 6:   virtual const char *getPassName() const {
 7:     return "Sample Assembly Printer";
 8:   }
 9:
10:   void EmitInstruction(const MachineInstr *MI);
11: };

AsmPrinterパスの本体となる重要なクラスですが，ターゲットで個別に実装する必要があるメソッドはEmitInstructionしかありません．実際には親クラスであるAsmPrinterクラスにほとんどの機能があり，そこから様々なクラスを操作しています．

SampleAsmPrinter::EmitInstruction

EmitInstructionの実装をリスト7.43に示します．

リスト7.43: SampleAsmPrinter::EmitInstruction

 1: void SampleAsmPrinter::
 2: EmitInstruction(const MachineInstr *MI) {
 3:   SampleMCInstLower MCInstLowering(OutContext, *Mang, *this);
 4:   MCInst TmpInst;
 5:   MCInstLowering.Lower(MI, TmpInst);
 6:   OutStreamer.EmitInstruction(TmpInst);
 7: }

このメソッドでは引数で与えられたMachineInstr(MI)をMCInst(MC)に変換する必要があります．実際の変換処理はSampleMCInstrLowerクラスに委譲します．MCInstに変換したあとどのように処理されるかはOutStreamer(MCStreamer)によって抽象化されています．アセンブリを出力する場合でもオブジェクトを出力する場合でもMCStreamerにMCInstを渡すだけで良いです．

7.12.2 SampleMCInstLowerクラス

SampleMCInstLowerクラスは特に何かを継承しているわけではなく，SampleAsmPrinterからのみ使われるクラスになります．このクラスの目的はMachineInstrをMCInstにLoweringすることです．クラス定義をリスト7.44に示します．

リスト7.44: SampleMCInstLowerのクラス定義

 1: class SampleMCInstLower {
 2:   typedef MachineOperand::MachineOperandType MachineOperandType;
 3:   MCContext &Ctx;
 4:   Mangler &Mang;
 5:   AsmPrinter &Printer;
 6:
 7:  public:
 8:   SampleMCInstLower(MCContext &ctx, Mangler &mang, AsmPrinter &printer)
 9:     : Ctx(ctx), Mang(mang), Printer(printer) {}
10:   void Lower(const MachineInstr *MI, MCInst &OutMI) const;
11:
12:  private:
13:   MCOperand LowerOperand(const MachineOperand& MO, unsigned offset = 0) const;
14:   MCOperand LowerSymbolOperand(const MachineOperand &MO,
15:                                MachineOperandType MOTy, unsigned Offset) const;
16: };

他のターゲットの実装を見ると，基本的にはMachineInstrからMCInstに変換するLower関数を外部から利用し，Lower関数の内部ではOperandやシンボルを変換するための個別の関数を利用するという実装になっています．

SampleMCInstLower::Lower

LowerはMachineInstrからMCInstに変換するための基本関数になります．SampleAsmPrinterのEmitInstructionから利用されます．実装をリスト7.45に示します．

リスト7.45: SampleMCInstLower::Lower

 1: void SampleMCInstLower::
 2: Lower(const MachineInstr *MI, MCInst &OutMI) const {
 3:   OutMI.setOpcode(MI->getOpcode());
 4:
 5:   for (unsigned i = 0, e = MI->getNumOperands(); i != e; ++i) {
 6:     const MachineOperand &MO = MI->getOperand(i);
 7:     MCOperand MCOp = LowerOperand(MO);
 8:
 9:     if (MCOp.isValid())
10:       OutMI.addOperand(MCOp);
11:   }
12: }

第一引数で渡されたMachineInstrをMCInstに変換し第二引数で返します．やることは単純でOpcodeとOperandをセットするだけです．Operandをセットする際にはLowerOperandを使っています．

SampleMCInstLower::LowerOperand

LowerOperandの実装をリスト7.46に示します．

リスト7.46: SampleMCInstLower::LowerOperand

 1: MCOperand SampleMCInstLower::
 2: LowerOperand(const MachineOperand& MO) const {
 3:   MachineOperandType MOTy = MO.getType();
 4:   switch (MOTy) {
 5:   case MachineOperand::MO_Register:
 6:     if (MO.isImplicit()) break;
 7:     return MCOperand::CreateReg(MO.getReg());
 8:   case MachineOperand::MO_Immediate:
 9:     return MCOperand::CreateImm(MO.getImm());
10:   case MachineOperand::MO_MachineBasicBlock:
11:   case MachineOperand::MO_GlobalAddress:
12:   case MachineOperand::MO_ExternalSymbol:
13:   case MachineOperand::MO_JumpTableIndex:
14:   case MachineOperand::MO_ConstantPoolIndex:
15:   case MachineOperand::MO_BlockAddress:
16:     return LowerSymbolOperand(MO, MOTy);
17:   case MachineOperand::MO_RegisterMask:
18:     break;
19:   default:
20:     llvm_unreachable("unknown operand type");
21:  }
22:
23:   return MCOperand();
24: }

Lowerから呼び出されるのみの関数になります．MachineOperandを受け取ってMCOperandを返します．Operandの種類によって処理を分岐させており，レジスタや即値の場合はその場で対応するMCOperandを生成し，それ以外はLowerSymbolOperandに処理を委譲しています．

SampleMCInstLower::LowerSymbolOperand

LowerSymbolOperandの実装をリスト7.47に示します．

リスト7.47: SampleMCInstLower::LowerSymbolOperand

 1: MCOperand SampleMCInstLower::
 2: LowerSymbolOperand(const MachineOperand &MO,
 3:                    MachineOperandType MOTy) const {
 4:   switch(MO.getTargetFlags()) {
 5:     default: llvm_unreachable("Invalid target flag!");
 6:     case 0: break;
 7:   }
 8:
 9:   const MCSymbol *Symbol;
10:   unsigned Offset = 0;
11:   switch (MOTy) {
12:   case MachineOperand::MO_MachineBasicBlock:
13:     Symbol = MO.getMBB()->getSymbol();
14:     break;
15:   case MachineOperand::MO_GlobalAddress:
16:     Symbol = Mang.getSymbol(MO.getGlobal());
17:     Offset = MO.getOffset();
18:     break;
19:   case MachineOperand::MO_BlockAddress:
20:     Symbol = Printer.GetBlockAddressSymbol(MO.getBlockAddress());
21:     break;
22:   case MachineOperand::MO_ExternalSymbol:
23:     Symbol = Printer.GetExternalSymbolSymbol(MO.getSymbolName());
24:     break;
25:   case MachineOperand::MO_JumpTableIndex:
26:     Symbol = Printer.GetJTISymbol(MO.getIndex());
27:     break;
28:   case MachineOperand::MO_ConstantPoolIndex:
29:     Symbol = Printer.GetCPISymbol(MO.getIndex());
30:     Offset = MO.getOffset();
31:     break;
32:   default:
33:     llvm_unreachable("<unknown operand type>");
34:   }
35:
36:   const MCExpr *Expr = MCSymbolRefExpr::Create(Symbol, Ctx);
37:
38:   if (Offset) {
39:     const MCConstantExpr *OffsetExpr =  MCConstantExpr::Create(Offset, Ctx);
40:     Expr = MCBinaryExpr::CreateAdd(Expr, OffsetExpr, Ctx);
41:   }
42:
43:   return MCOperand::CreateExpr(Expr);
44: }

LowerOperandから呼び出され，シンボルの処理を行う関数です．ターゲット固有のシンボルなどを定義していないためこれらの処理は他のターゲットで実装されているものと同じになります．

7.12.3 SampleInstPrinterクラス

SampleInstPrinterクラスはMCInstPrinterクラスを継承したクラスです．クラス定義をリスト7.48に示します．

リスト7.48: SampleInstPrinterのクラス定義

 1: class SampleInstPrinter : public MCInstPrinter {
 2:  public:
 3:   SampleInstPrinter(const MCAsmInfo &MAI, const MCInstrInfo &MII,
 4:                   const MCRegisterInfo &MRI)
 5:     : MCInstPrinter(MAI, MII, MRI) {}
 6:
 7:   // MCStreamerから呼び出される
 8:   virtual void printRegName(raw_ostream &OS, unsigned RegNo) const;
 9:   virtual void printInst(const MCInst *MI, raw_ostream &O, StringRef Annot);
10:
11: private:
12:   // TableGenにより自動生成
13:   void printInstruction(const MCInst *MI, raw_ostream &O);
14:   static const char *getRegisterName(unsigned RegNo);
15:
16:   // printInstructionからコールバックされる
17:   void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O);
18:   void printMemOperand(const MCInst *MI, int opNum, raw_ostream &O);
19: };

MCInstPrinterという名前から想像できるようにMCInstを表示，つまりアセンブリを出力するためのクラスになります．アセンブリを出力するMCStreamerであるMCAsmStreamerクラス，もしくはディスアセンブラのllvm-objdumpなどから利用されます．

SampleInstPrinter::printInst

命令を表示するための関数です．実装をリスト7.49に示します．

リスト7.49: SampleInstPrinter::printInst

 1: void SampleInstPrinter::
 2: printInst(const MCInst *MI, raw_ostream &O, StringRef Annot) {
 3:   printInstruction(MI, O);
 4:   printAnnotation(O, Annot);
 5: }

実際には命令を表示する関数はprintInstructionで定義されているため，単に呼び出すだけで問題ないです．printAnnotationはコメントを表示する関数です．

SampleInstPrinter::printRegName

レジスタを表示するための関数です．getRegisterNameでレジスタ番号に対応するレジスタ名を取得できるので，それを利用して表示するだけです．

SampleInstPrinter::printInstruction

この関数はTableGenから生成されたSampleGenAsmWriter.incに実装があるため，別途実装する必要はありません．命令(Opcode)の表示に関してはこの関数の中に全て実装されていますが，Operandの表示は実装されていないためOperandの表示に対応する関数であるprintOperandがコールバックされます．

SampleInstPrinter::getRegisterName

この関数も同様にTableGenから生成されたSampleGenAsmWriter.incに実装があるため，別途実装する必要はありません．レジスタ番号を引数に渡すとレジスタ名を文字列で返してくれる関数です．

SampleInstPrinter::printOperand

デフォルトのOperandを表示するための関数です．実装をリスト7.50に示します．

リスト7.50: SampleInstPrinter::printOperand

 1: void SampleInstPrinter::
 2: printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O) {
 3:   const MCOperand &Op = MI->getOperand(OpNo);
 4:   if (Op.isReg()) {
 5:     printRegName(O, Op.getReg());
 6:   } else if (Op.isImm()) {
 7:     O << Op.getImm();
 8:   } else {
 9:     assert(Op.isExpr() && "unknown operand kind in printOperand");
10:     O << *Op.getExpr();
11:   }
12: }

printInstructionからコールバックで呼び出されます．Operandによってはコールバックで呼び出す関数を指定できますが，特に指定していない場合はprintOperandが呼び出されます．

SampleInstPrinter::printMemOperand

printOperandと同様にOperandを表示するための関数です．実装をリスト7.51に示します．

リスト7.51: SampleInstPrinter::printMemOperand

 1: void SampleInstPrinter::
 2: printMemOperand(const MCInst *MI, int opNum, raw_ostream &O) {
 3:   printOperand(MI, opNum+1, O);
 4:   O << "(";
 5:   printOperand(MI, opNum, O);
 6:   O << ")";
 7: }

TableGenでmemというOperandを定義したときにprintMethodとしてprintMemOperandを指定したため，memのOperandを表示するためにこの関数がprintInstructionからコールバックで呼び出されます．

7.12.4 SampleAsmBackendクラス

SampleAsmBackendクラスは名前と異なりアセンブリというよりはオブジェクトの処理に関わるクラスです．クラス定義をリスト7.52に示します．

リスト7.52: SampleAsmBackendのクラス定義

 1: class SampleAsmBackend : public MCAsmBackend {
 2:   Triple::OSType OSType;
 3:
 4:  public:
 5:   SampleAsmBackend(const Target &T,  Triple::OSType _OSType)
 6:     :MCAsmBackend(), OSType(_OSType) {}
 7:
 8:   MCObjectWriter *createObjectWriter(raw_ostream &OS) const {
 9:     return createSampleELFObjectWriter(OS, OSType);
10:   }
11:
12:   void applyFixup(const MCFixup &Fixup, char *Data, unsigned DataSize,
13:                   uint64_t Value) const;
14:
15:   unsigned getNumFixupKinds() const { return Sample::NumTargetFixupKinds; }
16:
17:   const MCFixupKindInfo &getFixupKindInfo(MCFixupKind Kind) const;
18:
19:   bool mayNeedRelaxation(const MCInst &Inst) const {
20:     return false;
21:   }
22:
23:   bool fixupNeedsRelaxation(const MCFixup &Fixup,
24:                             uint64_t Value,
25:                             const MCInstFragment *DF,
26:                             const MCAsmLayout &Layout) const {
27:     return false;
28:   }
29:
30:   void relaxInstruction(const MCInst &Inst, MCInst &Res) const {
31:   }
32:
33:   bool writeNopData(uint64_t Count, MCObjectWriter *OW) const {
34:     return true;
35:   }
36: };

fixupは現時点では具体的な値を決定できないため，後に値を修正しなければいけないものです．関数のアドレスやグローバル変数のアドレスなど，リンク時にアドレスが決定するものなどが該当します．今回の実装でfixupが関わる部分としてはcall命令のOperandである関数のアドレスです．

SampleAsmBackend::createObjectWriter

createObjectWriterでオブジェクト生成時に使用するObjectWriterクラスのインスタンスを生成します．ObjectWriterクラスのインスタンスを生成するための関数としてcreateSampleELFObjectWriterを作成しているので，それをそのまま呼び出すだけで良いです．createSampleELFObjectWriterはSampleELFObjectWriterクラスのインスタンスを生成する関数です．

SampleAsmBackend::applyFixup

fixupが必要な値を修正する関数です．実装をリスト7.53に示します．

リスト7.53: SampleAsmBackend::applyFixup

 1: void SampleAsmBackend::
 2: applyFixup(const MCFixup &Fixup, char *Data, unsigned DataSize,
 3:            uint64_t Value) const {
 4:   // 修正後の値を求める
 5:   MCFixupKind Kind = Fixup.getKind();
 6:   Value = adjustFixupValue((unsigned)Kind, Value);
 7:
 8:   if (!Value)
 9:     return; // Doesn't change encoding.
10:
11:   // オブジェクトのどこから修正するか
12:   unsigned Offset = Fixup.getOffset();
13:   // 何バイト修正するか
14:   unsigned NumBytes = (getFixupKindInfo(Kind).TargetSize + 7) / 8;
15:
16:   // 現在の値を取得
17:   uint64_t CurVal = 0;
18:
19:   for (unsigned i = 0; i != NumBytes; ++i) {
20:     unsigned Idx = i;
21:     CurVal |= (uint64_t)((uint8_t)Data[Offset + Idx]) << (i*8);
22:   }
23:
24:   uint64_t Mask = ((uint64_t)(-1) >> (64 - getFixupKindInfo(Kind).TargetSize));
25:   CurVal |= Value & Mask;
26:
27:   // 修正後の値を書きだす
28:   for (unsigned i = 0; i != NumBytes; ++i) {
29:     unsigned Idx = i;
30:     Data[Offset + Idx] = (uint8_t)((CurVal >> (i*8)) & 0xff);
31:   }
32: }

adjustFixupValueが実際に値を決定する関数となります．この時点で値を決定できない場合は0が返ってくるため処理が終了します．値が決定できた場合は，Dataから現在の値を取得し，修正後の値で上書きします．

SampleAsmBackend::getNumFixupKinds

fixupの数を返します．SampleFixupKinds.hでターゲットで使用するfixupを定義しているので，ここではSample::NumTargetFixupKindsを返すだけで良いです．SampleFixupKinds.hについては本書では省略していますので，別途ソースコードを参照して下さい．

SampleAsmBackend::getFixupKindInfo

fixupの種類を返します．現在定義しているfixupの種類としてはcall命令のOperandで使うfixup_Sample_24だけです．実装をリスト7.54に示します．

リスト7.54: SampleAsmBackend::getFixupKindInfo

 1: const MCFixupKindInfo &SampleAsmBackend::
 2: getFixupKindInfo(MCFixupKind Kind) const {
 3:   const static MCFixupKindInfo Infos[Sample::NumTargetFixupKinds] = {
 4:     // name                  offset    bits flags
 5:     {"fixup_Sample_24",           0,     24,    0}
 6:   };
 7:
 8:   if (Kind < FirstTargetFixupKind)
 9:     return MCAsmBackend::getFixupKindInfo(Kind);
10:
11:   assert(unsigned(Kind - FirstTargetFixupKind) < getNumFixupKinds() &&
12:          "Invalid kind!");
13:   return Infos[Kind - FirstTargetFixupKind];
14: }

SampleAsmBackend::mayNeedRelaxation/fixupNeedsRelaxation/relaxInstruction

Relaxationというのは命令を(効率の良い)より長い命令で置き換えることを意味します．今回は特に使用していないため未実装となっています．

SampleAsmBackend::writeNopData

NOP(何もしない命令)を挿入するためのメソッドです．今回のターゲットではNOPを定義してないので使っていません．

7.12.5 SampleMCCodeEmitterクラス

SampleMCCodeEmitterクラスはMCInstをエンコードしてバイナリに変換する機能を提供します．クラス定義をリスト7.55に示します．外部から利用されるのはEncodeInstrcutionメソッドだけで，このメソッドから他のメソッドが利用されています．

リスト7.55: SampleMCCodeEmitterのクラス定義

 1: class SampleMCCodeEmitter : public MCCodeEmitter {
 2:   SampleMCCodeEmitter(const SampleMCCodeEmitter &); // DO NOT IMPLEMENT
 3:   void operator=(const SampleMCCodeEmitter &); // DO NOT IMPLEMENT
 4:   const MCInstrInfo &MCII;
 5:   const MCSubtargetInfo &STI;
 6:   MCContext &Ctx;
 7:
 8:  public:
 9:   SampleMCCodeEmitter(const MCInstrInfo &mcii, const MCSubtargetInfo &sti,
10:                       MCContext &ctx)
11:             : MCII(mcii), STI(sti) , Ctx(ctx) {}
12:   ~SampleMCCodeEmitter() {}
13:
14:   // 命令をバイナリにして出力する
15:   void EncodeInstruction(const MCInst &MI, raw_ostream &OS,
16:                          SmallVectorImpl<MCFixup> &Fixups) const;
17:
18:  private:
19:   // 命令のバイナリエンコーディングを取得
20:   uint64_t getBinaryCodeForInstr(const MCInst &MI,
21:                                  SmallVectorImpl<MCFixup> &Fixups) const;
22:
23:   // オペランドのバイナリエンコーディングを取得
24:   unsigned getMachineOpValue(const MCInst &MI,const MCOperand &MO,
25:                              SmallVectorImpl<MCFixup> &Fixups) const;
26:
27:   // load/storeのオペランドのバイナリエンコーディングを取得
28:   unsigned getMemEncoding(const MCInst &MI, unsigned OpNo,
29:                           SmallVectorImpl<MCFixup> &Fixups) const;
30:
31:   // move命令のオペランドのバイナリエンコーディングを取得
32:   unsigned getMoveTargetOpValue(const MCInst &MI, unsigned OpNo,
33:                                 SmallVectorImpl<MCFixup> &Fixups) const;
34:
35:   // call命令のオペランドのバイナリエンコーディングを取得
36:   unsigned getCallTargetOpValue(const MCInst &MI, unsigned OpNo,
37:                                 SmallVectorImpl<MCFixup> &Fixups) const;
38: };

SampleMCCodeEmitter::EncodeInstruction

EncodeInstructionは与えられた命令(MCInst)を出力するための関数です．実装をリスト7.56に示します．

リスト7.56: SampleMCCodeEmitter::EncodeInstruction

 1: void SampleMCCodeEmitter::
 2: EncodeInstruction(const MCInst &MI, raw_ostream &OS,
 3:                   SmallVectorImpl<MCFixup> &Fixups) const {
 4:   uint32_t Binary = getBinaryCodeForInstr(MI, Fixups);
 5:
 6:   // 全ての命令は4バイト
 7:   int Size = 4;
 8:
 9:   for (int i = Size - 1; i >= 0; --i) {
10:     unsigned Shift = i * 8;
11:     OS << char((Binary >> Shift) & 0xff);
12:   }
13: }

実装としては単純で，getBinaryCodeForInstrにMCInstを渡すと命令をエンコードした結果が返ってくるのでそれを表示するだけです．

SampleMCCodeEmitter::getBinaryCodeForInstr

getBinaryCodeForInstrはTableGenによって自動生成される関数です．命令をエンコードする際にOpcodeは自動的にエンコードされますが，Operandは自動的にエンコードされずエンコードするための専用の関数がコールバックされます．例えば汎用レジスタのエンコードにはgetMachineOpValueが呼び出されます．コールバック関数は別途実装する必要があります．

Operandのエンコーダ関数

OperandをデコードするためにそれぞれのOperandに対応する関数がgetBinaryCodeForInstrからコールバックされます．それぞれの実装をリスト7.57に示します．

リスト7.57: SampleMCCodeEmitterのエンコード関数

 1: unsigned SampleMCCodeEmitter::
 2: getMachineOpValue(const MCInst &MI, const MCOperand &MO,
 3:                   SmallVectorImpl<MCFixup> &Fixups) const {
 4:   if (MO.isReg()) {
 5:     unsigned Reg = MO.getReg();
 6:     unsigned RegNo = getSampleRegisterNumbering(Reg);
 7:     return RegNo;
 8:   } else if (MO.isImm()) {
 9:     return static_cast<unsigned>(MO.getImm());
10:   } else if (MO.isFPImm()) {
11:     return static_cast<unsigned>(APFloat(MO.getFPImm())
12:         .bitcastToAPInt().getHiBits(32).getLimitedValue());
13:   }
14:
15:   // Expr以外はエラー.
16:   assert(MO.isExpr());
17:   llvm_unreachable("not implemented");
18:   return 0;
19: }
20:
21: unsigned SampleMCCodeEmitter::
22: getMemEncoding(const MCInst &MI, unsigned OpNo,
23:                SmallVectorImpl<MCFixup> &Fixups) const {
24:   // レジスタが19-16bit目，即値が15-0bit目
25:   assert(MI.getOperand(OpNo).isReg());
26:   unsigned RegBits = getMachineOpValue(MI, MI.getOperand(OpNo),Fixups) << 16;
27:   unsigned OffBits = getMachineOpValue(MI, MI.getOperand(OpNo+1), Fixups);
28:
29:   return (OffBits & 0xFFFF) | RegBits;
30: }
31:
32: unsigned SampleMCCodeEmitter::
33: getMoveTargetOpValue(const MCInst &MI, unsigned OpNo,
34:                      SmallVectorImpl<MCFixup> &Fixups) const {
35:   // 即値が19-0bit目
36:   assert(MI.getOperand(OpNo).isImm());
37:   unsigned value = getMachineOpValue(MI, MI.getOperand(OpNo),Fixups);
38:   return value & 0xFFFFF;
39: }
40:
41: unsigned SampleMCCodeEmitter::
42: getCallTargetOpValue(const MCInst &MI, unsigned OpNo,
43:                      SmallVectorImpl<MCFixup> &Fixups) const {
44:
45:   const MCOperand &MO = MI.getOperand(OpNo);
46:   assert(MO.isExpr() && "getCallTargetOpValue expects only expressions");
47:
48:   const MCExpr *Expr = MO.getExpr();
49:   Fixups.push_back(MCFixup::Create(0, Expr,
50:                                    MCFixupKind(Sample::fixup_Sample_24)));
51:   return 0;
52: }

getMachineOpValueはデフォルトのOperandをエンコードするために呼び出されます．レジスタならレジスタ番号，即値ならその値を返します．Exprの場合は未実装のためエラーになります．

getMemEncodingはload/store命令で使っているmem Operandをエンコードするために呼び出されます．このOperandは1つのレジスタと1つの即値から構成されています．レジスタ番号を19-16bit目に，即値の値を15-0bit目に格納します．

getMoveTargetOpValueはmove命令で使っているmovetarget Operandをエンコードするために呼び出されます．即値を19-0bit目に格納します．

getCallTargetOpValueはcall命令で使っているcalltarget Operandをエンコードするために呼び出されます．call命令のアドレスはこの時点では値が決まらないため，fixupに登録しています．

7.12.6 SampleMCAsmInfoクラス

SampleMCAsmInfoはMCAsmInfoクラスを継承したクラスで，アセンブリの特徴を定義するためのクラスです．クラス定義をリスト7.58に示します．

リスト7.58: SampleMCAsmInfoのクラス定義

 1: class SampleMCAsmInfo : public MCAsmInfo {
 2:   virtual void anchor() {};
 3:  public:
 4:   explicit SampleMCAsmInfo(const Target &T, StringRef TT);
 5: };
 6:
 7: SampleMCAsmInfo::SampleMCAsmInfo(const Target &T, StringRef TT) {
 8:   PointerSize = 4;
 9:
10:   PrivateGlobalPrefix = ".L";
11:   PCSymbol=".";
12:   CommentString = ";";
13:
14:   AlignmentIsInBytes = false;
15:   AllowNameToStartWithDigit = true;
16:   UsesELFSectionDirectiveForBSS = true;
17: }

コンストラクタでターゲットで使うアセンブリの特徴を定義する必要があります．多くの設定可能な項目がありますが，今回は出力するアセンブリの仕様が特に決まっているわけではないので適当に記述しています．既存のターゲット向けのアセンブリを出力する場合には他にも設定が必要となるでしょう．

7.13 Disassemblerの実装

Disassemblerはコンパイルして生成したオブジェクトファイルをディスアセンブルして内容を確認したりする際に必要になります．コンパイルするだけなら特に必要ありませんが，コンパイル結果を確認するためにも作成しておいた方が良いでしょう．Disassemblerの実装自体はそれほど複雑ではありません．

Disassemblerは他のクラスと異なり，何らかのパス上から利用されるわけではありません．実際にDisassemblerを利用するプログラムはllvm-objdumpとllvm-mcだけのようです．どちらの場合もプログラム内で直接MCDisassemblerクラスをインスタンス化して操作しています．ディスアセンブルしたい場合には，そのインスタンスのgetInstructionメソッドにバイト列を渡すとMCInst(MC Layer)で返ってくるという実装になっています．llvm-objdumpの実装は割と単純なものなので自分で同等のものを作るのもそれほど難しくないでしょう．

7.13.1 SampleDisassemblerクラス

SampleDisassemblerクラスはMCDisassemblerクラスを継承したクラスとなります．クラス定義をリスト7.59に示します．

リスト7.59: SampleDisassemblerのクラス定義

 1: class SampleDisassembler : public MCDisassembler {
 2:  public:
 3:   SampleDisassembler(const MCSubtargetInfo &STI)
 4:       : MCDisassembler(STI) {}
 5:
 6:   ~SampleDisassembler() {}
 7:
 8:   // 命令のディスアセンブルの結果を返す
 9:   DecodeStatus getInstruction(MCInst &instr,
10:                               uint64_t &size,
11:                               const MemoryObject &region,
12:                               uint64_t address,
13:                               raw_ostream &vStream,
14:                               raw_ostream &cStream) const;
15:
16:   const EDInstInfo *getEDInfo() const;
17:
18: private:
19:   DecodeStatus readInstruction32(const MemoryObject &region,
20:                                  uint64_t address,
21:                                  uint64_t &size,
22:                                  uint32_t &insn) const;
23: };

メインで使われるgetInstructionメソッドとgetEDInfoメソッドだけが必ず必要なメソッドとなります．TableGenで生成されたSampleGenEDInfo.incとSampleGenDisassemblerTables.incをincludeする必要があります．

SampleDisassembler::getInstruction

getInstructionは最もメインとなるメソッドで，バイト列から命令を取り出してMCInst(MC Layer)を返します．実装をリスト7.60に示します．

リスト7.60: SampleDisassembler::getInstruction

 1: DecodeStatus SampleDisassembler::
 2: getInstruction(MCInst &instr,
 3:                uint64_t &Size,
 4:                const MemoryObject &Region,
 5:                uint64_t Address,
 6:                raw_ostream &vStream,
 7:                raw_ostream &cStream) const {
 8:   uint32_t Insn;
 9:
10:   DecodeStatus Result = readInstruction32(Region, Address, Size, Insn);
11:   if (Result == MCDisassembler::Fail)
12:     return MCDisassembler::Fail;
13:
14:   // TableGenによって生成された関数を呼び出す
15:   Result = decodeInstruction(DecoderTableSample32,
16:                              instr, Insn, Address, this, STI);
17:   if (Result != MCDisassembler::Fail) {
18:     Size = 4;
19:     return Result;
20:   }
21:
22:   return MCDisassembler::Fail;
23: }

まず，readInstrution32を呼び出して命令を取り出します．この時点ではまだディスアセンブルはしておらずバイナリのままです．次に，TableGenによって生成されたdecodeInstructionを呼び出します．これでMCInstがセットされて返ってくるため，ディスアセンブル完了となります．

なお，decodeInstructionでは，Opcodeに関しては自動的にデコード(バイナリからOpcodeやOperandに変換)されるものの，Operandに関しては自動的にはデコードされずにデコード用の関数がコールバックされます．例えば汎用レジスタのデコードにはDecodeCPURegsRegisterClassが呼ばれます．ここで呼ばれる関数はTableGenでDecoderMethodに設定したものになります．コールバックされる関数は別途実装する必要があります．

SampleDisassembler::getEDInfo

getEDInfoはTableGenで定義されたinstInfoSampleを返すだけのメソッドです．これが内部でどのように使われているかは不明ですが，ターゲット毎に特に実装することもないため問題ないです．

SampleDisassembler::readInstruction32

readInstruction32はバイト列から命令部分を取り出すメソッドです．実装をリスト7.61に示します．

リスト7.61: SampleDisassembler::readInstruction

 1: DecodeStatus SampleDisassembler::
 2: readInstruction32(const MemoryObject &region,
 3:                   uint64_t address,
 4:                   uint64_t &size,
 5:                   uint32_t &insn) const {
 6:   uint8_t Bytes[4];
 7:
 8:   // バイト列から4バイト読み込む
 9:   if (region.readBytes(address, 4, (uint8_t*)Bytes, NULL) == -1) {
10:     size = 0;
11:     return MCDisassembler::Fail;
12:   }
13:
14:   // リトルエンディアンで命令をエンコーディング
15:   insn = (Bytes[3] <<  0) |
16:          (Bytes[2] <<  8) |
17:          (Bytes[1] << 16) |
18:          (Bytes[0] << 24);
19:
20:   return MCDisassembler::Success;
21: }

regionでバイト列，addressで現在位置が渡されるので，読み込みたいバイト数を指定して命令部分を読み込みます．その後，エンディアンを意識してバイト列を詰め込みなおします．

Operandのデコーダ関数

既に述べたようにOperandのデコードは専用の関数がdecodeInstructionからコールバックされます．今回の実装では4種類のデコード関数があります．それぞれの実装をリスト7.62に示します．

リスト7.62: Operandのデコーダー

 1: // tablegenで作成したCPURegs(RegisterClass)を表示する
 2: static DecodeStatus DecodeCPURegsRegisterClass(MCInst &Inst,
 3:                                                unsigned RegNo,
 4:                                                uint64_t Address,
 5:                                                const void *Decoder) {
 6:   if (RegNo > 31)
 7:     return MCDisassembler::Fail;
 8:
 9:   Inst.addOperand(MCOperand::CreateReg(CPURegsTable[RegNo]));
10:   return MCDisassembler::Success;
11: }
12:
13: // load/store命令のoperandをデコード
14: static DecodeStatus DecodeMem(MCInst &Inst,
15:                               unsigned Insn,
16:                               uint64_t Address,
17:                               const void *Decoder) {
18:   int Offset = SignExtend32<16>(Insn & 0xffff);
19:   int Reg = (int)fieldFromInstruction(Insn, 16, 4);
20:   int Base = (int)fieldFromInstruction(Insn, 20, 4);
21:
22:   Inst.addOperand(MCOperand::CreateReg(CPURegsTable[Reg]));
23:   Inst.addOperand(MCOperand::CreateReg(CPURegsTable[Base]));
24:   Inst.addOperand(MCOperand::CreateImm(Offset));
25:
26:   return MCDisassembler::Success;
27: }
28:
29: // move命令のoperandをデコード
30: static DecodeStatus DecodeMoveTarget(MCInst &Inst,
31:                                      unsigned Insn,
32:                                      uint64_t Address,
33:                                      const void *Decoder) {
34:   int Offset = SignExtend32<20>(Insn & 0xfffff);
35:   int Reg = (int)fieldFromInstruction(Insn, 20, 4);
36:
37:   Inst.addOperand(MCOperand::CreateReg(CPURegsTable[Reg]));
38:   Inst.addOperand(MCOperand::CreateImm(Offset));
39:
40:   return MCDisassembler::Success;
41: }
42:
43: // call命令のoperandをデコード
44: static DecodeStatus DecodeCallTarget(MCInst &Inst,
45:                                      unsigned Insn,
46:                                      uint64_t Address,
47:                                      const void *Decoder) {
48:   unsigned CallOffset = fieldFromInstruction(Insn, 0, 24) << 2;
49:   Inst.addOperand(MCOperand::CreateImm(CallOffset));
50:   return MCDisassembler::Success;
51: }

DecodeCPURegsRegisterClassは汎用レジスタをデコードするための関数です．引数で渡されたレジスタ番号に対応するレジスタをMCOperand::CreateRegで生成してMCInstに追加します．

DecodeMemはload/store命令のOperandをデコードするための関数です．fieldFromInstructionを使って命令から任意の位置のbit列を取り出して，レジスタ2つ即値1つからなるOperandをMCInstに追加します．fieldFromInstructionはTableGenで自動生成された関数です．

DecodeMoveTargetはmove命令のOperandをデコードするための関数です．レジスタ1つと20bitの即値1つを取り出してMCInstに追加します．

DecodeCallTargetはcall命令のOperandをデコードするための関数です．24bitの即値を取り出してMCInstに追加します．

7.14 その他の実装

7.14.1 ターゲット初期化関数

他のプログラムからターゲットを利用する場合，以下の初期化関数を呼ぶ必要があります．

InitializeAllTargetInfos

InitializeAllTargets

InitializeAllTargetMCs

InitializeAllAsmPrinters

InitializeAllAsmParsers

InitializeAllDisassemblers

InitializeAllTargetsを呼ぶと内部でInitializeAllTargetInfosも呼ばれます．例えばコンパイルを行うllcではTargets，TargetMCs，AsmPrinters，AsmParsersを呼び出しています．これらの関数を呼び出すと，Target毎の初期化関数が個別に呼び出されます．InitializeAllTargetsであればInitializeXXXTarget，つまり今回の実装であればInitializeSampleTargetが呼び出されることになります．

LLVMInitializeSampleTargetInfo

この関数ではTarget自体の情報を登録します．実装をリスト7.63に示します．TargetクラスのインスタンスTheSampleTargetにTriple(Triple::sample)を対応付けたりします．

リスト7.63: LLVMInitializeSampleTargetInfo

 1: extern "C" void LLVMInitializeSampleTargetInfo() {
 2:   RegisterTarget<Triple::sample, /*HasJIT=*/false>
 3:     X(TheSampleTarget, "sample", "Sample");
 4: }

LLVMInitializeSampleTarget

実装をリスト7.64に示します．TheSampleTargetにSampleTargetMachineクラスを対応付けします．

リスト7.64: LLVMInitializeSampleTarget

 1: extern "C" void LLVMInitializeSampleTarget() {
 2:   RegisterTargetMachine<SampleTargetMachine> X(TheSampleTarget);
 3: }

LLVMInitializeSampleTargetMC

実装をリスト7.65に示します．

リスト7.65: LLVMInitializeSampleTargetMC

 1: extern "C" void LLVMInitializeSampleTargetMC() {
 2:   // MC asm infoの登録
 3:   RegisterMCAsmInfoFn X(TheSampleTarget, createSampleMCAsmInfo);
 4:   // MC codegen infoの登録
 5:   TargetRegistry::RegisterMCCodeGenInfo(TheSampleTarget,
 6:                                         createSampleMCCodeGenInfo);
 7:   // MC instruction infoの登録
 8:   TargetRegistry::RegisterMCInstrInfo(TheSampleTarget, createSampleMCInstrInfo);
 9:   // MC register infoの登録
10:   TargetRegistry::RegisterMCRegInfo(TheSampleTarget, createSampleMCRegisterInfo);
11:   // MC Code Emitterの登録
12:   TargetRegistry::RegisterMCCodeEmitter(TheSampleTarget,
13:                                         createSampleMCCodeEmitter);
14:   // object streamerの登録
15:   TargetRegistry::RegisterMCObjectStreamer(TheSampleTarget, createMCStreamer);
16:   // asm backendの登録
17:   TargetRegistry::RegisterMCAsmBackend(TheSampleTarget,
18:                                        createSampleAsmBackend);
19:   // MC subtarget infoの登録
20:   TargetRegistry::RegisterMCSubtargetInfo(TheSampleTarget,
21:                                           createSampleMCSubtargetInfo);
22:   // MCInstPrinterの登録
23:   TargetRegistry::RegisterMCInstPrinter(TheSampleTarget,
24:                                         createSampleMCInstPrinter);
25: }

この関数ではMIの操作に関連するクラスを登録する関数を実行する必要があります．実行可能な関数として以下のようなものがあります．

TargetRegistry::RegisterMCCodeGenInfo

TargetRegistry::RegisterMCInstrInfo

TargetRegistry::RegisterMCRegInfo

TargetRegistry::RegisterMCCodeEmitter

TargetRegistry::RegisterMCObjectStreamer

TargetRegistry::RegisterMCAsmBackend

TargetRegistry::RegisterMCSubtargetInfo

TargetRegistry::RegisterMCInstPrinter

全ての関数で引数はTargetクラスのインスタンス(TheSampleTarget)と各クラスのインスタンスを生成する関数ポインタになります．実行するとTargetクラスに生成関数が登録されるため，以後はcreateXXX関数を呼び出すだけでインスタンスを生成できるようになります．上記以外にもアセンブリパーサを登録するためのRegisterMCAsmLexerなどもあります．

LLVMInitializeSampleAsmPrinter

実装をリスト7.66に示します．TheSampleTargetにSampleAsmPrinterクラスの生成関数を登録します．createAsmPrinterでインスタンスが生成できるようになります．

リスト7.66: LLVMInitializeSampleAsmPrinter

 1: extern "C" void LLVMInitializeSampleAsmPrinter() {
 2:   RegisterAsmPrinter<SampleAsmPrinter> X(TheSampleTarget);
 3: }

LLVMInitializeSampleDisassembler

実装をリスト7.67に示します．TheSampleTargetにSampleDisassemblerクラスの生成関数を登録します．createMCDisassemblerでインスタンスが生成できるようになります．

リスト7.67: LLVMInitializeSampleDisassembler

 1: extern "C" void LLVMInitializeSampleDisassembler() {
 2:   TargetRegistry::RegisterMCDisassembler(TheSampleTarget,
 3:                                          createSampleDisassembler);
 4: }

7.14.2 TargetのTripleを追加

ターゲットとしてLLVMに登録するにはTripleのArchTypeにターゲット名を追加する必要があります．この定義はinclude/llvm/ADT/Triple.hにあります．また，プログラムからターゲットの情報を取得できるようにlib/Support/Triple.cppのgetArchTypeName，parseArch，getArchPointerBitWidth，get32BitArchVariant，get64BitArchVariantあたりを修正する必要があります．

LLVMのTopディレクトリでmakeした際にSampleターゲットが自動的にビルド対象になるようにするにはconfigureの修正なども必要になります．configureのTARGETS_TO_BUILDに追加されるように修正しましょう．

このあたり(lib/Target/Sample以外の部分)の修正はソースコードではパッチ形式で提供しています．後述するビルド方法でパッチの当て方を含むビルド方法を説明しています．

7.14.3 TargetのMakefileを設置

ターゲットの記述をしたディレクトリ毎にMakefileとLLVMBuild.txtを設置しなければいけません．おそらくLLVMBuild.txtの情報をもとにライブラリ間の依存関係をチェックしています．ディレクトリ毎に記述内容は異なりますが，ターゲットのTopディレクトリのMakefile(リスト7.68)とLLVMBuild.txt(リスト7.69)だけ載せておきます．

リスト7.68: Sample ターゲットのMakefile(lib/Target/Sample/Makefile)

 1: LEVEL = ../../..
 2: LIBRARYNAME = LLVMSampleCodeGen
 3: TARGET = Sample
 4:
 5: # Make sure that tblgen is run, first thing.
 6: BUILT_SOURCES = SampleGenRegisterInfo.inc SampleGenInstrInfo.inc \
 7:                 SampleGenAsmWriter.inc SampleGenCodeEmitter.inc \
 8:                 SampleGenDAGISel.inc SampleGenCallingConv.inc \
 9:                 SampleGenSubtargetInfo.inc SampleGenMCCodeEmitter.inc \
10:                 SampleGenEDInfo.inc SampleGenDisassemblerTables.inc
11:
12: DIRS = InstPrinter Disassembler TargetInfo MCTargetDesc
13:
14: include $(LEVEL)/Makefile.common

リスト7.69: SampleターゲットのLLVMBuild.txt(lib/Target/Sample/LLVMBuild.txt)

 1: [ common ]
 2: subdirectories = InstPrinter Disassembler MCTargetDesc TargetInfo
 3:
 4: [component_0]
 5: type = TargetGroup
 6: name = Sample
 7: parent = Target
 8: ;has_asmparser = 1
 9: has_asmprinter = 1
10: has_disassembler = 1
11: ;has_jit = 1
12:
13: [component_1]
14: type = Library
15: name = SampleCodeGen
16: parent = Sample
17: required_libraries = AsmPrinter CodeGen Core MC SampleAsmPrinter SampleDesc SampleInfo SelectionDAG Support Target
18: add_to_library_groups = Sample

7.15 コンパイルしよう！

ソースコードの準備はできました．ちなみにコンパイルというのはLLVM自体のコンパイルのことではなくLLVM IRからアセンブリやオブジェクトに変換することを意味しています．まだLLVMの準備ができてない方にもその手順から紹介します．その後，今回作成したターゲットを用いてLLVM IRからアセンブリとオブジェクトを生成し，ディスアセンブルして結果が正しいかを確認します．

コンパイルを始める前にもう一度，目標のソースコードを確認しましょう．リスト7.70とリスト7.71が今回目標としてきたソースコードになり，これからコンパイルするものになります．

リスト7.70: 目標のソースコード(sample_add.ll)

 1: define i32 @sample_add(i32 %a, i32 %b) nounwind readnone {
 2: entry:
 3:   %add = add nsw i32 %b, %a
 4:   ret i32 %add
 5: }

リスト7.71: 目標のソースコード(sample_call.ll)

 1: define i32 @add(i32 %a, i32 %b) nounwind readnone {
 2: entry:
 3:   %add = add nsw i32 %b, %a
 4:   ret i32 %add
 5: }
 6:
 7: define i32 @sample_call() nounwind readnone {
 8: entry:
 9:   %call = tail call i32 @add(i32 1, i32 2)
10:   ret i32 %call
11: }

7.15.1 LLVMの準備

まだLLVM自体をコンパイルしていないなら以下の手順に従ってビルドしましょう．今回作成したターゲットのソースコードは私のGitHub*13にあります．本書作成時のバージョンにはllvm-3.2というtagをつけていますのでそれをcheckoutしましょう．ターゲット固有のファイル以外は修正点をパッチで提供しています．パッチを当てたらコンパイルの準備はOK．configureでいっぱいオプションをつけてますが，これはデバッグモードで最大限動かすようにするためです．makeしたあとmake installでインストールしてもいいですが毎回installするのも面倒です．コンパイルしたLLVMのバイナリはconfigureしたディレクトリのDebug+Asserts/binの中にあります．これをこのまま実行しても問題ないです．なお，本書ではLLVM 3.2を対象としているため，LLVM 3.2以外の環境ではコンパイル及び実行が正しくできるかどうかの保証はできません．

## LLVMのダウンロード
$ wget http://llvm.org/releases/3.2/llvm-3.2.src.tar.gz
$ tar zxf llvm-3.2.src.tar.gz

## GitHubからターゲットのcheckout
$ cd llvm-3.2.src/lib/Target
$ git clone git://github.com/sabottenda/llvm-sample-target.git Sample
$ cd - && cd llvm-3.2.src/lib/Target/Sample
$ git checkout llvm-3.2

## パッチを当てる(configureの更新)
$ cd - && cd llvm-3.2.src
$ patch -p1 < ./lib/Target/Sample/config.patch

## コンパイルとインストール
$ cd - && mkdir build && cd build
$ ../llvm-3.2.src/configure --prefix=/opt/llvm \
  --enable-debug-runtime --enable-assertions \
  --enable-debug-symbols --disable-optimized --enable-debug-runtime
$ make -j4
$ sudo make install

7.15.2 あせんぶる！

さて，本題のLLVM IRからアセンブリへのコンパイルをしましょう．コンパイルのコマンドは次のようになります．

$ llc -march=sample -print-machineinstrs -debug sample_add.ll

-marchで今回作成したターゲットの名前を指定します．-print-machineinstrsはマシン命令を表示してくれます．-debugを指定するとデバッグ用のメッセージも表示されるようになります．コンパイルする対象はもちろん序盤で紹介したコードです．再度sample_add.ll(list[target-code])を確認しましょう．このソースコードをコンパイルするとsample_add.sというファイルができており，内容はリスト7.72のようになるはずです．

リスト7.72: sample_add.s

 1:         .file   "sample_add.ll"
 2:         .text
 3:         .globl  sample_add
 4:         .align  2
 5:         .type   sample_add,@function
 6: sample_add:                             ; @sample_add
 7: ; BB#0:                                 ; %entry
 8:         move    $t0, -64
 9:         add     $sp, $sp, $t0
10:         store   $ra, 60($sp)            ; 4-byte Folded Spill
11:         add     $a0, $a1, $a0
12:         add     $v0, $zero, $a0
13:         load    $ra, 60($sp)            ; 4-byte Folded Reload
14:         move    $t0, 64
15:         add     $sp, $sp, $t0
16:         ret     $ra
17: .Ltmp0:
18:         .size   sample_add, .Ltmp0-sample_add

更に次のコマンドのようにコンパイル時に-show-mc-encodingオプションを付けるとエンコーディングが16進数で出力され，-show-mc-instオプションをつけるとMCInstおよびMCOperandのダンプが見られます．リスト7.73に結果を示します．

$ llc -march=sample -show-mc-encoding -show-mc-inst -debug sample_add.ll

リスト7.73: sample_add.s(ダンプ付き)

 1:         .file   "sample_add.ll"
 2:         .text
 3:         .globl  sample_add
 4:         .align  2
 5:         .type   sample_add,@function
 6: sample_add:                             ; @sample_add
 7: ; BB#0:                                 ; %entry
 8:         move    $t0, -64                ; encoding: [0x02,0x6f,0xff,0xc0]
 9:                                         ; <MCInst #22 MOVE
10:                                         ;  <MCOperand Reg:11>
11:                                         ;  <MCOperand Imm:-64>>
12:         add     $sp, $sp, $t0           ; encoding: [0x05,0xee,0x60,0x0e]
13:                                         ; <MCInst #17 ADD
14:                                         ;  <MCOperand Reg:10>
15:                                         ;  <MCOperand Reg:10>
16:                                         ;  <MCOperand Reg:11>>
17:         store   $ra, 60($sp)            ; 4-byte Folded Spill
18:                                         ; encoding: [0x01,0xfe,0x00,0x3c]
19:                                         ; <MCInst #24 STORE
20:                                         ;  <MCOperand Reg:5>
21:                                         ;  <MCOperand Reg:10>
22:                                         ;  <MCOperand Imm:60>>
23:         add     $a0, $a1, $a0           ; encoding: [0x05,0x23,0x20,0x02]
24:                                         ; <MCInst #17 ADD
25:                                         ;  <MCOperand Reg:1>
26:                                         ;  <MCOperand Reg:2>
27:                                         ;  <MCOperand Reg:1>>
28:         add     $v0, $zero, $a0         ; encoding: [0x05,0x10,0x20,0x01]
29:                                         ; <MCInst #17 ADD
30:                                         ;  <MCOperand Reg:15>
31:                                         ;  <MCOperand Reg:16>
32:                                         ;  <MCOperand Reg:1>>
33:         load    $ra, 60($sp)            ; 4-byte Folded Reload
34:                                         ; encoding: [0x00,0xfe,0x00,0x3c]
35:                                         ; <MCInst #21 LOAD
36:                                         ;  <MCOperand Reg:5>
37:                                         ;  <MCOperand Reg:10>
38:                                         ;  <MCOperand Imm:60>>
39:         move    $t0, 64                 ; encoding: [0x02,0x60,0x00,0x40]
40:                                         ; <MCInst #22 MOVE
41:                                         ;  <MCOperand Reg:11>
42:                                         ;  <MCOperand Imm:64>>
43:         add     $sp, $sp, $t0           ; encoding: [0x05,0xee,0x60,0x0e]
44:                                         ; <MCInst #17 ADD
45:                                         ;  <MCOperand Reg:10>
46:                                         ;  <MCOperand Reg:10>
47:                                         ;  <MCOperand Reg:11>>
48:         ret     $ra                     ; encoding: [0x04,0xf0,0x00,0x0f]
49:                                         ; <MCInst #23 RET
50:                                         ;  <MCOperand Reg:5>>
51: .Ltmp0:
52:         .size   sample_add, .Ltmp0-sample_add

もう一つのsample_call.ll(リスト7.2)もコンパイルしましょう．リスト7.74に結果を示します．無事call命令も生成されているようです．

$ llc -march=sample -print-machineinstrs -debug sample_call.ll

リスト7.74: sample_call.s

 1:         .file   "sample_call.ll"
 2:         .text
 3:         .globl  add
 4:         .align  2
 5:         .type   add,@function
 6: add:                                    ; @add
 7: ; BB#0:                                 ; %entry
 8:         move    $t0, -64
 9:         add     $sp, $sp, $t0
10:         store   $ra, 60($sp)            ; 4-byte Folded Spill
11:         add     $a0, $a1, $a0
12:         add     $v0, $zero, $a0
13:         load    $ra, 60($sp)            ; 4-byte Folded Reload
14:         move    $t0, 64
15:         add     $sp, $sp, $t0
16:         ret     $ra
17: .Ltmp0:
18:         .size   add, .Ltmp0-add
19:
20:         .globl  sample_call
21:         .align  2
22:         .type   sample_call,@function
23: sample_call:                            ; @sample_call
24: ; BB#0:                                 ; %entry
25:         move    $t0, -64
26:         add     $sp, $sp, $t0
27:         store   $ra, 60($sp)            ; 4-byte Folded Spill
28:         move    $a0, 1
29:         move    $a1, 2
30:         call    add
31:         load    $ra, 60($sp)            ; 4-byte Folded Reload
32:         move    $t0, 64
33:         add     $sp, $sp, $t0
34:         ret     $ra
35: .Ltmp1:
36:         .size   sample_call, .Ltmp1-sample_call

7.15.3 こんぱいる！

さて，次にオブジェクトの生成をしましょう．llcでオブジェクトを生成する場合は-filetype=objをオプションに追加します．

$ llc -march=sample -filetype=obj sample_add.ll
$ llc -march=sample -filetype=obj sample_call.ll

コンパイルが完了するとsample_add.oもしくはsample_call.oができるはずです．これはバイナリなのでそのままでは正しく生成できたか確認する方法はありません．またオリジナルのターゲットであるため，GNU Binutilsのobjdumpは使えません．llvm-objdumpを使うことでディスアセンブルが可能です．llvm-objdumpは以下のように実行します．

$ llvm-objdump -d -arch=sample sample_add.o
$ llvm-objdump -d -arch=sample sample_call.o

sample_add.oとsample_call.oのディスアセンブルの結果をそれぞれリスト7.75とリスト7.76に示します．最初の8bit(16進数で2つ)がOpcodeでレジスタを使う場合は4bit(16進数1つ)ずつなので見やすいと思います．call命令のアドレスが0になっているのはアドレス解決がリンク時に解決されるためです．結果が関数の区切りもなくフラットに表示されているのはllvm-objdumpの仕様です．

リスト7.75: sample_add.oのディスアセンブル

 1: sample_add.o:   file format ELF32-unknown
 2:
 3: Disassembly of section .text:
 4: .text:
 5:        0:       02 6f ff c0               move    $t0, -64
 6:        4:       05 ee 60 0e               add     $sp, $sp, $t0
 7:        8:       01 fe 00 3c               store   $sp, 60($ra)
 8:        c:       05 23 20 02               add     $a0, $a1, $a0
 9:       10:       05 10 20 01               add     $v0, $zero, $a0
10:       14:       00 fe 00 3c               load    $sp, 60($ra)
11:       18:       02 60 00 40               move    $t0, 64
12:       1c:       05 ee 60 0e               add     $sp, $sp, $t0
13:       20:       04 f0 00 0f               ret     $ra

リスト7.76: sample_call.oのディスアセンブル

 1: sample_call.o:  file format ELF32-unknown
 2:
 3: Disassembly of section .text:
 4: .text:
 5:        0:       02 6f ff c0               move    $t0, -64
 6:        4:       05 ee 60 0e               add     $sp, $sp, $t0
 7:        8:       01 fe 00 3c               store   $sp, 60($ra)
 8:        c:       05 23 20 02               add     $a0, $a1, $a0
 9:       10:       05 10 20 01               add     $v0, $zero, $a0
10:       14:       00 fe 00 3c               load    $sp, 60($ra)
11:       18:       02 60 00 40               move    $t0, 64
12:       1c:       05 ee 60 0e               add     $sp, $sp, $t0
13:       20:       04 f0 00 0f               ret     $ra
14:       24:       02 6f ff c0               move    $t0, -64
15:       28:       05 ee 60 0e               add     $sp, $sp, $t0
16:       2c:       01 fe 00 3c               store   $sp, 60($ra)
17:       30:       02 20 00 01               move    $a0, 1
18:       34:       02 30 00 02               move    $a1, 2
19:       38:       03 00 00 00               call    0
20:       3c:       00 fe 00 3c               load    $sp, 60($ra)
21:       40:       02 60 00 40               move    $t0, 64
22:       44:       05 ee 60 0e               add     $sp, $sp, $t0
23:       48:       04 f0 00 0f               ret     $ra

7.16 バックエンドを作ってみて

これで完成です．今までいろんなクラスを作成してきた割にできることは少ないです．最初の実装はかなり大変ですが，これをベースに少しずつ拡張していけばよいでしょう．ここまで作成してきた知識を合わせれば実装しやすいと思います．

バックエンドに興味があるということはおそらくx86やARMなどのアーキテクチャに興味があるか，オリジナルの命令セットを作りたいという野望があるでしょう．LLVMのx86バックエンドは実装が進んでいますが，ARMはまだしも他のバックエンドは未成熟です．オブジェクト生成が出来なかったり，命令が使えなかったりということもあります．ぜひ様々なことに挑戦してLLVMへコミットしていきましょう．

[*1] 実際にはSelectionDAGへフォーマットを変化させる前にLLVM IR上でターゲット依存の最適化と前処理が行われます．

[*2] 電子書籍版v1.0.0以前及び商業誌版ではMachineCodeと記述していましたがMIに変更しました．MIやMI Layerも依然として正式な名称ではないようですが，開発者間では表現するときにこの名称が使われることがあるため採用しています．

[*3] 単にMCと言った場合、MIとの対比としてMCInstなどのクラスの総称を意味することもありますが，機械語(Machie Code)やMC Layerを指すこともあります．

[*4] http://llvm.org/docs/WritingAnLLVMBackend.html

[*5] http://llvm.org/docs/CodeGenerator.html

[*6] http://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm/

[*7] この分類や呼称が正しくない可能性がありますが，このように分類するとわかりやすくなると思います．

[*8] 実際にはload命令とstore命令では特殊なフォーマットを利用しているためこの形式通りではありません．詳しくはプログラム上で定義するときに説明します．

[*9] ちなみにMIPSがこのような仕様です．

[*10] LLVMの公式ページにTableGen Fundamentalsというドキュメントもあります．

[*11] https://github.com/sabottenda/

[*12] ターゲット名のヘッダである必要性がよくわかっていません．

[*13] https://github.com/sabottenda/llvm-sample-target

あとがき

ここまでお付き合い頂いてありがとうございました．そしてお疲れ様です．多少はLLVM について理解してもらうことができたでしょうか．

ひょっとすると何となくはわかったけれどもまだ自信がない，という方もいるかもしれません．けれどご安心ください．最初からすべてを理解できる人は滅多にいません．わかったつもりになって，“今ならLLVMで何かできるかも”と少しでも思えたならあなたはきっと一人でLLVMの世界を歩いて行けます．

本書を読んでくださったということはオリジナルの言語を作りたい，試してみたい最適化がある，好きなアーキテクチャのバックエンドを作成したい，などなど様々な野望があるのだと思います．本書がその思いにどの程度貢献できたかは定かではありませんが，皆さんそれぞれの目標に向かって是非チャレンジしてみてください．

はしがきにも書きましたが本書は元々はコミックマーケットにて同人誌として販売していました．コミックマーケットにはよく知られている漫画などの二次創作だけではなく，コンピュータ技術などを扱う技術島と呼ばれるブロックがあります．読み物のようなカジュアルなものから，本書以上にマニアックな内容を扱ったものまであります．規模としてはそれほど大きくはありませんが，気軽に自分たちが持つ情報を本に出来る場でもあり，それらに興味を持つ人たちと交流出来る場にもなっています．私たちのサークルにもぜひ足をお運び下さい。

最後になりましたが，本書の表紙を飾っているきつねさんには名前があります．金髪がユイで，黒髪がルリという名前です．特に深い意味はありませんが，少しでも惹かれるところがあったなら可愛がって貰えるとうれしいです．

参考文献

[1] nothingcosmos wiki, http://nothingcosmos.wiki.fc2.com/wiki/LLVMに関して

[2] LLVM.org, http://llvm.org/

[3] Chris Lattner and Vikram Adve, LLVM: An Infractructure for Multi-Stage Optimization. Master’s thesis, University of Illinois, 2002. See http://llvm.cs.uiuc.edu.

[4] Chris Lattner and Vikram Adve, LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation, Proceedings of the 2004 International Symposium on Code Generation and Optimization (CGO'04), Mar 2004.

[5] Arpan Sen, LLVM フレームワークで実用的なコンパイラーを作成する: 第 1 回, 2012. http://www.ibm.com/developerworks/jp/opensource/library/os-createcompilerllvm1/

著者/関係者一覧

柏木餅子

本書のフロントエンド担当

所謂言いだしっぺ

【twitter】@sui_moti

【github】https://github.com/Kmotiko

【web】http://d.hatena.ne.jp/motipizza/

風薬

本書のバックエンド担当

まきぞえその１

【twitter】@kazegusuri

【github】https://github.com/sabottenda

【web】http://kazegusuri.hateblo.jp/

矢上諒一

本書のイラスト担当

まきぞえその２

【twitter】@re_sonanz

【tumblr】http://amphase.tumblr.com/

【web】http://yew.digiweb.jp/

スペシャルサンクス

@shtaxxx さん

@saten_kanae さん

@xhl_kogitsune さん

@finalfusion さん

本書の改善に協力してくださった皆様

@cocoa_ruto さん

@cpp_akira さん

@cuzic さん

@hie さん

@icecocoa6 さん

@k_takata さん

@kawasakitoshiya さん

@kfujieda さん

@muo_jp さん

@nothingcosmos さん

@papix さん

@repeatedly さん

@sakira さん

@yhara さん

@znz さん

kariya_mitsuru さん

maropu さん

misaka さん

naiar さん

sharkpp さん

susi さん

tkuro11 さん

wakuwaku さん

yada さん

zan さん

おでん さん

きつねさんでもわかるLLVM

2013年2月8日 v0.9.0版発行
2013年3月13日 v0.9.1版発行
2013年7月2日 v0.9.2版発行
2014年2月12日 v1.0.0版発行

著　者 | 柏木餅子、風薬
イラスト | 矢上諒一
発行所 | 達人出版会

(C) 2012-2014 MotiPizza