コンパイラ作成(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型の加算と同じようにやれば良いんだよなあ。うーん、良く考えよう。