qse/qse/lib/cmn/task.c
hyung-hwan eb944bbf95 touched up some source files for borland and msvc.
added more options to bakefile description
2012-12-13 13:07:16 +00:00

604 lines
13 KiB
C

/*
* $Id$
*
Copyright 2006-2012 Chung, Hyung-Hwan.
This file is part of QSE.
QSE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
QSE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with QSE. If not, see <http://www.gnu.org/licenses/>.
*/
#include <qse/cmn/task.h>
#include "mem.h"
#if defined(_WIN64)
# if !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0400
# endif
# include <windows.h>
#else
# include <setjmp.h>
# if defined(HAVE_UCONTEXT_H)
# include <ucontext.h>
# endif
# if defined(HAVE_MAKECONTEXT) && defined(HAVE_SWAPCONTEXT) && \
defined(HAVE_GETCONTEXT) && defined(HAVE_SETCONTEXT)
# define USE_UCONTEXT
# endif
#endif
struct qse_task_t
{
qse_mmgr_t* mmgr;
qse_task_slice_t* dead;
qse_task_slice_t* current;
qse_size_t count;
qse_task_slice_t* head;
qse_task_slice_t* tail;
#if defined(_WIN64)
void* fiber;
#elif defined(USE_UCONTEXT)
ucontext_t uctx;
#else
jmp_buf backjmp;
#endif
};
struct qse_task_slice_t
{
#if defined(_WIN64)
void* fiber;
#elif defined(USE_UCONTEXT)
ucontext_t uctx;
#else
jmp_buf jmpbuf;
#endif
qse_task_t* task;
int id;
qse_task_fnc_t fnc;
void* ctx;
qse_size_t stksize;
qse_task_slice_t* prev;
qse_task_slice_t* next;
};
int qse_task_init (qse_task_t* task, qse_mmgr_t* mmgr);
void qse_task_fini (qse_task_t* task);
static void purge_slice (qse_task_t* task, qse_task_slice_t* slice);
static void purge_dead_slices (qse_task_t* task);
static void purge_current_slice (qse_task_slice_t* slice, qse_task_slice_t* to);
qse_task_t* qse_task_open (qse_mmgr_t* mmgr, qse_size_t xtnsize)
{
qse_task_t* task;
task = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(*task) + xtnsize);
if (task == QSE_NULL) return QSE_NULL;
if (qse_task_init (task, mmgr) <= -1)
{
QSE_MMGR_FREE (task->mmgr, task);
return QSE_NULL;
}
QSE_MEMSET (task + 1, 0, xtnsize);
return task;
}
void qse_task_close (qse_task_t* task)
{
qse_task_fini (task);
QSE_MMGR_FREE (task->mmgr, task);
}
int qse_task_init (qse_task_t* task, qse_mmgr_t* mmgr)
{
QSE_MEMSET (task, 0, QSE_SIZEOF(*task));
task->mmgr = mmgr;
return 0;
}
void qse_task_fini (qse_task_t* task)
{
QSE_ASSERT (task->dead == QSE_NULL);
QSE_ASSERT (task->current == QSE_NULL);
if (task->count > 0)
{
/* am i closing after boot failure? */
qse_task_slice_t* slice, * next;
slice = task->head;
while (slice)
{
next = slice->next;
purge_slice (task, slice);
slice = next;
}
task->count = 0;
task->head = QSE_NULL;
task->tail = QSE_NULL;
}
}
qse_mmgr_t* qse_task_getmmgr (qse_task_t* task)
{
return task->mmgr;
}
void* qse_task_getxtn (qse_task_t* task)
{
return (void*)(task + 1);
}
static qse_task_slice_t* alloc_slice (
qse_task_t* task, qse_task_fnc_t fnc,
void* ctx, qse_size_t stksize)
{
qse_task_slice_t* slice;
slice = QSE_MMGR_ALLOC (task->mmgr, QSE_SIZEOF(*slice) + stksize);
if (slice == QSE_NULL) return QSE_NULL;
QSE_MEMSET (slice, 0, QSE_SIZEOF(*slice));
slice->task = task;
slice->fnc = fnc;
slice->ctx = ctx;
slice->stksize = stksize;
return slice;
}
static void link_task (qse_task_t* task, qse_task_slice_t* slice)
{
if (task->head)
{
slice->next = task->head;
task->head->prev = slice;
task->head = slice;
}
else
{
task->head = slice;
task->tail = slice;
}
task->count++;
}
#if defined(_WIN64)
# define __CALL_BACK__ __stdcall
#else
# define __CALL_BACK__
#endif
static void __CALL_BACK__ execute_current_slice (qse_task_slice_t* slice)
{
qse_task_slice_t* to;
QSE_ASSERT (slice->task->current == slice);
to = slice->fnc (slice->task, slice, slice->ctx);
/* the task function is now terminated. we need to
* purge it from the slice list and switch to the next
* slice. */
purge_current_slice (slice, to);
QSE_ASSERT (!"must never reach here...");
}
#if defined(__WATCOMC__)
/* for watcom, i support i386/32bit only */
extern void* prepare_sp (void*);
#pragma aux prepare_sp = \
"mov dword ptr[eax+4], esp" \
"mov esp, eax" \
"mov eax, dword ptr[esp+0]" \
parm [eax] value [eax] modify [esp]
extern void* restore_sp (void);
#pragma aux restore_sp = \
"mov esp, dword ptr[esp+4]" \
modify [esp]
extern void* get_slice (void);
#pragma aux get_slice = \
"mov eax, dword ptr[esp+0]" \
value [eax]
#endif
qse_task_slice_t* qse_task_create (
qse_task_t* task, qse_task_fnc_t fnc,
void* ctx, qse_size_t stksize)
{
qse_task_slice_t* slice;
register void* tmp;
stksize = ((stksize + QSE_SIZEOF(void*) - 1) / QSE_SIZEOF(void*)) * QSE_SIZEOF(void*);
#if defined(_WIN64)
slice = alloc_slice (task, fnc, ctx, 0);
if (slice == QSE_NULL) return QSE_NULL;
slice->fiber = CreateFiberEx (
stksize, stksize, FIBER_FLAG_FLOAT_SWITCH,
execute_current_slice, slice);
if (slice->fiber == QSE_NULL)
{
QSE_MMGR_FREE (task->mmgr, slice);
return QSE_NULL;
}
#elif defined(USE_UCONTEXT)
slice = alloc_slice (task, fnc, ctx, stksize);
if (slice == QSE_NULL) return QSE_NULL;
if (getcontext (&slice->uctx) <= -1)
{
QSE_MMGR_FREE (task->mmgr, slice);
return QSE_NULL;
}
slice->uctx.uc_stack.ss_sp = slice + 1;
slice->uctx.uc_stack.ss_size = stksize;
slice->uctx.uc_link = QSE_NULL;
makecontext (&slice->uctx, execute_current_slice, 1, slice);
#else
if (stksize < QSE_SIZEOF(void*) * 3)
stksize = QSE_SIZEOF(void*) * 3; /* for t1 & t2 */
slice = alloc_slice (task, fnc, ctx, stksize);
if (slice == QSE_NULL) return QSE_NULL;
/* setjmp() doesn't allow different stacks for
* each execution content. let me use some assembly
* to change the stack pointer so that setjmp() remembers
* the new stack pointer for longjmp() later.
*
* this stack is used for the task function when
* longjmp() is made. */
tmp = ((qse_uint8_t*)(slice + 1)) + stksize - QSE_SIZEOF(void*);
tmp = (qse_uint8_t*)tmp - QSE_SIZEOF(void*);
*(void**)tmp = NULL; /* t1 */
tmp = (qse_uint8_t*)tmp - QSE_SIZEOF(void*);
*(void**)tmp = slice; /* t2 */
#if defined(__WATCOMC__)
tmp = prepare_sp (tmp);
#elif defined(__GNUC__) && (defined(__x86_64) || defined(__amd64))
__asm__ volatile (
"movq %%rsp, 8(%1)\n\t" /* t1 = %rsp */
"movq %1, %%rsp\n\t" /* %rsp = tmp */
"movq 0(%%rsp), %0\n" /* tmp = t2 */
: "=r"(tmp)
: "0"(tmp)
: "%rsp", "memory"
);
#elif defined(__GNUC__) && (defined(__i386) || defined(i386))
__asm__ volatile (
"movl %%esp, 4(%1)\n\t" /* t1 = %esp */
"movl %1, %%esp\n\t" /* %esp = tmp */
"movl 0(%%esp), %0\n" /* tmp = t2 */
: "=r"(tmp)
: "0"(tmp)
: "%esp", "memory"
);
#elif defined(__GNUC__) && (defined(__mips) || defined(mips))
__asm__ volatile (
"sw $sp, 4(%1)\n\t" /* t1 = $sp */
"move $sp, %1\n\t" /* %sp = tmp */
"lw %0, 0($sp)\n" /* tmp = t2 */
: "=r"(tmp)
: "0"(tmp)
: "$sp", "memory"
);
#elif defined(__GNUC__) && defined(__arm__)
__asm__ volatile (
"str sp, [%1, #4]\n\t" /* t1 = sp */
"mov sp, %1\n" /* sp = tmp */
"ldr %0, [sp, #0]\n" /* tmp = t2 */
: "=r"(tmp)
: "0"(tmp)
: "sp", "memory"
);
#else
/* TODO: support more architecture */
QSE_MMGR_FREE (task->mmgr, task);
return QSE_NULL;
#endif /* __WATCOMC__ */
/*
* automatic variables like 'task' and 'newsp' exist
* in the old stack. i can't access them.
* i access some key informaton via the global
* variables stored before stack pointer switching.
*
* this approach makes this function thread-unsafe.
*/
/* when qse_task_task_create() is called,
* setjmp() saves the context and return 0.
*
* subsequently, when longjmp() is made
* for this saved context, setjmp() returns
* a non-zero value.
*/
if (setjmp (((qse_task_slice_t*)tmp)->jmpbuf) != 0)
{
/* longjmp() is made to here. */
#if defined(__WATCOMC__)
tmp = get_slice ();
#elif defined(__GNUC__) && (defined(__x86_64) || defined(__amd64))
__asm__ volatile (
"movq 0(%%rsp), %0\n" /* tmp = t2 */
: "=r"(tmp)
);
#elif defined(__GNUC__) && (defined(__i386) || defined(i386))
__asm__ volatile (
"movl 0(%%esp), %0\n" /* tmp = t2 */
: "=r"(tmp)
);
#elif defined(__GNUC__) && (defined(__mips) || defined(mips))
__asm__ volatile (
"lw %0, 0($sp)\n" /* tmp = t2 */
: "=r"(tmp)
);
#elif defined(__GNUC__) && defined(__arm__)
__asm__ volatile (
"ldr %0, [sp, #0]\n" /* tmp = t2 */
: "=r"(tmp)
);
#endif /* __WATCOMC__ */
execute_current_slice ((qse_task_slice_t*)tmp);
QSE_ASSERT (!"must never reach here....\n");
}
/* restore the stack pointer once i finish saving the longjmp() context.
* this part is reached only when qse_task_task_create() is invoked. */
#if defined(__WATCOMC__)
restore_sp ();
#elif defined(__GNUC__) && (defined(__x86_64) || defined(__amd64))
/* i assume that %rsp didn't change after the call to setjmp() */
__asm__ volatile (
"movq 8(%%rsp), %%rsp\n" /* %rsp = t1 */
:
:
: "%rsp"
);
#elif defined(__GNUC__) && (defined(__i386) || defined(i386))
__asm__ volatile (
"movl 4(%%esp), %%esp\n" /* %esp = t1 */
:
:
: "%esp"
);
#elif defined(__GNUC__) && (defined(__mips) || defined(mips))
__asm__ volatile (
"lw $sp, 4($sp)\n" /* $sp = t1 */
:
:
: "$sp"
);
#elif defined(__GNUC__) && defined(__arm__)
__asm__ volatile (
"ldr sp, [sp, #4]\n" /* sp = t1 */
:
:
: "sp"
);
#endif /* __WATCOMC__ */
#endif
link_task (task, slice);
return slice;
}
int qse_task_boot (qse_task_t* task, qse_task_slice_t* to)
{
QSE_ASSERT (task->current == QSE_NULL);
if (to == QSE_NULL) to = task->head;
#if defined(_WIN64)
task->fiber = ConvertThreadToFiberEx (QSE_NULL, FIBER_FLAG_FLOAT_SWITCH);
if (task->fiber == QSE_NULL) return -1;
task->current = to;
SwitchToFiber (task->current->fiber);
ConvertFiberToThread ();
#elif defined(USE_UCONTEXT)
task->current = to;
if (swapcontext (&task->uctx, &task->current->uctx) <= -1)
{
task->current = QSE_NULL;
return -1;
}
#else
if (setjmp (task->backjmp) != 0)
{
/* longjmp() back */
goto done;
}
task->current = to;
longjmp (task->current->jmpbuf, 1);
QSE_ASSERT (!"must never reach here");
done:
#endif
QSE_ASSERT (task->current == QSE_NULL);
QSE_ASSERT (task->count == 0);
if (task->dead) purge_dead_slices (task);
return 0;
}
/* NOTE for __WATCOMC__.
when the number of parameters are more than 2 for qse_task_schedule(),
this setjmp()/longjmp() based tasking didn't work.
if i change this to
void qse_task_schedule (qse_task_slice_t* from, qse_task_slice_t* to)
{
qse_task_t* task = from->task
....
}
it worked. i stopped investigating this problem. so i don't know the
real cause of this problem. let me get back to this later when i have
time for this.
*/
void qse_task_schedule (
qse_task_t* task, qse_task_slice_t* from, qse_task_slice_t* to)
{
QSE_ASSERT (from != QSE_NULL);
if (to == QSE_NULL)
{
/* round-robin if the target is not specified */
to = from->next? from->next: task->head;
}
QSE_ASSERT (task == to->task);
QSE_ASSERT (task == from->task);
QSE_ASSERT (task->current == from);
if (to == from) return;
task->current = to;
#if defined(_WIN64)
/* current->fiber is handled by SwitchToFiber() implicitly */
SwitchToFiber (to->fiber);
if (task->dead) purge_dead_slices (task);
#elif defined(USE_UCONTEXT)
swapcontext (&from->uctx, &to->uctx);
if (task->dead) purge_dead_slices (task);
#else
if (setjmp (from->jmpbuf) != 0)
{
if (task->dead) purge_dead_slices (task);
return;
}
longjmp (to->jmpbuf, 1);
#endif
}
static void purge_dead_slices (qse_task_t* task)
{
qse_task_slice_t* slice;
while (task->dead)
{
slice = task->dead;
task->dead = slice->next;
purge_slice (task, slice);
}
}
static void purge_slice (qse_task_t* task, qse_task_slice_t* slice)
{
#if defined(_WIN64)
if (slice->fiber) DeleteFiber (slice->fiber);
#endif
QSE_MMGR_FREE (task->mmgr, slice);
}
static void purge_current_slice (qse_task_slice_t* slice, qse_task_slice_t* to)
{
qse_task_t* task = slice->task;
QSE_ASSERT (task->current == slice);
if (task->count == 1)
{
/* to purge later */
slice->next = task->dead;
task->dead = slice;
task->current = QSE_NULL;
task->head = QSE_NULL;
task->tail = QSE_NULL;
task->count = 0;
#if defined(_WIN64)
SwitchToFiber (task->fiber);
#elif defined(USE_UCONTEXT)
setcontext (&task->uctx);
#else
longjmp (task->backjmp, 1);
#endif
QSE_ASSERT (!"must not reach here....");
}
task->current = (to && to != slice)? to: (slice->next? slice->next: task->head);
if (slice->prev) slice->prev->next = slice->next;
if (slice->next) slice->next->prev = slice->prev;
if (slice == task->head) task->head = slice->next;
if (slice == task->tail) task->tail = slice->prev;
task->count--;
/* to purge later */
slice->next = task->dead;
task->dead = slice;
#if defined(_WIN64)
SwitchToFiber (task->current->fiber);
#elif defined(USE_UCONTEXT)
setcontext (&task->current->uctx);
#else
longjmp (task->current->jmpbuf, 1);
#endif
}