aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile15
-rw-r--r--example/test.cc33
-rw-r--r--lib/matcha.h77
-rw-r--r--lib/thread_create.s65
-rw-r--r--util.gdb6
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