コンパイラ作成(14) goto文

今回の目標

最近Haskellに浮気してて、こっち全然進めてなかったよ。新しいこと始めると、前にやってたのをほっぽり出しちゃう癖は駄目だなあ。ちょっとだけ反省。ってことでコンパイラの作成に戻るよ。
でなんだけど、今回の目標はgoto文にしたよ。一番簡単そうな制御文。

main()
{
lp:
    puts("Hello, World!");
    goto lp;
}

新しい要素としてはラベルの処理と、goto文の処理だね。

毎度おなじみClang先輩

まずはClangの吐き出すアセンブリコードを確認するよ。

#include <stdio.h>

main()
{
lp:
    puts("Hello, World!");
    goto lp;
}

sub()
{
lp:
    puts("Hello, World!");
    goto lp;
}

二つの関数内に同じ名前のラベルを定義してみたよ。

	.text
	.intel_syntax noprefix
	.file	"h.myc"
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
# BB#0:
	push	rbp
	mov	rbp, rsp
	sub	rsp, 16
	mov	dword ptr [rbp - 4], 0
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
	movabs	rdi, .L.str
	call	puts
	mov	dword ptr [rbp - 8], eax # 4-byte Spill
	jmp	.LBB0_1
.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
	sub	rsp, 16
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
	movabs	rdi, .L.str
	call	puts
	mov	dword ptr [rbp - 8], eax # 4-byte Spill
	jmp	.LBB1_1
.Lfunc_end1:
	.size	sub, .Lfunc_end1-sub
                                        # -- End function
	.type	.L.str,@object          # @.str
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str:
	.asciz	"Hello, World!"
	.size	.L.str, 14


	.ident	"clang version 5.0.1-4 (tags/RELEASE_501/final)"
	.section	".note.GNU-stack","",@progbits

なるほどラベル名は.LBB0_1と.LBB1_1に置き換わってるな。そうしないとラベル名かち合っちゃうもんなあ。

Lexerクラス

まずはLexerクラスを修正。

      # operatorの切り出し
      elsif @line[@idx].match(/=|\+|\-|:|;|\(|\)|\{|\}/)  then
        str += @line[@idx]
        @idx += 1
        return TK::SYMBOL, str

シンボルとして":"を追加したよ。

ラベル定義
    elsif kind == TK::ID then
      # 関数/ラベルの処理
      idname = str
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ":" then
        # ラベルの処理
        codegen ".LBB_" + @funcname + "_" + idname + ":"
      else
        # 関数呼出の処理
        if kind != TK::SYMBOL || str != "(" then perror end
        kind, str = @lex.gettoken
        if kind != TK::SYMBOL || str != ")" then perror end
        codegen "  call "+idname
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == ";" then return true end
        perror "expected ';' after function"
      end
      return true;

関数呼出とラベル定義はもう一個先のトークンを見ないと判別できないんで、その点注意しながら前回書いたコードに手を入れたよ。

goto文

goto文の処理は簡単。jmpするだけ。

    elsif kind == TK::ID && str == "goto" then
      # goto文の処理
      kind, str = @lex.gettoken
      if kind == TK::ID then
        codegen "  jmp .LBB_"+ @funcname + "_" + str
      end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after goto"
      return true;
@funcname

ラベル名の置き換えで必要なんで追加した。

  # 関数の構文解析
  def function()
    kind, str = @lex.gettoken
    if kind == TK::EOF then return false end
    if kind != TK::ID then perror end
    @funcname = str
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "(" then perror end
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != ")" then perror end
    codegen ".global "+@funcname
    codegen @funcname+":"
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "{" then perror end
    while statement do
    end
    codegen "  ret"
    @funcname = nil
    return true
  end
コンパイルテスト

コーディング終わったんでテストしてみるよ。テスト用のソースは最初のやつね。

.intel_syntax noprefix
.global main
main:
.LBB_main_lp:
  lea rdi, .L.str
  call puts
  jmp .LBB_main_lp
  ret
.L.str:
  .asciz  "Hello, World!"

上手くいったみたい。置き換えのラベル名はClangとちょっと変えてみたよ。文字列リテラルの時はClangと全く同じにしちゃったけど、あれもこれもみんな同じにするのは気が引けるって言うか、いろいろと拙いかもしれないんでね。アセンブル、リンクして動かしてみたらちゃんと無限ループしてHello, World!がいっぱい表示されたよ。

問題点

今回も細かいこと色々はっしょっちゃったんで問題ありありだよ。

  • ラベル定義の重複をチェックしていない
  • gotoの飛び先ラベルをチェックしていない

今回はテストが不十分なんで他にも拙い点があるかもなあ。前回の関数呼出の時の問題点もそのままになってるし拙いなあ。

無限ループ

今回ので無事無限ループするプログラムがコンパイルできるようになったよ。でもやっぱりループから脱出できるようにしたいよなあ。そのためにはカウンタ用の変数用意して、それをインクリメントして、さらにif文で条件判断しないと駄目かあ。こら大変だなあ。まだまだやること多いよ。そろそろ式の評価に手を付けるか。難しそうなんで今まで避けてきたんだけどね。