LLVM言語 学習メモ (1) - 戻り値を返すだけのmain関数からHello Worldまで
本記事はその自分用メモ。 第三者にわかりやすく解説する意図はない。 そういう記事はいつか書くかもしれないし書かないかもしれない。
公式のリファレンスを参考にする。
LLVM IRではHello World
を標準出力することすら初学者にとって難易度が高い。
https://releases.llvm.org/10.0.0/docs/LangRef.html#module-structureにサンプルコードがあるが、C言語の知識がある程度だと、わかるようなわからないような内容。
まずはただ値を返すだけのmain関数を定義するところからはじめて、Hello World
を標準出力できるところを目指す。
最適化に関するものなどはひとまず無視して、最低限動くものだけを作る。
目次
開発環境
- LLVM 9.0.1
- Windows 10 + PowerShell
最小のプログラム
まずは戻り値を返すだけのmain関数を一つだけ作成してみる。
以下の内容のテキストファイルを、拡張子.ll
で保存。
define i32 @main() { ret i32 111 }
以下のコマンドでコンパイル、実行、戻り値の確認。
llvm-as (ファイル名).ll lli (ファイル名).bc $lastexitcode
実行結果
111
https://releases.llvm.org/10.0.0/docs/LangRef.html#functionsによると、関数定義の構文は以下。
define [linkage] [PreemptionSpecifier] [visibility] [DLLStorageClass]
[cconv] [ret attrs]
<ResultType> @<FunctionName> ([argument list])
[(unnamed_addr|local_unnamed_addr)] [AddrSpace] [fn Attrs]
[section "name"] [comdat [($name)]] [align N] [gc] [prefix Constant]
[prologue Constant] [personality Constant] (!name !N)* { ... }
何やら大量に書けるようだが、ひとまず今はdefine <ResultType> @<FunctionName> ([argument list]) { ... }
にだけ注目する。
先のソースコードでは、戻り値<ResultType>
がi32
、関数名<FunctionName>
がmain
、引数リスト[argument list]
が()
つまり引数なし。
i32
は32ビット整数。
関数呼び出し
以下は、関数func1
を定義し、呼び出すサンプルコード。
define i32 @main() { %result = call i32 @func1() ret i32 %result } define i32 @func1() { ret i32 222 }
%result = call i32 @func1()
で関数を呼んでいる。
関数定義は、関数を呼ぶ位置より下でもいいらしい。
https://releases.llvm.org/10.0.0/docs/LangRef.html#i-callによると、関数呼び出しの構文は以下。
<result> = [tail | musttail | notail ] call [fast-math flags] [cconv] [ret attrs] [addrspace(<num>)]
<ty>|<fnty> <fnptrval>(<function args>) [fn attrs] [ operand bundles ]
難しそうな部分はひとまず無視して、今回使うのは<result> = call <ty> <fnptrval>(<function args>)
の部分。
<result>
が戻り値を受け取るレジスタを指すローカル識別子。変数のようなものだが、ローカル変数と言ってしまうとレジスタではなくスタック領域に配置されるものと紛らわしいため、公式ドキュメントのLocal identifierの直訳、ローカル識別子と呼ぶことにする。
ローカル識別子は%<名前>
で定義する。上のコードでは%result
。
<ty>
が戻り値の型。上のコードではi32
。
<fnptrval>
が関数名。上のコードではfunc1
。
https://releases.llvm.org/10.0.0/docs/LangRef.html#i-callによると<fnptrval>
は
An LLVM value containing a pointer to a function to be called.
らしい。
fnptrval
はfunction pointer value
の略語と思われる。
とりあえずここでは関数名をそのまま指定すればよいと理解しておく。
<function args>
が引数リスト。上のコードでは引数なし。
引数付き関数の定義
先ほどは引数なしのコードを書いた。 次は引数ありの関数定義とその呼び出し。
define i32 @func1(i32 %x) { ret i32 %x } define i32 @main() { %result = call i32 @func1(i32 333) ret i32 %result }
1行目のi32 %x
が引数リスト。x
が変数名。
ドキュメントで説明を見つけられなかったが、<function args>
は型 値
形式と思われる。複数の場合はカンマ区切り。
6行目のi32 333
が引数。
C言語と違い、関数呼び出し時の引数にも型を書く必要がある。
グローバル変数、定数
https://releases.llvm.org/10.0.0/docs/LangRef.html#global-variablesによると、グローバル変数の定義の構文は以下。
@<GlobalVarName> = [Linkage] [PreemptionSpecifier] [Visibility]
[DLLStorageClass] [ThreadLocal]
[(unnamed_addr|local_unnamed_addr)] [AddrSpace]
[ExternallyInitialized]
<global | constant> <Type> [<InitializerConstant>]
[, section "name"] [, comdat [($name)]]
[, align <Alignment>] (, !name !N)*
難しそうな部分はひとまず無視して、今回使うのは@<GlobalVarName> = <global | constant> <Type> [<InitializerConstant>]
の部分。
グローバル変数を定義し、参照するコードは以下。
@g_x = constant i32 444 define i32 @main() { %result = load i32, i32* @g_x ret i32 %result }
変数名がg_x
、定数にするのでconstant
、型がi32
、初期値が444
。
ret i32 @g_x
のようにグローバル変数を直接returnしようとすると、コンパイル時にerror: global variable reference must have pointer type
というエラーになる。
グローバル変数の参照はポインタ型でなければならないらしい。
https://releases.llvm.org/10.0.0/docs/LangRef.html#load-instructionのload
命令を使い、%result = load i32, i32* @g_x
でいったん変数にコピーしてからreturnするとうまくいった。
グローバル変数はレジスタではなく静的領域に配置されるため、そこからレジストにロードしてから使わなければならないということだろう。
Hello World
Hello World
を標準出力に出力するコードは以下。
https://releases.llvm.org/10.0.0/docs/LangRef.html#module-structureにあるサンプルコードよりいろいろと削っている。
@hello_world = constant [12 x i8] c"hello world\00" declare i32 @puts(i8*) define i32 @main() { %ptr = getelementptr [12 x i8], [12 x i8]* @hello_world, i32 0, i32 0 call i32 @puts(i8* %ptr) ret i32 0 }
文字列のグローバル定数
@hello_world = constant [12 x i8] c"hello world\00"
が文字列定数のグローバル変数の定義。
文字列はC言語と同じで\0
で終わる8bit値の配列。LLVMでは\00
のように必ず二桁で書かなければならない。
https://releases.llvm.org/10.0.0/docs/LangRef.html#array-typeによると、LLVM IRの配列の構文は以下。
[<# elements> x <elementtype>]
<# elements>
が要素数、<elementtype>
が要素の型。
文字列なら[\00を含む文字数 x i8]
となる。Hello World\00
なら[12 x i8]
。
https://releases.llvm.org/10.0.0/docs/LangRef.html#complex-constantsのArray Constants
の説明によると、文字列定数はc"
と"
で囲むらしい。
Hello World
という文字列定数ならc"Hello World\00"
。
puts関数の宣言
declare i32 @puts(i8*)
が関数の宣言。
公式のリファレンス内にdeclare
というキーワードの説明を見つけられなかったが、英単語の意味やサンプルコードでの使われ方からして、C言語における関数のプロトタイプ宣言のようなものと思われる。
puts
関数を定義しなくても動いていることから、定義は外部にあるものと思われる。
lli
コマンドで実行するまでにリンクのようなことをしなかったがなぜか動いた。
puts
関数は、C言語の同名の関数?
配列の先頭アドレスの取得
C言語のputs
関数は、char配列の先頭アドレス、つまり8bit整数のポインタ型を引数にとる。
declare i32 @puts(i8*)
もi8
型のポインタを引数にとる。
配列のメンバ変数のポインタを取得するにはgetelemntptr
を使う。
get element pointerの意味だと思われる。
getelemntptr
は、配列にかぎらずaggregate型の要素アドレスを取得するのにも使う。例えば、構造体のメンバ変数のアドレスなど。
%ptr = getelementptr [12 x i8], [12 x i8]* @hello_world, i32 0, i32 0
でグローバル変数hello_world
の0番目の要素のポインタ、つまり配列の先頭アドレスを取得する。
https://releases.llvm.org/10.0.0/docs/LangRef.html#getelementptr-instructionによると、構文は以下。
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
1つ目の<ty>
は
The first argument is always a type used as the basis for the calculations.
最初の引数は、常に計算の基礎として使用されるタイプです。
何を言っているのかわからないが、サンプルコードを見た限り、文字列"Hello World"
の型、つまり[12 x i8]
型を指定すれば良さそう。
2つ目の<ty>* <ptrval>
は
The second argument is always a pointer or a vector of pointers, and is the base address to start from.
今、puts
関数の引数に渡すために文字列の先頭アドレス、つまりi8
の配列の先頭のアドレスを取得したい。
グローバル変数@hello_world
のベースアドレス、つまりi8配列の先頭位置のアドレスを取得したいので、<ptrval>
には@hello_world
を指定する。
<ty>*
には@hello_world
の型のポインタ、つまり[12x i8]*
を指定する。
残りの{, [inrange] <ty> <idx>}*
は
The interpretation of each index is dependent on the type being indexed into. The first index always indexes the pointer value given as the second argument, the second index indexes a value of the type pointed to.
各インデックスの解釈は、インデックスが作成される型に依存します。最初のインデックスは、常に2番目の引数として指定されたポインタ値にインデックスを付け、2番目のインデックスは、ポイントされた型の値にインデックスを付けます。
これも何を言っているのかわからない。
今回作成したコードでいうと、%ptr = getelementptr [12 x i8], [12 x i8]* @hello_world, i32 0, i32 0
のi32 0, i32 0
に該当する。
2番目の引数で指定したポインタ値というのは、[12 * i8]* @hello_world
。
これにインデックスをつけるというのは
@hello_worldのベースアドレス + ( [12 * i8]のサイズ * インデックス値 )
の位置のアドレスを取得することを意味すると思われる。
今回は@hello_world
の先頭アドレスが欲しいので、最初のインデックス
にはi32 0
を指定する。
これで得られる値の型は[12 * i8]*
型である。
欲しいのは、puts
関数に渡すためのi8*
型なので、2番目のインデックス
を指定する必要がある。
2番目のインデックス
が、[12 x i8]
型に対するインデックス、つまり普通の配列のインデックスに相当すると思われる。
今回、配列の先頭ポインタが欲しいので0
を指定する。
もしここを1
にすると、Hello World
ではなくello World
が出力される。
こうやって取得したポインタをcall i32 @puts(i8* %ptr)
でputs
関数に渡すと、文字列が標準出力に出力される。
なお、getelementptr
は構造体のメンバ変数のポインタを取得する場合にも使う。
それについての説明はこわくないLLVM入門! - Qiitaの説明がわかりやすい。
また、公式にQAがある。 The Often Misunderstood GEP Instruction — LLVM 10 documentation