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
/**
* Cem Runtime - Context Switching (C implementation)
*
* This file contains the C portion of context switching.
* Assembly implementations are in context_<arch>.s
*/
#include "context.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
/**
* Initialize a context for a new strand
*
* This sets up the stack and registers so that when we switch to this
* context, it will start executing `func`. When `func` returns, control
* will pass to `return_func`.
*/
void cem_makecontext(cem_context_t *ctx, void *stack_base, size_t stack_size,
void (*func)(void), void (*return_func)(void)) {
// Validate inputs
assert(ctx != NULL && "context pointer cannot be NULL");
assert(stack_base != NULL && "stack base pointer cannot be NULL");
assert(stack_size > 0 &&
"stack size must be positive (for alignment safety)");
assert(stack_size >= CEM_MIN_STACK_SIZE &&
"stack size must be at least CEM_MIN_STACK_SIZE for safe execution");
assert(func != NULL && "function pointer cannot be NULL");
// Zero out the context
memset(ctx, 0, sizeof(cem_context_t));
#ifdef CEM_ARCH_ARM64
// ARM64: Stack grows downward (from high address to low address)
// stack_base is the LOW address of the allocated memory
// stack_top (high address) is where the stack pointer starts
uintptr_t stack_top = (uintptr_t)stack_base + stack_size;
// Align to 16 bytes (required by ARM64 ABI)
stack_top &= ~15ULL;
ctx->sp = stack_top;
// Set PC (stored in x30/LR) to the function to execute
// When we swapcontext, it will jump to this address
ctx->x30 = (uint64_t)func;
// Set frame pointer to stack top (no frame yet)
ctx->x29 = stack_top;
// NOTE: return_func is intentionally unused in the current implementation
//
// SAFETY: This is safe because:
// 1. All strands are created via strand_spawn() in scheduler.c
// 2. strand_spawn() ALWAYS uses strand_entry_trampoline as the entry point
// 3. The trampoline calls the actual strand function and handles returns
// 4. When a strand function returns, the trampoline:
// - Sets strand->state = STRAND_COMPLETED
// - Swaps back to scheduler_context
// - The scheduler cleans up the strand
//
// DEPENDENCY: This implementation requires that cem_makecontext is ONLY
// called from strand_spawn() with strand_entry_trampoline. Direct calls
// with arbitrary functions would need return_func to be implemented.
//
// FUTURE: If we want to support general-purpose context switching outside
// the scheduler, we would need to implement return_func properly, perhaps
// by storing it in a callee-saved register (e.g., x19) and having the
// assembly check and jump to it when func returns.
(void)return_func; // Unused - see safety note above
#elif defined(CEM_ARCH_X86_64)
// x86-64: Stack grows downward (from high address to low address)
// stack_base is the LOW address of the allocated memory
// stack_top (high address) is where the stack pointer starts
uintptr_t stack_top = (uintptr_t)stack_base + stack_size;
// Align to 16 bytes (required by x86-64 ABI)
stack_top &= ~15ULL;
// Push the function address onto the stack
// This will be the return address that 'ret' will jump to
stack_top -= sizeof(void *);
*(void **)stack_top = (void *)func;
// Note: Stack is now misaligned by 8 bytes (as expected after 'call')
// This matches what swapcontext expects
ctx->rsp = stack_top;
// Set frame pointer to stack top (no frame yet)
ctx->rbp = stack_top;
// Initialize MXCSR to default value (0x1F80)
// This enables all floating point exceptions masked
ctx->mxcsr = 0x1F80;
// Zero out other registers
ctx->rbx = 0;
ctx->r12 = 0;
ctx->r13 = 0;
ctx->r14 = 0;
ctx->r15 = 0;
// NOTE: return_func is intentionally unused (same reasoning as ARM64)
//
// SAFETY: This is safe because:
// 1. All strands are created via strand_spawn() in scheduler.c
// 2. strand_spawn() ALWAYS uses strand_entry_trampoline as the entry point
// 3. The trampoline calls the actual strand function and handles returns
// 4. When a strand function returns, the trampoline:
// - Sets strand->state = STRAND_COMPLETED
// - Swaps back to scheduler_context
// - The scheduler cleans up the strand
//
// THREAD-SAFETY NOTE for future work-stealing:
// This initialization is NOT thread-safe by itself, but that's fine because:
// - cem_makecontext() is only called during strand_spawn()
// - strand_spawn() must be synchronized by the scheduler
// - Once initialized, the context can be safely migrated between threads
// - The context contains no thread-local state (no TLS pointers, etc.)
(void)return_func; // Unused - see safety note above
#endif
}