コンパイラ作成(60) 関数のextern宣言

今回の目標

いきなりだけどextern行ってみるよ。

// 関数のextern宣言
extern int add(int a, int b);

int main()
{
   int a = 5, b = 12, c;
   c = add(a,b);
   printf("%d + %d = %d\n",a,b,c);
}

本来はextern宣言の場合、仮引数の名前は省略できるんだけど、面倒なんで省略できない仕様にしちゃった。将来見直したいなあ。

Lexerのinitialize

externを追加。

    @typeword = [
      "extern","int","char"
    ]

ここに追加するかreservedwordに追加するかちょっと悩んだよ。

function

extern宣言もfunctionメソッドで処理するよ。

  # 関数の構文解析
  def function()
    @labelcnt = 0
    @lvars = Hash.new
    @lvarsize = 0
    extern = false
    rettype = nil
    kind, str = @lex.gettoken
    rettype = "int"
    if kind == TK::TYPE && str == "extern" then
      extern = true
      kind, str = @lex.gettoken
    end
    if kind == TK::TYPE then
      if str == "extern" then perror end
      rettype = str
      kind, str = @lex.gettoken
    end
    if kind == TK::SYMBOL && str == "*" 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
    parametersize = []
    paratype = []
    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::TYPE then
        if str == "extern" then perror "invalid 'extern'" end
        type = str
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == "*" then
          type += str
          kind, str = @lex.gettoken
        end
        paratype << type
        if kind != TK::ID then perror "wrong parameter name" end
        print "para "+str+"\n" if $opt_d
        size = sizeof type
        @lvarsize += size
        parametersize << size
        if @lvars[str] then perror "redefinition parameter \"" + str +"\"" end
        @lvars[str] = [type,@lvarsize]
      else
        perror
      end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "," then
        kind, str = @lex.gettoken
      end
    end
    if @functions[@funcname] != nil then perror "redefinition of \"" + @funcname + "\"" end
    @functions[@funcname] = [rettype,paratype]
    kind, str = @lex.gettoken
    if extern then
      if kind != TK::SYMBOL || str != ";" then perror end
      return true;
    end
    codegen ".global "+@funcname
    codegen @funcname+":"
    codegen "  push rbp"
    codegen "  mov  rbp, rsp"
    # 仮のコードを作成
    idx = codegen "  sub  rsp, xx"
    # レジスタで渡された引数をスタックの領域に移す
    n = @lvars.size
    offset = 0
    (0...n).each do |i|
      offset += parametersize[i]
      if parametersize[i] == 4 then
        codegen "  mov  dword ptr [rbp - #{offset}], #{@regs32[i]}"
      else
        codegen "  mov  qword ptr [rbp - #{offset}], #{@regs64[i]}"
      end
    end
    # 関数本体の処理
    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"
    p @lvars if $opt_d   # デバッグ用
    optimize if $opt_O != 0
    codeflush
    @funcname = nil
    return true
  end

引数の型の情報も関数のHashテーブルに保存するようにしたよ。本来は関数コールの処理でこの情報を参照して、間違った型でコールしようとしてる場合エラーにしないといけないんだけど、その処理はまだ入ってないよ。うーむ、中途半端。

動作テスト
~/myc$ myc -d o17.myc add2.o
para a
para b
var a
[a, =, 5]
[[a, =, 5]]
var b
[b, =, 12]
[[b, =, 12]]
var c
[c, =, [add, (), [a], [b]]]
[[c, =, [add, (), [a], [b]]]]
[a]
[a]
[b]
[b]
[c]
[c]
{"a"=>["int", 4], "b"=>["int", 8], "c"=>["int", 12]}
{"add"=>["int", ["int", "int"]], "main"=>["int", []]}
~/myc$ ./o17
add: a = 5 b = 12
5 + 12 = 17
~/myc$ 

できたよ。これで同じソースをclang先輩でコンパイルしたときのワーニングをなくせるよ。