Ruby 4.1.0dev (2026-09-21 revision 61de3dd727146cfcf24e8051595bb2fb842f37aa)
Context.h
1#ifndef COROUTINE_AMD64_CONTEXT_H
2#define COROUTINE_AMD64_CONTEXT_H 1
3
4/*
5 * This file is part of the "Coroutine" project and released under the MIT License.
6 *
7 * Created by Samuel Williams on 10/5/2018.
8 * Copyright, 2018, by Samuel Williams.
9*/
10
11#pragma once
12
13#include <assert.h>
14#include <stddef.h>
15#include <stdint.h>
16#include <string.h>
17
18#define COROUTINE __attribute__((noreturn)) void
19
20enum {COROUTINE_REGISTERS = 6};
21
22#if defined(__SANITIZE_ADDRESS__)
23 #define COROUTINE_SANITIZE_ADDRESS
24#elif defined(__has_feature)
25 #if __has_feature(address_sanitizer)
26 #define COROUTINE_SANITIZE_ADDRESS
27 #endif
28#endif
29
30#if defined(COROUTINE_SANITIZE_ADDRESS)
31#include <sanitizer/common_interface_defs.h>
32#include <sanitizer/asan_interface.h>
33#endif
34
35#if defined(__SANITIZE_THREAD__)
36 #define COROUTINE_SANITIZE_THREAD
37#elif defined(__has_feature)
38 #if __has_feature(thread_sanitizer)
39 #define COROUTINE_SANITIZE_THREAD
40 #endif
41#endif
42
43#if defined(COROUTINE_SANITIZE_THREAD)
44/* ThreadSanitizer cannot follow a userspace stack switch on its own: its
45 * per-OS-thread shadow stack must be handed to the destination fiber on every
46 * coroutine switch via the fiber API, otherwise it leaks the shadow stack
47 * across switches and eventually faults inside libtsan. */
48#include <sanitizer/tsan_interface.h>
49#endif
50
51#if defined(__linux__) && defined(__CET__) && (__CET__ & 0x02) != 0
52#define COROUTINE_SHADOW_STACK
53
54#include <stdlib.h>
55#include <sys/mman.h>
56#include <sys/syscall.h>
57#include <unistd.h>
58
59#ifndef ARCH_SHSTK_STATUS
60#define ARCH_SHSTK_STATUS 0x5005
61#endif
62
63#ifndef ARCH_SHSTK_SHSTK
64#define ARCH_SHSTK_SHSTK (1UL << 0)
65#endif
66
67#ifndef SYS_map_shadow_stack
68#ifdef __NR_map_shadow_stack
69#define SYS_map_shadow_stack __NR_map_shadow_stack
70#else
71#define SYS_map_shadow_stack 453
72#endif
73#endif
74
75#ifndef SHADOW_STACK_SET_TOKEN
76#define SHADOW_STACK_SET_TOKEN (1UL << 0)
77#endif
78
79void *coroutine_initialize_shadow_stack(void *shadow_stack_pointer);
80COROUTINE coroutine_start_trampoline(void);
81
82static inline int coroutine_shadow_stack_enabled(void)
83{
84 unsigned long features = 0;
85
86 if (syscall(SYS_arch_prctl, ARCH_SHSTK_STATUS, &features) != 0) {
87 return 0;
88 }
89
90 return (features & ARCH_SHSTK_SHSTK) != 0;
91}
92
93static inline void *coroutine_allocate_shadow_stack(size_t size)
94{
95 void *base = (void *)syscall(
96 SYS_map_shadow_stack,
97 0,
98 size,
99 SHADOW_STACK_SET_TOKEN
100 );
101
102 if (base == MAP_FAILED) {
103 abort();
104 }
105
106 return base;
107}
108#endif
109
111{
112 void **stack_pointer;
113 void *argument;
114
115#if defined(COROUTINE_SANITIZE_ADDRESS)
116 void *fake_stack;
117 void *stack_base;
118 size_t stack_size;
119#endif
120
121#if defined(COROUTINE_SANITIZE_THREAD)
122 void *tsan_fiber;
123 /* Whether we created tsan_fiber (via __tsan_create_fiber, must be
124 * destroyed) or borrowed it from __tsan_get_current_fiber (the OS thread's
125 * implicit fiber, owned by TSan; must not be destroyed). */
126 int tsan_fiber_owned;
127#endif
128
129#if defined(COROUTINE_SHADOW_STACK)
130 void *shadow_stack;
131 size_t shadow_stack_size;
132#endif
133};
134
135typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
136
137static inline void coroutine_initialize_main(struct coroutine_context * context) {
138 context->stack_pointer = NULL;
139
140#if defined(COROUTINE_SANITIZE_THREAD)
141 /* The OS thread's implicit (already running) fiber, owned by TSan. */
142 context->tsan_fiber = __tsan_get_current_fiber();
143 context->tsan_fiber_owned = 0;
144#endif
145
146#if defined(COROUTINE_SHADOW_STACK)
147 context->shadow_stack = NULL;
148 context->shadow_stack_size = 0;
149#endif
150}
151
152static inline void coroutine_initialize(
153 struct coroutine_context *context,
154 coroutine_start start,
155 void *stack,
156 size_t size
157) {
158 assert(start && stack && size >= 1024);
159
160#if defined(COROUTINE_SHADOW_STACK)
161 void *shadow_stack_pointer = NULL;
162 void *entry = (void *)(uintptr_t)start;
163#endif
164
165#if defined(COROUTINE_SANITIZE_ADDRESS)
166 context->fake_stack = NULL;
167 context->stack_base = stack;
168 context->stack_size = size;
169#endif
170
171#if defined(COROUTINE_SANITIZE_THREAD)
172 context->tsan_fiber = __tsan_create_fiber(0);
173 context->tsan_fiber_owned = 1;
174#endif
175
176#if defined(COROUTINE_SHADOW_STACK)
177 if (coroutine_shadow_stack_enabled()) {
178 size_t shadow_stack_size = (size + 7) & ~(size_t)7;
179
180 context->shadow_stack = coroutine_allocate_shadow_stack(shadow_stack_size);
181 context->shadow_stack_size = shadow_stack_size;
182 shadow_stack_pointer = coroutine_initialize_shadow_stack(
183 (char *)context->shadow_stack + shadow_stack_size
184 );
185 entry = (void *)(uintptr_t)coroutine_start_trampoline;
186 } else {
187 context->shadow_stack = NULL;
188 context->shadow_stack_size = 0;
189 }
190#endif
191
192 // Stack grows down. Force 16-byte alignment.
193 char * top = (char*)stack + size;
194 context->stack_pointer = (void**)((uintptr_t)top & ~0xF);
195
196 *--context->stack_pointer = NULL;
197#if defined(COROUTINE_SHADOW_STACK)
198 *--context->stack_pointer = entry;
199#else
200 *--context->stack_pointer = (void*)(uintptr_t)start;
201#endif
202
203 context->stack_pointer -= COROUTINE_REGISTERS;
204 memset(context->stack_pointer, 0, sizeof(void*) * COROUTINE_REGISTERS);
205
206#if defined(COROUTINE_SHADOW_STACK)
207 if (shadow_stack_pointer) {
208 /* coroutine_start_trampoline jumps to the start function in r12. */
209 context->stack_pointer[3] = (void *)(uintptr_t)start;
210 }
211
212 *--context->stack_pointer = shadow_stack_pointer;
213#endif
214}
215
216struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target);
217
218static inline void coroutine_destroy(struct coroutine_context * context)
219{
220 context->stack_pointer = NULL;
221
222#if defined(COROUTINE_SANITIZE_THREAD)
223 /* Only destroy fibers we created. The borrowed __tsan_get_current_fiber()
224 * handle (the OS thread's implicit fiber) is owned by TSan; destroying it
225 * aborts libtsan (FiberDestroy -> ProcWire CheckFailed). */
226 if (context->tsan_fiber && context->tsan_fiber_owned) {
227 __tsan_destroy_fiber(context->tsan_fiber);
228 context->tsan_fiber = NULL;
229 context->tsan_fiber_owned = 0;
230 }
231#endif
232
233#if defined(COROUTINE_SHADOW_STACK)
234 if (context->shadow_stack) {
235 munmap(context->shadow_stack, context->shadow_stack_size);
236 context->shadow_stack = NULL;
237 }
238#endif
239}
240
241#endif /* COROUTINE_AMD64_CONTEXT_H */