The following examples are included for illustration only and have not been optimized. The examples assume that you are familiar with programming in assembler. Note that, in the examples, the stack grows toward the bottom of the page, and ESP always points to the top of the stack.
For the call
m = func(a,b,c);
a, b, and c are 32-bit integers, and func has two local variables, x and y (both 32-bit integers).
The stack for the call to func would look like this:
The instructions used to build this activation record on the stack look like this on the calling side:
PUSH a
PUSH b
PUSH c
CALL FUNC
.
.
.
MOV m, EAX
.
.
For the callee, the code looks like:
FUNC PROC
PUSH EBP
MOV EBP, ESP ; Allocating 8 bytes of storage
SUB ESP, 8 ; for two local variables.
PUSH EDI ; These would only be
PUSH ESI ; pushed if they were used
PUSH EBX ; in this function.
.
.
MOV EAX, [EBP - 8] ; Load y into EAX
MOV EBX, [EBP + 12] ; Load b into EBX
.
.
XOR EAX, EAX ; Zero the return value
POP EBX ; Restore the saved registers
POP ESI
POP EDI
LEAVE ; Equivalent to MOV ESP, EBP
; POP EBP
RET 0CH
FUNC ENDP
|
Like the _System calling convention, the saved register set is EBX, ESI, and EDI. The other registers (EAX, ECX, and EDX) can have their contents changed by a called routine.
Floating-point results are returned in ST(0). If there is no numeric coprocessor installed in the system, the OS/2 operating system emulates the coprocessor. Floating-point parameters are pushed on the run-time stack.
_Far32 _Pascal function pointers are returned with the offset in EAX and the segment in DX.
In some circumstances, the compiler will not use EBP to access automatic and parameter values, thus increasing the efficiency of the application. Whether it is used or not, EBP will not change across the call.
Structures are handled in the same way as they are under the _Pascal and _System calling conventions. When passing structures as value parameters, the compiler generates code to copy the structure on to the run-time stack. If the size of the structure is larger than a run-time page size (4K), the compiler generates code to copy the structure backward. (That is, the last byte in the structure is the first to be copied.)
Structures are not returned on the stack. The caller pushes the address where the returned structure is to be placed as a lexically first hidden parameter. A function that returns a structure must be aware that all parameters are 4 bytes farther away from EBP than they would be if no structure return were involved. The address of the returned structure is returned in EAX.
In the most common case, where the return from a function is simply assigned to a variable, the compiler merely pushes the address of the variable as the hidden parameter. For example:
struct test_tag {
int a;
int some_array[100];
} test_struct;
struct test_tag test_function(struct test_tag test_parm)
{
test_parm.a = 42;
return test_parm;
}
int main(void)
{
test_struct = test_function(test_struct);
return test_struct.a;
}
The code generated for the above example would be:
TEST_FUNCTION PROC PUSH EBP MOV EBP, ESP PUSH ESI PUSH EDI MOV DWORD PTR [ESP+0cH], 02aH ; test_parm.a MOV EAX, [EBP+08H] ; Get the target of the return value MOV EDI, EAX ; Value LEA ESI, [EBP+0cH] ; test_parm MOV ECX, 065H REP MOVSD POP EDI POP ESI LEAVE RET 198H TEST_FUNCTION ENDP PUBLIC main main PROC PUSH EBP MOV EBP, ESP PUSH ESI PUSH EDI SUB ESP, 0194H ; Adjust the stack pointer MOV EDI, ESP MOV ESI, OFFSET FLAT: test_struct MOV ECX, 065H REP MOVSD ; Copy the parameter PUSH OFFSET FLAT: test_struct ; Push the address of the target CALL TEST_FUNCTION MOV EAX, DWORD PTR test_struct ; Take care of the return POP EDI ; from main POP ESI LEAVE RET main ENDP |
In a slightly different case, where only one field of the structure is used by the caller (as shown in the following example), the compiler allocates sufficient temporary storage in the caller's local storage area on the stack to contain a copy of the structure. The address of this temporary storage will be pushed as the target for the return value. Once the call is completed, the desired member of the structure can be accessed as an offset from EAX, as can be seen in the code generated for the example:
struct test_tag {
int a;
int some_array[100];
} test_struct;
struct test_tag test_function(struct test_tag test_parm)
{
test_parm.a = 42;
return test_parm;
}
int main(void)
{
return test_function(test_struct).a;
}
The code generated for the example would be:
PUBLIC main main PROC PUSH EBP MOV EBP, ESP SUB ESP, 0194H ; Allocate space for compiler-generated PUSH ESI ; temporary variable PUSH EDI SUB ESP, 0194H MOV EDI, ESP MOV ESI, OFFSET FLAT: test_struct MOV ECX, 065H REP MOVSD LEA EAX, [ESP+0194H] PUSH EAX CALL TEST_FUNCTION MOV EAX, [EAX] ; Note the convenience of having the POP EDI ; address of the returned structure POP ESI ; in EAX LEAVE RET main ENDP |
![]()
Calling Conventions
Stack Allocation
![]()
_Pascal and _Far32 _ Pascal Calling
Conventions (OS/2)