コンパイラ作成(100) 配列の参照
今回の目標
今回は配列の参照だよ。
// 配列 int main() { int a[10], *p; p = a; *p = 42; printf("a[0] = %d\n", a[0]); printf("0[a] = %d\n", 0[a]); printf("10 + a[0] = %d\n", 10 + a[0]); printf("10 + 0[a] = %d\n", 10 + 0[a]); }
配列への代入は今回はやらないんでポインタを通して値を設定してる。
read_el
今回は修正箇所多いよ。まずは式リストの読み込み処理。
# 式の最後までのトークンを読み込む # 返り値 # el : expression's token list # kind : 処理しなかったトークン(セミコロンもしくは閉じ括弧) # str : 処理しなかったトークン(セミコロンもしくは閉じ括弧) def read_el(fkind,fstr,skind,sstr) el = [] if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") skind, sstr = @lex.gettoken loop do if skind == TK::SYMBOL && sstr == ")" then break end fkind, fstr = skind, sstr skind, sstr = @lex.gettoken pel, skind, sstr = read_modify_el fkind, fstr, skind, sstr sel << pel if skind == TK::SYMBOL && sstr == "," then skind, sstr = @lex.gettoken elsif skind != TK::SYMBOL || sstr != ")" then perror end end el << sel if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken elsif sstr == "[" then # 配列の処理 el << Token.new(fkind,fstr) el << Token.new(skind,"[]") fkind, fstr = @lex.gettoken skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr if sel.size > 1 then el << sel else el << sel[0] end skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end loop do if skind == TK::EOF then break end if sstr == ";" then break end if sstr == ")" then break end if sstr == "]" then break end if sstr == "," then break end if sstr == "}" then break end fkind, fstr = skind, sstr skind, sstr = @lex.gettoken if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") skind, sstr = @lex.gettoken loop do if skind == TK::SYMBOL && sstr == ")" then break end fkind, fstr = skind, sstr skind, sstr = @lex.gettoken pel, skind, sstr = read_el fkind, fstr, skind, sstr sel << pel if skind == TK::SYMBOL && sstr == "," then skind, sstr = @lex.gettoken elsif skind != TK::SYMBOL || sstr != ")" then perror end end el << sel if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken elsif sstr == "[" then # 配列の処理 el << Token.new(fkind,fstr) el << Token.new(skind,"[]") fkind, fstr = @lex.gettoken skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr if sel.size > 1 then el << sel else el << sel[0] end skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end end return el, skind, sstr end
配列をa[0]から(a[]0)へと変形してる。こうすることで二項演算子と同じになるんで後の処理が楽になるよ。今回の修正ではエラー処理とかすっ飛ばしちゃってるよ。とりあえず正常なソースをコンパイルできるところまで何とかするけど、それ以外はあちこち手抜きだよ。
read_modify_el
次はここ。
# 式の最後までのトークンを読み込み、変形する def read_modify_el(fkind,fstr,skind,sstr) el, kind, str = read_el fkind, fstr, skind, sstr puts to_str(el) if $opt_d # デバッグ用 el = modify_el_unaryop el, ["+","-","&","*"] el = modify_el el, ["[]"] el = modify_el el, ["*","/","%"] el = modify_el el, ["+","-"] el = modify_el el, ["<",">","<=",">="] el = modify_el el, ["==","!="] el = modify_el el, ["="], :r_to_l puts to_str(el) if $opt_d # デバッグ用 return el, kind, str end
[]を追加したんだけどこれ問題ありだよ。C言語の仕様では配列は単項演算子より優先順位が上なんで、本当はel = modify_el el, ["[]"]をel = modify_el_unaryop el, ["+","-","&","*"]より前に持っていかなきゃならないんだけど、modify_elは単項演算子が混じってるelに対応できるようになってないんだよね。それで今回はこういう順番にしちゃった。配列のサポートがある程度出来上がったら修正しようと思ってるよ。
新しいメソッド
codegen_elsから処理を切り出したよ。
# ポインタ型+int型のコード生成 def codegen_pointer_int(type_l,op,ostr,str) size = sizeof type_l[0,type_l.length-1] if str == op.str then codegen " " + ostr + " rax, " + (str.to_i*size).to_s elsif str == "r10d" then codegen " movsx r10, r10d" if size == 4 then codegen " shl r10, 2" codegen " " + ostr + " rax, r10" elsif size == 8 then codegen " shl r10, 2" codegen " " + ostr + " rax, r10" else (0...size).each do codegen " " + ostr + " rax, r10" end end else codegen " mov r10d, " + str codegen " movsx r10, r10d" if size == 4 then codegen " shl r10, 2" codegen " " + ostr + " rax, r10" elsif size == 8 then codegen " shl r10, 3" codegen " " + ostr + " rax, r10" else (0...size).each do codegen " " + ostr + " rax, r10" end end end end # int型+ポインタ型のコード生成 def codegen_int_pointer(type_r,op,ostr,str) size = sizeof type_r[0,type_r.length-1] codegen " movsx rax, eax" if size == 1 then elsif size == 4 then codegen " shl rax, 2" elsif size == 8 then codegen " shl rax, 3" else codegen " mov r11, rax" (0...size-1).each do codegen " add rax, r11" end end codegen " " + ostr + " rax, " + str end
配列の参照処理はポインタとint型の加算と同じような処理なんで切り出したメソッドを活用してくよ。
mnemonic
# ニーモニック def mnemonic(op, type) if type == "double" then if op.str == "+" then return "addsd " elsif op.str == "-" then return "subsd " elsif op.str == "*" then return "mulsd " elsif op.str == "/" then return "divsd " elsif op.str == "%" then return "divsd " elsif op.str == "==" then return "ucomisd" elsif op.str == "!=" then return "ucomisd" elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then return "ucomisd" elsif op.str == "[]" then return "addsd " else perror "unknown operator \"" + op.str + "\"" end else if op.str == "+" then return "add " elsif op.str == "-" then return "sub " elsif op.str == "*" then return "imul" elsif op.str == "/" then return "idiv" elsif op.str == "%" then return "idiv" elsif op.str == "==" then return "cmp " elsif op.str == "!=" then return "cmp " elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then return "cmp " elsif op.str == "[]" then return "add " else perror "unknown operator \"" + op.str + "\"" end end end
ここは[]を追加しただけ。
codegen_els
コード生成部。あちこちに手を入れたよ。
# 式のコード生成(二項演算の右側被演算子) def codegen_els(op, operand, type_l) # 右被演算子を評価 type_r = "int" if operand.kind_of?(Array) then if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then if type_l == "double" then codegen " sub rsp, 16" codegen " movsd [rsp], xmm8" type_r = codegen_func operand if type_r == "double" then codegen " movsd xmm9, xmm8" str = "xmm9" else codegen " mov r10d, eax" str = "r10d" end codegen " movsd xmm8, [rsp]" codegen " add rsp, 16" else codegen " sub rsp, 8" codegen " push rax" type_r = codegen_func operand if type_r == "double" then codegen " movsd xmm9, xmm8" str = "xmm9" else codegen " mov r10d, eax" str = "r10d" end codegen " pop rax" codegen " add rsp, 8" end else if type_l == "double" then codegen " sub rsp, 16" codegen " movsd [rsp], xmm8" type_r = codegen_el operand if type_r == "double" then codegen " movsd xmm9, xmm8" str = "xmm9" else codegen " mov r10d, eax" str = "r10d" end codegen " movsd xmm8, [rsp]" codegen " add rsp, 16" else codegen " sub rsp, 8" codegen " push rax" type_r = codegen_el operand if type_r == "double" then codegen " movsd xmm9, xmm8" str = "xmm9" else codegen " mov r10d, eax" str = "r10d" end codegen " pop rax" codegen " add rsp, 8" end end elsif operand.kind == TK::ID then v = get_var operand.str if v == nil then perror "undeclared variable \"" + operand.str + "\"" end type_r = v[0] if is_array_type? type_r then codegen " lea r10, [rbp - " + v[1].to_s + "]" str = "r10" elsif is_pointer_type? type_r then str = "qword ptr [rbp - " + v[1].to_s + "]" elsif type_r == "double" then str = "qword ptr [rbp - " + v[1].to_s + "]" elsif type_r == "char" then str = "byte ptr [rbp - " + v[1].to_s + "]" else str = "dword ptr [rbp - " + v[1].to_s + "]" end elsif operand.kind == TK::NUMBER then str = operand.str elsif operand.kind == TK::STRING then type_r = "char*" label = addliteral operand.str codegen " lea r10, "+label str = "r10" elsif operand.kind == TK::FLOAT then # 浮動小数点数リテラル type_r = "double" label = addliteral operand.str, :double str = "qword ptr [#{label}]" else perror end # 型チェック if type_l == "double" && type_r == "int" then if str != "r10d" then codegen " mov r10d, #{str}" end codegen " cvtsi2sd xmm9, r10d" str = "xmm9" type_r = type_l elsif type_l == "int" && type_r == "double" then codegen " cvtsi2sd xmm8, eax" type_l = type_r end if type_l != type_r then if is_pointer_type?(type_l) && type_r == "int" then if op.str != "+" && op.str != "-" then perror "mismatched types to binary operation" end elsif type_l == "int" && is_pointer_type?(type_r) then if op.str != "+" && op.str != "-" then perror "mismatched types to binary operation" end elsif is_array_type?(type_l) && type_r == "int" then if op.str != "[]" then perror "mismatched types to binary operation" end elsif type_l == "int" && is_array_type?(type_r) then if op.str != "[]" then perror "mismatched types to binary operation" end else perror "mismatched types to binary operation" end elsif is_pointer_type? type_l then perror "mismatched types to binary operation" elsif type_l == "double" && op.str == "%" then perror "mismatched types to binary operation" end reg = "eax" if type_l == "double" then reg = "xmm8" elsif type_l == "char" then reg = "al" end # 左被演算子と右被演算子とで計算 ostr = mnemonic op, type_l if op.str == "==" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " sete al" codegen " setnp r10b" codegen " and al, r10b" codegen " and eax, 1" type_l = "int" else codegen " sete al" codegen " and eax, 1" end elsif op.str == "!=" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " setne al" codegen " setp r10b" codegen " or al, r10b" codegen " and eax, 1" type_l = "int" else codegen " setne al" codegen " and eax, 1" end elsif op.str == "<" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " setb al" codegen " and eax, 1" type_l = "int" else codegen " setl al" codegen " and eax, 1" end elsif op.str == ">" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " seta al" codegen " and eax, 1" type_l = "int" else codegen " setg al" codegen " and eax, 1" end elsif op.str == "<=" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " setbe al" codegen " and eax, 1" type_l = "int" else codegen " setle al" codegen " and eax, 1" end elsif op.str == ">=" then codegen " " + ostr + " #{reg}, " + str if type_l == "double" then codegen " setae al" codegen " and eax, 1" type_l = "int" else codegen " setge al" codegen " and eax, 1" end elsif type_l != "double" && (op.str == "*" || op.str == "/" || op.str == "%") then mov = "mov" if type_r == "char" then mov = "movsx" end if type_l == "char" then codegen " movsx eax, al" end if str != "r10d" then codegen " #{mov} r10d, " + str end codegen " mov r11, rdx" if op.str == "/" || op.str == "%" then codegen " cdq" end codegen " " + ostr + " r10d" if op.str == "%" then codegen " mov eax, edx" end codegen " mov rdx, r11" else if is_pointer_type?(type_l) && type_r == "int" then # ポインタ型+int型の処理 codegen_pointer_int type_l, op, ostr, str elsif type_l == "int" && is_pointer_type?(type_r) then # int型+ポインタ型の処理 codegen_int_pointer type_r, op, ostr, str type_l = type_r elsif is_array_type?(type_l) && type_r == "int" then # ポインタ型+int型の処理 type_l = array_to_pointer type_l codegen_pointer_int type_l, op, ostr, str codegen " mov eax, [rax]" type_l = type_l[0,type_l.length-1] elsif type_l == "int" && is_array_type?(type_r) then type_r = array_to_pointer type_r codegen_int_pointer type_r, op, ostr, str codegen " mov eax, [rax]" type_l = type_r[0,type_r.length-1] else codegen " " + ostr + " #{reg}, " + str end end return type_l end
これで全部修正できたかな。
動作テスト
~/myc$ myc q4.myc ~/myc$ ./q4 a[0] = 42 0[a] = 42 10 + a[0] = 52 10 + 0[a] = 52 ~/myc$
おお、動いたよ。今回は修正箇所多くて大変だったけどなんとか上手く行った。さて次は何やろう。配列への代入かな。
コンパイラ作成(99) 配列のポインタへの変換
今回の目標
配列の実装を進めるよ。
// 配列 int main() { int a[10], *p; p = a; printf("p = %016lx\n", p); }
簡単そうなところから。
ヘルパーメソッド
# 配列型? def is_array_type?(type) return type.match(/\[.*\]$/) end # 配列型→pointer型 def array_to_pointer(type) return type.sub(/\[.*\]$/,"*") end
型情報関連のメソッドを二つ追加。正規表現でごにょごにょやってるけど、これどう考えても遅いよね。型情報の持ち方を工夫すれば良いのかな。この辺は将来的な課題だな。
codegen_assign
# 代入のコード生成 def codegen_assign(el) if el.size != 3 then perror end if el[2] == nil then perror "broken expression" end type_r = codegen_el [el[2]] if el[0].kind_of?(Array) then if el[0][0].str == "*" then codegen " sub rsp, 8" codegen " push rax" type_l = codegen_el [el[0][1]] if !is_pointer_type? type_l then perror end type_l = type_l[0,type_l.length-1] codegen " mov r10, rax" codegen " pop rax" codegen " add rsp, 8" if type_r == "void*" && is_pointer_type?(type_l) then type_r = type_l # 暗黙の型変換 elsif type_r == "int" && type_l == "char" then type_r = type_l # 暗黙の型変換 elsif type_r == "int" && type_l == "double" then codegen " cvtsi2sd xmm8, eax" type_r = type_l # 暗黙の型変換 elsif is_array_type? type_r then type_r = array_to_pointer type_r end if type_l != type_r then perror end if is_pointer_type? type_l then codegen " mov qword ptr [r10], rax" elsif type_l == "double" codegen " movsd qword ptr [r10], xmm8" elsif type_l == "char" codegen " mov byte ptr [r10], al" else codegen " mov dword ptr [r10], eax" end else perror end else if el[0].kind != TK::ID then perror end v = get_var el[0].str if v == nil then perror "undeclared variable \"" + el[0].str + "\"" end type_l = v[0] if type_r == "void*" && is_pointer_type?(type_l) then type_r = type_l # 暗黙の型変換 elsif type_r == "int" && type_l == "char" then type_r = type_l # 暗黙の型変換 elsif type_r == "int" && type_l == "double" then codegen " cvtsi2sd xmm8, eax" type_r = type_l # 暗黙の型変換 elsif is_array_type? type_r then type_r = array_to_pointer type_r end if type_l != type_r then perror end if is_pointer_type? type_l then codegen " mov qword ptr [rbp - " + v[1].to_s + "], rax" elsif type_l == "double" codegen " movsd qword ptr [rbp - " + v[1].to_s + "], xmm8" elsif type_l == "char" codegen " mov byte ptr [rbp - " + v[1].to_s + "], al" else codegen " mov dword ptr [rbp - " + v[1].to_s + "], eax" end end return type_l end
さっきのヘルパーメソッドを使って型を変換してるよ。
codegen_func
引数の処理部。
# 引数を順番に評価する (0...operand.size-2).each do |i| savegpr, savexmm = @numuseregs, @numusexmms @numuseregs, @numusexmms = i-xmm, xmm type = codegen_el operand[i+2] @numuseregs, @numusexmms = savegpr, savexmm if f != nil then if f[1][i] == "size_t" && type == "int" then codegen " movsx rax, eax" type = f[1][i] elsif f[1][i] == "void*" && is_pointer_type?(type) then type = f[1][i] elsif f[1][i] == "double" && type == "int" then codegen " cvtsi2sd xmm8, eax" type = f[1][i] elsif is_array_type? type then type = array_to_pointer type end if type != f[1][i] then perror "incompatible type parameter" end end if type == "int" || type == "char" then codegen " mov #{@regs32[i-xmm]}, eax" elsif type == "size_t" codegen " mov #{@regs64[i-xmm]}, rax" elsif is_pointer_type?(type) then codegen " mov #{@regs64[i-xmm]}, rax" elsif type == "double" then codegen " movsd xmm#{xmm}, xmm8" xmm += 1 else perror end end
ここも同じように修正。
動作テスト
~/myc$ myc q2.myc ~/myc$ ./q2 p = 00007ffd3a476548 ~/myc$
上手く行ったかな。良く分からないんでアセンブリコードも見てみるよ。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 48 lea rax, [rbp - 40] mov qword ptr [rbp - 48], rax lea rax, .L.str.0 mov rdi, rax mov rax, qword ptr [rbp - 48] mov rsi, rax mov al, 0 call printf .RET_main: add rsp, 48 pop rbp ret .L.str.0: .asciz "p = %016lx\n"
うん、ちゃんとコード生成されてる。もう一個試してみるよ。
// repl extern char *gets(char *s); extern int puts(char *s); extern int strcmp(char *string1, char *string2); int main() { char buffer[256]; for(;;) { printf("repl:"); gets(buffer); if(strcmp(buffer,"quit") == 0) break; printf("=>"); puts(buffer); } }
昔書いたreplもどきをmallocから配列に変更してみたよ。
~/myc$ ./q3 repl:123 =>123 repl:abc =>abc repl:12+16 =>12+16 repl:quit ~/myc$
ちゃんと動いてるね。次回は配列の参照かな。式の構文解析からやらないといけないから結構大変かなあ。コード生成部は前に作ったポインタ型とint型の加算と同じようにやれば良いんだよなあ。うーん、良く考えよう。
コンパイラ作成(98) 配列の宣言
今回の目標
いきなりだけど配列に手を出してみるよ。
// 配列 int main() { int a[10], b; }
まずは配列変数の宣言から。
Lexer
gettokenの修正。
# symbolの切り出し elsif m = @line[@idx,@line.length] .match(/^(==|!=|<=|>=|=|<|>|\+|\-|\*|\/|\%|&|:|,|;|\(|\)|\{|\}|\[|\])/) then str = m.to_s @idx += str.length return TK::SYMBOL, str end
括弧[]をを追加しただけ。
変数宣言
# 変数宣言の処理 def var_decl(kind, str) basetype = str loop do type = basetype ary = "" ary_size = nil kind, str = @lex.gettoken loop do if kind != TK::SYMBOL || str != "*" then break end type += str kind, str = @lex.gettoken end if kind != TK::ID then perror end var_name = str skind, sstr = @lex.gettoken if skind == TK::SYMBOL && sstr == "[" then ary += sstr skind, sstr = @lex.gettoken if skind != TK::NUMBER then perror end ary += sstr ary_size = sstr.to_i skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != "]" then perror end ary += sstr skind, sstr = @lex.gettoken end print "var #{var_name}:#{type}#{ary}\n" if $opt_d if ary_size then @lvarsize += (sizeof(type) * ary_size) else @lvarsize += sizeof(type) end if check_var str then perror "redefinition variable \"" + var_name +"\"" end if type == "void" then perror "invalid type 'void'" end set_var var_name, [type+ary,@lvarsize] if skind == TK::SYMBOL && sstr == "=" then kind, str = expr2 kind, str, skind, sstr; else kind, str = skind, sstr; end if kind != TK::SYMBOL || str != "," then break end end return kind, str end
変数の型をどう表現するか悩んだけどint[10]と表わすことにしたよ。扱いやすい形式じゃないから将来見直すかも。配列のサイズ情報は別に持った方が良いかもなあ。悩ましいけどとりあえずこれで実装してみるよ。あと配列の初期化については今のとこ考慮してないよ。多次元配列とかもね。まずは一番シンプルなとこから行くよ。
動作テスト
~/myc$ myc -dc q.myc var a:int[10] var b:int {"a"=>["int[10]", 40], "b"=>["int", 44]} {"main"=>["int", []]} ~/myc$
ちゃんと40byteの領域が確保されてる。大丈夫そうだね。えーと、次はポインターへの代入とかやってみるかな。割と簡単そうだしね。配列への代入や参照は難しそうだもんなあ。頑張ってちょっとずつ実装してこう。
コンパイラ作成(97) return文での型変換
今回の目標
int型→double型の型変換。
// int型→double型 double func(void) { return 5; } int main() { double x; x = func(); printf("x = %f\n", x); }
最近、return文のとこばかりだな。
statement
return文の処理を修正。
if kind == TK::RESERVE && str == "return" then # return文の処理 f = @functions[@funcname] kind, str = @lex.gettoken if kind != TK::SYMBOL && str != ";" then kind, str, type = expr kind, str if f[0] == "double" && type == "int" then codegen " cvtsi2sd xmm0, eax" type = f[0] elsif f[0] == "double" then codegen " movsd xmm0, xmm8" end if f[0] != type then if f[0] == "void" then perror "return a value at void function" else perror "return with incompatible result type" end end else if f[0] != "void" then perror "return no value at non-void function" end end codegen " jmp .RET_" + @funcname if kind != TK::SYMBOL || str != ";" then perror "expected ';' after return statement" end
int型以外からの変換も入れちゃおうかと思ったけど、中途半端になるかなと思ってやめちゃった。
動作テスト
~/myc$ myc p28.myc ~/myc$ ./p28 x = 5.000000 ~/myc$
ちゃんと変換されてる。これでint型→double型の変換は終わったよな。さて次回は何やろうかな。char型からの変換とかもやんなきゃいけないんだけど、ちょっと飽きてきたんで違うことやろうかな。
コンパイラ作成(96) 型変換のバグ修正
バグ修正
プログラム眺めてたらバグを見っけたよ。
// double型 double dadd(double a, double b) { printf("dadd: %f %f\n", a, b); return a + b; } int main() { double x = 12.3, y = -2.8; printf("%f %f %f\n", x, y, dadd(5.4, 3.2)); printf("%d %d %f\n", 12, 7, dadd(5.4, 3.2)); }
この前、int型→double型の変換処理を入れたけど、変換が要らないときにもcvtsi2sdしてたよ。
関数コール
functionの引数の処理部。
# 引数を順番に評価する (0...operand.size-2).each do |i| savegpr, savexmm = @numuseregs, @numusexmms @numuseregs, @numusexmms = i-xmm, xmm type = codegen_el operand[i+2] @numuseregs, @numusexmms = savegpr, savexmm if f != nil then if f[1][i] == "size_t" && type == "int" then codegen " movsx rax, eax" type = f[1][i] elsif f[1][i] == "void*" && is_pointer_type?(type) then type = f[1][i] elsif f[1][i] == "double" && type == "int" then codegen " cvtsi2sd xmm8, eax" type = f[1][i] end if type != f[1][i] then perror "incompatible type parameter" end end if type == "int" || type == "char" then codegen " mov #{@regs32[i-xmm]}, eax" elsif type == "size_t" codegen " mov #{@regs64[i-xmm]}, rax" elsif is_pointer_type?(type) then codegen " mov #{@regs64[i-xmm]}, rax" elsif type == "double" then codegen " movsd xmm#{xmm}, xmm8" xmm += 1 else perror end end
if文の条件が間違ってたよ。size_tの場合も拙かったんで併せて修正。
動作テスト
~/myc$ myc p25.myc ~/myc$ ./p25 dadd: 5.400000 3.200000 12.300000 -2.800000 8.600000 dadd: 5.400000 3.200000 12 7 8.600000 ~/myc$
修正完了。修正箇所のプログラムをよく見て一人レビューするの大事だな。それで怪しいとこ見つけたら即テスト。次回は今回やるはずだったreturn文での型変換をやるつもりだよ。
コンパイラ作成(95) return文での型のチェック
今回の目標
前回の続きでreturn文。
// 関数の呼び出し int *sub(void) { return 2.5; } int main() { printf("%d\n",sub()); }
型をチェックするよ。
expr
# 式の構文解析 def expr2(fkind,fstr,skind,sstr) el, kind, str = read_modify_el fkind, fstr, skind, sstr return kind, str, codegen_el(el) end
型の情報を返すようインターフェースを修正。
statement
return文の処理。
if kind == TK::RESERVE && str == "return" then # return文の処理 f = @functions[@funcname] kind, str = @lex.gettoken if kind != TK::SYMBOL && str != ";" then kind, str, type = expr kind, str if f[0] != type then if f[0] == "void" then perror "return a value at void function" else perror "return with incompatible result type" end end if f[0] == "double" then codegen " movsd xmm0, xmm8" end else if f[0] != "void" then perror "return no value at non-void function" end end codegen " jmp .RET_" + @funcname if kind != TK::SYMBOL || str != ";" then perror "expected ';' after return statement" end
exprからの型情報と関数情報テーブルの返値の型を見比べてエラーを出してるんだけど、色んな場合があって面倒だよ。
動作テスト
~/myc$ myc err45.myc err45.myc:4:15 error: return with incompatible result type ~/myc$
ちゃんとエラーになってるね。さらに別のパターンを二つチェックするよ。
// 関数の呼び出し void sub(void) { return 42; } int main() { printf("%d\n",sub()); }
void関数で値を返してる場合。
// 関数の呼び出し int sub(void) { return; } int main() { printf("%d\n",sub()); }
逆にvoid関数じゃないのに値を返してない場合。
~/myc$ myc err46.myc err46.myc:4:14 error: return a value at void function ~/myc$ myc err47.myc err47.myc:4:11 error: return no value at non-void function ~/myc$
大丈夫だね。一応これで全部のパターンチェックしたつもり。次回はreturn文でのint型→double型の変換をやるつもりだよ。
コンパイラ作成(94) 値を返さないreturn文
今回の目標
単純なreturn文。
// 関数の呼び出し void sub(void) { return; } int main() { sub(); printf("%d\n",42); }
これサポートしてなかったよ。
statement
return文の処理。
def statement(kind, str) if kind == TK::RESERVE && str == "return" then # return文の処理 kind, str = @lex.gettoken if kind != TK::SYMBOL && str != ";" then kind, str = expr kind, str f = @functions[@funcname] if f[0] == "double" then codegen " movsd xmm0, xmm8" end end codegen " jmp .RET_" + @funcname if kind != TK::SYMBOL || str != ";" then perror "expected ';' after return statement" end
if文を一個追加しただけ。
動作テスト
~/myc$ myc g4.myc ~/myc$ ./g4 42 ~/myc$
コンパイル通るようになったよ。返値の型のチェックも入れようと思ったんだけど、exprにも手を入れて型を返すようにしないと駄目なんで今回は諦めたよ。今回、ちっとも進んでないなあ。明日頑張ろう。