callback - closures with variable arguments as first-class C functions
#include <callback.h>
void function (void* data, va_alist alist)
{
va_start_type(alist[, return_type]);
arg = va_arg_type(alist[, arg_type]);
va_return_type(alist[[, return_type], return_value]);
}
callback = alloc_callback(&function,
data);
free_callback(callback);
is_callback(callback)
callback_address(callback)
callback_data(callback)
These functions implement
closures with variable arguments as first-class
C functions.
Closures as
first-class C functions means that they fit into a function
pointer and can be called exactly like any other C function. Moreover, they
can be called with variable arguments and can return variable return values.
callback = alloc_callback(&function,
data) allocates a callback. When
callback gets
called, it arranges to call
function, passing
data as first
argument and, as second argument, the entire sequence of arguments passed to
callback.
Function calling conventions differ considerably on different machines,
therefore the arguments are accessed and the result value is stored through
the same macros as used by the
vacall package, see below.
The callbacks are functions with indefinite extent:
callback is only
deallocated when
free_callback(callback) is called.
is_callback(callback) checks whether the C function
callback was produced by a call to
alloc_callback. If this
returns true, the arguments given to
alloc_callback can be retrieved:
callback_address(callback) returns
&function,
callback_data(callback) returns
data.
Within
function, the following macros can be used to walk through the
argument list and specify a return value:
-
va_start_type(alist[,
return_type]);
- starts the walk through the argument list and specifies the
return type.
-
arg =
va_arg_type(alist[,
arg_type]);
- fetches the next argument from the argument list.
-
va_return_type(alist[[,
return_type], return_value]);
- ends the walk through the argument list and specifies the
return value.
The
type in
va_start_type and
va_return_type
shall be one of
void,
int,
uint,
long,
ulong,
longlong,
ulonglong,
double,
struct,
ptr or (for ANSI C calling conventions only)
char,
schar,
uchar,
short,
ushort,
float, depending on the
class of
return_type.
The
type specifiers in
va_start_type and
va_return_type must be the same. The
return_type
specifiers passed to
va_start_type and
va_return_type must be the same.
The
type in
va_arg_type shall be one of
int,
uint,
long,
ulong,
longlong,
ulonglong,
double,
struct,
ptr or (for ANSI C calling conventions
only)
char,
schar,
uchar,
short,
ushort,
float, depending on the class of
arg_type.
In
va_start_struct(alist, return_type,
splittable); the
splittable flag specifies whether
the struct
return_type can be returned in registers such that every
struct field fits entirely in a single register. This needs to be specified
for structs of size 2*sizeof(long). For structs of size <= sizeof(long),
splittable is ignored and assumed to be 1. For structs of size >
2*sizeof(long),
splittable is ignored and assumed to be 0. There are
some handy macros for this:
va_word_splittable_1 (type1)
va_word_splittable_2 (type1, type2)
va_word_splittable_3 (type1, type2, type3)
va_word_splittable_4 (type1, type2, type3, type4)
For a struct with three slots
struct { type1 id1; type2 id2; type3 id3; }
you can specify
splittable as
va_word_splittable_3
(type1, type2, type3) .
Functions which want to emulate Kernighan & Ritchie style functions (i.e.,
in ANSI C, functions without a typed argument list) cannot use the
type
values
char,
schar,
uchar,
short,
ushort,
float. As prescribed by the default K&R C expression promotions,
they have to use
int instead of
char,
schar,
uchar,
short,
ushort and
double instead of
float.
The macros
va_start_longlong(),
va_start_ulonglong(),
va_return_longlong(),
va_return_ulonglong(),
va_arg_longlong() and
va_arg_ulonglong() work only if the C
compiler has a working
long long 64-bit integer type.
The struct types used in
va_start_struct() and
va_struct() must
only contain (signed or unsigned) int, long, long long or pointer fields.
Struct types containing (signed or unsigned) char, short, float, double or
other structs are not supported.
vacall(3),
trampoline(3).
The current implementations have been tested on a selection of common cases but
there are probably still many bugs.
There are typically built-in limits on the size of the argument-list, which may
also include the size of any structure arguments.
The decision whether a struct is to be returned in registers or in memory
considers only the struct's size and alignment. This is inaccurate: for
example, gcc on m68k-next returns
struct { char a,b,c; } in registers
and
struct { char a[3]; } in memory, although both types have the same
size and the same alignment.
The argument list can only be walked once.
All information is passed in CPU registers and the stack. The
callback
package is therefore multithread-safe.
Porting
callback consists in first porting the
vacall and
trampoline packages, then choosing a CPU register for passing the
closure from
trampoline to
vacall. This register is normally the
register designated by STATIC_CHAIN_REGNUM in the gcc source, file
gcc-2.7.2/config/
cpu/
cpu.h.
Bruno Haible <
[email protected]>
Many ideas were cribbed from the gcc source.