_the_answer:
subl $12, %esp
movl $42, %eax
addl $12, %esp
ret
最初の subl は、スタック ポインターをデクリメントして、関数で使用される可能性のある変数のためのスペースを作ります。たとえば、1 つのスロットをフレーム ポインタに使用し、別のスロットをリターン アドレスを保持するために使用できます。フレームポインターを省略すべきだと言いました。これは通常、フレームポインタを保存/復元するためのロード/ストアを省略していることを意味します。しかし、多くの場合、コードはまだメモリを予約しています。その理由は、スタックを分析するコードがはるかに簡単になるからです。スタックのオフセットに最小限の幅を与えるのは簡単なので、フレーム ポインターの保存を省略した場合でも、いつでも FP+0x12 にアクセスして最初のローカル変数スロットを取得できることがわかります。
まあ、eax
私の知る限り、x86では呼び出し元への戻り値を処理するために使用されます。最後の addl は、関数用に以前に作成されたフレームを破棄するだけです。
関数の最初と最後に命令を生成するコードを関数の「エピローグ」「プロローグ」と呼びます。これは、GCC で関数のプロローグを作成する必要がある場合に、私のポートが行うことです (可能な限り高速で用途が広いことを意図した実際のポートでは、より複雑になります)。
void eco32_prologue(void) {
int i, j;
/* reserve space for all callee saved registers, and 2 additional ones:
* for the frame pointer and return address */
int regs_saved = registers_to_be_saved() + 2;
int stackptr_off = (regs_saved * 4 + get_frame_size());
/* decrement the stack pointer */
emit_move_insn(stack_pointer_rtx,
gen_rtx_MINUS(SImode, stack_pointer_rtx,
GEN_INT(stackptr_off)));
/* save return adress, if we need to */
if(eco32_ra_ever_killed()) {
/* note: reg 31 is return address register */
emit_move_insn(gen_rtx_MEM(SImode,
plus_constant(stack_pointer_rtx,
-4 + stackptr_off)),
gen_rtx_REG(SImode, 31));
}
/* save the frame pointer, if it is needed */
if(frame_pointer_needed) {
emit_move_insn(gen_rtx_MEM(SImode,
plus_constant(stack_pointer_rtx,
-8 + stackptr_off)),
hard_frame_pointer_rtx);
}
/* save callee save registers */
for(i=0, j=3; i<FIRST_PSEUDO_REGISTER; i++) {
/* if we ever use the register, and if it's not used in calls
* (would be saved already) and it's not a special register */
if(df_regs_ever_live_p(i) &&
!call_used_regs[i] && !fixed_regs[i]) {
emit_move_insn(gen_rtx_MEM(SImode,
plus_constant(stack_pointer_rtx,
-4 * j + stackptr_off)),
gen_rtx_REG(SImode, i));
j++;
}
}
/* set the new frame pointer, if it is needed now */
if(frame_pointer_needed) {
emit_move_insn(hard_frame_pointer_rtx,
plus_constant(stack_pointer_rtx, stackptr_off));
}
}
主に GCC に例外処理にとって重要な命令 (つまり、フレーム ポインタが格納されている場所など) を伝えるなど、他の問題を処理するコードをいくつか省略しました。さて、呼び出し先保存レジスタは、呼び出し元が呼び出し前に保存する必要のないものです。呼び出された関数は、必要に応じてそれらを保存/復元します。最初の行でわかるように、常にリターン アドレスとフレーム ポインターにスペースを割り当てます。そのスペースはほんの数バイトであり、問題にはなりません。ただし、ストア/ロードは必要な場合にのみ生成します。最後に、「ハード」フレーム ポインタが「実際の」フレーム ポインタ レジスタであることに注意してください。これは、gcc の内部的な理由から必要です。「frame_pointer_needed」フラグは、できないときはいつでもGCCによって設定されますフレームポインターの保存を省略します。場合によっては、alloca
(スタックポインタを動的に変更する) を使用する場合など、保存する必要があります。GCC はそのすべてを気にします。そのコードを書いてからしばらく経っていることに注意してください。したがって、上記で追加した追加のコメントがすべて間違っていないことを願っています:)