コンパイラ作成(68) int*型、アドレス演算子
今回の目標
アドレス演算子を追加するよ。
// アドレス演算子 int main() { int a = 55; int *p = &a; printf("p = %016lx\n",p); }
それとint*型の対応も頑張るよ。char*型の時よりもうちょっと汎用的になるようにしたいなあ。
Lexerクラス
まずは演算子の追加。
# symbolの切り出し elsif m = @line[@idx,@line.length] .match(/^(==|!=|<=|>=|=|<|>|\+|\-|\*|\/|\%|&|:|,|;|\(|\)|\{|\})/) then str = m.to_s @idx += str.length return TK::SYMBOL, str end
read_modify_el
ここにも追加。
# 式の最後までのトークンを読み込み、変形する def read_modify_el(fkind,fstr,skind,sstr) el, kind, str = read_el fkind, fstr, skind, sstr puts to_str(el) if $opt_d # デバッグ用 el = modify_el_unaryop el, ["+","-","&"] el = modify_el el, ["*","/","%"] el = modify_el el, ["+","-"] el = modify_el el, ["<",">","<=",">="] el = modify_el el, ["==","!="] el = modify_el el, ["="], :r_to_l puts to_str(el) if $opt_d # デバッグ用 return el, kind, str end
単項演算子は同じ優先順位。C の演算子の優先順位 - cppreference.comをまた参照したよ。
codegen_unaryop
今回のメイン修正箇所。
# 単行演算子のコード生成 def codegen_unaryop(op, operand) type = "int" if op.str == "-" then type = codegen_el operand if type != "int" then perror "unsupported type with unary '-'" end codegen " neg eax" elsif op.str == "+" then type = codegen_el operand elsif op.str == "&" then if operand[0].kind_of?(Array) then perror "invalid operation with '&'" end if operand[0].kind != TK::ID then perror "invalid operation with '&'" end v = get_var operand[0].str if v == nil then perror "undeclared variable \"" + el[0].str + "\"" end type = v[0] + "*" codegen " lea rax, [rbp - " + v[1].to_s + "]" else perror "unknown operator '#{op.str}'" end return type end
前に単項演算子を追加したときに作ったメソッドだけど、かなりいい加減なコーディングだったよ。今回はもうちょっと真面目に取り組んでみた。ちゃんと型を意識するようにして、返値で型情報を返すようにしたよ。
コード生成部
codegen_unaryopが型情報を返すようになったんでそれに対応。
# 式のコード生成 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 type = codegen_els op, operand, type end end return type 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 perror end 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 # 暗黙の型変換 end if type_l != type_r then p type_l,type_r;perror end if is_pointer_type? type_l then codegen " mov qword ptr [rbp - " + v[1].to_s + "], rax" else codegen " mov dword ptr [rbp - " + v[1].to_s + "], eax" end return type_l end
それとis_pointer_type?を使うようにしたよ。前はchar*かどうかしか見てなかったけど、これでどんなタイプのポインタ型にも対応できるようになったはず。たぶん、おそらく、メイビー。
動作テスト
どうかな。
~/myc$ myc o21.myc ~/myc$ ./o21 p = 00007ffcbc8d0b3c ~/myc$
大丈夫かな。ちょっと不安なんでアセンブリコードを見てみるよ。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 16 mov eax, 55 mov dword ptr [rbp - 4], eax lea rax, [rbp - 4] mov qword ptr [rbp - 12], rax lea rax, .L.str mov rdi, rax mov rax, qword ptr [rbp - 12] mov rsi, rax mov al, 0 call printf .RET_main: add rsp, 16 pop rbp ret .L.str: .asciz "p = %016lx\n"
よさそうだね。ポインタのとこは全部64bitになってるよね。次は間接参照演算子か。右辺値と左辺値の両方あるからちょっと厄介かな。