2019-06-27 02:59:25 -04:00
/*
* This file is part of the " Coroutine " project and released under the MIT License .
*
* Created by Samuel Williams on 24 / 6 / 2019.
2019-12-27 18:41:47 -05:00
* Copyright , 2019 , by Samuel Williams .
2019-06-27 02:59:25 -04:00
*/
# include "Context.h"
// http://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html
# ifndef __GNUC__
# define __asm__ asm
# endif
# if defined(__sparc)
__attribute__ ( ( noinline ) )
// https://marc.info/?l=linux-sparc&m=131914569320660&w=2
static void coroutine_flush_register_windows ( ) {
__asm__
# ifdef __GNUC__
__volatile__
# endif
# if defined(__sparcv9) || defined(__sparc_v9__) || defined(__arch64__)
# ifdef __GNUC__
( " flushw " : : : " %o7 " )
# else
( " flushw " )
# endif
# else
( " ta 0x03 " )
# endif
;
}
# else
static void coroutine_flush_register_windows ( ) { }
# endif
int coroutine_save_stack ( struct coroutine_context * context ) {
void * stack_pointer = & stack_pointer ;
assert ( context - > stack ) ;
assert ( context - > base ) ;
// At this point, you may need to ensure on architectures that use register windows, that all registers are flushed to the stack.
coroutine_flush_register_windows ( ) ;
// Save stack to private area:
if ( stack_pointer < context - > base ) {
size_t size = ( char * ) context - > base - ( char * ) stack_pointer ;
assert ( size < = context - > size ) ;
memcpy ( context - > stack , stack_pointer , size ) ;
context - > used = size ;
} else {
size_t size = ( char * ) stack_pointer - ( char * ) context - > base ;
assert ( size < = context - > size ) ;
memcpy ( context - > stack , context - > base , size ) ;
context - > used = size ;
}
// Save registers / restore point:
return _setjmp ( context - > state ) ;
}
__attribute__ ( ( noreturn , noinline ) )
static void coroutine_restore_stack_padded ( struct coroutine_context * context , void * buffer ) {
void * stack_pointer = & stack_pointer ;
assert ( context - > base ) ;
// Restore stack from private area:
if ( stack_pointer < context - > base ) {
void * bottom = ( char * ) context - > base - context - > used ;
assert ( bottom > stack_pointer ) ;
memcpy ( bottom , context - > stack , context - > used ) ;
} else {
void * top = ( char * ) context - > base + context - > used ;
assert ( top < stack_pointer ) ;
memcpy ( context - > base , context - > stack , context - > used ) ;
}
// Restore registers:
// The `| (int)buffer` is to force the compiler NOT to elide he buffer and `alloca`.
_longjmp ( context - > state , 1 | ( int ) buffer ) ;
}
static const size_t GAP = 128 ;
// In order to swap between coroutines, we need to swap the stack and registers.
// `setjmp` and `longjmp` are able to swap registers, but what about swapping stacks? You can use `memcpy` to copy the current stack to a private area and `memcpy` to copy the private stack of the next coroutine to the main stack.
// But if the stack yop are copying in to the main stack is bigger than the currently executing stack, the `memcpy` will clobber the current stack frame (including the context argument). So we use `alloca` to push the current stack frame *beyond* the stack we are about to copy in. This ensures the current stack frame in `coroutine_restore_stack_padded` remains valid for calling `longjmp`.
__attribute__ ( ( noreturn ) )
void coroutine_restore_stack ( struct coroutine_context * context ) {
void * stack_pointer = & stack_pointer ;
void * buffer = NULL ;
ssize_t offset = 0 ;
// We must ensure that the next stack frame is BEYOND the stack we are restoring:
if ( stack_pointer < context - > base ) {
offset = ( char * ) stack_pointer - ( ( char * ) context - > base - context - > used ) + GAP ;
if ( offset > 0 ) buffer = alloca ( offset ) ;
} else {
offset = ( ( char * ) context - > base + context - > used ) - ( char * ) stack_pointer + GAP ;
if ( offset > 0 ) buffer = alloca ( offset ) ;
}
assert ( context - > used > 0 ) ;
coroutine_restore_stack_padded ( context , buffer ) ;
}
struct coroutine_context * coroutine_transfer ( struct coroutine_context * current , struct coroutine_context * target )
{
struct coroutine_context * previous = target - > from ;
// In theory, either this condition holds true, or we should assign the base address to target:
assert ( current - > base = = target - > base ) ;
// If you are trying to copy the coroutine to a different thread
// target->base = current->base
target - > from = current ;
assert ( current ! = target ) ;
// It's possible to come here, even thought the current fiber has been terminated. We are never going to return so we don't bother saving the stack.
if ( current - > stack ) {
if ( coroutine_save_stack ( current ) = = 0 ) {
coroutine_restore_stack ( target ) ;
}
} else {
coroutine_restore_stack ( target ) ;
}
target - > from = previous ;
return target ;
}