ギークもどきの日記帳

雑多な知識が垂れ流される場所。ほとんど無害。

"int main(void) { return 0; }"のLLVM IRを読んだメモ

とりあえずの学習メモとして残す。 理解が怪しいところは?や(?)などをつけている。 理解が進んだ後に整理したものを書きたい。

int main (void) {
  return 0;
}

このプログラムはCにおける(多分)最小のプログラムで、単にステータスコード0を返すだけのプログラム。

これをclang -S -emit-llvmLLVM IRに変換するとこうなった。(macOSで吐いた)

; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.12.0"

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

attributes #0 = { noinline nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "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-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"clang version 4.0.0 (tags/RELEASE_400/final)"}

まず、';‘から始まる行はコメントなのでプログラムとしては無視する。

source_filename = "hello.c"は見ての通り元のソースファイルの名前で、プロファイル時にユニークなローカル関数の識別子を生成するために使っているらしい。

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"はメモリ上のデータレイアウトを記述している。

  • ‘-'が区切り文字。
  • ‘e'はリトルエンディアン。
  • ’m:o'は名前修飾の方法を指定している。'o'なので'Mach-O'式の名前修飾を行う。
  • ‘i64:64'は64bit整数のアライメントを64に指定している。この場合は64の倍数?
  • ‘f80:128'も浮動小数点数であること以外は上に同様
  • ‘n8:16:32:64'はありうる整数のbitを指定している。これはx86-64の場合。
  • ‘S128'はスタックのアライメント。
define i32 @main() #0 {
    %1 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    ret i32 0
}

今回のプログラムの本体がここ。 LLVM IRはこのようにC+アセンブラっぽい文法で書かれている。

‘i32'は32bit整数。LLVMのドキュメントに様々な型について詳細な情報がある。Rustっぽい。

‘%1'のように’%‘で始まるものがローカル識別子。 この場合、%1はアライメント4, 32bit整数のレジスタとして定義されている。

‘store i32 0, i32* %1, align 4'で、%1に0を代入している。alignは%1のものと一致するように指定される(?)。

‘ret i32 0'はC言語の'return 0;'に対応する。

%1の意味は… 最適化のせいかと考えたが、-O0でも結果は変わらず。あとで調べる。 (2017 6/20追記) よく考えたら-Sだと-O0の結果が出力されてる。 clang -emit-llvm -S -01で出力させると削除される。

define i32 @main() local_unnamed_addr #0 {
  ret i32 0
}

(追記ここまで)

attributes #0 = { noinline nounwind ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "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-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

この一行は#0という名前のattribute groupを宣言している。先程のmain() #0の'#0'の部分へ代入されるっぽい。

#0#1という2つのattribute groupがあるとき、#0 #1と書くと2つが接続される。

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"clang version 4.0.0 (tags/RELEASE_400/final)"}

ここではモジュール全体のメタデータを定義している。

もう少し複雑なプログラムについてはまた今度。