去年から低レイヤーのためのCPU入門でコツコツとCコンパイラを作ってました。
いったん本の形になっているところまでやってみて仕切り直したくなった。 去年からやったことはgithubのCソース見てRustに移植しているだけなので、ちゃんと理解していない自覚がある。特にアセンブラ出すところは、なんでこれで動くのかよくわからないままコード生成している。
言語変えて最初からできるだけ独力でやってみよう。PRGで言うところの、クラスを変えて最初からやってみる感じ。F#が好きなので久しぶりのF#プログラミング。コンパイラ書いたらRust力上がったので、これでF#力も上げたい。
そんな思いで、コンパイラを書いていく準備ができたのでまとめてみた。
最初の一歩
低レイヤーのためのCPU入門で使われているプロセスのリターンコードで値を返す方法は、制限多いし期待値比較も面倒。整数を表示するだけの関数を用意してリンクさせることにした。このアイデアも低レイヤーのためのCPU入門からもらった。ありがとう @rui314。
最初のプログラム
int main(void) { putd(5); return 0; }
リンクするCソース
#include <stdio.h> void putd(int d) { printf("%d\n", d); }
これで、5を出力する実行ファイルができるので、gccで同じようにコンパイルした結果と比較できる。 文字列必要になったりしたら拡張していく。関数呼び出しができるようになったら、引数2個限定にしてprintfを呼び出す作戦。
RISCV
RISCV興味あるので、RISCVのアセンブラも出すようにした。 コンパイルは、riscv64-unknown-elf-gccを使い、spikeで実行する。
ここの準備は、作って学ぶコンピュータアーキテクチャを参考にした。
さっきのCソースのコンパイル結果はこう。実行すると5が表示される。よし。
.globl main .text main: # Prologue addi sp,sp,-16 sd ra,8(sp) sd s0,0(sp) addi s0,sp,16 li a0, 5 call putd # Epilogue .L.return.main: li a0, 0 ld ra,8(sp) ld s0,0(sp) addi sp,sp,16 jr ra
テスト環境
testの下にあるディレクトリを順番に回って、cソースを見つけたらgccでコンパイルして実行し、その結果をテキストに保存する。2回目は自作コンパイラでアセンブラを出し、gccでコンパイルして実行してその結果もテキトに保存する。最後に、お互いの内容が一致していることを確認している。
とりあえず、ディレクトリにCソースを突っ込んでおけばテストしてくれる。
何も考えずPythonで書いたが、せっかくだから別の言語でも良かった気がする。gaucheとかguileとか。 テキストを比較するdifflib使うと楽な気がして使ってみたが、完全一致しているかどうかならこれ使うまでもないな。
プロジェクトのトップでスクリプトを実行すると、期待値作成、コンパイラ自体のbuild、buildしたコンパイラでx86のテスト、riscvのテストまで実行できる。すでにいろいろ書き直したい。
natu@star:~/honocc$ ./test_all.sh current dir = /home/natu/honocc ['00_base'] test/expected/00_base/first > test/expected/00_base/first.txt test/expected/00_base/second > test/expected/00_base/second.txt current dir = /home/natu/honocc MSBuild version 17.3.2+561848881 for .NET 復元対象のプロジェクトを決定しています... 復元対象のすべてのプロジェクトは最新です。 honocc -> /home/natu/honocc/honocc/bin/Debug/net6.0/honocc.dll ビルドに成功しました。 0 個の警告 0 エラー 経過時間 00:00:01.22 ---- start test for x64 ['00_base'] PASS: 00_base : test/csrc/00_base/first.c PASS: 00_base : test/csrc/00_base/second.c ALL test ( x64 ) finished passed( 2 / 2 ) ---- start test for riscv ['00_base'] PASS: 00_base : test/csrc/00_base/first.c PASS: 00_base : test/csrc/00_base/second.c ALL test ( riscv ) finished passed( 2 / 2 )
この後
この後は、コンパイラ作りの基本である四則演算をputdの中に書いていって、だんだん変数定義とか関数呼び出しとか足していけば良いと思う。
int main(void) { putd(5+3); return 0; }
リポジトリはここ