v / vlib / goroutines / context_nix.h
69 lines · 60 sloc · 2.27 KB · 726559c029ba7e9171e3f7cb5dcfd204ee42f7e3
Raw
1#ifndef GOROUTINES_CONTEXT_NIX_H
2#define GOROUTINES_CONTEXT_NIX_H
3
4#include <ucontext.h>
5#include <stdint.h>
6#include <stdlib.h>
7
8// Extended context: ucontext_t + entry function info for first-time starts.
9typedef struct {
10 ucontext_t uctx;
11 void (*entry_fn)(void *);
12 void *entry_arg;
13} goroutine_ctx_t;
14
15// Thread-local pointer to the target goroutine context.
16// Set just before swapcontext/setcontext so the trampoline can find its fn/arg.
17#if defined(_WIN32) || defined(_WIN64)
18static __declspec(thread) goroutine_ctx_t *_goroutine_start_ctx = NULL;
19#else
20static _Thread_local goroutine_ctx_t *_goroutine_start_ctx = NULL;
21#endif
22
23static void *goroutines_context_alloc(void) {
24 return calloc(1, sizeof(goroutine_ctx_t));
25}
26
27// Zero-argument trampoline. Reads fn/arg from the TLS-stored context.
28// This avoids passing arguments through makecontext, which is broken on macOS ARM64.
29static void goroutines__context_trampoline(void) {
30 goroutine_ctx_t *gctx = _goroutine_start_ctx;
31 _goroutine_start_ctx = NULL;
32 void (*fn)(void *) = gctx->entry_fn;
33 void *arg = gctx->entry_arg;
34 // Clear so future context switches to this goroutine don't re-trigger
35 gctx->entry_fn = NULL;
36 gctx->entry_arg = NULL;
37 fn(arg);
38}
39
40static void goroutines_context_init(void *ctx_buf, void *stack, int stack_size, void *entry_fn, void *arg) {
41 goroutine_ctx_t *gctx = (goroutine_ctx_t *)ctx_buf;
42 gctx->entry_fn = (void (*)(void *))entry_fn;
43 gctx->entry_arg = arg;
44 getcontext(&gctx->uctx);
45 gctx->uctx.uc_stack.ss_sp = stack;
46 gctx->uctx.uc_stack.ss_size = (size_t)stack_size;
47 gctx->uctx.uc_link = NULL;
48 makecontext(&gctx->uctx, (void (*)())goroutines__context_trampoline, 0);
49}
50
51static void goroutines_context_switch(void *from_buf, void *to_buf) {
52 goroutine_ctx_t *from = (goroutine_ctx_t *)from_buf;
53 goroutine_ctx_t *to = (goroutine_ctx_t *)to_buf;
54 // If switching to a goroutine that hasn't started yet, set TLS for trampoline
55 if (to->entry_fn != NULL) {
56 _goroutine_start_ctx = to;
57 }
58 swapcontext(&from->uctx, &to->uctx);
59}
60
61static void goroutines_context_set(void *to_buf) {
62 goroutine_ctx_t *to = (goroutine_ctx_t *)to_buf;
63 if (to->entry_fn != NULL) {
64 _goroutine_start_ctx = to;
65 }
66 setcontext(&to->uctx);
67}
68
69#endif
70