実践・C コンパイラ演習

実装の約束ごと

Python 版コンパイラ(sessions/ と final/)を書くときの規約。 C 版へ移植するときの規約は発展課題 P1_selfhost にある。

ここに書くのは「どう書くか」の規約であり、「何を受理するか」は language_spec.md(第 2 版、2026 年改訂)が定める。

読みどき: コード生成を書き始めるコマ2 の前に一度、全体に目を通す。 ただし最重要の codegen() と codegen_lval() を分けるは コマ3 で最小形を導入しコマ7 で拡張する内容なので、両方の回で改めてここに戻ること。


編集するファイル

時期 編集対象
通常回 sessions/NN_xxx/mycc.py
コマ15 以降 final/mycc.py(各回の成果を統合したもの)

scaffold/ は書き換えない。Lexer / Parser / AST 定義は提供物として扱う (自作したい場合は発展課題の F 系列で扱う)。

守ってほしい設計

codegen() と codegen_lval() を分ける

この教材でいちばん重要な設計である。コマ3 で codegen_lval() を 'Var'(ローカル変数)だけの最小形として導入し、コマ7 で 'Deref'(*p)へ 拡張する。以降ずっと使う。

def codegen(self, node):
    """rvalue(値)を a0 に残す"""

def codegen_lval(self, node):
    """lvalue(アドレス)を a0 に残す"""

ポインタ・配列・構造体メンバへのアクセスは、すべてこの2つの組み合わせで書ける。 1つの関数に混ぜると、*p = *q; のような式で破綻する。

出力は emit() を通す

self.emit("  add a0, a1, a0")     # ○
print("  add a0, a1, a0")         # × 直接呼ばない

出力先を差し替えられる状態を保つ。発展課題(最適化・意味論の各回)は 生成されたアセンブリを行単位で書き換えるので、この前提に依存している。

シンボルテーブルは dict でよい

Python 版では素直に dict を使う。C 版へ移植するときに連結リストへ置き換える (対応表は ../advanced/P1_selfhost/README.md)。

アラインメントはヘルパーに任せる

align_to(n, 16) を必ず使う。手で計算すると無音クラッシュの原因になる。 詳細は rv64_reference.md を参照。

命名の目安

対象 例
コード生成メソッド codegen、codegen_lval、gen_func
型の問い合わせ _type_of_expr、_type_of_lval
型サイズ・要素型 size_of_ty_str、elem_ty_str、is_ptr_ty_str
型付きのロード/ストア _load_ty、_store_ty
ラベル生成 new_label
一時値の退避 _push_a0、_pop_into
関数ごとの状態 _locals、_stack_offset、_reset_func_state
グローバルな状態 _globals、_struct_defs、_strings

先頭の _ は「その回の実装の内部状態」を表す目印として使っている。

size_of_ty_str はコマ8 で size_of_ty_str(ty_str) として入り、 コマ12 で struct のサイズを引くために size_of_ty_str(ty_str, struct_defs=None) へ引数が1つ増える(詳細はコマ12 の原稿)。名前は変えない。

_push_a0 / _pop_into は名前を変えないこと。 発展課題(B・O・S・L 系列)のラッパーは、この名前でコード生成クラスを探す。 別の名前にすると、発展課題に進んだ時点で「コード生成クラスが見つからない」と拒否される。

コメント

複雑な処理にだけ短く書く。行数を稼ぐためのコメントは書かない。 「なぜこうしたか」が読み取れないところに1〜2行入れるのが目安である。

スケルトンで前回回の実装をコメントとして残す

前回のコマで動いていたコードを今回のコマで書き換えさせるスケルトンでは、 前回版の実装をコメントとして raise NotImplementedError(...) の直前に残してよい。 コマ12 の alloc_local がその例である。

def alloc_local(self, name: str, ty_str: str = 'int') -> None:
    # TODO: self._struct_defs を渡して struct のサイズで領域確保する。
    #       コマ11 版は次の形だった。size_of_ty_str に self._struct_defs を足す。
    #           sz = self.align_to(self.size_of_ty_str(ty_str), 8)
    #           self._stack_offset += sz
    #           self._locals[name] = (-(16 + self._stack_offset), ty_str)
    raise NotImplementedError("alloc_local を実装してください")

前回回で書いたコードを学習者自身が覚えている前提を置かず、 「何がどう変わるか」を見える形にするための例外的な措置である。 行数稼ぎのコメントを禁じる原則とは矛盾しない(前回版の再掲であって水増しではない)。

この資料の原稿を見る