コンパイラ作成(40) System V AMD64 ABIへの準拠
mycの問題点
以前、ABIに付いて調べたときから気になっていた点を見直すよ。mycの問題点は2つ。1件目はrbxが保存されてないこと。これは対処はそう難しくないかな。rbxの使用をやめて、r10にすれば良いんだと思う。r10は保存する必要ないからね。2件目はスタックのアライメントの話。こっちは厄介かなあ。
調査
現状どうなってるか調べてみる。まず、調査用の関数を作成するよ。
.intel_syntax noprefix .global rspcheck rspcheck: push rbp mov rbp, rsp lea rdi, .L.str mov rsi, rsp mov al, 0 call printf pop rbp ret .L.str: .asciz "rsp = %016x\n"
見て分かる通りrspの値をprintfで表示する関数。clangでアセンブルしてrspcheck.oを作っとくよ。で、これをコールするプログラムを作る。
// rsp int main() { rspcheck(); sub() + rspcheck(); } int sub() { return 42; }
試してみるよ。
~/myc$ myc m23.myc /tmp/m23-4c6d95.o: 関数 `main' 内: (.text+0x9): `rspcheck' に対する定義されていない参照です (.text+0x14): `rspcheck' に対する定義されていない参照です clang: error: linker command failed with exit code 1 (use -v to see invocation) ~/myc$ clang-5.0 m23.s rspcheck.o -o m23 ~/myc$ ./m23 rsp = 0000000043f54770 rsp = 0000000043f54768 ~/myc$
やっぱり2個目のrspcheckはスタックのアライメントが16byte境界になってないね。計算式の途中でraxを保存するときにずれちゃってる。clangのエラーが出てるのは気にしないでね。mycが自動でclang呼ぶときrspcheck.oをリンクするようにしてないからこうなっちゃう。myc m23.myc rspcheck.oみたいにできるようにすれば良いんだけどね。この辺は将来対処するつもり。(いつになるか分からないけど)
~/myc$ cd clang ~/myc/clang$ ./m23 rsp = 0000000057219370 rsp = 0000000057219370 ~/myc/clang$
話を元に戻してアライメントの件、同じソースをclangでコンパイルして確かめてみた。ちゃんとアライメントが揃ってるよ。さすがclang先輩だ。
コード生成
てことでコード生成部を修正するよ。
# 式のコード生成(二項演算の右側被演算子) def codegen_els(op, operand) if op.str == "+" then ostr = "add " elsif op.str == "-" then ostr = "sub " elsif op.str == "*" then ostr = "mul " elsif op.str == "/" then ostr = "div " elsif op.str == "==" then ostr = "cmp " elsif op.str == "!=" then ostr = "cmp " elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then ostr = "cmp " else perror "unknown operator \"" + op.str + "\"" end 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" codegen " call " + operand[0].str codegen " mov r10d, eax" codegen " pop rax" codegen " add rsp, 8" else codegen " sub rsp, 8" codegen " push rax" codegen_el operand codegen " mov r10d, eax" codegen " pop rax" codegen " add rsp, 8" end str = "r10d" elsif operand.kind == TK::ID then v = @lvars[operand.str] if v == nil then perror "undeclared variable \"" + operand.str + "\"" end str = "dword ptr [rbp - " + v[1].to_s + "]" elsif operand.kind == TK::NUMBER then str = operand.str end if op.str == "==" then codegen " " + ostr + " eax, " + str codegen " sete al" codegen " and eax, 1" elsif op.str == "!=" then codegen " " + ostr + " eax, " + str codegen " setne al" codegen " and eax, 1" elsif op.str == "<" then codegen " " + ostr + " eax, " + str codegen " setl al" codegen " and eax, 1" elsif op.str == ">" then codegen " " + ostr + " eax, " + str codegen " setg al" codegen " and eax, 1" elsif op.str == "<=" then codegen " " + ostr + " eax, " + str codegen " setle al" codegen " and eax, 1" elsif op.str == ">=" then codegen " " + ostr + " eax, " + str codegen " setge al" codegen " and eax, 1" elsif op.str == "*" || op.str == "/" then if str != "r10d" then codegen " mov r10d, " + str end codegen " mov r11, rdx" if op.str == "/" then codegen " xor edx, edx" end codegen " " + ostr + " r10d" codegen " mov rdx, r11" else codegen " " + ostr + " eax, " + str end end
修正箇所はここだけかなあ。
動作テスト
では行ってみるよ。
~/myc$ myc m23.myc /tmp/m23-5aed64.o: 関数 `main' 内: (.text+0x9): `rspcheck' に対する定義されていない参照です (.text+0x18): `rspcheck' に対する定義されていない参照です clang: error: linker command failed with exit code 1 (use -v to see invocation) ~/myc$ clang-5.0 m23.s rspcheck.o -o m23 ~/myc$ ./m23 rsp = 000000008c5062c0 rsp = 000000008c5062b0 ~/myc$
アライメント揃ったよ。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 64 call rspcheck call sub sub rsp, 8 push rax call rspcheck mov r10d, eax pop rax add rsp, 8 add eax, r10d .RET_main: add rsp, 64 pop rbp ret .global sub sub: push rbp mov rbp, rsp sub rsp, 64 mov eax, 42 jmp .RET_sub .RET_sub: add rsp, 64 pop rbp ret
生成されたコードはこんな感じ。今回の件はコーディング自体は大したことないんだけど、ABIを正しく理解するのが難しかったよ。