C言語のコンパイル その2 コンバータのアルゴリズム

当面の目標

まず、簡単なプログラムをModasm形式にコンバートできるコンバータを作成する。その後、徐々に難しいプログラムをコンバートできるように拡張を行っていく。

まず最初に、以下のプログラムをコンパイルできることを目標とする。

#include<stdio.h>

int main(void) {
    return 42;
}

これを、以下のコマンドを用いて

clang -S -emit-llvm main.c

LLVM IRファイルmain.llを作成する。

ここら辺の内容はこの記事を参考にした。 itchyny.hatenablog.com

自分の実行環境では以下のようになった。

; ModuleID = 'main.c'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: nounwind uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  ret i32 42
}

attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.ident = !{!0}

!0 = !{!"clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)"}

このうち、コード部分を抜き出す。

define i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  ret i32 42
}

この記事を参考にし、以下のことがわかった。 LLVMでLLVM-IRを生成して眺める · GitHub

  • 関数はdefine [戻り値の型] @ [関数名()] #0 {...}の形式で表す
  • レジスタは%1,%2,...で表す
  • alloca命令は静的なメモリ領域の確保を行っており、メモリアドレス(ポインタ)を返す。
    • align 4によってメモリアドレスは4の倍数となっている必要がある。
  • store命令はレジスタ値をメモリに書き込む
    • store [型] [即値orレジスタ], [型] [メモリアドレス(ポインタ)を保持するレジスタ], align [書き込み先のメモリアドレスの制約]
  • ret命令は戻り値を表す。
    • ret [型] [即値]

LLVM IR表現に出てくるすべての命令は、当然ながらLLVMの公式サイトに最も詳しい説明が載っている。 ちょくちょく参考にする必要がありそう。

LLVM Language Reference Manual — LLVM 8 documentation

以上から、コンバータを以下の構成とすることにした。

コンバータの構成