diff options
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | example/test.cc | 33 | ||||
-rw-r--r-- | lib/matcha.h | 77 | ||||
-rw-r--r-- | lib/thread_create.s | 65 | ||||
-rw-r--r-- | util.gdb | 6 |
5 files changed, 196 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e86f7fa --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CXX := g++ +CXXFLAGS := -g -O0 -Wall -Wextra -I. + +example/test: example/test.cc lib/thread_create.s lib/matcha.h + $(CXX) -o $@ $^ $(CXXFLAGS) + +fmt: + fd --type f '.+.h$$|.+.cc$$' --exec clang-format -i {} + +gdb: example/test + which cgdb && cgdb -x util.gdb -ex 'start' example/test \ + || gdb -x util.gdb -ex 'start' example/test + +clean: + rm -f example/test diff --git a/example/test.cc b/example/test.cc new file mode 100644 index 0000000..7307bec --- /dev/null +++ b/example/test.cc @@ -0,0 +1,33 @@ +#include "lib/matcha.h" + +#include <cassert> +#include <cstdio> + +Thread* gThread1 = nullptr; + +void thread1_1() { + puts("thread1_1"); +} + +void thread1() { + puts("start thread1"); + thread1_1(); + puts("finish thread1"); + + assert(gThread1 != nullptr); + yield_from(*gThread1); +} + +int main() { + puts("start main thread"); + + Thread t(thread1); + gThread1 = &t; + + yield_to(t); + + gThread1 = nullptr; + + puts("finish main thread"); + return 0; +} diff --git a/lib/matcha.h b/lib/matcha.h new file mode 100644 index 0000000..9387bf9 --- /dev/null +++ b/lib/matcha.h @@ -0,0 +1,77 @@ +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <sys/mman.h> +#include <unistd.h> // sysconf + +extern "C" void thread_create(); +extern "C" void yield(void* new_stack, void** old_stack); + +long get_pagesize() { + return sysconf(_SC_PAGESIZE); +} + +struct Thread { + Thread(const Thread&) = delete; + Thread& operator=(const Thread&) = delete; + Thread(void (*fn)()); + + static void entry(void* obj); + + // private: + void* mStackPtr; + void (*mUserFn)(); +}; + +Thread::Thread(void (*fn)()) : mUserFn(fn) { + const long PAGE_SIZE = get_pagesize(); + const long STACK_SIZE = 8 * PAGE_SIZE; + + // create new stack + void* stack = mmap(nullptr, STACK_SIZE, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1 /* fd */, 0 /* offset */); + assert(stack != MAP_FAILED); + + // protect last stack page (lowest addr) + int ret = mprotect(stack, PAGE_SIZE, PROT_NONE); + assert(ret == 0); + + // stack grows downwards + mStackPtr = static_cast<uint8_t*>(stack) + STACK_SIZE; + { + uint64_t* stack = static_cast<uint64_t*>(mStackPtr); + *(--stack) = (uint64_t)this; + *(--stack) = (uint64_t)Thread::entry; + + // initial values for yield epilog + *(--stack) = (uint64_t)thread_create; + *(--stack) = (uint64_t)0; + + // initial values for callee saved regs + *(--stack) = (uint64_t)0; // rbx + *(--stack) = (uint64_t)(static_cast<uint64_t*>(mStackPtr) - 4); // rbp + *(--stack) = (uint64_t)0; // r12 + *(--stack) = (uint64_t)0; // r13 + *(--stack) = (uint64_t)0; // r14 + *(--stack) = (uint64_t)0; // r15 + + mStackPtr = static_cast<void*>(stack); + } +} + +void Thread::entry(void* obj) { + Thread* t = static_cast<Thread*>(obj); + + puts("thread entry"); + t->mUserFn(); + puts("thread done"); +} + +void* gOriginalStack; + +void yield_to(const Thread& t) { + yield(t.mStackPtr, &gOriginalStack); +} + +void yield_from(Thread& t) { + yield(gOriginalStack, &t.mStackPtr); +} diff --git a/lib/thread_create.s b/lib/thread_create.s new file mode 100644 index 0000000..6d6fa96 --- /dev/null +++ b/lib/thread_create.s @@ -0,0 +1,65 @@ +# SysV AMD64 ABI +# int/ptr args : rdi, rsi, rdx, rcx, r8, r9 +# int/ptr ret : rax + + .intel_syntax noprefix + .section .text ,"ax",@progbits + + # extern "C" void thread_create(); + .global thread_create + .type thread_create, @function +thread_create: + .cfi_startproc + mov rdi, qword ptr [rsp+0x8] + mov rsi, qword ptr [rsp] + + call rsi + + # FIXME: no return from thread after user fn finished. +1: + jmp 1b + .cfi_endproc + .size thread_create, .-thread_create + + + + # extern "C" void yield(void* new_stack, void** old_stack); + # ^^^^^^^^^ ^^^^^^^^^ + # rdi rsi + .global yield + .type yield, @function +yield: + .cfi_startproc + // prologue + push rbp + mov rbp, rsp + + // push callee saved registers + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + // arg0: rdi holds new stack + // arg1: rsi holds addr to location current stack must be saved + mov [rsi], rsp # save current stack ptr + mov rsp, rdi # switch to new stack ptr + + // pop callee saved registers + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + // epilogue + mov rsp, rbp + pop rbp + + ret + .cfi_endproc + .size yield, .-yield + diff --git a/util.gdb b/util.gdb new file mode 100644 index 0000000..d1dbb44 --- /dev/null +++ b/util.gdb @@ -0,0 +1,6 @@ +define show + echo -- regs --\n + info reg rdi rsi rsp rbp + echo -- stack -- + x/10a $rsp +end |