I also had the problem that I needed the current function's address when I created a macro template coroutine abstraction that people can use like modern coroutine language features (await and async). It compensates for a missing RTOS when there is a central loop which schedules different asynchronous functions as (cooperative) tasks. Turning interrupt handlers into asynchronous functions even causes race conditions like in a preemptive multi-tasking system.
I noticed that I need to know the caller function's address for the final return address of a coroutine (which is not return address of the initial call of course). Only asynchronous functions need to know their own address so that they can pass it as hidden first argument in an AWAIT() macro. Since instrumenting the code with a macro solution is as simple as just defining the function it suffices to have an async-keyword-like macro.
This is a solution with GCC extensions:
#define _VARGS(...) _VARGS0(__VA_ARGS__)
#define _VARGS0(...) ,##__VA_ARGS__
typedef union async_arg async_arg_t;
union async_arg {
void (*caller)(void*);
void *retval;
};
#define ASYNC(FUNCNAME, FUNCARGS, ...) \
void FUNCNAME (async_arg_t _arg _VARGS FUNCARGS) \
GENERATOR( \
void (*const THIS)(void*) = (void*) &FUNCNAME;\
static void (*CALLER)(void*), \
CALLER = _arg.caller; \
__VA_ARGS__ \
)
#define GENERATOR(INIT,...) { \
__label__ _entry, _start, _end; \
static void *_state = (void*)0; \
INIT; \
_entry:; \
if (_state - &&_start <= &&_end - &&_start) \
goto *_state; \
_state = &&_start; \
_start:; \
__VA_ARGS__; \
_end: _state = &&_entry; \
}
#define AWAIT(FUNCNAME,...) ({ \
__label__ _next; \
_state = &&_next; \
return FUNCNAME((async_arg_t)THIS,##__VA_ARGS__);\
_next: _arg.retval; \
})
#define _I(...) __VA_ARGS__
#define IF(COND,THEN) _IF(_I(COND),_I(THEN))
#define _IF(COND,THEN) _IF0(_VARGS(COND),_I(THEN))
#define _IF0(A,B) _IF1(A,_I(B),)
#define _IF1(A,B,C,...) C
#define IFNOT(COND,ELSE) _IFNOT(_I(COND),_I(ELSE))
#define _IFNOT(COND,ELSE) _IFNOT0(_VARGS(COND),_I(ELSE))
#define _IFNOT0(A,B) _IFNOT1(A,,_I(B))
#define _IFNOT1(A,B,C,...) C
#define IF_ELSE(COND,THEN,ELSE) IF(_I(COND),_I(THEN))IFNOT(_I(COND),_I(ELSE))
#define WAIT(...) ({ \
__label__ _next; \
_state = &&_next; \
IF_ELSE(_I(__VA_ARGS__), \
static __typeof__(__VA_ARGS__) _value;\
_value = (__VA_ARGS__); \
return; \
_next: _value; \
, return; _next:;) \
})
#define YIELD(...) do { \
__label__ _next; \
_state = &&_next; \
return IF(_I(__VA_ARGS__),(__VA_ARGS__));\
_next:; \
} while(0)
#define RETURN(VALUE) do { \
_state = &&_entry; \
if (CALLER != 0) \
CALLER((void*)(VALUE +0));\
return; \
} while(0)
#define ASYNCALL(FUNC, ...) FUNC ((void*)0,__VA_ARGS__)
I know, a more portable (and maybe secure) solution would use the switch-case statement instead of label addresses but I think, gotos are more efficient than switch-case-statements. It also has the advantage that you can use the macros within any other control structures easily and break
will have no unexpected effects.
You can use it like this:
#include <stdint.h>
int spi_start_transfer(uint16_t, void *, uint16_t, void(*)());
#define SPI_ADDR_PRESSURE 0x24
ASYNC(spi_read_pressure, (void* dest, uint16_t num),
void (*callback)(void) = (void*)THIS; //see here! THIS == &spi_read_pressure
int status = WAIT(spi_start_transfer(SPI_ADDR_PRESSURE,dest,num,callback));
RETURN(status);
)
int my_gen() GENERATOR(static int i,
while(1) {
for(i=0; i<5; i++)
YIELD(i);
}
)
extern volatile int a;
ASYNC(task_read, (uint16_t threshold),
while(1) {
static uint16_t pressure;
int status = (int)AWAIT(spi_read_pressure, &pressure, sizeof pressure);
if (pressure > threshold) {
a = my_gen();
}
}
)
You must use AWAIT
to call asynchronous functions for return value and ASYNCALL
without return value. AWAIT
can only be called by ASYNC
-functions. You can use WAIT
with or without value. WAIT
results in the expression which was given as argument, which is returned AFTER the function is resumed. WAIT
can be used in ASYNC
-functions only. Keeping the argument with WAIT
wastes one new piece of static memory for each WAIT()
call with argument though so it is recommended to use WAIT()
without argument. It could be improved, if all WAIT
calls would use the same single static variable for the entire function.
It is only a very simple version of a coroutine abstraction. This implementation cannot have nested or intertwinned calls of the same function because all static variables comprise one static stack frame.
If you want to solve this problem, you also need to distinguish resuming an old and starting a new function call. You can add details like a stack-frame queue at the function start in the ASYNC macro. Create a custom struct for each function's stack frame (which also can be done within the macro and an additional macro argument). This custom stack frame type is loaded from a queue when entering the macro, is stored back when exiting it or is removed when the call finishes.
You could use a stack frame index as alternative argument in the async_arg_t
union. When the argument is an address, it starts a new call or when given a stack frame index it resumes an old call. The stack frame index or continuation must be passed as user-defined argument to the callback that resumes the coroutine.