コンパイラ作成(11) Hello, World!
Hello, World!に挑戦
最近停滞気味だったので一気に行くよ。プログラミング言語と言ったらハロワ。これがコンパイルできたらもう半分はできちゃったようなもんだよね。(そんな分けない)
main()
{
puts("Hello, World!");
}
新しい要素としては文字列リテラルの導入。それと標準関数putsの呼出だね。putsの方はこないだ作ったprintと大して変わらないんでサクッとできると思うよ。問題は文字列リテラルの扱いだね。
お願いclang先輩!
テスト用のプログラムを作って調べてみる。
main() { "Hi everyone!"; "Are you ready?"; } sub() { "Let's go!"; }
clangのコンパイル結果はこんな感じだった。
.text .intel_syntax noprefix .file "e.myc" .globl main # -- Begin function main .p2align 4, 0x90 .type main,@function main: # @main # BB#0: push rbp mov rbp, rsp xor eax, eax pop rbp ret .Lfunc_end0: .size main, .Lfunc_end0-main # -- End function .globl sub # -- Begin function sub .p2align 4, 0x90 .type sub,@function sub: # @sub # BB#0: push rbp mov rbp, rsp mov eax, dword ptr [rbp - 4] pop rbp ret .Lfunc_end1: .size sub, .Lfunc_end1-sub # -- End function .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "Hi everyone!" .size .L.str, 13 .type .L.str.1,@object # @.str.1 .L.str.1: .asciz "Are you ready?" .size .L.str.1, 15 .type .L.str.2,@object # @.str.2 .L.str.2: .asciz "Let's go!" .size .L.str.2, 10 .ident "clang version 5.0.1-4 (tags/RELEASE_501/final)" .section ".note.GNU-stack","",@progbits
ふむふむ、文字列リテラルはプログラムの最期のところに纏められてるんだね。それぞれ.L.str.1みたいなラベルがつけられてるな。文字列リテラルのテーブル用意して管理すればいいのかな。やってみよう。
文字列リテラルのコンパイル
ちょちょいとプログラミング。
# n番目の文字列リテラルのラベル名 def literallabel(num) if num == 0 then return ".L.str" else return ".L.str." + num.to_s end end # 文字列リテラルをテーブルに追加 def addliteral(str) num = @literalcnt @literalcnt += 1 @literaltable << str return literallabel(num) end # 文字列リテラルを出力 def genliterals @literaltable.each_with_index do |literal, i| codegen (literallabel(i) + ":") codegen (" .asciz \"" + literal + "\"") end end
できた!あれ、これテーブルって言うか単なるリストじゃね?ま、細かいことは置いといて、早速試してみるよ。
main() { "Hi everyone!"; "Are you ready?"; }
mycはまだmain関数しかコンパイルできないんで、subの部分を削除。で、コンパイルしてみる。
.intel_syntax noprefix .global main main: ret .L.str: .asciz "Hi everyone!" .L.str.1: .asciz "Are you ready?"
よしよし、良い調子。このままputsへ進むよ。
標準関数putsの呼び出し
ひょこひょことコーディング。
elsif kind == TK::ID && str == "puts" then # 標準関数putsの処理 kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind == TK::STRING then label = addliteral str codegen " mov rdi, offset "+label codegen " call puts" end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end kind, str = @lex.gettoken if kind == TK::SYMBOL && str == ";" then return true end perror "expected ';' after function" return true;
組み込み関数printの処理とほとんど同じだよ。addliteralでテーブルに追加していってるのと、生成してるコードがちょっと変わってるぐらい。ではコンパイル。
~/myc$ ./myc.rb f.myc ~/myc$ clang-5.0 f.s -o f f.s:4:25: error: unknown token in expression mov rdi, offset .L.str ^ ~/myc$
ありゃりゃ、なんかエラーが出た。
.intel_syntax noprefix .global main main: mov rdi, offset .L.str call puts ret .L.str: .asciz "Hello, World!"
うーむ、コンパイル結果は問題なさそうなんだけどなあ。さて、どうしたもんか。
clang先輩のばかぁ
原因が良く分からないんでclangのコンパイル結果をもう一度眺めてみる。しばし見つめてても分かんないもんは分かんない。はあ困った。
~/myc/clang$ clang-5.0 -x c -S -mllvm -x86-asm-syntax=intel -fno-asynchronous-unwind-tables -O0 f.myc f.myc:3:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int] main() ^ 1 warning generated. ~/myc/clang$ clang-5.0 f.s -o f f.s:12:2: error: invalid operand for instruction movabs rdi, .L.str ^ ~/myc/clang$
おいおい、なんてこったい。clangで生成したアセンブリコードをclang自身がアセンブルできてないじゃん。これは予想外だよ。ググってそれらしい情報を見つけた。
[llvm-bugs] [Bug 32530] New: inline assembly incompatibility between gcc and clang - mov with offset in intel dialect
これ見るとclangのバグみたい。intel形式のアセンブルはあんまりテストされてない感じかな。インストールされてるclangのバージョン上げれば解決するんだろうか?
mov rdi, offset .L.str movabs rdi, .L.str lea rdi, .L.str
予想外のことで時間くっちゃったけど、lea使うようにしたらエラー出なくなったよ。上の3つは本来同じコードにアセンブルされるはずだよね。時間があればGasでも試してみたいなあ。いや、その前にclangのバージョンアップか。
Hello, World!
lea版でテスト行くよ。
~/myc$ ./myc.rb f.myc ~/myc$ clang-5.0 f.s -o f ~/myc$ ./f Hello, World! ~/myc$
お、できた。