riscv64.c (8174B)
1 /* 2 * lib/coro/riscv64.c -- RISC-V 64-bit (LP64D) implementations of 3 * setjmp / longjmp (<setjmp.h>) 4 * __kit_coro_ctx_init / __kit_coro_switch / trampoline (<kit/coro.h>) 5 * 6 * RISC-V LP64D callee-saved set: 7 * ra (x1) -- saved manually so longjmp/__kit_coro_switch can 8 * "return" to the original call site 9 * sp (x2) 10 * s0-s11 (x8-x9, x18-x27) 11 * fs0-fs11 (f8-f9, f18-f27) 12 * 13 * Layout (matches xOS rv64 tick_coro_ctx): 14 * 15 * regs[0]: ra 16 * regs[1]: sp 17 * regs[2..13]: s0-s11 18 * fp_regs[0..11]: fs0-fs11 (offset 112) 19 * 20 * sizeof = 14*8 + 12*8 = 208, 16-byte aligned. Fits in the 256-byte 21 * storage carved out by jmp_buf and coro_ctx. 22 * 23 * setjmp(env) a0=env 24 * longjmp(env, val) a0=env, a1=val 25 * __kit_coro_switch(f, t, val) a0=from, a1=to, a2=val 26 * 27 * Value-passing trick: the destination context "returns" via 28 * ld ra, 0(a1); ... ret 29 * where `ret` is `jalr x0, 0(ra)`. By moving the value into a0 just 30 * before `ret`, both a fresh trampoline (entry(value)) and a previously 31 * suspended __kit_coro_switch (= the value its switch call returned) see it 32 * as the a0 return register. 33 * 34 * SAVE_/RESTORE_ are C string-concat macros so the same byte sequence 35 * is emitted in setjmp, longjmp, and __kit_coro_switch without duplication. 36 * 37 * Symbol naming uses __USER_LABEL_PREFIX__ so labels match the C 38 * compiler's call-site mangling (empty on RISC-V ELF). 39 */ 40 41 #include <kit/coro.h> 42 #include <setjmp.h> 43 #include <stddef.h> 44 #include <stdint.h> 45 46 struct __kit_riscv64_ctx { 47 uintptr_t regs[14]; 48 uint64_t fp_regs[12]; 49 } __attribute__((aligned(16))); 50 51 _Static_assert(sizeof(struct __kit_riscv64_ctx) == 208, "layout"); 52 _Static_assert(_Alignof(struct __kit_riscv64_ctx) == 16, "align"); 53 _Static_assert(offsetof(struct __kit_riscv64_ctx, fp_regs) == 112, "fp off"); 54 _Static_assert(sizeof(struct __kit_riscv64_ctx) <= sizeof(coro_ctx), 55 "fits coro_ctx"); 56 _Static_assert(sizeof(struct __kit_riscv64_ctx) <= sizeof(jmp_buf), 57 "fits jmp_buf"); 58 _Static_assert(_Alignof(coro_ctx) >= _Alignof(struct __kit_riscv64_ctx), 59 "align coro_ctx"); 60 61 extern void __kit_coro_trampoline(void); 62 63 void __kit_coro_ctx_init(coro_ctx* ctx, void* stack_base, size_t stack_len, 64 void (*entry)(uintptr_t)) { 65 struct __kit_riscv64_ctx* c = (struct __kit_riscv64_ctx*)ctx; 66 67 /* RISC-V stacks grow down; align top to 16. */ 68 uintptr_t top = (uintptr_t)stack_base + stack_len; 69 top &= ~(uintptr_t)(CORO_STACK_ALIGN - 1); 70 71 for (size_t i = 0; i < sizeof(*c) / sizeof(uintptr_t); ++i) 72 ((uintptr_t*)c)[i] = 0; 73 74 c->regs[0] = (uintptr_t)__kit_coro_trampoline; /* ra */ 75 c->regs[1] = top; /* sp */ 76 c->regs[2] = (uintptr_t)entry; /* s0 -- entry fn */ 77 } 78 79 #define STR_(x) #x 80 #define STR(x) STR_(x) 81 #define SYM(n) STR(__USER_LABEL_PREFIX__) #n 82 83 /* Save callee-saved state into [reg]. reg is a register name string, 84 e.g. "a0". Emits straight-line sd/fsd; no scratch register needed. */ 85 #define SAVE_INTO(reg) \ 86 " sd ra, 0(" reg \ 87 ")\n" \ 88 " sd sp, 8(" reg \ 89 ")\n" \ 90 " sd s0, 16(" reg \ 91 ")\n" \ 92 " sd s1, 24(" reg \ 93 ")\n" \ 94 " sd s2, 32(" reg \ 95 ")\n" \ 96 " sd s3, 40(" reg \ 97 ")\n" \ 98 " sd s4, 48(" reg \ 99 ")\n" \ 100 " sd s5, 56(" reg \ 101 ")\n" \ 102 " sd s6, 64(" reg \ 103 ")\n" \ 104 " sd s7, 72(" reg \ 105 ")\n" \ 106 " sd s8, 80(" reg \ 107 ")\n" \ 108 " sd s9, 88(" reg \ 109 ")\n" \ 110 " sd s10, 96(" reg \ 111 ")\n" \ 112 " sd s11, 104(" reg \ 113 ")\n" \ 114 " fsd fs0, 112(" reg \ 115 ")\n" \ 116 " fsd fs1, 120(" reg \ 117 ")\n" \ 118 " fsd fs2, 128(" reg \ 119 ")\n" \ 120 " fsd fs3, 136(" reg \ 121 ")\n" \ 122 " fsd fs4, 144(" reg \ 123 ")\n" \ 124 " fsd fs5, 152(" reg \ 125 ")\n" \ 126 " fsd fs6, 160(" reg \ 127 ")\n" \ 128 " fsd fs7, 168(" reg \ 129 ")\n" \ 130 " fsd fs8, 176(" reg \ 131 ")\n" \ 132 " fsd fs9, 184(" reg \ 133 ")\n" \ 134 " fsd fs10, 192(" reg \ 135 ")\n" \ 136 " fsd fs11, 200(" reg ")\n" 137 138 /* Restore callee-saved state from [reg]. */ 139 #define RESTORE_FROM(reg) \ 140 " fld fs0, 112(" reg \ 141 ")\n" \ 142 " fld fs1, 120(" reg \ 143 ")\n" \ 144 " fld fs2, 128(" reg \ 145 ")\n" \ 146 " fld fs3, 136(" reg \ 147 ")\n" \ 148 " fld fs4, 144(" reg \ 149 ")\n" \ 150 " fld fs5, 152(" reg \ 151 ")\n" \ 152 " fld fs6, 160(" reg \ 153 ")\n" \ 154 " fld fs7, 168(" reg \ 155 ")\n" \ 156 " fld fs8, 176(" reg \ 157 ")\n" \ 158 " fld fs9, 184(" reg \ 159 ")\n" \ 160 " fld fs10, 192(" reg \ 161 ")\n" \ 162 " fld fs11, 200(" reg \ 163 ")\n" \ 164 " ld ra, 0(" reg \ 165 ")\n" \ 166 " ld sp, 8(" reg \ 167 ")\n" \ 168 " ld s0, 16(" reg \ 169 ")\n" \ 170 " ld s1, 24(" reg \ 171 ")\n" \ 172 " ld s2, 32(" reg \ 173 ")\n" \ 174 " ld s3, 40(" reg \ 175 ")\n" \ 176 " ld s4, 48(" reg \ 177 ")\n" \ 178 " ld s5, 56(" reg \ 179 ")\n" \ 180 " ld s6, 64(" reg \ 181 ")\n" \ 182 " ld s7, 72(" reg \ 183 ")\n" \ 184 " ld s8, 80(" reg \ 185 ")\n" \ 186 " ld s9, 88(" reg \ 187 ")\n" \ 188 " ld s10, 96(" reg \ 189 ")\n" \ 190 " ld s11, 104(" reg ")\n" 191 192 __asm__ ( 193 ".text\n" 194 ".align 2\n" 195 196 /* setjmp(env) -- env in a0. ra at call time is the caller's return 197 address, which is exactly what longjmp must restore. */ 198 ".weak " SYM(setjmp) "\n" 199 ".type " SYM(setjmp) ", @function\n" 200 SYM(setjmp) ":\n" 201 SAVE_INTO("a0") 202 " li a0, 0\n" 203 " ret\n" 204 ".size " SYM(setjmp) ", .-" SYM(setjmp) "\n" 205 206 /* longjmp(env, val) -- env in a0, val in a1. 207 longjmp(_, 0) must deliver 1 (C11 7.13.2.1p4). Branch-free: 208 seqz t0, a1 -> t0 = (a1==0); a0 = a1 + t0. RESTORE_FROM 209 doesn't touch t0/a0/a1, so the seqz/add can run after it and 210 write a0 directly -- one fewer instruction than munging a1 211 first and mv'ing later. */ 212 ".weak " SYM(longjmp) "\n" 213 ".type " SYM(longjmp) ", @function\n" 214 SYM(longjmp) ":\n" 215 RESTORE_FROM("a0") 216 " seqz t0, a1\n" 217 " add a0, a1, t0\n" 218 " ret\n" 219 ".size " SYM(longjmp) ", .-" SYM(longjmp) "\n" 220 221 /* __kit_coro_switch(from, to, value) -- a0=from, a1=to, a2=value. 222 Save into [a0], restore from [a1] (which clobbers a0 and a1's 223 roles -- ra/sp/s* are loaded from the to-context), then deliver 224 value in a0 just before ret. */ 225 ".globl " SYM(__kit_coro_switch) "\n" 226 ".type " SYM(__kit_coro_switch) ", @function\n" 227 SYM(__kit_coro_switch) ":\n" 228 SAVE_INTO("a0") 229 RESTORE_FROM("a1") 230 " mv a0, a2\n" 231 " ret\n" 232 ".size " SYM(__kit_coro_switch) ", .-" SYM(__kit_coro_switch) "\n" 233 234 /* __kit_coro_trampoline -- on first entry: a0=value (delivered), 235 s0=entry fn (set by __kit_coro_ctx_init), sp aligned to 16. ebreak if entry 236 returns. */ 237 ".globl " SYM(__kit_coro_trampoline) "\n" 238 ".type " SYM(__kit_coro_trampoline) ", @function\n" 239 SYM(__kit_coro_trampoline) ":\n" 240 " jalr s0\n" 241 " ebreak\n" 242 ".size " SYM(__kit_coro_trampoline) ", .-" SYM(__kit_coro_trampoline) "\n" 243 244 ".section .note.GNU-stack,\"\",%progbits\n" 245 );