Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
switch_x86_64.S
Go to the documentation of this file.
1/*
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/* x86-64 fiber switch and trampoline. On another architecture this assembles
20 * to nothing and the sibling switch_<arch>.S supplies srn_fiber_swap/srn_fiber_trampoline
21 * instead. The whole runtime source set can then be handed to a cross assembler and only the
22 * matching architecture emits symbols.
23 */
24#if defined(__x86_64__)
25
26 .text
27
28/* Notes to myself:
29 * Some of the instructions that I'm sure I'll forget:
30 * subq $n,%rsp move rsp down n bytes reserve stack space / realign
31 * addq $n,%rsp move rsp up n bytes release that space
32 * stmxcsr m store MXCSR (32-bit) to m SSE rounding/exception ctl+status
33 * ldmxcsr m load MXCSR from m fnstcw m store x87 control word
34 * (16-bit) to m (n = no-wait variant)
35 * fldcw m load x87 control word from m
36 * callq *%r13 push return addr, jump *r13 indirect -> position independent
37 * ud2 undefined instruction -> #UD trap; marks "unreachable"
38 *
39 * Floating-point control state across a fiber switch.
40 *
41 * x86-64 has two FP units, each with a control register whose *mode* bits
42 * govern how every subsequent FP instruction behaves:
43 * - MXCSR (SSE, 32-bit): rounding mode, the six exception masks, FTZ/DAZ.
44 * SSE carries all scalar float/double arithmetic on x86-64.
45 * - x87 control word (16-bit): rounding mode, precision, exception masks.
46 *
47 * These modes are part of a fiber's state, not scratch. If a fiber selects,
48 * say, round-toward-zero or unmasks divide-by-zero, is switched away from,
49 * and later resumed, it must find its control words intact -- otherwise it
50 * silently runs with whatever another fiber left set: a quiet numeric
51 * correctness bug, no crash. So the switch saves the outgoing fiber's control
52 * words and restores the incoming one's, just like the callee-saved GP regs.
53 *
54 * The System V AMD64 ABI requires this: the *control* bits of MXCSR and the
55 * x87 control word are callee-saved (a function must preserve them), so a
56 * routine that returns into a different fiber must preserve them too. The FP
57 * *data* registers (xmm0-15, st0-7) and MXCSR *status* bits are caller-saved,
58 * so the compiler spills them around the call and the switch leaves them be.
59 *
60 * System V AMD64 ABI: https://gitlab.com/x86-psABIs/x86-64-ABI
61 * Bit layouts of MXCSR and the x87 control word Intel 64 and IA-32 Software
62 * Developer's Manual:
63 * https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
64 *
65 * Saved/loaded with stmxcsr/ldmxcsr (MXCSR) and fnstcw/fldcw (x87 control
66 * word, the `n` is the no-wait store). srn_fiber_ctx_make seeds a fresh fiber
67 * with the standard startup environment -- MXCSR 0x1F80 (all exceptions
68 * masked, round-to-nearest, FTZ/DAZ off) and x87 control word 0x037F (all
69 * masked, extended precision, round-to-nearest) -- so a new fiber begins like
70 * a freshly started C program.
71 */
72
73/* Saves the current callee-saved state on the current stack, stores rsp into
74 * *from, loads rsp from *to, restores, and returns onto *to's stack. The frame
75 * laid down here is the exact contract srn_fiber_ctx_make reconstructs. */
76 .globl srn_fiber_swap
77 .type srn_fiber_swap, @function
78/* void srn_fiber_swap(srn_fiber_ctx_t *from, srn_fiber_ctx_t *to); */
79srn_fiber_swap:
80 /* Save the outgoing fiber's callee-saved general-purpose registers onto
81 * its current stack. The push order must be the exact reverse of the pops
82 * below, and it is the layout srn_fiber_ctx_make fabricates for a fresh fiber */
83 pushq %rbp
84 pushq %rbx
85 pushq %r12
86 pushq %r13
87 pushq %r14
88 pushq %r15
89
90 /* Also callee-saved per the ABI: the SSE control/status word (MXCSR) and
91 * the x87 FPU control word. Reserve a 16-byte slot and stash both --
92 * stmxcsr writes 4 bytes at %rsp+0, fnstcw writes 2 bytes at %rsp+8. */
93 subq $16, %rsp /* MXCSR (4 bytes) + x87 control word (2 bytes) */
94 stmxcsr (%rsp)
95 fnstcw 8(%rsp)
96
97 /* The switch. After the pushes, %rsp points at the bottom of the outgoing
98 * fiber's saved frame -- that one value captures the entire context, so
99 * store it into *from. Then load the incoming fiber's saved sp from *to;
100 * from this instruction on we are running on the new stack. */
101 movq %rsp, (%rdi) /* from->sp = rsp */
102 movq (%rsi), %rsp /* rsp = to->sp */
103
104 /* Restore the incoming fiber in mirror order: FP control words, drop the
105 * 16-byte slot, then pop the GP registers in reverse of the push. For a
106 * fiber suspended mid-flight this rebuilds exactly what it had; for a fresh
107 * fiber these are the defaults/zeros srn_fiber_ctx_make wrote. */
108 ldmxcsr (%rsp)
109 fldcw 8(%rsp)
110 addq $16, %rsp
111 popq %r15
112 popq %r14
113 popq %r13
114 popq %r12
115 popq %rbx
116 popq %rbp
117
118 /* Return onto the incoming stack. `ret` pops the return address sitting
119 * just above the registers we restored:
120 * - a fiber suspended inside an earlier srn_fiber_swap returns to the
121 * instruction after that call (it simply resumes);
122 * - a brand-new fiber returns into srn_fiber_trampoline, the address
123 * srn_fiber_ctx_make planted in the return-address slot. */
124 ret
125 .size srn_fiber_swap, .-srn_fiber_swap
126
127/* srn_fiber_trampoline — first instruction a fresh fiber runs (reached via the
128 * fake return address). srn_fiber_ctx_make seeds r13 = fn, r12 = arg. Move arg
129 * into the first C argument, realign the stack for the call, and invoke fn(arg).
130 * fn must never return; trap if it does. The call is register-indirect, so this
131 * is position independent. */
132 .globl srn_fiber_trampoline
133 .type srn_fiber_trampoline, @function
134srn_fiber_trampoline:
135 /* Move the seeded argument into the first C argument register. */
136 movq %r12, %rdi
137
138 /* Alignment: the ABI wants %rsp ≡ 0 (mod 16) at a `call`. ctx_make made the
139 * return-address slot 16-aligned, so after the `ret` that landed us here
140 * %rsp ≡ 8 (mod 16) -- exactly the state at a normal function entry.
141 * Subtract 8 to realign before calling out. */
142 subq $8, %rsp
143
144 /* Call entry(arg). Register-indirect, so this needs no relocation and stays
145 * position independent. entry must never return -- it is expected to
146 * srn_fiber_swap away when the fiber yields or finishes. */
147 callq *%r13
148
149 /* Unreachable: if entry returns there is no frame beneath us, so trap
150 * rather than fall off the bottom of the stack. */
151 ud2
152 .size srn_fiber_trampoline, .-srn_fiber_trampoline
153
154 /* This object needs no executable stack; say so to silence the linker. */
155 .section .note.GNU-stack,"",@progbits
156
157#endif /* __x86_64__ */