kit

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

i386.c (5658B)


      1 /*
      2  * lib/coro/i386.c -- i386 System V (cdecl, ILP32) implementations of
      3  *   setjmp / longjmp                       (<setjmp.h>)
      4  *   __kit_coro_ctx_init / __kit_coro_switch / trampoline   (<kit/coro.h>)
      5  *
      6  * cdecl callee-saved set: ebx, esi, edi, ebp, esp. Args are pushed
      7  * right-to-left on the stack: at function entry, 4(%esp)=arg0,
      8  * 8(%esp)=arg1, 12(%esp)=arg2, (%esp)=return-address.
      9  *
     10  *   regs[0]:  ebx     (also stashes entry fn for the trampoline)
     11  *   regs[1]:  esi
     12  *   regs[2]:  edi
     13  *   regs[3]:  ebp
     14  *   regs[4]:  esp     (caller's pre-call esp)
     15  *   regs[5]:  eip     (return address)
     16  *
     17  * 6 × 4 = 24 bytes of state, padded to sizeof = 32 by the 16-byte
     18  * over-alignment (vs. natural 4) so coro_ctx's 16-byte alignment is
     19  * matched.
     20  *
     21  *   setjmp(env)             4(%esp)=env
     22  *   longjmp(env, val)       4(%esp)=env, 8(%esp)=val
     23  *   __kit_coro_switch(f, t, val)  4(%esp)=from, 8(%esp)=to, 12(%esp)=value
     24  *
     25  * The "save esp/eip" trick: at function entry, (%esp) holds the caller's
     26  * return address (just pushed by `call`); 4(%esp) is the caller's
     27  * pre-call esp. Saving those two lets longjmp/__kit_coro_switch "land" at the
     28  * call site exactly as if the function had returned.
     29  *
     30  * Modern SysV i386 (ABI rev 1.1+) requires 16-byte stack alignment
     31  * before each `call`; the trampoline `andl $-16, %esp` enforces this
     32  * defensively for fresh contexts.
     33  */
     34 
     35 #include <kit/coro.h>
     36 #include <setjmp.h>
     37 #include <stddef.h>
     38 #include <stdint.h>
     39 
     40 struct __kit_i386_ctx {
     41   uintptr_t regs[6];
     42 } __attribute__((aligned(16)));
     43 
     44 _Static_assert(sizeof(struct __kit_i386_ctx) == 32, "layout");
     45 _Static_assert(_Alignof(struct __kit_i386_ctx) == 16, "align");
     46 _Static_assert(sizeof(struct __kit_i386_ctx) <= sizeof(coro_ctx),
     47                "fits coro_ctx");
     48 _Static_assert(sizeof(struct __kit_i386_ctx) <= sizeof(jmp_buf),
     49                "fits jmp_buf");
     50 _Static_assert(_Alignof(coro_ctx) >= _Alignof(struct __kit_i386_ctx),
     51                "align coro_ctx");
     52 
     53 extern void __kit_coro_trampoline(void);
     54 
     55 void __kit_coro_ctx_init(coro_ctx* ctx, void* stack_base, size_t stack_len,
     56                          void (*entry)(uintptr_t)) {
     57   struct __kit_i386_ctx* c = (struct __kit_i386_ctx*)ctx;
     58 
     59   /* i386 stacks grow down; align top to 16. */
     60   uintptr_t top = (uintptr_t)stack_base + stack_len;
     61   top &= ~(uintptr_t)(CORO_STACK_ALIGN - 1);
     62 
     63   for (size_t i = 0; i < sizeof(*c) / sizeof(uintptr_t); ++i)
     64     ((uintptr_t*)c)[i] = 0;
     65 
     66   c->regs[0] = (uintptr_t)entry;                 /* ebx -- entry fn */
     67   c->regs[3] = 0;                                /* ebp */
     68   c->regs[4] = top;                              /* esp */
     69   c->regs[5] = (uintptr_t)__kit_coro_trampoline; /* eip */
     70 }
     71 
     72 #define STR_(x) #x
     73 #define STR(x) STR_(x)
     74 #define SYM(n) STR(__USER_LABEL_PREFIX__) #n
     75 
     76 /* Save callee-saved + (caller's) esp + eip into [reg]; clobbers %eax.
     77    Used at function-entry stack discipline: (%esp)=ret-addr, 4(%esp)=pre-call
     78    esp. */
     79 #define SAVE_INTO(reg)       \
     80   "    movl %ebx,  0(" reg   \
     81   ")\n"                      \
     82   "    movl %esi,  4(" reg   \
     83   ")\n"                      \
     84   "    movl %edi,  8(" reg   \
     85   ")\n"                      \
     86   "    movl %ebp, 12(" reg   \
     87   ")\n"                      \
     88   "    leal 4(%esp), %eax\n" \
     89   "    movl %eax, 16(" reg   \
     90   ")\n"                      \
     91   "    movl (%esp), %eax\n"  \
     92   "    movl %eax, 20(" reg ")\n"
     93 
     94 /* Restore callee-saved + esp from [reg], leave eip in %ecx ready to
     95    jmp. Caller delivers the destination value in %eax beforehand. */
     96 #define RESTORE_FROM(reg) \
     97   "    movl  0(" reg      \
     98   "), %ebx\n"             \
     99   "    movl  4(" reg      \
    100   "), %esi\n"             \
    101   "    movl  8(" reg      \
    102   "), %edi\n"             \
    103   "    movl 12(" reg      \
    104   "), %ebp\n"             \
    105   "    movl 16(" reg      \
    106   "), %esp\n"             \
    107   "    movl 20(" reg "), %ecx\n"
    108 
    109 __asm__ (
    110     ".text\n"
    111     ".p2align 4\n"
    112 
    113     /* setjmp(env) -- env at 4(%esp). */
    114     ".weak " SYM(setjmp) "\n"
    115     SYM(setjmp) ":\n"
    116     "    movl 4(%esp), %edx\n"
    117     SAVE_INTO("%edx")
    118     "    xorl %eax, %eax\n"
    119     "    ret\n"
    120 
    121     /* longjmp(env, val) -- env at 4(%esp), val at 8(%esp).
    122        longjmp(_, 0) must deliver 1 (C11 7.13.2.1p4). */
    123     ".weak " SYM(longjmp) "\n"
    124     SYM(longjmp) ":\n"
    125     "    movl 4(%esp), %edx\n"        /* env */
    126     "    movl 8(%esp), %eax\n"        /* val */
    127     "    testl %eax, %eax\n"
    128     "    movl  $1, %ecx\n"
    129     "    cmovel %ecx, %eax\n"
    130     RESTORE_FROM("%edx")
    131     "    jmp *%ecx\n"
    132 
    133     /* __kit_coro_switch(from, to, value) -- 4(%esp)=from, 8(%esp)=to, 12(%esp)=value.
    134        Read all three args before SAVE_INTO clobbers the stack frame. */
    135     ".globl " SYM(__kit_coro_switch) "\n"
    136     SYM(__kit_coro_switch) ":\n"
    137     "    movl 4(%esp),  %edx\n"       /* from */
    138     SAVE_INTO("%edx")
    139     "    movl 8(%esp),  %edx\n"       /* to (re-read; SAVE clobbered %eax not stack) */
    140     "    movl 12(%esp), %eax\n"       /* value -- delivered as return reg */
    141     RESTORE_FROM("%edx")
    142     "    jmp *%ecx\n"
    143 
    144     /* __kit_coro_trampoline -- on first entry: %eax=value, %ebx=entry,
    145        %esp=stack_top (no return addr pushed -- __kit_coro_switch reaches here
    146        via jmp). cdecl needs the arg pushed; align defensively, then
    147        reserve 12 bytes + push value so that after the upcoming `call`
    148        pushes the 4-byte return addr, the callee sees %esp+4 16-aligned. */
    149     ".globl " SYM(__kit_coro_trampoline) "\n"
    150     SYM(__kit_coro_trampoline) ":\n"
    151     "    andl $-16, %esp\n"
    152     "    subl $12, %esp\n"
    153     "    pushl %eax\n"                /* arg0 = value */
    154     "    calll *%ebx\n"               /* entry(value) */
    155     "    ud2\n"
    156 );