push and pop. This is because it is easy to implement
these instructions with just regular copy, add and subtraction
instructions.
Let us try to push %ecx without using push %ecx:
subl $4,%esp
movl %ecx,(%esp)
In the previous code, it is important that the stack pointer be
subtracted before we copy the value of ecx. In other words,
we cannot use the following instructions:
movl %ecx,-4(%esp)
subl $4,%esp
But why? You can try both versions in a debugger, and you cannot see the difference.
The problem is due to interrupts. Interrupts are asynchronous hardware
events that ``call'' interrupt service routines (ISR). Invoking an ISR
also needs to use the stack, and the processor assumes the stack is
only used up to the address stored in esp. This means locations
lower than the address stored in esp can all potentially be
changed by an ISR.
Imagine that you have an interrupt
between the two instructions. Because esp is not decremented
when the ISR is invoked, the value of ecx will be overwritten
by the handling of the interrupt! We'll talk about this again when
we deal with interrupts.
Note that this code is not exactly the same as a push instruction
because the subl instruction modifies the status flags. On most
RISC architectures, you can specify a modifier on the equivalent
movl instruction so that the indirect register is pre-decremented
by 4 without changing the status flags.
How about pop? The following code implements pop %ecx.
movl (%esp),%ecx
addl $4,%esp
Note that the order is once again important. You cannot use the following sequence:
addl $4,%esp
movl -4(%esp),%ecx
Once again, the reason is that an interrupt can occur between the two
instructions. After we add 4 to esp, an interrupt can corrupt
the four bytes that will be copied to ecx. After the
interrupt is handled, the movl instruction copies the
values left behind by the ISR to ecx, not the values originally
stored at (%esp) before the addl instruction.