Skip to main content

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 FP to save the caller's FP, then assigns the new FP. If there are local variables or temporary variables, their memory is allocated using ADD SP, <number>.

  • epilogue: Frees the local variables using ADD SP, -<number>. Restores the caller's FP. Uses POP IP to 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.

AddressContentsNotes
FP-21I:RETURNreturn value
FP-20
FP-19
FP-18
FP-17I:SELFCLASS pointer
FP-16
FP-15
FP-14
FP-13I:ARG_Afunction parameter
FP-12
FP-11
FP-10
FP-9B:ARG_Bfunction parameter
FP-8PUSH RETURN IPreturn address
FP-7
FP-6
FP-5🡅 caller's frame
FP-4PUSH FP🡇 callee's frame
FP-3
FP-2
FP-1
FP+0P:LOCAL_Ylocal variable
FP+1
FP+2B:LOCAL_Xlocal variable
FP+3

Important points:

  • For a MODULE member function, the stack layout is the same as above, except I:SELF will be omitted.
  • For a function with no return value, the stack layout is the same as above, except I:RETURN will be omitted.
  • If a caller does not use the return value, then I:RETURN is 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_X at FP + 0, then P:LOCAL_Y would have to start at FP + 2 since the index for a P: register must be a multiple of 2. To avoid wasting the FP + 1 byte, 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, or I:764. In practice, this allows for over 100 local variables in each function. Well-written programs easily stay within this limit.