This appendix describes the programming model supported for compiler code generation, including register conventions, and common object file formats.
This section defines compiler and assembly language conventions for the use of certain aspects of the processor. These standards must be followed to guarantee that compilers, applications programs, and operating systems written by different people and organizations will work together. The conventions supported by the PGCC ANSI C compiler implement the application binary interface (ABI) as defined in the System V Application Binary Interface: Intel Processor Supplement and the System V Application Binary Interface, listed in the section "Related Publications," in the Preface.
This section describes the standard function calling sequence, including the stack frame, register usage, and parameter passing.
Register Assignment
Table A-1 defines the standard for register allocation. The Intel Architecture (IA) provides a number of registers. All the integer registers and all the floating-point registers are global to all procedures in a running program.
Type
|
Name
|
Purpose
|
---|---|---|
General |
%eax |
integer return value |
%edx |
dividend register (for divide operations) | |
%ecx |
count register (shift and string operations) | |
%ebx |
local register variable | |
%ebp |
stack frame pointer | |
%esi |
local register variable | |
%edi |
local register variable | |
%esp |
stack pointer | |
Floating-point |
%st(0) |
floating-point stack top, return value |
%st(1) |
floating-point next to stack top | |
%st(...) |
||
%st(7) |
floating-point stack bottom |
In addition to the registers, each function has a frame on the run-time stack. This stack grows downward from high addresses. Table A-2 shows the stack frame organization.
Position
|
Contents
|
Frame
|
---|---|---|
4n+8 (%ebp) |
argument word n |
previous |
8 (%ebp) |
argument word 0 |
|
4 (%ebp) |
return address |
|
0 (%ebp) |
caller's %ebp |
current |
-4 (%ebp) |
n bytes of local |
|
-n (%ebp) |
variables and temps |
Several key points concerning the stack frame:
All registers on a IA system are global and thus visible to both a calling and a called function. Registers %ebp, %ebx, %edi, %esi, and %esp belong to the calling function. In other words, a called function must preserve these registers' values for its caller. Remaining registers belong to the called function. If a calling function wants to preserve such a register value across a function call, it must save a value in its local stack frame.
Some registers have assigned roles in the standard calling sequence:
Signals can interrupt processes. Functions called during signal handling have no unusual restriction on their use of registers. Moreover, if a signal handling function returns, the process resumes its original execution path with registers restored to their original values. Thus, programs and compilers may freely use all registers without danger of signal handlers changing their values.
A function that returns an integral or pointer value places its result in register %eax.
A function that returns a long long integer value places its result in the registers %edx and %eax. The most significant word is placed in %edx and the least significant word is placed in %eax.
A floating-point return value appears on the top of the fpu register stack. The caller must then remove the value from the fpu stack, even if it doesn't use the value. Failure of either side to meet its obligations leads to undefined program behavior. The standard calling sequence does not include any method to detect such failures nor to detect return value type mismatches. Therefore the user must declare all functions properly. There is no difference in the representation of single-, double- or extended-precision values in floating-point registers.
Functions that return no value (also called procedures or void functions) put no particular value in any register.
A call instruction pushes the address of the next instruction ( the return address) onto the stack. The return instruction pops the address off the stack and effectively continues execution at the next instruction after the call instruction. A function that returns a scalar or no value must preserve the caller's registers as described above. Additionally, the called function must remove the return address from the stack, leaving the stack pointer (%esp) with the value it had before the call instruction was executed.
Functions Returning Structures or Unions
If a function returns a structure of union, then the caller provides space for the return value and places its address on the stack as argument word zero. In effect, this address becomes a hidden first argument. Having the caller supply the return objects spaces allows re-entrancy.
A function that returns a structure or union also sets %eax to the value of the original address of the caller's area before it returns. Thus when the caller receives control again, the address of the returned object resides in register %eax and can be used to access the object. Both the calling and the called functions must cooperate to pass the return value successfully:
Failure of either side to meet its obligation leads to undefined program behavior. The standard function calling sequence does not include any method to detect such failures nor to detect structure and union type mismatches. Therefore the user must declare the function properly.
Table A-3 illustrates the stack contents when the function receives control, after the call instruction, and when the calling function again receives control, after the ret instruction.
Position
|
After Call
|
After Return
|
Position
| |
---|---|---|---|---|
4n+8 (%esp) |
argument word n |
argument word n |
4n-4 (%esp) | |
8 (%esp) |
argument word 1 |
argument word 1 |
0 (%esp) | |
4 (%esp) |
value address |
undefined |
||
0 (%esp) |
return address |
The following sections of this appendix describe where arguments appear on the stack. The examples are written as if the function prologue described above had been used.
As mentioned, a function receives all its arguments through the stack; the last argument is pushed first. In the standard calling sequence, the first argument is at offset 8 (%ebp), the second argument is at offset 12 (%ebp), etc. Functions pass all integer-valued arguments as words, expanding or padding signed or unsigned bytes and halfwords as needed.
Call
|
Argument
|
Stack Address
|
---|---|---|
g(1, 2, 3, (void *)0); |
1 |
8 (%ebp) |
2 |
12 (%ebp) | |
3 |
16 (%ebp) | |
(void *) 0 |
20 (%ebp) |
The stack also holds floating-point arguments: single-precision values use one word, and double-precision use two. The example below uses only double-precision arguments.
Call
|
Argument
|
Stack Address
|
---|---|---|
h(1.414, 1, 2.998e10); |
word 0, 1.414 |
8 (%ebp) |
word 1, 1.414 |
12 (%ebp) | |
1 |
16 (%ebp) | |
word 0 2.998e10 |
20 (%ebp) | |
word 1, 2.998e10 |
24 (%ebp) |
As described in the data representation section, structures and unions can have byte, halfword, or word alignment, depending on the constituents. An argument's size is increased, if necessary, to make it a multiple of words. This may require tail padding, depending on the size of the argument. To ensure that data in the stack is properly aligned, the stack pointer should always point to a double word boundary. Structure and union arguments are pushed onto the stack in the same manner as integral arguments, described above. This provides call-by-value semantics, letting the called function modify its arguments without affecting the calling functions object.
Call |
Argument |
Stack Address |
---|---|---|
i(1,s); |
1 |
8 (%ebp) |
word 0, s |
12 (%ebp) | |
word 1, s |
16 (%ebp) | |
... |
... |
Implementing a Stack
In general, compilers and programmers must maintain a software stack. Register %esp is the stack pointer. Register %esp is set by the operating system for the application when the program is started. The stack must be a grow-down stack.
A separate frame pointer enables calls to routines that change the stack pointer to allocate space on the stack at run-time (e.g. alloca). Some languages can also return values from a routine allocated on stack space below the original top-of-stack pointer. Such a routine prevents the calling function from using %esp-relative addressing to get at values on the stack. If the compiler does not call routines that leave %esp in an altered state when they return, a frame pointer is not needed and is not used if the compiler option -Mnoframe is specified.
Although not required, the stack should be kept aligned on 8-byte boundaries. Each routine should use stack space in multiples of 8 bytes. PGI's compilers allocate stack space in multiples of 8 bytes.
Variable Length Parameter Lists
Parameter passing in registers can handle a variable number of parameters. The C language uses a special method to access variable-count parameters. The stdarg.h and varargs.h files define several functions to access these parameters. A C routine with variable parameters must use the va_start macro to set up a data structure before the parameters can be used. The va_arg macro must be used to access the successive parameters.
C Parameter Conversion
In C, for a called prototyped function, the parameter type in the called function must match the argument type in the calling function. If the called function is not prototyped, the calling convention uses the types of the arguments but promotes char or short to int, and unsigned char or unsigned short to unsigned int and promotes float to double, unless you use the -Msingle option. For more information on the -Msingle option, refer to Chapter 7, Command-line Options. If the called function is prototyped, the unused bits of a register containing a char or short parameter are undefined and the called function must extend the sign of the unused bits when needed.
Calling Assembly Language Programs
/* File: testmain.c */
main()
{
long l_para1 = 0x3f800000;
float f_para2 = 1.0;
double d_para3 = 0.5;
float f_return;
extern float sum_3 (
long para1,
float para2,
double para3);
f_return = sum_3(l_para1, f_para2, d_para3);
printf("Parameter one, type long = %08x\n", l_para1);
printf("Parameter two, type float = %f\n", f_para2);
printf("Parameter three, type double = %g\n", d_para3);
printf("The sum after conversion = %f\n", f_return);
}
// File: sum_3.s
// Computes ( para1 + para2 ) + para3
/ PGC Rel 1.1 -opt 1
.align 4
.long .EN1-sum_3+0xc8000000
.text
.align 16
.globl sum
sum_3:
.EN1:
subl $12,%esp
movl %ebp,8(%esp)
leal 8(%esp),%ebp
fildl 8(%ebp)
fadds 12(%ebp)
faddl 16(%ebp)
fsts 12(%ebp)
fstps -4(%ebp)
flds -4(%ebp)
movl %ebp,%esp
popl %ebp
ret
.type sum_3,@function
sum_3,.-sum_3