The Caller pushes the Arguments on the Stack in Order.
The Callee then must pop them into registers in reverse Order. That happens before anything else in the Callee.
TAC_CONST_VALUE t1 = 19 TAC_PARAM t1 TAC_CONST_VALUE t2 = 24 TAC_PARAM t2 TAC_CALL myfunction TAC_RETURN t2 ---------------- TAC_CONST_VALUE t0 = 0 TAC_RETURN t0
main: ldi r16, 19 push r16 ldi r16, 24 push r16 call myfunction mov r0, r16 ret myfunction: pop r17 pop r18 ret
For the x86-64 backend, espl is using the
System V AMD64 ABI.
Basically what gcc is doing on Linux.
So there is nothing special here. This is to make it easier to call C code.
| Usage | Storage |
| return value | rax |
| arg 0 | rdi |
| arg 1 | rsi |
| arg 2 | rdx |
| arg 3 | rcx |
| arg 4 | r8 |
| arg 5 | r9 |
| arg > 5 | stored on stack |
Example to see the stack usage for local variables. This is implementation detail (not part of calling convention).
fn f1 (int8 arg0, int8 arg1) ~> int {
int16 x = 0; // local var 0, 2 bytes
int8 y = 0; // local var 1, 1 byte
x += 1;
return x
}
... |
return address byte 1 |
return address byte 0 | <- rsp
| ...
| 0x0
... |
return address byte 1 |
return address byte 0 |
... callee saved regs | <- rsp (callee saved registers have been pushed)
| ...
| 0x0
... |
return address byte 1 |
return address byte 0 |
... callee saved regs | <- rbp, rsp (rbp has been set)
| ...
| 0x0
... |
return address byte 0 |
... callee saved regs | <- rbp
| ...
| <- rsp (has been decremented by 5 bytes to make space for local vars and args)
| ...
| 0x0
... |
return address byte 0 |
... callee saved regs | <- rbp
local var 0, byte 1 |
local var 0, byte 0 |
local var 1, byte 0 |
arg 0, byte 0 |
arg 1, byte 0 | <- rsp
| ...
| 0x0
... |
return address byte 0 |
... callee saved regs |<- rbp, rsp (rsp has been reset before return)
| ...
| 0x0
... |
return address byte 0 | <- rbp, rsp (callee saved regs have been restored)
| ...
| 0x0