コンパイラ作成(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は関数定義の情報全然参照してなくて、その辺からやらないと駄目なんで今回は諦めたよ。次回はその辺をやるつもりだよ。
コンパイラ作成(92) 引数の無い関数の定義でのvoid
今回の目標
いくつかテストしてる時に気が付いたんだけど、voidを使って関数に引数が無いことを明示する記述の仕方、まだ対応してなかったよ。
// 関数の呼び出し int answer(void) { // Answer to the Ultimate Question of Life, the Universe, and Everything return 42; } int main() { printf("%d\n",answer()); }
簡単にできそうなんでやってみるよ。
function
引数の処理のところを修正するよ。
# 引数の処理 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 loop do if kind != TK::SYMBOL || str != "*" then break end type += str kind, str = @lex.gettoken end if type == "void" \ && kind == TK::SYMBOL && str == ")" \ && paratype == [] then break end paratype << type if kind != TK::ID then if type == "void" then perror "invalid type 'void'" else perror "wrong parameter name" end end print "para "+str+"\n" if $opt_d size = sizeof type @lvarsize += size parametersize << size if check_var str then perror "redefinition parameter \"" + str +"\"" end if type == "void" then perror "invalid type 'void'" end set_var str, [type,@lvarsize] else perror end if kind != TK::SYMBOL || str != ")" then kind, str = @lex.gettoken end if kind == TK::SYMBOL && str == "," then kind, str = @lex.gettoken end end
voidが一個だけの場合は受け付けるようにして、それ以外の時はエラーになるように修正。初めは単純な処理だったのが、こんがらがった処理になっちゃったなあ。
動作テスト
~/myc$ myc l6.myc ~/myc$ ./l6 42 ~/myc$
コンパイル通るようになったよ。もういっちょ行ってみる。
// 関数の呼び出し int answer(int a,void) { // Answer to the Ultimate Question of Life, the Universe, and Everything return 42; } int main() { printf("%d\n",answer()); }
今度はエラーにしなきゃいけないパターン。
~/myc$ myc err44.myc err44.myc:2:22 error: invalid type 'void' ~/myc$
ちゃんとエラーになったよ。次回はint型からdouble型への変換をやりたいなあ。今回それをやろうと思ってたんだけど、voidが未対応だって気付いたら、気になって気になってついついそっちを先にやっちゃったよ。
コンパイラ作成(91) 浮動小数点数リテラルの処理
今回の目標
重複したリテラルを一個に纏めるよ。
// 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)); }
それとリテラルに付けるラベル名を.L.floatから.L.doubleに変更するよ。
リテラル管理
# n番目のリテラルのラベル名 def literallabel(num,type) if type == :string then l = ".L.str" else l = ".L.double" end return "#{l}.#{num}" end # リテラルをテーブルに追加 def addliteral(str, type=:string) @literaltable.each_with_index do |x, i| if x == [type,str] then return literallabel(i,type) end end num = @literalcnt @literalcnt += 1 @literaltable << [type,str] return literallabel(num,type) end # リテラルを出力 def genliterals @literaltable.each_with_index do |literal, i| if literal[0] == :string then codegen (literallabel(i,:string) + ":") codegen (" .asciz \"" + literal[1] + "\"") elsif literal[0] == :double then codegen (literallabel(i,:double) + ":") ieee754binary = [literal[1].to_f] .pack('G').bytes .map{|n| "%08b" % n} .join.to_i(2).to_s(16) codegen " .quad #{ieee754binary}H # #{literal[1]}" end end codeflush end
addliteralで登録する前に同じのが既にあるかチェックするようにしたよ。
コード生成部
elsif operand.kind == TK::FLOAT then # 浮動小数点数リテラル type = "double" label = addliteral operand.str, :double codegen " movsd xmm8, qword ptr [#{label}]"
elsif operand.kind == TK::FLOAT then # 浮動小数点数リテラル type_r = "double" label = addliteral operand.str, :double str = "qword ptr [#{label}]"
floatをdoubleに変更。これトークンがTK::FLOATなんでそれにつられてfloatにしちゃったんだな。失敗失敗。
動作テスト
~/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$
動作は問題ないね。リテラルが纏められてるかアセンブリコード見てみるよ。
.intel_syntax noprefix .global dadd dadd: push rbp mov rbp, rsp sub rsp, 16 movsd qword ptr [rbp - 8], xmm0 movsd qword ptr [rbp - 16], xmm1 lea rax, .L.str.0 mov rdi, rax movsd xmm8, qword ptr [rbp - 8] movsd xmm0, xmm8 movsd xmm8, qword ptr [rbp - 16] movsd xmm1, xmm8 mov al, 2 call printf movsd xmm8, qword ptr [rbp - 8] addsd xmm8, qword ptr [rbp - 16] movsd xmm0, xmm8 .RET_dadd: add rsp, 16 pop rbp ret .global main main: push rbp mov rbp, rsp sub rsp, 16 movsd xmm8, qword ptr [.L.double.1] movsd qword ptr [rbp - 8], xmm8 movsd xmm8, qword ptr [.L.double.2] movq rax, xmm8 movabs r10, 8000000000000000H xor rax, r10 movq xmm8, rax movsd qword ptr [rbp - 16], xmm8 lea rax, .L.str.3 mov rdi, rax movsd xmm8, qword ptr [rbp - 8] movsd xmm0, xmm8 movsd xmm8, qword ptr [rbp - 16] movsd xmm1, xmm8 sub rsp, 32 mov qword ptr [rsp + 0], rdi movsd qword ptr [rsp + 8], xmm0 movsd qword ptr [rsp + 16], xmm1 movsd xmm8, qword ptr [.L.double.4] movsd xmm0, xmm8 movsd xmm8, qword ptr [.L.double.5] movsd xmm1, xmm8 call dadd movsd xmm8, xmm0 mov rdi, qword ptr [rsp + 0] movsd xmm0, qword ptr [rsp + 8] movsd xmm1, qword ptr [rsp + 16] add rsp, 32 movsd xmm2, xmm8 mov al, 3 call printf lea rax, .L.str.6 mov rdi, rax mov eax, 12 mov esi, eax mov eax, 7 mov edx, eax sub rsp, 32 mov qword ptr [rsp + 0], rdi mov qword ptr [rsp + 8], rsi mov qword ptr [rsp + 16], rdx movsd xmm8, qword ptr [.L.double.4] movsd xmm0, xmm8 movsd xmm8, qword ptr [.L.double.5] movsd xmm1, xmm8 call dadd movsd xmm8, xmm0 mov rdi, qword ptr [rsp + 0] mov rsi, qword ptr [rsp + 8] mov rdx, qword ptr [rsp + 16] add rsp, 32 movsd xmm0, xmm8 mov al, 1 call printf .RET_main: add rsp, 16 pop rbp ret .L.str.0: .asciz "dadd: %f %f\n" .L.double.1: .quad 402899999999999aH # 12.3 .L.double.2: .quad 4006666666666666H # 2.8 .L.str.3: .asciz "%f %f %f\n" .L.double.4: .quad 401599999999999aH # 5.4 .L.double.5: .quad 400999999999999aH # 3.2 .L.str.6: .asciz "%d %d %f\n"
二回ずつ使われてる5.4と3.2のリテラルが一個になってる。うまくいったよ。さて次回は何やろうかな。int型とdouble型の変換でもやろうかな。
コンパイラ作成(90) ネストした関数呼出
今回の目標
double型引数の関数呼出が入子になってる場合に対応するよ。
// 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)); }
関数daddの呼出で既にセットされてるxmmレジスタの値が潰されちゃうんで、daddコールの前後で退避・復帰する処理を追加する。昔、int型の時にも同じような処理を入れたけど、そこを改変するよ。
initialize
# コンストラクタ def initialize(fname) @fname = fname # ソースファイルのファイル名 @asmfname = fname.sub(/\.myc$/,'.s') # アセンブリコードのファイル名 @regs32 = ["edi", "esi","edx","ecx","r8d","r9d"] # 32bitレジスタ @regs64 = ["rdi", "rsi","rdx","rcx","r8", "r9" ] # 64bitレジスタ @lex = Lexer.new(@fname) # 字句解析 @funcname = nil # 現在処理している関数名 @labelcnt = nil # 自動生成するラベルの個数(関数単位) @literalcnt = 0 # 文字列リテラルの数 @literaltable = [] # 文字列リテラルのリスト @functions = Hash.new # 関数 @lvars = nil # ローカル変数 @lvarsize = nil # スタックに確保する領域のサイズ @breaklabel = nil # breakの飛び先のラベル @codebuffer = [] # コードバッファ @numuseregs = 0 # 関数コールで使用している一般レジスタの数 @numusexmms = 0 # 関数コールで使用しているxmmレジスタの数 @numblock = nil # blockの個数 @blocks = nil # ネストしたblock(["B2#","B1#",""]) end
使用しているxmmレジスタの数用の変数を一個追加。
codegen_func
# 関数コールのコード生成 def codegen_func operand rettype = "int" xmm = 0 # xmmレジスタの使用数 f = @functions[operand[0].str] # 既に使用されているレジスタをスタックに保存する if @numuseregs + @numusexmms != 0 then size = (@numuseregs + @numusexmms) * 8 if size % 16 != 0 then size += 8 end codegen " sub rsp, #{size}" offset = 0 (0...@numuseregs).each do |i| codegen " mov qword ptr [rsp + #{offset}], #{@regs64[i]}" offset += 8 end (0...@numusexmms).each do |i| codegen " movsd qword ptr [rsp + #{offset}], xmm#{i}" offset += 8 end end if f != nil then if operand.size - 2 != f[1].size then perror "wrong number of parameters" end end # 引数を順番に評価する (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 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 f == nil then codegen " mov al, #{xmm}" end codegen " call " + operand[0].str if f != nil then rettype = f[0] end if rettype == "double" codegen " movsd xmm8, xmm0" end # スタックに保存したレジスタを復帰する if @numuseregs + @numusexmms != 0 then size = (@numuseregs + @numusexmms) * 8 if size % 16 != 0 then size += 8 end offset = 0 (0...@numuseregs).each do |i| codegen " mov #{@regs64[i]}, qword ptr [rsp + #{offset}]" offset += 8 end (0...@numusexmms).each do |i| codegen " movsd xmm#{i}, qword ptr [rsp + #{offset}]" offset += 8 end codegen " add rsp, #{size}" end return rettype end
int型の時はゼネラルパーパスレジスタの退避/復帰にpush/pop使ってたんだけど、xmmレジスタはpush/popできないんでmovsd使ってるよ。で、この際なんでゼネラルパーパスレジスタの方もpush/popやめてmovにした。ちゃんと確かめてないけどこっちの方がクロック数的にお得なんじゃないかな。違うかな。
動作テスト
~/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$
大丈夫だよね。アセンブリコードも見ておくよ。
.intel_syntax noprefix .global dadd dadd: push rbp mov rbp, rsp sub rsp, 16 movsd qword ptr [rbp - 8], xmm0 movsd qword ptr [rbp - 16], xmm1 lea rax, .L.str.0 mov rdi, rax movsd xmm8, qword ptr [rbp - 8] movsd xmm0, xmm8 movsd xmm8, qword ptr [rbp - 16] movsd xmm1, xmm8 mov al, 2 call printf movsd xmm8, qword ptr [rbp - 8] addsd xmm8, qword ptr [rbp - 16] movsd xmm0, xmm8 .RET_dadd: add rsp, 16 pop rbp ret .global main main: push rbp mov rbp, rsp sub rsp, 16 movsd xmm8, qword ptr [.L.float.1] movsd qword ptr [rbp - 8], xmm8 movsd xmm8, qword ptr [.L.float.2] movq rax, xmm8 movabs r10, 8000000000000000H xor rax, r10 movq xmm8, rax movsd qword ptr [rbp - 16], xmm8 lea rax, .L.str.3 mov rdi, rax movsd xmm8, qword ptr [rbp - 8] movsd xmm0, xmm8 movsd xmm8, qword ptr [rbp - 16] movsd xmm1, xmm8 sub rsp, 32 mov qword ptr [rsp + 0], rdi movsd qword ptr [rsp + 8], xmm0 movsd qword ptr [rsp + 16], xmm1 movsd xmm8, qword ptr [.L.float.4] movsd xmm0, xmm8 movsd xmm8, qword ptr [.L.float.5] movsd xmm1, xmm8 call dadd movsd xmm8, xmm0 mov rdi, qword ptr [rsp + 0] movsd xmm0, qword ptr [rsp + 8] movsd xmm1, qword ptr [rsp + 16] add rsp, 32 movsd xmm2, xmm8 mov al, 3 call printf lea rax, .L.str.6 mov rdi, rax mov eax, 12 mov esi, eax mov eax, 7 mov edx, eax sub rsp, 32 mov qword ptr [rsp + 0], rdi mov qword ptr [rsp + 8], rsi mov qword ptr [rsp + 16], rdx movsd xmm8, qword ptr [.L.float.7] movsd xmm0, xmm8 movsd xmm8, qword ptr [.L.float.8] movsd xmm1, xmm8 call dadd movsd xmm8, xmm0 mov rdi, qword ptr [rsp + 0] mov rsi, qword ptr [rsp + 8] mov rdx, qword ptr [rsp + 16] add rsp, 32 movsd xmm0, xmm8 mov al, 1 call printf .RET_main: add rsp, 16 pop rbp ret .L.str.0: .asciz "dadd: %f %f\n" .L.float.1: .quad 402899999999999aH # 12.3 .L.float.2: .quad 4006666666666666H # 2.8 .L.str.3: .asciz "%f %f %f\n" .L.float.4: .quad 401599999999999aH # 5.4 .L.float.5: .quad 400999999999999aH # 3.2 .L.str.6: .asciz "%d %d %f\n" .L.float.7: .quad 401599999999999aH # 5.4 .L.float.8: .quad 400999999999999aH # 3.2
daddの前後でちゃんと退避・復帰されてる。昨日の夜遅い時間に考えてた時は難しく感じたんだけど、実際にやってみたらさほど難しくはなかったよ。遅い時間だと頭のクロック数が落ちてるからかな。
さて次回はなにやるかな。上のアセンブリコード見てたらリテラルが重複してるの気になるから修正しようかな。clang先輩はちゃんと一個に纏めてたよなあ。それに.L.floatってのも変だよなあ。どうして.L.doubleにしなかったんだろ。将来double型以外にfloat型をサポートするとき困るよなあ。うん、次回修正しよう。
コンパイラ作成(89) 式のエラーチェック
今回の目標
前回に続きエラーチェック。
// 壊れてる式 int main() { int i; i = ; }
壊れた代入。
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]]
codegen_elを呼ぶ前にチェックをするように修正。
動作テスト
~/myc$ myc err42.myc err42.myc:5:8 error: broken expression ~/myc$
大丈夫だね。今回は一個しかチェックできなかったよ。二回続けてエラーチェックまわりをやったけど、次回はdouble型関連に戻るかな。
コンパイラ作成(88) 式のエラーチェック
今回の目標
double型関連にちょっと疲れたんで別の事やるよ。壊れてる式をチェックしてエラーに。
// 壊れてる式 int main() { 1 + ; }
// 壊れてる式 int main() { + ; }
// 壊れてる式 int main() { / ; }
// 壊れてる式 int main() { int i; i++; }
ちょっとづつ違う4パターン。最後のはC言語としては正しいんだけど、mycはまだインクリメント演算子をサポートしてないんでエラー。
modify_el_unaryop
# elの変形(単行演算子の処理) # [-, 2, +, 3] # =>[[-, 2], + 3] # [-, 2, +, -, 3] # =>[[-, 2], + [-, 3]] def modify_el_unaryop(el, opl) mel = [] prev = nil loop do if el.size == 0 then break end x = el.shift if ((prev == nil) || (!prev.kind_of?(Array) && (prev.kind == TK::SYMBOL))) \ && !x.kind_of?(Array) && x.kind == TK::SYMBOL then if opl.include? x.str then tel = [] tel << x if el == [] then perror "broken expression" end x = el.shift if x.kind_of?(Array) then x = modify_el_unaryop x, opl elsif opl.include? x.str then sel = [] loop do sel << x if !opl.include? x.str then break end if el.size == 0 then break end x = el.shift end x = modify_el_unaryop sel, opl if x.size == 1 && x[0].kind_of?(Array) then x = x[0] end end tel << x mel << tel prev = tel else perror "broken expression" end else if x.kind_of?(Array) then x = modify_el_unaryop x, opl end mel << x prev = x end end return mel end
codegen_el
# 式のコード生成 def codegen_el(el) type = "int" if !el[0].kind_of?(Array) && el[0].kind == TK::SYMBOL type = codegen_unaryop el[0], [el[1]] elsif (el.size > 1) && el[1].str == "=" then type = codegen_assign el else type = codegen_elf el.shift loop do if el == [] then break end op = el.shift operand = el.shift if operand == nil then perror "broken expression" end type = codegen_els op, operand, type end end return type end
動作テスト
4つ纏めて行くよ。
~/myc$ myc err38.myc err38.myc:4:8 error: broken expression ~/myc$ myc err39.myc err39.myc:4:7 error: broken expression ~/myc$ myc err40.myc err40.myc:4:7 error: broken expression ~/myc$ myc err41.myc err41.myc:5:8 error: broken expression ~/myc$
みんなエラーになったよ。今回はこの4件をチェックしたけど、まだまだ漏れてるのあるだろうなあ。代入演算子、アドレス演算子、参照演算子がらみのチェックは不十分だよなあ。次回もエラーチェックかな。
コンパイラ作成(87) 引数がdouble型の関数定義
今回の目標
double型の関数だよ。
// double型 double dadd(double a, double b) { return a + b; } int main() { double x = 12.3, y = -2.8, z = dadd(x, y); printf("%f + %f = %f\n", x, y, z); }
引数も返値もdouble型の場合。
function
# レジスタで渡された引数をスタックの領域に移す n = @lvars.size offset = 0 gpr_cnt = xmm_cnt = 0 (0...n).each do |i| offset += parametersize[i] if paratype[i] == "double" then codegen " movsd qword ptr [rbp - #{offset}], xmm#{xmm_cnt}" xmm_cnt += 1 else if parametersize[i] == 4 then codegen " mov dword ptr [rbp - #{offset}], #{@regs32[gpr_cnt]}" else codegen " mov qword ptr [rbp - #{offset}], #{@regs64[gpr_cnt]}" end gpr_cnt += 1 end end
引数の型を見て処理を分けてるよ。そういやここの処理、引数がレジスタから溢れてスタックに入れられてる場合に対応してないなあ。面倒そうなんで放ったらかしにしてたよ。
statement
# return文の処理 kind, str = @lex.gettoken kind, str = expr kind, str f = @functions[@funcname] if f[0] == "double" then codegen " movsd xmm0, xmm8" end codegen " jmp .RET_" + @funcname if kind != TK::SYMBOL || str != ";" then perror "expected ';' after return statement" end
ここも修正が必要。レジスタ割り当てが適切ならこの処理要らないんだよなあ。ていうかmycはレジスタの割り当てしてないからなあ。この辺は遠い将来の課題だな。
動作テスト
そいではテスト。
~/myc$ myc p23.myc ~/myc$ ./p23 12.300000 + -2.800000 = 9.500000 ~/myc$
えーと、計算合ってるよね。もう一個テスト。
// double型 double dimul(double a, int b) { double sum = 0.0; for(int i = 0; i < b; i = i + 1) sum = sum + a; return sum; } int main() { int y = 10; double x = 12.3, z = dimul(x, y); printf("%f * %d = %f\n", x, y, z); }
引数がdouble型とint型のミックスの場合。
~/myc$ myc p24.myc ~/myc$ ./p24 12.300000 * 10 = 123.000000 ~/myc$
これもできたよ。これ本当はreturn a * b;ってやりたいんだけど、型変換の論理が入ってないんでまだ無理。これもやんなきゃいけないんだけど、次回はネストした関数の呼出かな。