ruby--ruby/coroutine/copy/Context.c

142 lines
4.6 KiB
C

/*
* This file is part of the "Coroutine" project and released under the MIT License.
*
* Created by Samuel Williams on 24/6/2019.
* Copyright, 2019, by Samuel Williams.
*/
#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;
}