コンパイラ作成(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文で条件判断しないと駄目かあ。こら大変だなあ。まだまだやること多いよ。そろそろ式の評価に手を付けるか。難しそうなんで今まで避けてきたんだけどね。