From 33f286000db35fe50639c237caa736deea304585 Mon Sep 17 00:00:00 2001 From: johannst Date: Tue, 22 Sep 2020 23:48:09 +0200 Subject: split classes into separate files, add arch specific subdir --- .gitignore | 2 +- Makefile | 13 ++++---- example/demo1.cc | 33 ++++++++++++++++++++ example/test.cc | 32 -------------------- lib/arch/x86_64/README.md | 9 ++++++ lib/arch/x86_64/asm.h | 4 +++ lib/arch/x86_64/thread_create.s | 20 ++++++++++++ lib/arch/x86_64/yield.s | 44 +++++++++++++++++++++++++++ lib/compile_guard.cc | 5 +++ lib/executor.cc | 20 ++++++++++++ lib/executor.h | 27 +++++++++++++++++ lib/matcha.cc | 65 --------------------------------------- lib/matcha.h | 50 ------------------------------ lib/thread.cc | 62 ++++++++++++++++++++++++++++++++++++++ lib/thread.h | 26 ++++++++++++++++ lib/thread_create.s | 67 ----------------------------------------- 16 files changed, 258 insertions(+), 221 deletions(-) create mode 100644 example/demo1.cc delete mode 100644 example/test.cc create mode 100644 lib/arch/x86_64/README.md create mode 100644 lib/arch/x86_64/asm.h create mode 100644 lib/arch/x86_64/thread_create.s create mode 100644 lib/arch/x86_64/yield.s create mode 100644 lib/compile_guard.cc create mode 100644 lib/executor.cc create mode 100644 lib/executor.h delete mode 100644 lib/matcha.cc delete mode 100644 lib/matcha.h create mode 100644 lib/thread.cc create mode 100644 lib/thread.h delete mode 100644 lib/thread_create.s diff --git a/.gitignore b/.gitignore index 1a2de75..126d07f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.o *.a -test +demo1 .clangd compile_commands.json diff --git a/Makefile b/Makefile index ec64564..3f89456 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ CXX := g++ CXXFLAGS := -g -O0 -Wall -Wextra -I. AR := ar -lib/libmatcha.a: lib/matcha.o lib/thread_create.o lib/matcha.h +lib/libmatcha.a: lib/compile_guard.o lib/thread.o lib/executor.o lib/arch/x86_64/thread_create.o lib/arch/x86_64/yield.o \ + lib/thread.h lib/executor.h lib/arch/x86_64/asm.h $(AR) rcs $@ $(filter %.o,$^) %.o: %.cc @@ -18,13 +19,13 @@ lib/libmatcha.a: lib/matcha.o lib/thread_create.o lib/matcha.h fmt: fd --type f '.+.h$$|.+.cc$$' --exec clang-format -i {} -example/test: example/test.o lib/libmatcha.a +example/demo1: example/demo1.o lib/libmatcha.a $(CXX) -o $@ $^ -gdb: example/test - which cgdb && cgdb -x util.gdb -ex 'start' example/test \ - || gdb -x util.gdb -ex 'start' example/test +gdb: example/demo1 + which cgdb && cgdb -x util.gdb -ex 'start' example/demo1 \ + || gdb -x util.gdb -ex 'start' example/demo1 clean: - rm -f example/test + rm -f example/demo1 rm -f **/*.o **/lib*.a diff --git a/example/demo1.cc b/example/demo1.cc new file mode 100644 index 0000000..1c910cd --- /dev/null +++ b/example/demo1.cc @@ -0,0 +1,33 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#include "lib/executor.h" +#include "lib/thread.h" + +#include +#include + +struct TestThread : public nMatcha::Thread { + TestThread(const char* name) : Thread(), mName(name) {} + + virtual void threadFn() override { + printf("[%s] starting up TestThread -> yield()\n", mName); + yield(); + printf("[%s] yield() -> finishing TestThreads\n", mName); + } + + private: + const char* mName; +}; + +int main() { + puts("[main] start main thread"); + + nMatcha::Executor e; + e.spawn(std::make_unique("Thread1")); + e.spawn(std::make_unique("Thread2")); + e.spawn(std::make_unique("Thread3")); + e.run(); + + puts("[main] finish main thread"); + return 0; +} diff --git a/example/test.cc b/example/test.cc deleted file mode 100644 index c9691f3..0000000 --- a/example/test.cc +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (c) 2020 Johannes Stoelp */ - -#include "lib/matcha.h" - -#include -#include - -struct TestThread : public Thread { - TestThread(const char* name) : Thread(), mName(name) {} - - virtual void threadFn() override { - printf("[%s] starting up TestThread -> yield()\n", mName); - yield(); - printf("[%s] yield() -> finishing TestThreads\n", mName); - } - - private: - const char* mName; -}; - -int main() { - puts("[main] start main thread"); - - Executor e; - e.spawn(std::make_unique("Thread1")); - e.spawn(std::make_unique("Thread2")); - e.spawn(std::make_unique("Thread3")); - e.run(); - - puts("[main] finish main thread"); - return 0; -} diff --git a/lib/arch/x86_64/README.md b/lib/arch/x86_64/README.md new file mode 100644 index 0000000..f573843 --- /dev/null +++ b/lib/arch/x86_64/README.md @@ -0,0 +1,9 @@ +# SystemV AMD64 ABI + +- Integer/pointer arguments via `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` +- Integer/pointer return values via `rax` +- Callee saved registers `rbx`, `rbp`, `r12` – `r15` + +## Reference +- [johannst x86_64 notes](https://johannst.github.io/notes/arch/x86_64.html) + diff --git a/lib/arch/x86_64/asm.h b/lib/arch/x86_64/asm.h new file mode 100644 index 0000000..962ff1b --- /dev/null +++ b/lib/arch/x86_64/asm.h @@ -0,0 +1,4 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +extern "C" void thread_create(); +extern "C" void yield(const void* new_stack, void* const* old_stack); diff --git a/lib/arch/x86_64/thread_create.s b/lib/arch/x86_64/thread_create.s new file mode 100644 index 0000000..37c368c --- /dev/null +++ b/lib/arch/x86_64/thread_create.s @@ -0,0 +1,20 @@ +# Copyright (c) 2020 Johannes Stoelp + + .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 diff --git a/lib/arch/x86_64/yield.s b/lib/arch/x86_64/yield.s new file mode 100644 index 0000000..d40bcd7 --- /dev/null +++ b/lib/arch/x86_64/yield.s @@ -0,0 +1,44 @@ +# Copyright (c) 2020 Johannes Stoelp + + .intel_syntax noprefix + .section .text, "ax", @progbits + + # extern "C" void yield(const void* new_stack, void* const* 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/lib/compile_guard.cc b/lib/compile_guard.cc new file mode 100644 index 0000000..61c7ad5 --- /dev/null +++ b/lib/compile_guard.cc @@ -0,0 +1,5 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#if !defined(linux) || (!defined(__x86_64__) && !defined(__amd64__)) +static_assert(false, "Matcha Threads only supported on Linux x86_64!"); +#endif diff --git a/lib/executor.cc b/lib/executor.cc new file mode 100644 index 0000000..fb79b49 --- /dev/null +++ b/lib/executor.cc @@ -0,0 +1,20 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#include "executor.h" + +#include "arch/x86_64/asm.h" + +namespace nMatcha { + void Executor::spawn(std::unique_ptr t) { + mThreads.push_back(std::move(t)); + mThreads.back()->mExecutor = this; + } + + void Executor::run() { + for (const std::unique_ptr& t : mThreads) { + yield_to(t.get()); + } + } + + void Executor::yield_to(const Thread* t) const { ::yield(t->mStackPtr, &mStackPtr); } +} // namespace nMatcha diff --git a/lib/executor.h b/lib/executor.h new file mode 100644 index 0000000..5d2e5b6 --- /dev/null +++ b/lib/executor.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#pragma once + +#include "thread.h" + +#include +#include + +namespace nMatcha { + struct Executor { + Executor(const Executor&) = delete; + Executor& operator=(const Executor&) = delete; + Executor() = default; + + const void* getStackPtr() const { return mStackPtr; } + + void spawn(std::unique_ptr t); + void run(); + + private: + void* mStackPtr; + std::vector> mThreads; + + void yield_to(const Thread* t) const; + }; +} // namespace nMatcha diff --git a/lib/matcha.cc b/lib/matcha.cc deleted file mode 100644 index 4315e9e..0000000 --- a/lib/matcha.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (c) 2020 Johannes Stoelp */ - -#include "matcha.h" - -#include -#include -#include -#include // sysconf - -// asm fns -extern "C" void thread_create(); -extern "C" void yield(const void* new_stack, void* const* old_stack); - -long get_pagesize() { - return sysconf(_SC_PAGESIZE); -} - -Thread::Thread() { - 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(stack) + STACK_SIZE; - { - uint64_t* stack = static_cast(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(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(stack); - } -} - -void Thread::entry(void* obj) { - Thread* t = static_cast(obj); - t->threadFn(); -} - -void Thread::yield() { - assert(mExecutor); - ::yield(mExecutor->getStackPtr(), &mStackPtr); -} - -void Executor::yield_to(const Thread* t) const { - ::yield(t->mStackPtr, &mStackPtr); -} diff --git a/lib/matcha.h b/lib/matcha.h deleted file mode 100644 index 5843cb7..0000000 --- a/lib/matcha.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2020 Johannes Stoelp */ - -#include -#include - -struct Executor; - -struct Thread { - Thread(const Thread&) = delete; - Thread& operator=(const Thread&) = delete; - Thread(); - virtual ~Thread() {} - - virtual void threadFn() = 0; - - protected: - void yield(); - - private: - static void entry(void* obj); - void* mStackPtr; - - friend struct Executor; - const Executor* mExecutor; -}; - - -struct Executor { - Executor(const Executor&) = delete; - Executor& operator=(const Executor&) = delete; - Executor() = default; - - const void* getStackPtr() const { return mStackPtr; } - - void spawn(std::unique_ptr t) { - mThreads.push_back(std::move(t)); - mThreads.back()->mExecutor = this; - } - void run() { - for (const std::unique_ptr& t : mThreads) { - yield_to(t.get()); - } - } - - private: - void* mStackPtr; - std::vector> mThreads; - - void yield_to(const Thread* t) const; -}; diff --git a/lib/thread.cc b/lib/thread.cc new file mode 100644 index 0000000..ad07d96 --- /dev/null +++ b/lib/thread.cc @@ -0,0 +1,62 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#include "thread.h" + +#include "arch/x86_64/asm.h" +#include "executor.h" + +#include +#include // uintN_t +#include // mmap +#include // sysconf + +namespace { + static long get_pagesize() { return sysconf(_SC_PAGESIZE); } +} // namespace + +namespace nMatcha { + Thread::Thread() { + 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(stack) + STACK_SIZE; + { + uint64_t* stack = static_cast(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(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(stack); + } + } + + void Thread::entry(void* obj) { + Thread* t = static_cast(obj); + t->threadFn(); + } + + void Thread::yield() { + assert(mExecutor); + ::yield(mExecutor->getStackPtr(), &mStackPtr); + } +} // namespace nMatcha diff --git a/lib/thread.h b/lib/thread.h new file mode 100644 index 0000000..e392052 --- /dev/null +++ b/lib/thread.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2020 Johannes Stoelp */ + +#pragma once + +namespace nMatcha { + struct Executor; + + struct Thread { + Thread(const Thread&) = delete; + Thread& operator=(const Thread&) = delete; + Thread(); + virtual ~Thread() {} + + virtual void threadFn() = 0; + + protected: + void yield(); + + private: + static void entry(void* obj); + void* mStackPtr; + + friend struct Executor; + const Executor* mExecutor; + }; +} // namespace nMatcha diff --git a/lib/thread_create.s b/lib/thread_create.s deleted file mode 100644 index 2aeb758..0000000 --- a/lib/thread_create.s +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2020 Johannes Stoelp - -# 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(const void* new_stack, void* const* 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 - -- cgit v1.2.3