Why do we have commands like push and pop?
From what I understand pop
and push
are basically the same as doing a (mov
then add
) and (sub
then mov
) on esp
respectively.
For example wouldn't:
pushl %eax
be equivalent to:
subl $4, %esp
movl %eax, (%esp-4)
please correct me if stack access is not (%esp-4)
, I'm still learning assembly
The only true benefit I can see is if doing both operation simultaneously offers some advantage; however I don't see how it could.
But then, there's no reason for a CALL
instruction, either. After all, you can simulate a call with:
sub esp,4
mov [esp-4], offset return_address
jmp myproc
And there's no need for a RET
instruction, either, because you can simulate it with:
mov eax,[esp]
add esp,4
jmp [eax]
If you look hard enough, you'll find lots of instructions that can be simulated by combining other instructions. What's the point?
The answer to these types of questions is rooted in the long history of the x86 processor family, and in processors that came before it. The designers studied how programmers use processors and created an instruction set that was efficient in terms of execution speed and memory use.
In the late '70s, 64 kilobytes was a lot of RAM, and RAM was much slower. Every instruction byte was precious, and there was a huge amount of overhead just fetching an instruction from memory. It was not uncommon for instruction fetch to take longer than execution. So there was a huge performance gain to be had by encoding things in as few instruction bytes as possible.
RAM is still incredibly slow when compared to CPU clock speeds, so there's still a gain to be had by encoding in as few instruction bytes as possible. It's true that the large CPU caches we have help a great deal, as does branch prediction and prefetch logic, but every byte transferred from RAM to the CPU cache is still expensive. It pays to be frugal with instruction encodings.
About calling procedures:
The standard way of calling procedures in assembly language is to push the parameters and then call
the procedure. For example, this passes two dword values:
push eax
push ebx
call proc ; pushes the return address and jumps to proc
...
proc:
; at this point, [esp] contains the return address
The ret
instruction pops the return address into the instruction pointer.
Somebody has to clean up the stack, of course. The caller can clean up the stack by incrementing the stack pointer. Or the called procedure can clean up the stack by using ret 8
, which will pop the return address and increment the stack pointer.
See http://www.delorie.com/djgpp/doc/ug/asm/calling.html for more info on calling conventions.
Well. If you have two instructions, then they probably use more space. Which requires cpu to transfer more data. And that takes more time. Two separate instructions also typically require more cpu resources. Even if cpu parallelizes them, there is more work for fetcher, decoder... If you try to remove all but absolutely neccessary instructions you end up with turing machine. Ultimate risc, but not very effective in practice.
1 opcode > 2 opcodes, at least when you are trying to reduce CPU usage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With