/* uctx.h - assembly stack switching * * Copyright (C) 2009 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 or later as * published by the Free Software Foundation. * * See http://www.gnu.org/ for details. */ #include #include #include #ifdef VALGRIND #include #endif #define STACK_GUARD 0xbad57ac4 struct tf_uctx { int *stack_guard; void *alloc; void *current_sp; #ifdef VALGRIND unsigned int stack_id; #endif struct tf_fiber fiber; }; #if defined(__i386__) #define switch_fiber(prev, next) \ do { \ unsigned eax, ebx, ecx, edx, esi, edi; \ __asm__ __volatile__ ( \ "push %%ebp \n" \ "call 1f \n" \ "1: \n" \ "addl $2f-1b, (%%esp) \n" \ "movl %%esp, %[prev_sp] \n" \ "movl %[next_sp], %%esp \n" \ "ret \n" \ "2: \n" \ "pop %%ebp \n" \ : [prev_sp] "=m"(prev->current_sp), \ "=a"(eax), "=b"(ebx), "=c"(ecx), \ "=d"(edx), "=S"(esi), "=D"(edi) \ : [next_sp] "m"(next->current_sp) \ : "memory", "cc"); \ } while (0) #define STACK_GROWS_UP #else #error Your architecture is not supported. Contact timo.teras@iki.fi. #endif #if defined(STACK_GROWS_UP) static inline void *stack_pointer(void *base, size_t size) { return base + size; } static inline void *stack_guard(void *base, size_t size) { return base; } static inline void *stack_push(void **stackptr, size_t size) { (*stackptr) -= size; return (*stackptr); } #else #error Stack growing direction undefined. #endif static inline void stack_push_ptr(void **stackptr, void *ptr) { *(void**) stack_push(stackptr, sizeof(ptr)) = ptr; } static inline struct tf_fiber *tf_uctx_create(tf_fiber_proc fiber_main, int private_size) { size_t size = TF_STACK_SIZE; struct tf_uctx *uctx; void *stack, *stack_base; /* Allocate new stack */ stack_base = malloc(size); if (stack_base == NULL) return NULL; stack = stack_pointer(stack_base, size); private_size += sizeof(struct tf_uctx); /* Construct inital frame for call the main function and if it * happens to return, it'll jump back to tf_exit() which kills * the fiber (cdecl calling convetion assumed) */ uctx = stack_push(&stack, TF_ALIGN(private_size, 64)); stack_push_ptr(&stack, uctx->fiber.data); stack_push_ptr(&stack, &tf_exit); stack_push_ptr(&stack, fiber_main); uctx->current_sp = stack; #ifdef VALGRIND uctx->stack_id = VALGRIND_STACK_REGISTER(stack_base, size); #endif uctx->alloc = stack_base; uctx->stack_guard = stack_guard(stack_base, size); *uctx->stack_guard = STACK_GUARD; return &uctx->fiber; } static inline void tf_uctx_destroy(struct tf_fiber *fiber) { struct tf_uctx *uctx = container_of(fiber, struct tf_uctx, fiber); #ifdef VALGRIND VALGRIND_STACK_DEREGISTER(uctx->stack_id); #endif free(uctx->alloc); } static inline void tf_uctx_transfer(struct tf_fiber *from, struct tf_fiber *to) { struct tf_uctx *ufrom = container_of(from, struct tf_uctx, fiber); struct tf_uctx *uto = container_of(to, struct tf_uctx, fiber); /* Switch stack pointers */ TF_BUG_ON(*ufrom->stack_guard != STACK_GUARD); switch_fiber(ufrom, uto); }