コンパイラ作成(93) int型→double型の変換
今回の目標
型変換を頑張るよ。
// int型→double型 int func(void) { return 5; } int func2(double a) { return 10; } int main() { int a = 12; double x, *p = &x; x = a; printf("x = %f\n", x); x = x * 2; printf("x = %f\n", x); x = x - a; printf("x = %f\n", x); x = x + (1 + 2); printf("x = %f\n", x); x = x + func(); printf("x = %f\n", x); x = 7 + x; printf("x = %f\n", x); x = x + func2(2); printf("x = %f\n", x); *p = 55; printf("x = %f\n", x); }
いろんな場合があるんで結構大変だよ。多分これで全部の場合を網羅できてると思うんだけど自信はないな。int型からの変換はcvtsi2sdってのでできるみたいなんで、これをあっちこっちに入れてくよ。
代入
まずは一番単純な場合。
# 代入のコード生成 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 # 暗黙の型変換 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 # 暗黙の型変換 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
単なる代入とポインターを介した代入があるんで二箇所修正。
二項演算
今回一番修正が多かった場所。
# 式のコード生成(二項演算の右側被演算子) 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_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 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型の処理 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 elsif type_l == "int" && is_pointer_type?(type_r) then # int型+ポインタ型の処理 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 type_l = type_r else codegen " " + ostr + " #{reg}, " + str end end return type_l end
右側被演算子の処理のところをまず修正。さらに型チェックの前にint型→double型の変換処理を追加。それとmnemonic メソッドを呼ぶタイミングを変更。
関数コール
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" then codegen " movsx rax, eax" type = f[1][i] elsif f[1][i] == "void*" && is_pointer_type?(type) then type = f[1][i] end if f[1][i] == "double" 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
引数の型と実際の型を見比べて必要なら変換。
動作テスト
~/myc$ myc p26.myc ~/myc$ ./p26 x = 12.000000 x = 24.000000 x = 12.000000 x = 15.000000 x = 20.000000 x = 27.000000 x = 37.000000 x = 55.000000 ~/myc$
大丈夫そうだね。
// モンテカルロ法による円周率の計算 extern double sqrt(double x); extern size_t time(size_t *t); extern void srand48(size_t seed); extern double drand48(); int main() { size_t t; srand48(time(&t)); int count = 0; for(int i = 0; i < 1000000; i = i + 1) { double x = drand48(); double y = drand48(); if( x*x + y*y <= 1.0 ) count = count + 1; } printf("pi = %10.8f\n",count / 1000000.0 * 4.0); }
前に作ったのをちょっと修正してみたよ。以前はcountがdouble型になってたんだけどint型に変更。今回の修正でこれもコンパイル通るはず。
~/myc$ myc p27.myc ~/myc$ ./p27 pi = 3.14179600 ~/myc$
うん、ちゃんと計算されてる。あ、そうだ。int型とdouble型をミックスして計算できるようになったから、drand48じゃなくて普通のrandを呼べるようになったのか。確か今のLinuxの標準関数だとrandの方が精度の良い乱数が生成されるんだったよね。今度やってみよう。
これでint型→double型の変換はできるようになったけど、まだ未対応のとこがあるよ。return文での型変換。今回それもやっちゃおうと思ったんだけど、今のmycは関数定義の情報全然参照してなくて、その辺からやらないと駄目なんで今回は諦めたよ。次回はその辺をやるつもりだよ。