Calling convention
A function's calling convention formally specifies how the CPU stack is used during a function call. It specifies the storage for the function parameters and return value, the return address, the local variables, and so forth. This information is important if you want to manually author assembly language that can be called by compiler-generated functions.
Example input
Consider the example code below, which illustrates a typical Hybrix function C::F() and its caller MAIN::TEST().
CLASS C
FUNC F(A: INT, B: BYTE): INT
VAR X: BYTE, Y: PAIR
RETURN A
END FUNC
END CLASS
MODULE MAIN
FUNC TEST(C: C)
VAR RESULT: INT
C.F(1,2) -> RESULT
END FUNC
END MODULE
Emitted assembly
The compiler emits the assembly language shown below. The prologue and epilogue are highlighted for both functions.
-
prologue: Uses
PUSH FPto save the caller'sFP, then assigns the newFP. If there are local variables or temporary variables, their memory is allocated usingADD SP, <number>. -
epilogue: Frees the local variables using
ADD SP, -<number>. Restores the caller'sFP. UsesPOP IPto return from the call.
# FUNC C::F()
@C.F:
ALIAS I:-21 = I:RETURN
ALIAS I:-17 = I:SELF
ALIAS I:-13 = I:ARG_A
ALIAS B:-9 = B:ARG_B
ALIAS P:0 = P:LOCAL_Y
ALIAS B:2 = B:LOCAL_X
PUSH FP
MOVE FP, SP
ADD SP, 6
MOVE I:RETURN, I:ARG_A
@C.F.EXIT:
ADD SP, -6
POP FP
POP IP
# FUNC MAIN::TEST()
@MAIN.TEST:
ALIAS I:-12 = I:ARG_C
ALIAS I:0 = I:LOCAL_RESULT
PUSH FP
MOVE FP, SP
ADD SP, 4
PUSH INT 0
PUSH I:ARG_C
GPUSH PUSHED
PUSH INT 1
PUSH BYTE 2
PUSH RETURN IP
JUMP @C.F
GPOP
ADD SP, -9
POP I:LOCAL_RESULT
@MAIN.TEST.EXIT:
ADD SP, -4
POP FP
POP IP
Stack layout
The table below illustrates the resulting SP stack layout from the perspective of C::F()'s FP register and variable frame.
| Address | Contents | Notes |
|---|---|---|
| FP-21 | I:RETURN | return value |
| FP-20 | ||
| FP-19 | ||
| FP-18 | ||
| FP-17 | I:SELF | CLASS pointer |
| FP-16 | ||
| FP-15 | ||
| FP-14 | ||
| FP-13 | I:ARG_A | function parameter |
| FP-12 | ||
| FP-11 | ||
| FP-10 | ||
| FP-9 | B:ARG_B | function parameter |
| FP-8 | PUSH RETURN IP | return address |
| FP-7 | ||
| FP-6 | ||
| FP-5 | 🡅 caller's frame | |
| FP-4 | PUSH FP | 🡇 callee's frame |
| FP-3 | ||
| FP-2 | ||
| FP-1 | ||
| FP+0 | P:LOCAL_Y | local variable |
| FP+1 | ||
| FP+2 | B:LOCAL_X | local variable |
| FP+3 |
Important points:
- For a
MODULEmember function, the stack layout is the same as above, exceptI:SELFwill be omitted. - For a function with no return value, the stack layout is the same as above, except
I:RETURNwill be omitted. - If a caller does not use the return value, then
I:RETURNis still allocated. The caller discards it afterwards. - The function parameters are always pushed in left to right order.
- The maximum number of function parameters is limited by the negative register index, which cannot exceed
FP - 64. Well-written programs easily stay within this limit. - If the compiler had placed
B:LOCAL_XatFP + 0, thenP:LOCAL_Ywould have to start atFP + 2since the index for aP:register must be a multiple of 2. To avoid wasting theFP + 1byte, the compiler can reorder the local variables. - The maximum number of local variables is limited by the positive register index, whose maximum depends on the register sizes:
B:191,P:382, orI:764. In practice, this allows for over 100 local variables in each function. Well-written programs easily stay within this limit.