HaskellでLisp to LLVM IRコンパイラ その2 LLVM IRへの出力(簡単なテンプレート)
LLVM IRというのはLLVMコンパイラ・フレームワークが定義・利用する中間言語だ。
https://en.wikipedia.org/wiki/LLVM#Intermediate_representation
LLVMフレームワークでは、コンパイラのFront Endはソース言語をこのIRに翻訳することに集中し、Back EndはこのIRを(IR上で)最適化していってからターゲット・アーキテクチャのアセンブリ言語に翻訳する。
なので今回は「Lispフロントエンドを書く」ということになる。
LLVM IRの枠組み
こんなC言語のコードを書いてみる:
#include <stdio.h> int lispfn() { int a = 42; return a; } int main() { printf("%d\n", lispfn()); return 0; }
lispfn.c
というファイルに保存して、clang -cc1 -O0 -emit-llvm lispfn.c
でコンパイルしてみると、以下のlispfn.ll
ファイルが生成される:
@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00" define i32 @lispfn() { %a = alloca i32 store i32 42, i32* %a %1 = load i32, i32* %a ret i32 %1 } define i32 @main() { %call = call i32 @lispfn() %call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 %call) ret i32 0 } declare i32 @printf(i8*, ...)
いや、嘘だ。もっといろいろとタグ情報などがついたファイルが作成される。実行に必要のない部分は全部削除して読みやすくしたのが上記のコードだ。このままでもlli lispfn.ll
でちゃんとJITコンパイルされて42と出力される。
このコードをテンプレートとして使って:
@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00" define i32 @lispfn() { ; ここにコンパイルした結果のLLVM IRコマンドを出力 %a1 = ; 最終的な結果を代入 ret i32 %a1 } define i32 @main() { %call = call i32 @lispfn() %call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 %call) ret i32 0 } declare i32 @printf(i8*, ...)
という形で最終出力の.ll
ファイルを作成することにする。