Arantium Maestum

プログラミング、囲碁、読書の話題

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ファイルを作成することにする。