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