コンパイラ作成(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行超えてたよ。