/* libco.amd64 (2016-09-14) author: byuu license: public domain */ #include "libco.h" #include #include #include #include #include #include // allocations are 16k larger than asked for, which is all used as guard space #define GUARD_SIZE 0x4000 typedef struct { // used by coswap.s, has to be at the beginning of the struct struct { uint64_t rsp; uint64_t rbp; // we have to save rbp because unless fomit-frame-pointer is set, the compiler regards it as "special" and won't allow clobbers uint64_t rip; } jmp_buf; // points to the lowest address in the stack // NB: because of guard space, this is not valid stack void* stack; // length of the stack that we allocated in bytes uint64_t stack_size; } cothread_impl; // the cothread that represents the real host thread we started from static cothread_impl co_host_buffer; // what cothread are we in right now static cothread_impl* co_active_handle; static void free_thread(cothread_impl* co) { if (munmap(co->stack, co->stack_size) != 0) abort(); free(co); } static cothread_impl* alloc_thread(uint64_t size) { cothread_impl* co = calloc(1, sizeof(*co)); if (!co) return NULL; // align up to 4k size = (size + 4095) & ~4095ul; size += GUARD_SIZE; co->stack = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (co->stack == (void*)(-1)) { free(co); return NULL; } if (mprotect(co->stack, GUARD_SIZE, PROT_NONE) != 0) { free_thread(co); return NULL; } co->stack_size = size; return co; } static void crash(void) { __asm__("int3"); // called only if cothread_t entrypoint returns } ECL_EXPORT void co_clean(void) { memset(&co_host_buffer, 0, sizeof(co_host_buffer)); } cothread_t co_active(void) { if (!co_active_handle) co_active_handle = &co_host_buffer; return co_active_handle; } cothread_t co_create(unsigned int sz, void (*entrypoint)(void)) { cothread_impl* co; if (!co_active_handle) co_active_handle = &co_host_buffer; if ((co = alloc_thread(sz))) { uint64_t* p = (uint64_t*)((char*)co->stack + co->stack_size); // seek to top of stack *--p = (uint64_t)crash; // crash if entrypoint returns co->jmp_buf.rsp = (uint64_t)p; // stack pointer co->jmp_buf.rip = (uint64_t)entrypoint; // start of function } return co; } void co_delete(cothread_t handle) { free_thread(handle); } void co_switch(cothread_t handle) { cothread_impl* co = handle; cothread_impl* co_previous_handle = co_active_handle; co_active_handle = co; register uint64_t _rdi __asm__("rdi") = (uint64_t)co_previous_handle; register uint64_t _rsi __asm__("rsi") = (uint64_t)co_active_handle; /* mov [rdi + 0], rsp mov [rdi + 8], rbp lea rax, [rip + 17] mov [rdi + 16], rax mov rsp, [rsi + 0] mov rbp, [rsi + 8] mov rax, [rsi + 16] jmp rax */ __asm__( "mov %%rsp, 0(%%rdi)\n" "mov %%rbp, 8(%%rdi)\n" "lea 17(%%rip), %%rax\n" "mov %%rax, 16(%%rdi)\n" "mov 0(%%rsi), %%rsp\n" "mov 8(%%rsi), %%rbp\n" "mov 16(%%rsi), %%rax\n" "jmp *%%rax\n" ::"r"(_rdi), "r"(_rsi) :"rax", "rbx", "rcx", "rdx", /*"rbp",*/ /*"rsi", "rdi",*/ "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5", "zmm6", "zmm7", "zmm8", "zmm9", "zmm10", "zmm11", "zmm12", "zmm13", "zmm14", "zmm15", /*"zmm16", "zmm17", "zmm18", "zmm19", "zmm20", "zmm21", "zmm22", "zmm23", "zmm24", "zmm25", "zmm26", "zmm27", "zmm28", "zmm29", "zmm30", "zmm31",*/ "memory" ); } cothread_t co_derive(void* memory, unsigned sz, void (*entrypoint)(void)) { return NULL; } int co_serializable(void) { return 0; }