chibicc compiler を6800向けに改造する (2) 関数呼び出しの仕組み

BASICMASTER, 昔のパソコン

去年の末から、 rui314 (Rui Ueyama) さんの rui314/chibicc: A small C compiler を MC6800向けに改造しています。

今回は関数呼び出しの仕組みのメモです。


関数呼び出し

chibicc は x64アーキテクチャー用に作成されていて、関数呼び出しの仕組みは複雑です。

x64で使えるレジスタは汎用レジスタが16本、XMMレジスタが16本、FPUレジスタが8本あります。
これらのレジスタはレジスタ引数として使うものもありますし、関数が呼ばれた際にはABIに従って退避したりしなかったりします。

MC6800だとレジスタ引数として使えそうなレジスタはA,B,IXの3つですが、IXを受け側で保存することを考えると頭が痛くなるので、レジスタ引数は1つ(AccA,B)だけとします。

関数呼び出しの際は、引数を右からpushしていって、一番左の引数をレジスタで渡します。
chibiccではここのところを簡素化していて、全部pushしたあとに、関数呼び出し直前にpopしています。

積んだ引数のバイト数を覚えておいて、関数から戻った後に呼んだ側が削除します。高速ですが、メモリ使用量は増えます。

Fuzix C compiler や CC6303 は、呼ばれた側が削除するのでメモリ効率は良いのですが、面倒なスタック操作が発生するので速度は落ちます。トレードオフですね。

呼ばれた側の関数の処理

Cではローカル変数が使えて再起呼び出しが可能なので、スタックフレームを作ってローカル変数やその他の情報を管理します。

再起呼び出しをしない言語であれば、ローカル変数は、その関数からのみ見えるグローバル変数にしてしまえば簡単です。
昔はそのような言語もありました。Pascalは再起呼び出しできるぜ!すげー!って時代。

スタックフレームは(現在は)以下の構造です。ローカル変数の先頭を指すポインタ(@bp)をゼロページに置いて、そこからのオフセットでローカル変数をアクセスします。


// @bp points old @fp,@bp,argument
//
// SP  -> stack top
//        alloca/VLA area
// @bp -> local var top
//             :
//        local var end
//        old @bp
//        argment passed by register (if any)
//        return address
//        argment passed by stack

オリジナルのchibiccでは、return addressの上に古いrbpレジスタがpushされていて、rbpはそこを指しています。レジスタ引数はローカル変数と一体の領域に保存され、区別はありません。

オリジナルの実装はMC6800には向かないので、順序を変更しています。

  • SPを増減させるためにもAccA,Bは使われるので、最初にpushする必要がある
  • IX修飾アドレッシングでは、正のオフセットしか使えないので、ローカル変数の先頭を指す必要がある
  • 古い@bpを指す変数があると関数からの復帰が速くなるが、更新・復帰が面倒くさいので止めた。あとで実装するかもしれない

Fuzix CCやCC6303、acwjなどはBPを持たず、現在のSP位置からのオフセットを使って ローカル変数や引数をアクセスします。この方が速いし、メモリも喰わないです。

しかしSPの追跡が面倒くさい(しかも、失敗するとスタックを壊す)のと、alloca/VLAを実装してみたかったので、chibiccに合わせて SPと@bpを分離しています。

引数は何バイト積むか問題

chibiccでは、32bitよりサイズの小さな引数は、32bit単位でスタックに積まれます。型昇格・アラインメントを考えると、こうなります。

MC6800ではアラインメントは考えなくて良いので、変更しました。すると、呼ばれた側の関数がchar引数は1バイトだと考えたコードを出すようになりました。頭いいですね。
(x64やリトルエンディアンのCPUだと、1バイトでも何バイトでも、アドレスの示す先から実体があれば良いので、計算は一緒なのです)

しかし、ビッグエンディアンのCPUだと、実体の位置がずれるのでバグが発覚します。

どうやって治すか? 呼ぶ側を直すか、呼ばれた側を直すか。呼ばれた側を直すと、アドレス計算の前提が狂うので、呼ぶ側を修正するしかありません。

char引数だと1バイトpushするようにしました。バグりました。

chibiccでは、関数引数は全部pushしてから、関数を呼ぶ直前にレジスタ引数だけをpopして、関数を呼び出します。

しかし、popするときにはpushした情報を忘れているので、intでpopしてスタックが壊れます。

積んだ引数の情報を覚えておいて、popするときにその単位で戻す必要があります。

今回のMC6800の実装では使えるレジスタ引数は1個にしたので、最後にpushした引数の型を覚えておいて、それを戻して解決しています。
(グローバル変数使ったけど大丈夫かいな…、あ、ダメだわ。後で直そう → 直した)

MC6800でもAccA,Bを別々に考えれば、charのレジスタ引数2個か intを1個使えますが、どう考えても面倒くさいだけなので1個にしました。

以上、現時点でのメモです。