Gemini問答へのリンク https://gemini.google.com/share/e373ec3fe206
$fpにスタックフレームを一時退避して、$spを移動させる$gp(グローバルポインタ)と命令数その通りです。
通常、32bitアドレスを指定するには lui + ori(または lw)の2命令が必要ですが、$gp 相対であれば lw $reg, offset($gp) の 1命令 で済みます。これがチリも積もれば大きな速度差(およびコードサイズの削減)になります。
その通りです。正確な理解です。
コンパイラ: 変数が「静的(static/global)」であることを見抜き、それらをデータセグメント(.data や .bss)にまとめます。
リンカ: 全オブジェクトファイルをまとめ上げる際、「どの変数がよく使われるか」「どの変数が小さいか」を基準に、gpから16bit( \pm 32\text{KB}$) 以内に収まる .sdata(small data)セクション にそれらを配置します。
スタック領域の決定タイミング その通りです。 C99のVLAや alloca() が登場すると、関数が必要とするメモリ量は「実行時にその行を通るまで」決まりません。
alloca() と $fp(フレームポインタ)
「$fp に退避」というよりは、「$fp をアンカー(基準)にする」という表現が適切です。
通常、ローカル変数は $sp 相対でアクセスしますが、alloca() で $sp が勝手に動くと、他の変数の位置(オフセット)がズレてしまいます。
そこで、関数の開始時の $sp を $fp にコピーして固定しておきます。
こうすれば、alloca() でどれだけ $sp を引き下げても、既存のローカル変数は $fp からの固定オフセットでアクセスし続けられます。
VLAの場合は、呼び出し側で決定できるので、呼び出し時には固定値で確保される
これは少し違います。VLAは「呼び出し側(Caller)」ではなく、「呼ばれた関数側(Callee)」の中でスタックを動的に確保するものです。
動作: 関数の中で int arr[n]; という行に到達したとき、実行時に n の値を計算し、その分だけ $sp を引き下げます。
性質: 仕組みとしては alloca() とほぼ同じです。つまり、VLAを使う関数も、スタックフレームのサイズはコンパイル時には決まりません。
呼び出し側ができること: 呼び出し側ができるのは、引数を渡すことだけです。その引数を使って「どれだけスタックを伸ばすか」を決めるのは、呼ばれた関数自身の責任になります。