コンパイラ作成(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$ 

お、できた。