Toyアーキテクチャのヒント
単に箇条書きで羅列。
- 昔はスタックがない機械もあったらしい。まー、自前でも作れるが、メンドーだからスタックはあったほうがいい。
- 明確なコール/リターン命令がない機械もある。が、メンドーだからあったほうがいい。
- コールで、戻り番地(当該命令の次の番地)をリンクレジスタに入れる方式と、スタックに積む方式がある。呼び出しが入れ子になれば、どっちにしろスタックに積むことになる。よってスタックに積む。
- fp(フレームポインタ)がフレーム内のどこを指すかは勝手。そもそもフレームの定義もいろいろ。
- fpがない、使わない方式もあるらしいが、使おう。
- 引数は「呼び側の領域か、呼ばれ側の領域か?」微妙だ。
- fpはローカル変数が始まるところを指すのがいいと思う。最初or最後の引数のを指す方式もある。
- sp(スタックポインタ)を大量に増減させるとき、pop/pushは不向き。
- インテルのretはスタックトップにあるアドレスにジャンプする。
- 関数内のプロローグコード(入り口)、エピローグコード(出口)は色々。
- インテルCPU + Cでは、(1) 今のbp(fpと同じ)をスタックに積む(待避) (2) 今のspをbpに設定(bpが今のスタックトップを指し、スタックトップにはbp待避値) (3) ローカル領域分spを増やす(実際は引き算)。
- 同じくエピローグコードは、(1)spをbpにセット (2)popでスタックトップをbpに入れる(古いbpを回復) (3)retでスタックトップにあるアドレスにジャンプ(戻り番地はpopされる)
- インテルでは、エピローグ用にenter、プロローグ用にleaveがある。
- よって、call, enter, ...., leave, ret となる。
- レジスタの保存は規約の問題
- MIPSだと、temporaryレジスタは呼び側の責任、その他は呼ばれたほうで待避と回復をする。
- 汎用レジスタに関しては、どっちか一方の責任にしてもいいような気がする。
- ジャンプの飛び先をターゲットアドレスと呼ぶことがある。
- インテル対象のアセンブリ言語では、mov α,β は α→β の方向のコピー。cpコマンドと同じだが、代入とは逆。
- レジスタを使うときは、左の引数から順にレジスタに割り当て、残りは右から順にスタックに積み上げるのが普通。
- 可変引数があると、左からスタックに積むと、1番目の引数が特定できない。
- インテルでは、bp(fp)がローカルデータのはじまりを指す。
- 結局、引数右→引数左→戻り番地→1つ前のbp→ローカルデータ
- 引数を積むのは呼び側、戻り番地はcall命令、以前のbpはプロローグ(enter)が積む。新しいbpのセットもプロローグコード。
- 最初の(最後に積んだ)引数とローカルデータの間に戻り番地と旧bpで8バイト分使う。
それと、参考になりそうなスライド: