コンパイラ作成(81) double型の単純な四則演算
今回の目標
double型の計算を実装するよ。
// double型 int main() { double a = 2.7, b = 5.1, c; c = a + b; printf("%f + %f = %f\n", a, b, c); c = a - b; printf("%f - %f = %f\n", a, b, c); c = a * b; printf("%f * %f = %f\n", a, b, c); c = a / b; printf("%f / %f = %f\n", a, b, c); c = a + 2.0; printf("%f + 2.0 = %f\n", a, c); c = a - 2.0; printf("%f - 2.0 = %f\n", a, c); c = a * 2.0; printf("%f * 2.0 = %f\n", a, c); c = a / 2.0; printf("%f / 2.0 = %f\n", a, c); }
今回は単純な四則演算。
コード生成部
# ニーモニック 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 "cmp " elsif op.str == "!=" then return "cmp " elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then return "cmp " 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 " else perror "unknown operator \"" + op.str + "\"" end end end # 式のコード生成(二項演算の右側被演算子) def codegen_els(op, operand, type_l) ostr = mnemonic op, 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 codegen " sub rsp, 8" codegen " push rax" type_r = codegen_func operand codegen " mov r10d, eax" codegen " pop rax" codegen " add rsp, 8" else codegen " sub rsp, 8" codegen " push rax" type_r = codegen_el operand codegen " mov r10d, eax" codegen " pop rax" codegen " add rsp, 8" end str = "r10d" 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, :float str = "qword ptr [#{label}]" else perror 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 # 左被演算子と右被演算子とで計算 if op.str == "==" then codegen " " + ostr + " #{reg}, " + str codegen " sete al" codegen " and eax, 1" elsif op.str == "!=" then codegen " " + ostr + " #{reg}, " + str codegen " setne al" codegen " and eax, 1" elsif op.str == "<" then codegen " " + ostr + " #{reg}, " + str codegen " setl al" codegen " and eax, 1" elsif op.str == ">" then codegen " " + ostr + " #{reg}, " + str codegen " setg al" codegen " and eax, 1" elsif op.str == "<=" then codegen " " + ostr + " #{reg}, " + str codegen " setle al" codegen " and eax, 1" elsif op.str == ">=" then codegen " " + ostr + " #{reg}, " + str codegen " setge al" codegen " and eax, 1" 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型のときはeaxとr10dで計算してたのを、double型のときはxmm8とxmm9で計算するようにした。それと剰余演算はエラーになるようにチェック。
動作テスト
~/myc$ myc p9.myc ~/myc$ ./p9 2.700000 + 5.100000 = 7.800000 2.700000 - 5.100000 = -2.400000 2.700000 * 5.100000 = 13.770000 2.700000 / 5.100000 = 0.529412 2.700000 + 2.0 = 4.700000 2.700000 - 2.0 = 0.700000 2.700000 * 2.0 = 5.400000 2.700000 / 2.0 = 1.350000 ~/myc$
大丈夫そうだね。今回は単純な式にしか対応できなかったよ。2.0 + 3.5 * 4.1みたいなちょっと複雑なだけで計算できなくなる。次回はその辺頑張るよ。