summaryrefslogtreecommitdiff
path: root/coroutine/copy/Context.c
blob: a1b8a7120037fea45796c9509876b2c910352ff0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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;
}