kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

x86_64.c (5061B)


      1 /*
      2  * lib/coro/x86_64.c -- x86_64 System V ABI implementations of
      3  *   setjmp / longjmp                       (<setjmp.h>)
      4  *   __kit_coro_ctx_init / __kit_coro_switch / trampoline   (<kit/coro.h>)
      5  *
      6  * Callee-saved set on SysV: rbx, rbp, r12-r15. (No callee-saved xmm
      7  * regs -- those are MS-ABI specific; see x86_64_win.c.)
      8  *
      9  *   regs[0]:  rbx     regs[4]:  r14
     10  *   regs[1]:  rbp     regs[5]:  r15
     11  *   regs[2]:  r12     regs[6]:  rsp
     12  *   regs[3]:  r13     regs[7]:  rip
     13  *
     14  * sizeof = 64, 16-byte aligned.
     15  *
     16  *   setjmp(env)             %rdi=env
     17  *   longjmp(env, val)       %rdi=env, %esi=val
     18  *   __kit_coro_switch(f, t, val)  %rdi=from, %rsi=to, %rdx=val
     19  *
     20  * The "save rsp/rip" trick: at function entry, (%rsp) holds the
     21  * caller's return address (just pushed by `call`); 8(%rsp) is the
     22  * caller's pre-call rsp. Saving those two lets longjmp/__kit_coro_switch
     23  * "land" at the call site exactly as if the function had returned.
     24  */
     25 
     26 #include <kit/coro.h>
     27 #include <setjmp.h>
     28 #include <stddef.h>
     29 #include <stdint.h>
     30 
     31 struct __kit_x86_64_ctx {
     32   uintptr_t regs[8];
     33 } __attribute__((aligned(16)));
     34 
     35 _Static_assert(sizeof(struct __kit_x86_64_ctx) == 64, "layout");
     36 _Static_assert(_Alignof(struct __kit_x86_64_ctx) == 16, "align");
     37 _Static_assert(sizeof(struct __kit_x86_64_ctx) <= sizeof(coro_ctx),
     38                "fits coro_ctx");
     39 _Static_assert(sizeof(struct __kit_x86_64_ctx) <= sizeof(jmp_buf),
     40                "fits jmp_buf");
     41 _Static_assert(_Alignof(coro_ctx) >= _Alignof(struct __kit_x86_64_ctx),
     42                "align coro_ctx");
     43 
     44 extern void __kit_coro_trampoline(void);
     45 
     46 void __kit_coro_ctx_init(coro_ctx* ctx, void* stack_base, size_t stack_len,
     47                          void (*entry)(uintptr_t)) {
     48   struct __kit_x86_64_ctx* c = (struct __kit_x86_64_ctx*)ctx;
     49 
     50   /* x86_64 stacks grow down; align top to 16. */
     51   uintptr_t top = (uintptr_t)stack_base + stack_len;
     52   top &= ~(uintptr_t)(CORO_STACK_ALIGN - 1);
     53 
     54   for (size_t i = 0; i < sizeof(*c) / sizeof(uintptr_t); ++i)
     55     ((uintptr_t*)c)[i] = 0;
     56 
     57   c->regs[1] = 0;                                /* rbp */
     58   c->regs[3] = (uintptr_t)entry;                 /* r13 -- entry fn */
     59   c->regs[6] = top;                              /* rsp */
     60   c->regs[7] = (uintptr_t)__kit_coro_trampoline; /* rip */
     61 }
     62 
     63 #define STR_(x) #x
     64 #define STR(x) STR_(x)
     65 #define SYM(n) STR(__USER_LABEL_PREFIX__) #n
     66 
     67 /* Save callee-saved + (caller's) rsp + rip into [reg]; clobbers %rax.
     68    Used at function-entry stack discipline: (%rsp)=ret-addr, 8(%rsp)=pre-call
     69    rsp. */
     70 #define SAVE_INTO(reg)       \
     71   "    movq %rbx,  0(" reg   \
     72   ")\n"                      \
     73   "    movq %rbp,  8(" reg   \
     74   ")\n"                      \
     75   "    movq %r12, 16(" reg   \
     76   ")\n"                      \
     77   "    movq %r13, 24(" reg   \
     78   ")\n"                      \
     79   "    movq %r14, 32(" reg   \
     80   ")\n"                      \
     81   "    movq %r15, 40(" reg   \
     82   ")\n"                      \
     83   "    leaq 8(%rsp), %rax\n" \
     84   "    movq %rax, 48(" reg   \
     85   ")\n"                      \
     86   "    movq (%rsp), %rax\n"  \
     87   "    movq %rax, 56(" reg ")\n"
     88 
     89 /* Restore callee-saved + rsp from [reg], leave rip in %rcx ready to
     90    jmp. Caller delivers the destination value in %rax beforehand. */
     91 #define RESTORE_FROM(reg) \
     92   "    movq  0(" reg      \
     93   "), %rbx\n"             \
     94   "    movq  8(" reg      \
     95   "), %rbp\n"             \
     96   "    movq 16(" reg      \
     97   "), %r12\n"             \
     98   "    movq 24(" reg      \
     99   "), %r13\n"             \
    100   "    movq 32(" reg      \
    101   "), %r14\n"             \
    102   "    movq 40(" reg      \
    103   "), %r15\n"             \
    104   "    movq 48(" reg      \
    105   "), %rsp\n"             \
    106   "    movq 56(" reg "), %rcx\n"
    107 
    108 __asm__ (
    109     ".text\n"
    110     ".p2align 4\n"
    111 
    112     /* setjmp(env) -- env=%rdi */
    113     ".weak " SYM(setjmp) "\n"
    114     SYM(setjmp) ":\n"
    115     SAVE_INTO("%rdi")
    116     "    xorl %eax, %eax\n"
    117     "    ret\n"
    118 
    119     /* longjmp(env, val) -- env=%rdi, val=%esi.
    120        longjmp(_, 0) must deliver 1 (C11 7.13.2.1p4). */
    121     ".weak " SYM(longjmp) "\n"
    122     SYM(longjmp) ":\n"
    123     "    movslq %esi, %rax\n"        /* sign-extend int → long */
    124     "    testq %rax, %rax\n"
    125     "    movl  $1, %edx\n"
    126     "    cmoveq %rdx, %rax\n"
    127     RESTORE_FROM("%rdi")
    128     "    jmpq *%rcx\n"
    129 
    130     /* __kit_coro_switch(from, to, value) -- from=%rdi, to=%rsi, value=%rdx. */
    131     ".globl " SYM(__kit_coro_switch) "\n"
    132     SYM(__kit_coro_switch) ":\n"
    133     SAVE_INTO("%rdi")
    134     "    movq %rdx, %rax\n"          /* deliver value as return reg */
    135     RESTORE_FROM("%rsi")
    136     "    jmpq *%rcx\n"
    137 
    138     /* __kit_coro_trampoline -- on first entry: %rax=value,
    139        %r13=entry, %rsp=stack_top (no return addr pushed -- __kit_coro_switch
    140        reaches here via jmp). System V wants %rsp+8 ≡ 16 (mod 16) at
    141        function entry; the andq below makes that hold defensively. */
    142     ".globl " SYM(__kit_coro_trampoline) "\n"
    143     SYM(__kit_coro_trampoline) ":\n"
    144     "    andq $-16, %rsp\n"
    145     "    movq %rax, %rdi\n"          /* value → first arg */
    146     "    callq *%r13\n"               /* entry(value) */
    147     "    ud2\n"
    148 );