A bunch of random code samples
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 line
12 KiB

  1. // Build with: clang++ --std=c++20 -g -O1 schedtest.cpp
  2. #include <array>
  3. #include <span>
  4. #include <memory>
  5. #include <list>
  6. #include <functional>
  7. #include <iostream>
  8. #include <map>
  9. #include <chrono>
  10. #include <exception>
  11. #include <atomic>
  12. #include <set>
  13. #include <vector>
  14. #include <optional>
  15. #if defined(__x86_64__)
  16. #if defined(__GNUG__)
  17. constexpr bool is_x86_64_linux = true;
  18. #else
  19. #if defined(__clang__)
  20. constexpr bool is_x86_64_linux = true;
  21. #else
  22. constexpr bool is_x86_64_linux = false;
  23. #endif
  24. #endif
  25. #else
  26. constexpr bool is_x86_64_linux = false;
  27. #endif
  28. static_assert(is_x86_64_linux, "This code probably only works on x86_64 GNU Extensions C++");
  29. struct platform_data {
  30. platform_data() = default;
  31. platform_data(std::span<char> stack_str)
  32. : stack_ptr(&*(stack_str.end()-16))
  33. , base_ptr(stack_ptr)
  34. {}
  35. uint64_t rbx, r12, r13, r14, r15;
  36. void* stack_ptr;
  37. void* base_ptr;
  38. void pull() __attribute__((always_inline))
  39. {
  40. __asm__ __volatile__(
  41. "movq %%rsp, %0\n"
  42. "movq %%rbp, %1\n"
  43. "movq %%rbx, %2\n"
  44. "movq %%r12, %3\n"
  45. "movq %%r13, %4\n"
  46. "movq %%r14, %5\n"
  47. "movq %%r15, %6\n"
  48. : "=m"(stack_ptr)
  49. , "=m"(base_ptr)
  50. , "=m"(rbx)
  51. , "=m"(r12)
  52. , "=m"(r13)
  53. , "=m"(r14)
  54. , "=m"(r15)
  55. );
  56. }
  57. void* push(void* location) __attribute__((always_inline))
  58. {
  59. volatile void* volatile tmp = static_cast<char*>(stack_ptr) - sizeof(void*);
  60. *static_cast<volatile void* volatile * volatile>(tmp) = location;
  61. __asm__ __volatile__(
  62. "movq %1, %%rsp\n"
  63. "movq %2, %%rbp\n"
  64. "movq %3, %%rbx\n"
  65. "movq %4, %%r12\n"
  66. "movq %5, %%r13\n"
  67. "movq %6, %%r14\n"
  68. "movq %7, %%r15\n"
  69. "popq %0\n"
  70. : "+r"(location)
  71. : "m"(tmp)
  72. , "m"(base_ptr)
  73. , "m"(rbx)
  74. , "m"(r12)
  75. , "m"(r13)
  76. , "m"(r14)
  77. , "m"(r15)
  78. : "memory"
  79. );
  80. return location;
  81. }
  82. };
  83. enum class process_status {
  84. inactive = 0,
  85. running = 1,
  86. waiting = 2,
  87. finished = 3,
  88. zombie = 4
  89. };
  90. struct process {
  91. static int64_t counter;
  92. char* stack;
  93. size_t sz;
  94. platform_data scheduling_swapper;
  95. process_status state = process_status::inactive;
  96. std::function<void()> fn;
  97. int64_t t_id;
  98. process(std::function<void()> _fn, size_t _sz = 16384)
  99. : stack(new char[_sz])
  100. , sz(_sz)
  101. , scheduling_swapper(std::span<char>(stack, sz))
  102. , fn(_fn)
  103. , t_id(counter++)
  104. {}
  105. process(char* stack_base)
  106. : stack(stack_base)
  107. , sz(0)
  108. , t_id(counter++)
  109. {}
  110. process(const process&) = delete;
  111. ~process() {
  112. if(sz) delete[] stack;
  113. }
  114. };
  115. int64_t process::counter = 0;
  116. __attribute__((noinline)) struct system* spawner (struct system* sys);
  117. struct system {
  118. static system sys;
  119. std::list<std::unique_ptr<process>>
  120. running,
  121. waiting,
  122. naughty;
  123. std::unique_ptr<process> previous;
  124. std::unique_ptr<process> current;
  125. std::unique_ptr<process> one() {
  126. auto v = std::move(running.back());
  127. running.pop_back();
  128. return v;
  129. }
  130. void rid(std::unique_ptr<process> current) {
  131. switch(current->state) {
  132. case process_status::inactive:
  133. case process_status::running:
  134. running.push_front(std::move(current));
  135. break;
  136. case process_status::finished:
  137. clean(std::move(current));
  138. break;
  139. case process_status::zombie:
  140. naughty.push_front(std::move(current));
  141. break;
  142. case process_status::waiting:
  143. waiting.push_front(std::move(current));
  144. break;
  145. }
  146. }
  147. void clean(std::unique_ptr<process>) {}
  148. void yield_to(std::unique_ptr<process> target) noexcept {
  149. current->scheduling_swapper.pull();
  150. sys.rid(std::move(current));
  151. current = std::move(target);
  152. current->scheduling_swapper.push(this);
  153. spawner(&sys);
  154. }
  155. void yield() noexcept {
  156. current->scheduling_swapper.pull();
  157. sys.rid(std::move(current));
  158. current = one();
  159. current->scheduling_swapper.push(this);
  160. spawner(&sys);
  161. }
  162. template<typename fn>
  163. void steal_and_yield(fn func) noexcept {
  164. current->scheduling_swapper.pull();
  165. func(std::move(current));
  166. current = one();
  167. current->scheduling_swapper.push(this);
  168. spawner(&sys);
  169. }
  170. };
  171. // Needs to return the system one way or another
  172. __attribute__((noinline)) struct system* spawner (struct system* sys) {
  173. auto& proc = *system::sys.current;
  174. if(proc.state == process_status::inactive) {
  175. proc.state = process_status::running;
  176. proc.fn();
  177. proc.state = process_status::finished;
  178. sys->current->scheduling_swapper.pull();
  179. sys->yield();
  180. }
  181. return sys;
  182. }
  183. struct system system::sys;
  184. /********************* ***********************/
  185. class dirty_bottleneck {
  186. std::atomic_bool flag;
  187. [[nodiscard]] bool try_lock() {
  188. bool f = false;
  189. bool t = true;
  190. return flag.compare_exchange_strong(f,t,std::memory_order::acquire);
  191. }
  192. [[nodiscard]] bool try_unlock() {
  193. bool f = false;
  194. bool t = true;
  195. return flag.compare_exchange_strong(t,f,std::memory_order::release);
  196. }
  197. public:
  198. dirty_bottleneck() = default;
  199. dirty_bottleneck(dirty_bottleneck&) = delete;
  200. dirty_bottleneck(dirty_bottleneck&&) = delete;
  201. void lock() {
  202. while(not try_lock());
  203. }
  204. void unlock() {
  205. if(!try_unlock()) throw std::runtime_error("Unlocking failed in dirty_bottleneck: potential double unlocking issue");
  206. }
  207. };
  208. /********************* ***********************/
  209. template<typename T>
  210. class lock_guard {
  211. T& ref;
  212. public:
  213. lock_guard(T& _ref)
  214. : ref(_ref)
  215. {
  216. ref.lock();
  217. }
  218. ~lock_guard() {
  219. ref.unlock();
  220. }
  221. };
  222. /********************* ***********************/
  223. using mutex_handle = size_t;
  224. enum class thread_state {
  225. locking,
  226. waiting,
  227. unlocking
  228. };
  229. enum class mutex_state {
  230. remove = 0,
  231. create = 1
  232. };
  233. using namespace std::chrono_literals;
  234. void mutex_state_update(mutex_handle, mutex_state);
  235. void signal_locking(thread_state state, mutex_handle mtx, int64_t thrd);
  236. class fast_bottleneck {
  237. /** This is a secret tool that will help us later **/
  238. static std::atomic<size_t> counter;
  239. const mutex_handle handle;
  240. dirty_bottleneck trigger_lock;
  241. std::list<std::unique_ptr<process>> waiting;
  242. std::atomic_bool flag;
  243. [[nodiscard]] bool try_lock() {
  244. bool f = false;
  245. bool t = true;
  246. return flag.compare_exchange_strong(f,t,std::memory_order::acquire);
  247. }
  248. [[nodiscard]] bool try_unlock() {
  249. bool f = false;
  250. bool t = true;
  251. return flag.compare_exchange_strong(t,f,std::memory_order::release);
  252. }
  253. public:
  254. fast_bottleneck()
  255. : flag()
  256. , handle(counter.fetch_add(1)) //< We have to initialize that
  257. {
  258. mutex_state_update(handle, mutex_state::create);
  259. }
  260. fast_bottleneck(fast_bottleneck&) = delete;
  261. fast_bottleneck(fast_bottleneck&&) = delete;
  262. fast_bottleneck& operator=(fast_bottleneck&) = delete;
  263. fast_bottleneck& operator=(fast_bottleneck&&) = delete;
  264. ~fast_bottleneck() {
  265. mutex_state_update(handle, mutex_state::remove);
  266. }
  267. void lock() {
  268. /// The exponential backing variables
  269. constexpr std::chrono::milliseconds max{1};
  270. std::chrono::nanoseconds wait{256};
  271. while(not try_lock()) {
  272. /// The implementation of our little trick when waiting
  273. signal_locking(thread_state::waiting, handle, system::sys.current->t_id);
  274. system::sys.steal_and_yield([&](std::unique_ptr<process> p){
  275. lock_guard triggers(trigger_lock);
  276. p->state = process_status::waiting;
  277. waiting.push_front(std::move(p));
  278. });
  279. /// The exponential backing
  280. //std::this_thread::sleep_for(wait);
  281. wait += wait < max ? std::chrono::nanoseconds(wait.count()/2) : 0ns;
  282. }
  283. /// The implementation of our little trick when locking
  284. signal_locking(thread_state::locking, handle, system::sys.current->t_id);
  285. }
  286. void unlock() {
  287. if(!try_unlock()) throw std::runtime_error("Unlocking failed in fast_bottleneck: potential double unlocking issue");
  288. /// The implementation of our little trick when unlocking
  289. signal_locking(thread_state::unlocking, handle, system::sys.current->t_id);
  290. {
  291. lock_guard triggers(trigger_lock);
  292. if(waiting.size()) {
  293. system::sys.running.push_front(std::move(waiting.back()));
  294. waiting.pop_back();
  295. }
  296. }
  297. }
  298. };
  299. dirty_bottleneck checker_lock;
  300. dirty_bottleneck lister_lock;
  301. std::map<int64_t, std::vector<mutex_handle>> owned_locks;
  302. std::map<int64_t, std::optional<mutex_handle>> waiting_locks;
  303. std::set<mutex_handle> locks_that_exist;
  304. void mutex_state_update(mutex_handle mtx, mutex_state state) {
  305. lock_guard lister(lister_lock);
  306. switch(state) {
  307. case mutex_state::create: {
  308. locks_that_exist.insert(mtx);
  309. }break;
  310. case mutex_state::remove: {
  311. locks_that_exist.erase(mtx);
  312. }break;
  313. }
  314. }
  315. bool build_dependency_graph (
  316. const mutex_handle mtx,
  317. const int64_t thrd,
  318. std::map<int64_t, std::vector<mutex_handle>>& owned_locks,
  319. std::map<int64_t, std::optional<mutex_handle>>& waiting_locks
  320. ) {
  321. std::map<mutex_handle, std::set<mutex_handle>> graph;
  322. for(auto& elem : waiting_locks) {
  323. if(elem.second.has_value()) {
  324. for(auto& n : owned_locks[elem.first]) {
  325. graph[n].insert(elem.second.value());
  326. }
  327. }
  328. }
  329. std::set<mutex_handle> nodes;
  330. {
  331. lock_guard lister(lister_lock);
  332. nodes = locks_that_exist;
  333. }
  334. bool happened = true;
  335. while(happened) {
  336. happened = false;
  337. for(auto& n : nodes) {
  338. if(graph[n].size() == 0)
  339. {
  340. happened = true;
  341. for(auto v : graph) {
  342. v.second.erase(n);
  343. }
  344. nodes.erase(n);
  345. break;
  346. }
  347. }
  348. }
  349. return nodes.size();
  350. }
  351. void signal_locking(thread_state state, mutex_handle mtx, int64_t thrd) {
  352. bool bad = false;
  353. {
  354. lock_guard checker(checker_lock);
  355. switch(state) {
  356. case thread_state::locking: {
  357. waiting_locks[thrd].reset();
  358. owned_locks[thrd].push_back(mtx);
  359. } break;
  360. case thread_state::unlocking: {
  361. auto it = std::find(owned_locks[thrd].begin(), owned_locks[thrd].end(), mtx);
  362. if(it != owned_locks[thrd].end()) {
  363. owned_locks[thrd].erase(it);
  364. }
  365. } break;
  366. case thread_state::waiting: {
  367. waiting_locks[thrd] = mtx;
  368. bad = build_dependency_graph(mtx, thrd, owned_locks, waiting_locks);
  369. } break;
  370. }
  371. }
  372. if(bad) throw std::runtime_error("Deadlock detected");
  373. }
  374. std::atomic<size_t> fast_bottleneck::counter;
  375. /********************* ***********************/
  376. fast_bottleneck A;
  377. int main() {
  378. char c;
  379. system::sys.current = std::make_unique<process>(&c);
  380. std::cout << "1" << std::endl;
  381. A.lock();
  382. system::sys.current->state = process_status::running;
  383. system::sys.yield_to(std::make_unique<process>([](){
  384. A.lock();
  385. std::cout << "A" << std::endl;
  386. A.unlock();
  387. }));
  388. A.unlock();
  389. system::sys.yield();
  390. system::sys.yield_to(std::make_unique<process>([](){
  391. A.lock();
  392. std::cout << "B" << std::endl;
  393. A.unlock();
  394. }));
  395. std::cout << "3" << std::endl;
  396. return 0;
  397. }