2024-11-09

「低レイヤを知りたい人のためのCコンパイラ作成入門」第十二回勉強会まとめ

学習範囲

ステップ5:四則演算のできる言語の作成

四則演算のできる言語の作成

前章までに作ってきたコンパイラを拡張して、優先順位のカッコを含む四則演算の式を扱えるようにしました。

※arm64環境のコードです。

int main(int argc, char **argv) {
  if (argc != 2) {
    error("引数の個数が正しくありません");
    return 1;
  }

  //トークナイズしてパースする
  user_input = argv[1];
  token = tokenize(argv[1]);
  Node *node = expr();

  //アセンブリの前半部分を出力
  printf(".globl main\n");
  printf("main:\n");

  //抽象構文木を下りながらコード生成
  gen(node);

  printf(" ret\n");
  return 0;
}

5 + 20 -4 のアセンブリの解説

ロード命令

mov : データをある場所(レジスタや即値)からレジスタにコピーすること。
※即値とはプログラム内に直接書き込まれた具体的な数値やデータのこと。

ldr : メモリに格納されている値をレジスタに読み込むこと。

str : レジスタに入っている値をメモリに書き出すこと。
※レジスタとは記憶装置のこと

[sp, #-16]! : 保存する場所

以下はarm64 gccの環境で出力されたアセンブリです。それぞれの命令にコメントを追加しました。

.globl main
main:

 //レジスタw0に5を入れる
 mov w0, 5

 //スタックに5をプッシュする
 str w0, [sp, #-16]!

 //レジスタw0に20を入れる
 mov w0, 20

 //スタックに20をプッシュする
 str w0, [sp, #-16]!

 //スタックから20を取り出し、w1に格納する
 ldr w1, [sp], #16

 //スタックから5を取り出し、w0に格納する
 ldr w0, [sp], #16

 //w0とw1を加算し、その結果をw0に格納する。つまり20+5を計算している
 add w0, w0, w1

 //25をスタックにプッシュする
 str w0, [sp, #-16]!

 //レジスタw0に4をロードする
 mov w0, 4

 //4をスタックにプッシュする
 str w0, [sp, #-16]!

 //スタックから4を取り出し、w1に格納する
 ldr w1, [sp], #16

 //スタックから25を取り出し、w0に格納する
 ldr w0, [sp], #16

 //w0からw1を減算し、その結果をw0に格納する。つまり25-4を行う
 sub w0, w0, w1

 //計算結果21をスタックにプッシュする
 str w0, [sp, #-16]!

 ret

参考

Linux で Arm64 アセンブリプログラミング (04) ロード命令

コラム:9ccにおけるメモリ管理

ガーベッジコレクション

プログラムで使っていないメモリを解放する機能のことです。サーバーが動いている限りプログラムは実行されるので、使わないメモリは解放されないと永遠に残ってしまいます。

プログラムが確保したメモリを占領して解放されずメモリの使える領域が減っていくことをメモリリークといいます。

メモリポリシー

手元で動かす程度の短命なプログラムにおいては、わざわざ複雑な技術であるガーベッジコレクションは実装しないとった手段をとることもあります。その理由は、手動メモリ管理によって引き起こされる不可解なバグが発生するのを防ぐためです。

このようにあえてメモリ管理を行わないメモリポリシーもあります。

まとめ

今回の実装で優先順位のカッコを含む四則演算の式を扱えるようなりました。またアセンブリを読み解くことで正しく動作しているか確認することができました。テストが失敗しているときは、アセンブリを読むことでデバッグできるという事も学びになりました。

次回は"-"から始まる式を扱えるようになります。2項の-は左辺から右辺を引く演算として定義されていますが、単項-にはそもそも左辺がないので、2項-の定義は正しく計算ができません。-から始まる式をどのように拡張して扱えるようにするのか楽しみです。