コンパイラ作成(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先輩でコンパイルしたときのワーニングをなくせるよ。