Ruby: coroutine/copy/Context.c Source File

Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)

Context.c
Go to the documentation of this file.
1/*
2 * This file is part of the "Coroutine" project and released under the MIT License.
3 *
4 * Created by Samuel Williams on 24/6/2019.
5 * Copyright, 2019, by Samuel Williams.
6*/
7
8#include "Context.h"
9
10#include <stdint.h>
11
12// http://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html
13#ifndef __GNUC__
14#define __asm__ asm
15#endif
16
17#if defined(__sparc)
18 __attribute__((noinline))
19// https://marc.info/?l=linux-sparc&m=131914569320660&w=2
20static void coroutine_flush_register_windows(void) {
21 __asm__
22#ifdef __GNUC__
23 __volatile__
24#endif
25#if defined(__sparcv9) || defined(__sparc_v9__) || defined(__arch64__)
26#ifdef __GNUC__
27 ("flushw" : : : "%o7")
28#else
29 ("flushw")
30#endif
31#else
32 ("ta 0x03")
33#endif
34 ;
35}
36#else
37static void coroutine_flush_register_windows(void) {}
38#endif
39
40 __attribute__((noinline))
41void *coroutine_stack_pointer(void) {
42 return (void*)(
43 (char*)__builtin_frame_address(0)
44 );
45}
46
47// Save the current stack to a private area. It is likely that when restoring the stack, this stack frame will be incomplete. But that is acceptable since the previous stack frame which called `setjmp` should be correctly restored.
48 __attribute__((noinline))
49int coroutine_save_stack_1(struct coroutine_context * context) {
50 assert(context->stack);
51 assert(context->base);
52
53 void *stack_pointer = coroutine_stack_pointer();
54
55 // At this point, you may need to ensure on architectures that use register windows, that all registers are flushed to the stack, otherwise the copy of the stack will not contain the valid registers:
56 coroutine_flush_register_windows();
57
58 // Save stack to private area:
59 if (stack_pointer < context->base) {
60 size_t size = (char*)context->base - (char*)stack_pointer;
61 assert(size <= context->size);
62
63 memcpy(context->stack, stack_pointer, size);
64 context->used = size;
65 } else {
66 size_t size = (char*)stack_pointer - (char*)context->base;
67 assert(size <= context->size);
68
69 memcpy(context->stack, context->base, size);
70 context->used = size;
71 }
72
73 // Initialized:
74 return 0;
75}
76
77// Copy the current stack to a private memory buffer.
78 int coroutine_save_stack(struct coroutine_context * context) {
79 if (_setjmp(context->state)) {
80 // Restored.
81 return 1;
82 }
83
84 // We need to invoke the memory copy from one stack frame deeper than the one that calls setjmp. That is because if you don't do this, the setjmp might be restored into an invalid stack frame (truncated, etc):
85 return coroutine_save_stack_1(context);
86}
87
88 __attribute__((noreturn, noinline))
89void coroutine_restore_stack_padded(struct coroutine_context *context, void * buffer) {
90 void *stack_pointer = coroutine_stack_pointer();
91
92 assert(context->base);
93
94 // At this point, you may need to ensure on architectures that use register windows, that all registers are flushed to the stack, otherwise when we copy in the new stack, the registers would not be updated:
95 coroutine_flush_register_windows();
96
97 // Restore stack from private area:
98 if (stack_pointer < context->base) {
99 void * bottom = (char*)context->base - context->used;
100 assert(bottom > stack_pointer);
101
102 memcpy(bottom, context->stack, context->used);
103 } else {
104 void * top = (char*)context->base + context->used;
105 assert(top < stack_pointer);
106
107 memcpy(context->base, context->stack, context->used);
108 }
109
110 // Restore registers. The `buffer` is to force the compiler NOT to elide he buffer and `alloca`:
111 _longjmp(context->state, (int)(1 | (intptr_t)buffer));
112}
113
114// In order to swap between coroutines, we need to swap the stack and registers.
115// `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.
116// 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`.
117 __attribute__((noreturn))
118void coroutine_restore_stack(struct coroutine_context *context) {
119 void *stack_pointer = coroutine_stack_pointer();
120 void *buffer = NULL;
121
122 // We must ensure that the next stack frame is BEYOND the stack we are restoring:
123 if (stack_pointer < context->base) {
124 intptr_t offset = (intptr_t)stack_pointer - ((intptr_t)context->base - context->used);
125 if (offset > 0) buffer = alloca(offset);
126 } else {
127 intptr_t offset = ((intptr_t)context->base + context->used) - (intptr_t)stack_pointer;
128 if (offset > 0) buffer = alloca(offset);
129 }
130
131 assert(context->used > 0);
132
133 coroutine_restore_stack_padded(context, buffer);
134}
135
136 struct coroutine_context *coroutine_transfer(struct coroutine_context *current, struct coroutine_context *target)
137{
138 struct coroutine_context *previous = target->from;
139
140 // In theory, either this condition holds true, or we should assign the base address to target:
141 assert(current->base == target->base);
142 // If you are trying to copy the coroutine to a different thread
143 // target->base = current->base
144
145 target->from = current;
146
147 assert(current != target);
148
149 // 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.
150
151 if (current->stack) {
152 if (coroutine_save_stack(current) == 0) {
153 coroutine_restore_stack(target);
154 }
155 } else {
156 coroutine_restore_stack(target);
157 }
158
159 target->from = previous;
160
161 return target;
162}
coroutine_save_stack
int coroutine_save_stack(struct coroutine_context *context)
Definition: Context.c:78
coroutine_transfer
struct coroutine_context * coroutine_transfer(struct coroutine_context *current, struct coroutine_context *target)
Definition: Context.c:136
coroutine_restore_stack
COROUTINE coroutine_restore_stack(struct coroutine_context *context)
assert
#define assert(x)
Definition: dlmalloc.c:1176
alloca
#define alloca
Definition: ffi_common.h:27
memcpy
#define memcpy(d, s, n)
Definition: ffi_common.h:55
__attribute__
void *PTR64 __attribute__((mode(DI)))
Definition: ffi.c:41
size
voidpf void uLong size
Definition: ioapi.h:138
offset
voidpf uLong offset
Definition: ioapi.h:144
top
unsigned int top
Definition: nkf.c:4323
NULL
#define NULL
Definition: regenc.h:69
coroutine_context
Definition: Context.h:20
coroutine_context::from
struct coroutine_context * from
Definition: Context.h:41
coroutine_context::used
size_t used
Definition: Context.h:34
coroutine_context::stack
void * stack
Definition: Context.h:33
coroutine_context::state
jmp_buf state
Definition: Context.h:39
coroutine_context::base
void * base
Definition: Context.h:37
intptr_t
int intptr_t
Definition: win32.h:90
Context.h