summaryrefslogtreecommitdiff
path: root/coroutine/copy/Context.c
diff options
context:
space:
mode:
Diffstat (limited to 'coroutine/copy/Context.c')
-rw-r--r--coroutine/copy/Context.c141
1 files changed, 141 insertions, 0 deletions
diff --git a/coroutine/copy/Context.c b/coroutine/copy/Context.c
new file mode 100644
index 0000000000..a1b8a71200
--- /dev/null
+++ b/coroutine/copy/Context.c
@@ -0,0 +1,141 @@
+/*
+ * 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. All rights reserved.
+*/
+
+#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;
+}