RustでCコンパイラ その10 変数宣言の改善と、Rustのエラーハンドリング(番外編)

2026-04-14 21:12:44

下記をRustで実装する続き。
STEP18のポインタ型の導入なんだけど、そこ入る前に色々改善しておきたかったところをやった。

低レイヤを知りたい人のためのCコンパイラ作成入門

Githubは下記。

ryotakato/tvcc

今回のコミットは、
learnt step 17の後の3つのコミット。

最初の変数宣言の改善は、
STEP17で導入したint型において、作った後に下記の参照実装を見たら、

Add keyword “int” and make variable definition mandatory · rui314/chibicc@b4e82cf

宣言と同時の初期化や、複数の変数を一度に宣言することとかもサポートしていて、
あれ、本書の内容と違くない?ってなったところ。

後回しでやろうって思っていたのだけど、STEP18前にやっておかないとややこしいことになりそうだったので、先に考える。
併せて、EBNFも、参照実装になるべく合わせるべく、関数名とか変えたりしている。

で、実装した後にバグで少しハマった。
というのも、

'int main() { int bar; int foo123; foo123=3; bar=5; return foo123+bar; }'

というの実行結果が一致しなかったのだ。
8が答えのはずなのに、10になってしまう。

おかしいなって思って、自前デバッガーでアセンブリをデバッグしてみると、
スタックに積んでた変数の値が上書きされている。
上記の例でいうなら、bar=5にて、barの変数の値を入れるときに、
foo123のアドレスの値が上書きされてしまって5+5ってことになってた。

最初はlea命令を使っていないからスタックがおかしくなっているのかなって思った(参照実装はスタックの使い方が最小限になっている)けど、
それもおかしな話だよなって思って、
本書を、スタックが関係する、最初のほうから読み返してみた。

そこで気付いたのが、STEP14の関数呼び出しのあたりで、
RSPの値を16の倍数にしている話。
これを見返していたときに、自分のプログラムみたら、
そういえばそんな計算を関数の仮引数分のメモリを確保するときにやっていたなって思い出したこと。

関数の仮引数分のメモリを確保しているなら、
関数内でのローカル変数のメモリはどこで確保しているんだ?って気付いた。
結論からいうと、これができていなくて、
関数の仮引数分しかメモリを確保できていなかった。
そのため、上記の例のような場合はmain関数の仮引数が0個だから、
全くメモリを確保しておらず、スタックで使ってしまっていた。
変数を参照するときは確保した前提で参照していたので、
いつの間にかメモリ上で値が書き換わっているという事態が発生していたわけだ。

そのため、関数内で宣言された変数の分だけメモリを確保してから呼び出しを行うように修正した。

参照実装のほうは、じつは関数を導入したのが、
STEP17の後(ここ、本書と参照実装で、順番が異なっている)で、
それによりSTEP17の参照実装をいくら眺めても分からなかった。

まあ、でも、おかげで改めてアセンブリとレジスタの使い方が整理できた。

次に、Rustのエラーハンドリングをどうにかしたかった。
というのも、今は、実装しているコンパイラでC言語としてのコンパイルエラーが出たときは、
process::exitで強制的に終わらせていたんだけど、
異常終了ならpanic!のほうがいいかなって思った。そのほうがちゃんとクリーンしてくれるみたいだし。

また、Result型を多用しているけど、
もっとRustのエラーハンドリングを活用できるような気がしていたんだよね。

ということで、下記を参考にして、

Rustにおけるエラーハンドリング
プログラミングRust 7章を参考に複数種類のエラーをハンドリングする
Rustのエラー処理を極める:ResultとOptionを超えて|Leapcell

Errorトレイトを実装した自前エラー型である、CompileError構造体を定義し、
基本どのメソッドも、Result<何かの型, CompileError> で返すようにした。
これにより、Rustの?キーワードを使って、CompileErrorが出たらすぐにreturnするコードが書きやすくなった。
さらに、余計な判定文が削れたことで、プログラムも短くなったし、
本質的なコードがどれかはよく分かるようになった。

また、エラーは最終的に、main.rs内でキャッチしてeprintlnでエラー内容を表示するようにしたので、かなりシンプルになったと思う。

そのうち、ファイルからプログラムを読んだりするので、
何行目とかを表示しないといけないけど、まあそれはその時。

今はこれでかなり満足している。

ふぃー、Rustは難しいけど、学ぶのは楽しいな。
だんだん分かってきた感が強いので、向上している気がする。
if-letとかlet-elseとかunwrap_or_elseとか、必要なときに必要な記載がすぐに書けるようになったきた。

次はようやくSTEP18。



コメント