From 98bbefa622333430ccf66c21f3a83da570d48e83 Mon Sep 17 00:00:00 2001 From: Archivist Date: Thu, 4 Nov 2021 14:24:21 +0000 Subject: [PATCH] Add 'less_minimal_scheduler.cpp' --- less_minimal_scheduler.cpp | 469 +++++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 less_minimal_scheduler.cpp diff --git a/less_minimal_scheduler.cpp b/less_minimal_scheduler.cpp new file mode 100644 index 0000000..10f82fe --- /dev/null +++ b/less_minimal_scheduler.cpp @@ -0,0 +1,469 @@ +// Build with: clang++ --std=c++20 -g -O1 schedtest.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__x86_64__) + #if defined(__GNUG__) + constexpr bool is_x86_64_linux = true; + #else + #if defined(__clang__) + constexpr bool is_x86_64_linux = true; + #else + constexpr bool is_x86_64_linux = false; + #endif + #endif +#else + constexpr bool is_x86_64_linux = false; +#endif + +static_assert(is_x86_64_linux, "This code probably only works on x86_64 GNU Extensions C++"); + +struct platform_data { + platform_data() = default; + + platform_data(std::span stack_str) + : stack_ptr(&*(stack_str.end()-16)) + , base_ptr(stack_ptr) + {} + + uint64_t rbx, r12, r13, r14, r15; + + void* stack_ptr; + void* base_ptr; + + void pull() __attribute__((always_inline)) + { + __asm__ __volatile__( + "movq %%rsp, %0\n" + "movq %%rbp, %1\n" + "movq %%rbx, %2\n" + "movq %%r12, %3\n" + "movq %%r13, %4\n" + "movq %%r14, %5\n" + "movq %%r15, %6\n" + : "=m"(stack_ptr) + , "=m"(base_ptr) + , "=m"(rbx) + , "=m"(r12) + , "=m"(r13) + , "=m"(r14) + , "=m"(r15) + ); + } + + void* push(void* location) __attribute__((always_inline)) + { + volatile void* volatile tmp = static_cast(stack_ptr) - sizeof(void*); + *static_cast(tmp) = location; + __asm__ __volatile__( + "movq %1, %%rsp\n" + "movq %2, %%rbp\n" + "movq %3, %%rbx\n" + "movq %4, %%r12\n" + "movq %5, %%r13\n" + "movq %6, %%r14\n" + "movq %7, %%r15\n" + "popq %0\n" + : "+r"(location) + : "m"(tmp) + , "m"(base_ptr) + , "m"(rbx) + , "m"(r12) + , "m"(r13) + , "m"(r14) + , "m"(r15) + : "memory" + ); + return location; + } +}; + +enum class process_status { + inactive = 0, + running = 1, + waiting = 2, + finished = 3, + zombie = 4 +}; + +struct process { + static int64_t counter; + + char* stack; + size_t sz; + platform_data scheduling_swapper; + process_status state = process_status::inactive; + std::function fn; + int64_t t_id; + + process(std::function _fn, size_t _sz = 16384) + : stack(new char[_sz]) + , sz(_sz) + , scheduling_swapper(std::span(stack, sz)) + , fn(_fn) + , t_id(counter++) + {} + + process(char* stack_base) + : stack(stack_base) + , sz(0) + , t_id(counter++) + {} + + process(const process&) = delete; + + ~process() { + if(sz) delete[] stack; + } +}; +int64_t process::counter = 0; + + +__attribute__((noinline)) struct system* spawner (struct system* sys); + +struct system { + static system sys; + + std::list> + running, + waiting, + naughty; + + std::unique_ptr previous; + std::unique_ptr current; + + std::unique_ptr one() { + auto v = std::move(running.back()); + running.pop_back(); + return v; + } + + void rid(std::unique_ptr current) { + switch(current->state) { + case process_status::inactive: + case process_status::running: + running.push_front(std::move(current)); + break; + case process_status::finished: + clean(std::move(current)); + break; + case process_status::zombie: + naughty.push_front(std::move(current)); + break; + case process_status::waiting: + waiting.push_front(std::move(current)); + break; + } + } + + void clean(std::unique_ptr) {} + + void yield_to(std::unique_ptr target) noexcept { + current->scheduling_swapper.pull(); + sys.rid(std::move(current)); + current = std::move(target); + current->scheduling_swapper.push(this); + spawner(&sys); + } + + + void yield() noexcept { + current->scheduling_swapper.pull(); + sys.rid(std::move(current)); + current = one(); + current->scheduling_swapper.push(this); + spawner(&sys); + } + + template + void steal_and_yield(fn func) noexcept { + current->scheduling_swapper.pull(); + func(std::move(current)); + current = one(); + current->scheduling_swapper.push(this); + spawner(&sys); + } +}; + + +// Needs to return the system one way or another +__attribute__((noinline)) struct system* spawner (struct system* sys) { + auto& proc = *system::sys.current; + if(proc.state == process_status::inactive) { + proc.state = process_status::running; + proc.fn(); + proc.state = process_status::finished; + sys->current->scheduling_swapper.pull(); + sys->yield(); + } + return sys; +} + +struct system system::sys; + +/********************* ***********************/ + +class dirty_bottleneck { + std::atomic_bool flag; + + [[nodiscard]] bool try_lock() { + bool f = false; + bool t = true; + return flag.compare_exchange_strong(f,t,std::memory_order::acquire); + } + + [[nodiscard]] bool try_unlock() { + bool f = false; + bool t = true; + return flag.compare_exchange_strong(t,f,std::memory_order::release); + } +public: + dirty_bottleneck() = default; + dirty_bottleneck(dirty_bottleneck&) = delete; + dirty_bottleneck(dirty_bottleneck&&) = delete; + + void lock() { + while(not try_lock()); + } + + void unlock() { + if(!try_unlock()) throw std::runtime_error("Unlocking failed in dirty_bottleneck: potential double unlocking issue"); + } +}; + +/********************* ***********************/ + +template +class lock_guard { + T& ref; +public: + lock_guard(T& _ref) + : ref(_ref) + { + ref.lock(); + } + + ~lock_guard() { + ref.unlock(); + } +}; + +/********************* ***********************/ + +using mutex_handle = size_t; + +enum class thread_state { + locking, + waiting, + unlocking +}; + +enum class mutex_state { + remove = 0, + create = 1 +}; + +using namespace std::chrono_literals; + +void mutex_state_update(mutex_handle, mutex_state); +void signal_locking(thread_state state, mutex_handle mtx, int64_t thrd); + +class fast_bottleneck { + + /** This is a secret tool that will help us later **/ + static std::atomic counter; + const mutex_handle handle; + dirty_bottleneck trigger_lock; + std::list> waiting; + + std::atomic_bool flag; + + [[nodiscard]] bool try_lock() { + bool f = false; + bool t = true; + return flag.compare_exchange_strong(f,t,std::memory_order::acquire); + } + + [[nodiscard]] bool try_unlock() { + bool f = false; + bool t = true; + return flag.compare_exchange_strong(t,f,std::memory_order::release); + } +public: + fast_bottleneck() + : flag() + , handle(counter.fetch_add(1)) //< We have to initialize that + { + mutex_state_update(handle, mutex_state::create); + } + fast_bottleneck(fast_bottleneck&) = delete; + fast_bottleneck(fast_bottleneck&&) = delete; + fast_bottleneck& operator=(fast_bottleneck&) = delete; + fast_bottleneck& operator=(fast_bottleneck&&) = delete; + + ~fast_bottleneck() { + mutex_state_update(handle, mutex_state::remove); + } + + + void lock() { + /// The exponential backing variables + constexpr std::chrono::milliseconds max{1}; + std::chrono::nanoseconds wait{256}; + while(not try_lock()) { + /// The implementation of our little trick when waiting + signal_locking(thread_state::waiting, handle, system::sys.current->t_id); + + system::sys.steal_and_yield([&](std::unique_ptr p){ + lock_guard triggers(trigger_lock); + p->state = process_status::waiting; + waiting.push_front(std::move(p)); + }); + + /// The exponential backing + //std::this_thread::sleep_for(wait); + wait += wait < max ? std::chrono::nanoseconds(wait.count()/2) : 0ns; + } + /// The implementation of our little trick when locking + signal_locking(thread_state::locking, handle, system::sys.current->t_id); + } + + void unlock() { + if(!try_unlock()) throw std::runtime_error("Unlocking failed in fast_bottleneck: potential double unlocking issue"); + /// The implementation of our little trick when unlocking + signal_locking(thread_state::unlocking, handle, system::sys.current->t_id); + + { + lock_guard triggers(trigger_lock); + if(waiting.size()) { + system::sys.running.push_front(std::move(waiting.back())); + waiting.pop_back(); + } + } + } +}; + +dirty_bottleneck checker_lock; +dirty_bottleneck lister_lock; +std::map> owned_locks; +std::map> waiting_locks; +std::set locks_that_exist; + +void mutex_state_update(mutex_handle mtx, mutex_state state) { + lock_guard lister(lister_lock); + switch(state) { + case mutex_state::create: { + locks_that_exist.insert(mtx); + }break; + case mutex_state::remove: { + locks_that_exist.erase(mtx); + }break; + } +} + +bool build_dependency_graph ( + const mutex_handle mtx, + const int64_t thrd, + std::map>& owned_locks, + std::map>& waiting_locks +) { + std::map> graph; + for(auto& elem : waiting_locks) { + if(elem.second.has_value()) { + for(auto& n : owned_locks[elem.first]) { + graph[n].insert(elem.second.value()); + } + } + } + + std::set nodes; + { + lock_guard lister(lister_lock); + nodes = locks_that_exist; + } + + bool happened = true; + + while(happened) { + happened = false; + for(auto& n : nodes) { + if(graph[n].size() == 0) + { + happened = true; + for(auto v : graph) { + v.second.erase(n); + } + nodes.erase(n); + break; + } + } + } + + return nodes.size(); +} + +void signal_locking(thread_state state, mutex_handle mtx, int64_t thrd) { + bool bad = false; + { + lock_guard checker(checker_lock); + switch(state) { + case thread_state::locking: { + waiting_locks[thrd].reset(); + owned_locks[thrd].push_back(mtx); + } break; + case thread_state::unlocking: { + auto it = std::find(owned_locks[thrd].begin(), owned_locks[thrd].end(), mtx); + if(it != owned_locks[thrd].end()) { + owned_locks[thrd].erase(it); + } + } break; + case thread_state::waiting: { + waiting_locks[thrd] = mtx; + bad = build_dependency_graph(mtx, thrd, owned_locks, waiting_locks); + } break; + } + } + if(bad) throw std::runtime_error("Deadlock detected"); +} + +std::atomic fast_bottleneck::counter; + +/********************* ***********************/ + +fast_bottleneck A; +fast_bottleneck B; + +int main() { + char c; + system::sys.current = std::make_unique(&c); + + std::cout << "1" << std::endl; + A.lock(); + system::sys.current->state = process_status::running; + system::sys.yield_to(std::make_unique([](){ + A.lock(); + std::cout << "A" << std::endl; + A.unlock(); + })); + A.unlock(); + system::sys.yield(); + system::sys.yield_to(std::make_unique([](){ + A.lock(); + std::cout << "B" << std::endl; + A.unlock(); + })); + std::cout << "3" << std::endl; + return 0; +}