コンパイラ作成(79) double型の代入・参照
今回の目標
前回、Lexerの修正したんで、今回はdouble型の代入と参照まで実装するよ。
// double型 int main() { double d = 10.78; double e = 0.13e-3; printf("d = %f\n", d); printf("e = %f\n", e); }
double型追加
まずはここ。
@typeword = [ "extern","void","int","char","size_t","double" ]
sizeofも修正。
# 型のサイズ def sizeof(type) if is_pointer_type?(type) then return 8 end case type when "void" return 1 when "char" return 1 when "int" return 4 when "size_t" return 8 when "double" return 8 else perror "unknown type '#{type}'" end end
これでdouble型の変数が宣言できるようになるはず。
リテラルの管理
clang先輩のコンパイル結果を見てみたら浮動小数点リテラルは文字列リテラルと同じような感じになってた。
# n番目のリテラルのラベル名 def literallabel(num,type) if type == :string then l = ".L.str" else l = ".L.float" end return "#{l}.#{num}" end # リテラルをテーブルに追加 def addliteral(str, type=:string) 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] == :float then codegen (literallabel(i,:float) + ":") 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
代入処理
# 代入のコード生成 def codegen_assign(el) if el.size != 3 then perror 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 # 暗黙の型変換 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 # 暗黙の型変換 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
double型のときはxmmレジスタを使うように。double型は64bitなんでqwordだよ。
参照処理
# 式のコード生成(二項演算の左側被演算子) def codegen_elf(operand) type = "int" if operand.kind_of?(Array) then if !operand[0].kind_of?(Array) && operand[0].kind == TK::ID && operand[1].str == "()" then type = codegen_func operand else type = codegen_el operand end elsif operand.kind == TK::NUMBER then codegen " mov eax, " + operand.str elsif operand.kind == TK::ID then v = get_var operand.str if v == nil then perror "undeclared variable \"" + operand.str + "\"" end type = v[0] if is_pointer_type? type then codegen " mov rax, qword ptr [rbp - " + v[1].to_s + "]" elsif type == "double" then codegen " movsd xmm8, qword ptr [rbp - " + v[1].to_s + "]" elsif type == "char" then codegen " mov al, byte ptr [rbp - " + v[1].to_s + "]" else codegen " mov eax, dword ptr [rbp - " + v[1].to_s + "]" end elsif operand.kind == TK::STRING then type = "char*" label = addliteral operand.str codegen " lea rax, "+label elsif operand.kind == TK::FLOAT then type = "double" label = addliteral operand.str, :float codegen " movsd xmm8, qword ptr [#{label}]" else perror end return type end
double型変数の参照と、リテラルの処理を追加。
関数コール
printfをコールしたいんで修正。
# 関数コールのコード生成 def codegen_func operand rettype = "int" f = @functions[operand[0].str] if @numuseregs != 0 then if @numuseregs % 2 == 1 then codegen " sub rsp, 8" end (0...@numuseregs).each do |i| codegen " push #{@regs64[i]}" 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| save = @numuseregs @numuseregs = i type = codegen_el operand[i+2] @numuseregs = save 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]}, eax" elsif type == "size_t" codegen " mov #{@regs64[i]}, rax" =begin elsif type == "char*" codegen " mov #{@regs64[i]}, rax" elsif type == "void*" codegen " mov #{@regs64[i]}, rax" =end elsif is_pointer_type?(type) then codegen " mov #{@regs64[i]}, rax" elsif type == "double" then codegen " movsd xmm0, xmm8" else perror end end if f == nil then codegen " mov al, 1" end codegen " call " + operand[0].str if f != nil then rettype = f[0] end if @numuseregs != 0 then (0...@numuseregs).reverse_each do |i| codegen " pop #{@regs64[i]}" end if @numuseregs % 2 == 1 then codegen " add rsp, 8" end end return rettype end
本当はもっと色々論理を組み込まないといけないんだけど、今回は手抜きの良い加減コーディングだよ。double型変数一個の場合以外は上手く行かないよ。xmm0固定になってるしね。
動作テスト
変更箇所多かったけどどうかな。
~/myc$ myc p7.myc ~/myc$ ./p7 d = 10.780000 e = 0.000130 ~/myc$
おお、表示できたよ。アセンブリコードも見てみる。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 16 movsd xmm8, qword ptr [.L.float.0] movsd qword ptr [rbp - 8], xmm8 movsd xmm8, qword ptr [.L.float.1] movsd qword ptr [rbp - 16], xmm8 lea rax, .L.str.2 mov rdi, rax movsd xmm8, qword ptr [rbp - 8] movsd xmm0, xmm8 mov al, 1 call printf lea rax, .L.str.3 mov rdi, rax movsd xmm8, qword ptr [rbp - 16] movsd xmm0, xmm8 mov al, 1 call printf .RET_main: add rsp, 16 pop rbp ret .L.float.0: .quad 40258f5c28f5c28fH # 10.78 .L.float.1: .quad 3f210a137f38c543H # 0.13e-3 .L.str.2: .asciz "d = %f\n" .L.str.3: .asciz "e = %f\n"
うんうん良さそうです。今回一番苦労した点はxmmレジスタの使い方だったよ。mov xmm0,xmm8ってやったら怒られちゃったしね。参考になりそうな良いサイト無いかとググってみたんだけど、SIMD関連の情報ばかりで見つからなかったよ。もっと基本的な情報が欲しかったんだけどさあ。Intel® 64 and IA-32 Architectures Software Developer Manuals | Intel® Softwareを見て勉強するしかないのかなあ。
次回は今回サボった関数コールを何とかしたいよ。