コンパイラ作成(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になってるよね。次は間接参照演算子か。右辺値と左辺値の両方あるからちょっと厄介かな。