コンパイラ作成(99) 配列のポインタへの変換
今回の目標
配列の実装を進めるよ。
// 配列 int main() { int a[10], *p; p = a; printf("p = %016lx\n", p); }
簡単そうなところから。
ヘルパーメソッド
# 配列型? def is_array_type?(type) return type.match(/\[.*\]$/) end # 配列型→pointer型 def array_to_pointer(type) return type.sub(/\[.*\]$/,"*") end
型情報関連のメソッドを二つ追加。正規表現でごにょごにょやってるけど、これどう考えても遅いよね。型情報の持ち方を工夫すれば良いのかな。この辺は将来的な課題だな。
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]] 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 # 暗黙の型変換 elsif is_array_type? type_r then type_r = array_to_pointer type_r 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 # 暗黙の型変換 elsif is_array_type? type_r then type_r = array_to_pointer type_r 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
さっきのヘルパーメソッドを使って型を変換してるよ。
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" && type == "int" then codegen " movsx rax, eax" type = f[1][i] elsif f[1][i] == "void*" && is_pointer_type?(type) then type = f[1][i] elsif f[1][i] == "double" && type == "int" then codegen " cvtsi2sd xmm8, eax" type = f[1][i] elsif is_array_type? type then type = array_to_pointer type 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 q2.myc ~/myc$ ./q2 p = 00007ffd3a476548 ~/myc$
上手く行ったかな。良く分からないんでアセンブリコードも見てみるよ。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 48 lea rax, [rbp - 40] mov qword ptr [rbp - 48], rax lea rax, .L.str.0 mov rdi, rax mov rax, qword ptr [rbp - 48] mov rsi, rax mov al, 0 call printf .RET_main: add rsp, 48 pop rbp ret .L.str.0: .asciz "p = %016lx\n"
うん、ちゃんとコード生成されてる。もう一個試してみるよ。
// repl extern char *gets(char *s); extern int puts(char *s); extern int strcmp(char *string1, char *string2); int main() { char buffer[256]; for(;;) { printf("repl:"); gets(buffer); if(strcmp(buffer,"quit") == 0) break; printf("=>"); puts(buffer); } }
昔書いたreplもどきをmallocから配列に変更してみたよ。
~/myc$ ./q3 repl:123 =>123 repl:abc =>abc repl:12+16 =>12+16 repl:quit ~/myc$
ちゃんと動いてるね。次回は配列の参照かな。式の構文解析からやらないといけないから結構大変かなあ。コード生成部は前に作ったポインタ型とint型の加算と同じようにやれば良いんだよなあ。うーん、良く考えよう。