コンパイラ作成(51) 引数のある関数の定義
今回の目標
引数のある関数を定義できるようにするよ。
// 引数のある関数の定義 int main() { int a = 5, b = 12, c; c = add(a,b); printf("%d + %d = %d\n", a, b, c); } int add(int a, int b) { printf("add: a = %d b = %d\n", a, b); return a + b; }
前回はclang先輩に頼ってた関数addも自力で頑張る。
function
今回の修正箇所はここだけ。
# 関数の構文解析 def function() @labelcnt = 0 @lvars = Hash.new @lvarsize = 0; rettype = nil kind, str = @lex.gettoken if kind == TK::RESERVE && str == "int" then rettype = str kind, str = @lex.gettoken end if kind == TK::EOF then return false end if kind != TK::ID then perror "expected identifier" end @funcname = str kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken loop do if kind == TK::SYMBOL && str == ")" then break end if kind == TK::RESERVE && str == "int" then kind, str = @lex.gettoken if kind != TK::ID then perror end print "para "+str+"\n" if $opt_d @lvarsize += 4 if @lvars[str] then perror "redefinition parameter \"" + str +"\"" end @lvars[str] = ["int",@lvarsize] else perror end kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "," then kind, str = @lex.gettoken end end codegen ".global "+@funcname codegen @funcname+":" codegen " push rbp" codegen " mov rbp, rsp" # 仮のコードを作成 idx = codegen " sub rsp, xx" n = @lvars.size (0...n).each do |i| codegen " mov dword ptr [rbp - #{(i+1)*4}], #{@regs32[i]}" end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "{" then perror end kind, str = block codegen ".RET_" + @funcname + ":" # 16の倍数になるように揃える size = (@lvarsize+15) / 16 * 16 # 正しいサイズでコードを生成し置き換える if size != 0 then codechange idx," sub rsp, #{size}" codegen " add rsp, #{size}" else codechange idx,nil end codegen " pop rbp" codegen " ret" if @functions[@funcname] != nil then perror "redefinition of \"" + @funcname + "\"" end @functions[@funcname] = [rettype,[]] p @lvars if $opt_d # デバッグ用 optimize if $opt_O != 0 codeflush @funcname = nil return true end
レジスタに入ってる値をスタック上に格納してる。関数がリーフファンクションの場合は必ずしもやらなくても良い処理なんだけど、場合分けしてコード生成するの大変なんで無条件にやってるよ。
動作テスト
どうかな。
~/myc$ myc o6.myc ~/myc$ ./o6 add: a = 5 b = 12 5 + 12 = 17 ~/myc$
動いたよ。他にも試してみる。
// 階乗の計算 int main() { int i; for(i=1; i<=10; i=i+1) printf("%2d! = %d\n", i, fact(i)); } int fact(int n) { if( n <= 1 ) return 1; else return n * fact(n-1); }
階乗だよ。
~/myc$ myc o7.myc ~/myc$ ./o7 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 ~/myc$
おお、できた。ちょっと感動。もういっちょ行ってみるよ。
// フィボナッチ数列 int main() { int i; for(i=1; i<=10; i=i+1) printf("fib(%2d) = %d\n", i, fib(i)); } int fib(int n) { if( n < 2 ) return 1; else return fib(n-2) + fib(n-1); }
フィボナッチ数列だよ。
~/myc$ myc o8.myc ~/myc$ ./o8 fib( 1) = 1 fib( 2) = 2 fib( 3) = 3 fib( 4) = 5 fib( 5) = 8 fib( 6) = 13 fib( 7) = 21 fib( 8) = 34 fib( 9) = 55 fib(10) = 89 ~/myc$
これもちゃんとコンパイルできてる。これで引数のある関数の定義と呼出が両方ともできるようになったよ。大分C言語っぽくなってきた。ただ、まだ制限事項があるよ。引数の数が6個までしか対応できてないって点。7個目からはレジスタ渡しじゃなくスタック渡しになるんだけど、その論理が入ってないよ。いずれなんとかしたいけど当面このままで次回は別の事するつもりだよ。
ちなみにmyc.rbは今1073行。いつのまにか1000行超えてたよ。