mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
141 lines
4.6 KiB
C
141 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;
|
|
}
|