efl/src/lib/ecore/ecore_coroutine.c

342 lines
7.0 KiB
C

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <sys/time.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>
#if defined(USE_UCONTEXT)
# include <sys/time.h>
# include <sys/resource.h>
# include <ucontext.h>
#elif defined(USE_SETJMP)
# include <sys/time.h>
# if HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
# endif
# include <setjmp.h>
#endif
#ifdef HAVE_EVIL
# include <Evil.h>
#endif
#ifdef _WIN32
# define USE_FIBERS
#endif
#include "Ecore.h"
#include "ecore_private.h"
typedef struct _Ecore_Coroutine_Defer Ecore_Coroutine_Defer;
struct _Ecore_Coroutine_Defer
{
Eina_Free_Cb func;
void *data;
};
struct _Ecore_Coroutine
{
#if defined(USE_FIBERS)
void *context;
#elif defined(USE_UCONTEXT)
ucontext_t context;
#elif defined(USE_SETJMP)
jmp_buf context;
#else
# error "No coroutine implementation !"
#endif
Eina_Inarray defer;
Ecore_Coroutine_Cb func;
void *data;
Ecore_Coroutine_State state;
#ifdef USE_VALGRIND
int vg_stack_id;
#endif
int yield_value;
unsigned char stack[1];
};
#ifdef __x86_64__
static const int const _ecore_coroutine_default_stack_size = 16 * 1024;
#else
static const int const _ecore_coroutine_default_stack_size = 12 * 1024;
#endif
static void
_ecore_coroutine_finish(Ecore_Coroutine *coro)
{
int return_value = coro->func(coro->data, coro);
coro->state = ECORE_COROUTINE_FINISHED;
ecore_coroutine_yield(coro, return_value);
}
#if defined(USE_UCONTEXT)
# ifdef __x86_64__
union ptr_splitter {
void *ptr;
uint32_t part[sizeof(void *) / sizeof(uint32_t)];
};
static void
_ecore_coroutine_entry_point(uint32_t part0, uint32_t part1)
{
union ptr_splitter p = {
.part = { part0, part1 }
};
Ecore_Coroutine *coro = p.ptr;
_ecore_coroutine_finish(coro);
}
# else
static void
_ecore_coroutine_entry_point(Ecore_Coroutine *coro)
{
_ecore_coroutine_finish(coro);
}
# endif
#else
static void
_ecore_coroutine_entry_point(Ecore_Coroutine *coro)
{
_ecore_coroutine_finish(coro);
}
# if defined(USE_SETJMP)
static void
_ecore_coroutine_setjmp(Ecore_Coroutine *coro)
{
setjmp(coro->context);
/* The idea of this trick come from libcoroutine */
/* __jmpbuf[6] == stack pointer */
/* __jmpbuf[7] == program counter */
coro->context->env[0].__jmpbuf[6] = ((uintptr_t)(&coro->stack));
coro->context->env[0].__jmpbuf[7] = ((uintptr_t)_ecore_coroutine_entry_point);
}
# endif
#endif
#if defined(USE_FIBERS)
static void *caller;
static void *callee;
#elif defined(USE_UCONTEXT)
static ucontext_t caller;
static ucontext_t callee;
#elif defined(USE_SETJMP)
static jmp_buf caller;
static jmp_buf callee;
#endif
void
_ecore_coroutine_init(void)
{
#if defined(USE_FIBERS)
caller = GetCurrentFiber();
if (caller == (LPVOID) 0x1e00)
{
caller = ConvertThreadToFiber(NULL);
}
#else
memset(&caller, 0, sizeof (caller));
memset(&callee, 0, sizeof (callee));
#endif
}
void
_ecore_coroutine_shutdown(void)
{
#ifdef USE_FIBERS
ConvertFiberToThread();
#endif
// FIXME: should we track lost coroutine ?
}
EAPI Ecore_Coroutine *
ecore_coroutine_add(int stack_size, Ecore_Coroutine_Cb func, void *data)
{
Ecore_Coroutine *coro;
unsigned char *stack;
if (stack_size <= 0)
{
#if defined(USE_UCONTEXT) || defined(USE_SETJMP)
struct rlimit check;
if (getrlimit(RLIMIT_STACK, &check))
check.rlim_cur = _ecore_coroutine_default_stack_size;
stack_size = check.rlim_cur;
#elif defined(USE_FIBERS)
stack_size = _ecore_coroutine_default_stack_size;
#endif
if (stack_size < _ecore_coroutine_default_stack_size)
stack_size = _ecore_coroutine_default_stack_size;
}
coro = malloc(sizeof (Ecore_Coroutine) + stack_size - 1);
if (!coro) return NULL;
stack = coro->stack;
#ifdef USE_VALGRIND
coro->vg_stack_id = VALGRIND_STACK_REGISTER(stack, stack + stack_size);
#endif
coro->state = ECORE_COROUTINE_NEW;
coro->func = func;
coro->data = data;
eina_inarray_step_set(&coro->defer,
sizeof (Eina_Inarray), sizeof (Ecore_Coroutine_Defer),
8);
#if defined(USE_UCONTEXT)
getcontext(&coro->context);
coro->context.uc_stack.ss_sp = stack;
coro->context.uc_stack.ss_size = stack_size;
coro->context.uc_stack.ss_flags = 0;
coro->context.uc_link = NULL;
# ifdef __x86_64__
union ptr_splitter p = { .ptr = coro };
makecontext(&coro->context, (void (*)())_ecore_coroutine_entry_point,
2, p.part[0], p.part[1]);
# else
makecontext(&coro->context, (void (*)())_ecore_coroutine_entry_point,
1, coro);
# endif
#elif defined(USE_FIBERS)
coro->context = CreateFiber(stack_size,
(LPFIBER_START_ROUTINE)_ecore_coroutine_entry_point,
coro);
if (!coro->context)
{
free(coro);
return NULL;
}
#elif defined(USE_SETJMP)
/* We use an intermediate function call to setup the stack with the right arguments */
_ecore_coroutine_setjmp(coro);
#endif
return coro;
}
EAPI void *
ecore_coroutine_del(Ecore_Coroutine *coro)
{
void *data;
data = coro->data;
while (eina_inarray_count(&coro->defer))
{
Ecore_Coroutine_Defer *defer;
defer = eina_inarray_pop(&coro->defer);
defer->func(defer->data);
}
eina_inarray_flush(&coro->defer);
#ifdef USE_VALGRIND
VALGRIND_STACK_DEREGISTER(coro->vg_stack_id);
#endif
#ifdef USE_FIBERS
DeleteFiber(coro->context);
#endif
free(coro);
return data;
}
EAPI int
ecore_coroutine_resume(Ecore_Coroutine *coro)
{
#if defined(USE_FIBERS)
void *prev_caller;
#elif defined(USE_UCONTEXT)
ucontext_t prev_caller;
#elif defined(USE_SETJMP)
jmp_buf prev_caller;
#endif
if (coro->state == ECORE_COROUTINE_FINISHED)
return 0;
coro->state = ECORE_COROUTINE_RUNNING;
prev_caller = caller;
#if defined(USE_FIBERS)
SwitchToFiber(coro->context);
#elif defined(USE_UCONTEXT)
swapcontext(&caller, &coro->context);
#elif defined(USE_SETJMP)
setjmp(caller);
longjmp(coro->context);
#endif
#ifndef USE_FIBERS
// As fiber do handle the callee stack for us, no need here
coro->context = callee;
#endif
caller = prev_caller;
return coro->yield_value;
}
EAPI void
ecore_coroutine_yield(Ecore_Coroutine *coro, int value)
{
coro->yield_value = value;
#if defined(USE_FIBERS)
SwitchToFiber(caller);
#elif defined(USE_UCONTEXT)
swapcontext(&callee, &caller);
#elif defined(USE_SETJMP)
setjmp(callee);
longjmp(caller);
#endif
}
EAPI void *
ecore_coroutine_data_get(Ecore_Coroutine *coro)
{
return coro->data;
}
EAPI Ecore_Coroutine_State
ecore_coroutine_state_get(Ecore_Coroutine *coro)
{
return coro->state;
}
EAPI void
ecore_coroutine_defer(Ecore_Coroutine *coro, Eina_Free_Cb func, void *data)
{
Ecore_Coroutine_Defer *defer;
defer = eina_inarray_grow(&coro->defer, 1);
defer->func = func;
defer->data = data;
}
EAPI void *
ecore_coroutine_alloc(Ecore_Coroutine *coro, size_t size)
{
void *data;
data = malloc(size);
ecore_coroutine_defer(coro, free, data);
return data;
}