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 );