A bunch of random code samples
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

468 lignes
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. }