コンパイラ作成(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型をサポートするとき困るよなあ。うん、次回修正しよう。