Discussion:
unknown
1970-01-01 00:00:00 UTC
Permalink
void callee(int, int, int, int, int, int, int, int);

The main difference is located in the way the callee function manages
variadic arguments.

In the following we present two different implementation proposals for
such an ABI with this property.

## Proposal 1 ##

A common solution is to define the `__builtin_va_list` type as a
structure that will hold all the parameters necessary to implement the
`va_arg` construct.

In our first proposal we have the following definitions:

struct __or1k_va_list {
unsigned reg_size;
void *reg_save_area;
void *stack_area;
};

typedef struct __or1k_va_list ___builtin_va_list[1];

In the `va_list` we have two pointer fields:

* `reg_save_area` points to the callee stack section reserved for
variadic parameters passed by registers,
* `stack_area` points to caller stack section reserved for stack
parameters.

We suggest the following stack layout:

| | (higher addresses)
+-----------------+
| |
| stack params |
| | caller
----+-----------------+----------------
| RA slot (opt) | callee
+-----------------+
| FP slot (opt) |
+-----------------+
| |
| reg save area |
| |
+-----------------+
| |
| CSR slots |
| |
+-----------------+ /\
| | / \
| Local variables | ||
| | ||
+-----------------+ addresses grow upwards
| |
| Dynamic objects | stack grows downwards
| | ||
+-----------------+ ||
| | \ /
| stack params | \/
| |
+-----------------+ (lower addresses)

On the entry of a variadic function registers that may hold parameters
must be saved on the stack in a predefined location. The `va_start`
macro will just initialize the `va_list` fields as follows:

* `reg_save_area` points to the stack section of variadic register
parameters,
* `stack_area` points to the stack parameters
* `reg_size` is the size (in byte) of ordinary register parameters.

The `va_arg` implementation should do the following:

va_arg(va_list ap, type t) {
int size = 4;
if (t == long long || t == double)
size = 8;
void *ptr = 0;
// If it doesn't fit in the registers switch to the stack
if (ap->reg_size + size > 24) {
ptr = ap->stack_area;
ap->stack_area = (char*)(ap->stack_area) + size;
ap->reg_size = 24;
} else {
ptr = ap->reg_save_area;
ap->reg_save_area = (char*)(ap->reg_save_area) + size;
ap->reg_size += size;
}
if (is_aggregate(t)) {
return **((t**)ptr);
}
return *((t*)ptr);
}

## Proposal 2 ##

An alternative approach for laying out the stack for a variadic
function is the following:

| | (higher addresses)
+-----------------+
| |
| stack params |
| | caller
----+-----------------+----------------
| | callee
| reg save area |
| |
+-----------------+
| RA slot (opt) |
+-----------------+
| FP slot (opt) |
+-----------------+
| |
| CSR slots |
| |
+-----------------+ /\
| | / \
| Local variables | ||
| | ||
+-----------------+ addresses grow upwards
| |
| Dynamic objects | stack grows downwards
| | ||
+-----------------+ ||
| | \ /
| stack params | \/
| |
+-----------------+ (lower addresses)

This layout, at first sight, keeps the parameters contiguous on the
stack, which allows easier access to the arguments using a single
pointer which is properly incremented at each `va_arg(...)` call.

However, due to the above mentioned issue about 8-bytes long
parameters, in general we cannot assume that the parameters will
actually be contiguous. A simple example is the following prototype:

void foo(int, long long, long long, ...);

The first parameter is passed using `r3`.
The second parameter is passed using `r4-r5`.
The third parameter is passed using `r6-r7`.

Now if the first variadic parameter is an aggregate type (indirect
passage using a pointer to the aggregate type) or its size is at most 4
bytes then is passed using `r8`. The other possible case is the first
variadic parameters is a `long long` or a `double`, thus requiring two
registers. The standard ABI says that in this case we should not split
the parameter half in a register and half on the stack, so we have to
pass the parameter using the stack. This particular case will produce a
hole in the stack layout that cannot be avoided.

However we want the `va_list` type to be `void*` and thus have a simple
`va_arg` that just use the `va_list` as pointer to the argument.

For this reason we propose two solutions.

### Solution A ###

Force variadic functions to always have a frame pointer defined that
points to the beginning of the reg save area. This constraint allows us
to compute the amount of bytes of the parameters we have already used
as follows:

params_bytes = va_list_value - FP + #static_params_bytes

Note that `#static_params_bytes` is a compile time constant.

The `va_arg` construct for `long long` or `double` must handle the
particular case of `#params_bytes` equals to 20 differently: the
pointer for the argument is `va_list_value + 4` instead of just
`va_list_value`.

### Solution B ###

Change the ABI to require that 8-byte parameters are split among
registers and the stack if appropriate.

This approach has a reduced overhead if compared to the previous one,
which however requires a change in the ABI outside the context of
variadic functions.


In both solution A and B we need to relax the frame pointer definition
to guarantee the correctness: the frame pointer must always point to
the base of the reg save area, i.e. right after the return address
slot. For ordinary functions the reg save area size is zero, thus the
frame pointer will be the value of the stack pointer at function entry
point as in the standard ABI. For variadic functions this is generally
false. This relaxation of the frame pointer definition is necessary in
order to be able to follow the linked list of function frames.

Using this relaxed definition for the frame pointer we are not able to
identify the stack frames correctly: the reg save area would virtually
become part of the caller stack frame, since there is no way to check
if a given stack frame refers to either a variadic or an ordinary
function.

Loading...