1use alloc::boxed::Box;
31use core::cell::UnsafeCell;
32use core::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, AtomicU64, AtomicUsize, Ordering::SeqCst};
33
34use crate::{Sem, Thread, cpu_count, thread_spawn, thread_yield, wake};
35
36pub const THR_MAX: usize = 64;
37const SPIN_LOCK: u32 = 64; const SPIN_IDLE: u32 = 256; const SPIN_WAIT: u32 = 2000; const WAIT_POLL_MS: i32 = 2; const THR_STACK: usize = 0x10_0000; const JF_QUEUED: u32 = 1;
43const JF_ASYNC: u32 = 2;
44const TS_NONE: u32 = 0;
45const TS_INIT: u32 = 1;
46const TS_READY: u32 = 2;
47const JS_RUNNING: u32 = 1;
48const JS_DONE: u32 = 2;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct Cancelled;
53
54type ItemFn = dyn Fn(usize, &JobCtl) + Sync;
55
56pub struct JobCtl {
58 f: UnsafeCell<*const ItemFn>, count: UnsafeCell<u64>, next: AtomicU64, done: AtomicU64, cancel: AtomicU32,
63 state: AtomicU32,
64 active: UnsafeCell<u32>, flags: UnsafeCell<u32>, link: UnsafeCell<*const JobCtl>, }
68
69unsafe impl Sync for JobCtl {}
72unsafe impl Send for JobCtl {}
73
74impl JobCtl {
75 fn new(f: *const ItemFn, count: u64) -> JobCtl {
76 JobCtl {
77 f: UnsafeCell::new(f),
78 count: UnsafeCell::new(count),
79 next: AtomicU64::new(0),
80 done: AtomicU64::new(0),
81 cancel: AtomicU32::new(0),
82 state: AtomicU32::new(0),
83 active: UnsafeCell::new(0),
84 flags: UnsafeCell::new(0),
85 link: UnsafeCell::new(core::ptr::null()),
86 }
87 }
88
89 pub fn cancel(&self) {
91 job_cancel(self)
92 }
93
94 pub fn cancelled(&self) -> bool {
96 self.cancel.load(SeqCst) != 0
97 }
98
99 pub fn count(&self) -> u64 {
100 unsafe { *self.count.get() }
102 }
103
104 pub fn items_done(&self) -> u64 {
106 self.done.load(SeqCst)
107 }
108
109 fn result(&self) -> Result<(), Cancelled> {
110 if self.cancel.load(SeqCst) != 0 { Err(Cancelled) } else { Ok(()) }
111 }
112}
113
114struct Pool {
117 lock: AtomicU32,
118 head: AtomicPtr<JobCtl>,
119 tail: AtomicPtr<JobCtl>,
120 work: UnsafeCell<Option<Sem>>, donesem: UnsafeCell<Option<Sem>>, dwait: AtomicI32,
123 state: AtomicU32,
124 n: AtomicU32,
125 quit: AtomicU32,
126 threads: UnsafeCell<[Option<Thread>; THR_MAX]>,
127 stk: [AtomicUsize; THR_MAX], }
129
130unsafe impl Sync for Pool {}
133
134static POOL: Pool = Pool {
135 lock: AtomicU32::new(0),
136 head: AtomicPtr::new(core::ptr::null_mut()),
137 tail: AtomicPtr::new(core::ptr::null_mut()),
138 work: UnsafeCell::new(None),
139 donesem: UnsafeCell::new(None),
140 dwait: AtomicI32::new(0),
141 state: AtomicU32::new(TS_NONE),
142 n: AtomicU32::new(0),
143 quit: AtomicU32::new(0),
144 threads: UnsafeCell::new([const { None }; THR_MAX]),
145 stk: [const { AtomicUsize::new(0) }; THR_MAX],
146};
147
148fn work_sem() -> Option<&'static Sem> {
149 unsafe { (*POOL.work.get()).as_ref() }
151}
152
153fn done_sem() -> Option<&'static Sem> {
154 unsafe { (*POOL.donesem.get()).as_ref() }
156}
157
158pub fn thr_init(n: u32) -> u32 {
160 loop {
161 if POOL.state.load(SeqCst) == TS_READY {
162 return POOL.n.load(SeqCst);
163 }
164 if POOL.state.compare_exchange(TS_NONE, TS_INIT, SeqCst, SeqCst).is_ok() {
165 break;
166 }
167 thread_yield(); }
169 let mut want = n as usize;
170 if let Some(v) = crate::arg_value("--threads").and_then(|v| v.parse::<i32>().ok()) {
171 if v > 0 {
172 want = v as usize;
173 }
174 }
175 if want == 0 {
176 want = cpu_count() as usize;
177 }
178 let want = want.min(THR_MAX);
179 POOL.quit.store(0, SeqCst);
180 POOL.head.store(core::ptr::null_mut(), SeqCst);
181 POOL.tail.store(core::ptr::null_mut(), SeqCst);
182 POOL.dwait.store(0, SeqCst);
183 let mut started = 0;
184 unsafe {
186 *POOL.work.get() = Sem::new(0);
187 *POOL.donesem.get() = Sem::new(0);
188 if work_sem().is_some() && done_sem().is_some() {
189 let threads = &mut *POOL.threads.get();
190 while started < want {
191 let k = started;
192 match thread_spawn(THR_STACK, Box::new(move || worker(k))) {
193 Some(t) => threads[k] = Some(t),
194 None => break,
195 }
196 started += 1;
197 }
198 }
199 }
200 POOL.n.store(started as u32, SeqCst);
201 POOL.state.swap(TS_READY, SeqCst);
202 started as u32
203}
204
205pub fn thr_count() -> u32 {
207 if POOL.state.load(SeqCst) == TS_READY { POOL.n.load(SeqCst) } else { thr_init(0) }
208}
209
210pub fn thr_shutdown() {
212 if POOL.state.load(SeqCst) != TS_READY {
213 return;
214 }
215 POOL.quit.store(1, SeqCst);
216 let n = POOL.n.load(SeqCst);
217 if let Some(s) = work_sem() {
218 s.post(n);
219 }
220 unsafe {
222 let threads = &mut *POOL.threads.get();
223 for t in threads.iter_mut().take(n as usize) {
224 if let Some(t) = t.take() {
225 t.join();
226 }
227 }
228 *POOL.work.get() = None;
229 *POOL.donesem.get() = None;
230 }
231 POOL.n.store(0, SeqCst);
232 POOL.state.store(TS_NONE, SeqCst);
233}
234
235fn q_lock() {
238 loop {
239 if POOL.lock.swap(1, SeqCst) == 0 {
240 return;
241 }
242 let mut spin = SPIN_LOCK;
243 loop {
244 core::hint::spin_loop();
245 if POOL.lock.load(SeqCst) == 0 {
246 break;
247 }
248 spin -= 1;
249 if spin == 0 {
250 thread_yield();
251 break;
252 }
253 }
254 }
255}
256
257fn q_unlock() {
258 POOL.lock.store(0, SeqCst);
259}
260
261unsafe fn q_push_head(j: *const JobCtl) {
264 unsafe {
265 *(*j).link.get() = POOL.head.load(SeqCst);
266 POOL.head.store(j as *mut JobCtl, SeqCst);
267 if POOL.tail.load(SeqCst).is_null() {
268 POOL.tail.store(j as *mut JobCtl, SeqCst);
269 }
270 *(*j).flags.get() |= JF_QUEUED;
271 }
272}
273
274unsafe fn q_push_tail(j: *const JobCtl) {
275 unsafe {
276 *(*j).link.get() = core::ptr::null();
277 let t = POOL.tail.load(SeqCst);
278 if t.is_null() {
279 POOL.head.store(j as *mut JobCtl, SeqCst);
280 } else {
281 *(*t).link.get() = j;
282 }
283 POOL.tail.store(j as *mut JobCtl, SeqCst);
284 *(*j).flags.get() |= JF_QUEUED;
285 }
286}
287
288unsafe fn q_unlink(j: *const JobCtl) {
289 unsafe {
290 let mut prev: *const JobCtl = core::ptr::null();
291 let mut cur = POOL.head.load(SeqCst) as *const JobCtl;
292 while !cur.is_null() {
293 if cur == j {
294 let next = *(*j).link.get();
295 if prev.is_null() {
296 POOL.head.store(next as *mut JobCtl, SeqCst);
297 } else {
298 *(*prev).link.get() = next;
299 }
300 if POOL.tail.load(SeqCst) as *const JobCtl == j {
301 POOL.tail.store(prev as *mut JobCtl, SeqCst);
302 }
303 *(*j).flags.get() &= !JF_QUEUED;
304 return;
305 }
306 prev = cur;
307 cur = *(*cur).link.get();
308 }
309 }
310}
311
312fn pick() -> *const JobCtl {
314 q_lock();
315 let h = POOL.head.load(SeqCst) as *const JobCtl;
316 if !h.is_null() {
317 unsafe { *(*h).active.get() += 1 };
319 }
320 q_unlock();
321 h
322}
323
324fn run_items(j: &JobCtl) {
326 let f = unsafe { &**j.f.get() };
328 let count = j.count();
329 while j.cancel.load(SeqCst) == 0 {
330 let i = j.next.fetch_add(1, SeqCst);
331 if i >= count {
332 break;
333 }
334 f(i as usize, j);
335 j.done.fetch_add(1, SeqCst);
336 }
337}
338
339fn leave(j: *const JobCtl) {
342 q_lock();
343 let fin = unsafe {
345 let j = &*j;
346 if *j.flags.get() & JF_QUEUED != 0
347 && (j.cancel.load(SeqCst) != 0 || j.next.load(SeqCst) >= j.count())
348 {
349 q_unlink(j); }
351 *j.active.get() -= 1;
352 *j.active.get() == 0 && *j.flags.get() & JF_QUEUED == 0
353 };
354 q_unlock();
355 if fin {
356 finish(j);
357 }
358}
359
360fn finish(j: *const JobCtl) {
363 unsafe {
365 let flags = *(*j).flags.get();
366 (*j).state.swap(JS_DONE, SeqCst);
367 finish_rest(flags);
369 }
370}
371
372fn finish_rest(flags: u32) {
373 if flags & JF_ASYNC != 0 {
374 wake();
375 }
376 let d = POOL.dwait.swap(0, SeqCst);
377 if d > 0 {
378 if let Some(s) = done_sem() {
379 s.post(d as u32);
380 }
381 }
382}
383
384fn wait_done(j: &JobCtl) {
386 for _ in 0..SPIN_WAIT {
387 if j.state.load(SeqCst) == JS_DONE {
388 return;
389 }
390 core::hint::spin_loop();
391 }
392 loop {
393 POOL.dwait.fetch_add(1, SeqCst);
395 if j.state.load(SeqCst) == JS_DONE {
396 return;
397 }
398 match done_sem() {
399 Some(s) => {
400 if !s.wait(WAIT_POLL_MS) {
401 let mut v = POOL.dwait.load(SeqCst);
403 while v > 0 {
404 match POOL.dwait.compare_exchange(v, v - 1, SeqCst, SeqCst) {
405 Ok(_) => break,
406 Err(x) => v = x,
407 }
408 }
409 }
410 }
411 None => thread_yield(),
412 }
413 if j.state.load(SeqCst) == JS_DONE {
414 return;
415 }
416 }
417}
418
419fn worker(k: usize) {
420 let top = 0u8;
421 POOL.stk[k].store(core::hint::black_box(&top) as *const u8 as usize, SeqCst);
422 'outer: loop {
423 let j = pick();
424 if !j.is_null() {
425 let j = unsafe { &*j };
427 run_items(j);
428 leave(j);
429 continue;
430 }
431 if POOL.quit.load(SeqCst) != 0 {
432 break;
433 }
434 for _ in 0..SPIN_IDLE {
435 core::hint::spin_loop();
436 if !POOL.head.load(SeqCst).is_null() {
437 continue 'outer;
438 }
439 }
440 if let Some(s) = work_sem() {
441 s.wait(-1);
442 }
443 }
444 POOL.stk[k].store(0, SeqCst);
445}
446
447pub fn parallel_for(count: usize, f: &(dyn Fn(usize, &JobCtl) + Sync)) -> Result<(), Cancelled> {
451 if count == 0 {
452 return Ok(());
453 }
454 let scopes = crate::policy::scopes_here();
456 if !scopes.is_empty() {
457 let s = &scopes;
458 return loop_items(count, &|i, j| crate::policy::in_scopes(s, || f(i, j)));
459 }
460 loop_items(count, f)
461}
462
463fn loop_items(count: usize, f: &(dyn Fn(usize, &JobCtl) + Sync)) -> Result<(), Cancelled> {
464 let fp: *const ItemFn = unsafe { core::mem::transmute::<&(dyn Fn(usize, &JobCtl) + Sync), *const ItemFn>(f) };
467 let job = JobCtl::new(fp, count as u64);
468 job.state.store(JS_RUNNING, SeqCst);
469 unsafe { *job.active.get() = 1 }; let n = thr_count();
472 if n == 0 || count <= 1 {
473 run_items(&job);
474 job.state.store(JS_DONE, SeqCst);
475 } else {
476 q_lock();
477 unsafe { q_push_head(&job) }; q_unlock();
480 if let Some(s) = work_sem() {
481 s.post((count - 1).min(n as usize) as u32);
482 }
483 run_items(&job);
484 leave(&job);
485 wait_done(&job);
486 }
487 job.result()
488}
489
490fn job_cancel(j: &JobCtl) {
493 j.cancel.swap(1, SeqCst);
494 if j.state.load(SeqCst) != JS_RUNNING {
495 return;
496 }
497 q_lock();
498 let fin = unsafe {
500 if *j.flags.get() & JF_QUEUED != 0 && *j.active.get() == 0 {
501 q_unlink(j);
502 true
503 } else {
504 false
505 }
506 };
507 q_unlock();
508 if fin {
509 finish(j);
510 }
511}
512
513pub struct Job {
515 rec: Box<JobCtl>,
516 f: Option<Box<dyn Fn(usize, &JobCtl) + Send + Sync>>,
517}
518
519impl Default for Job {
520 fn default() -> Self {
521 Self::new()
522 }
523}
524
525impl Job {
526 pub fn new() -> Job {
527 Job { rec: Box::new(JobCtl::new(core::ptr::null::<fn(usize, &JobCtl)>() as *const ItemFn, 0)), f: None }
528 }
529
530 fn running(&self) -> bool {
531 self.rec.state.load(SeqCst) == JS_RUNNING
532 }
533
534 pub fn set(&mut self, count: u64, f: impl Fn(usize, &JobCtl) + Send + Sync + 'static) -> bool {
536 if self.running() {
537 return false;
538 }
539 let b: Box<dyn Fn(usize, &JobCtl) + Send + Sync> = Box::new(f);
540 unsafe {
542 *self.rec.f.get() = &*b as &(dyn Fn(usize, &JobCtl) + Sync) as *const ItemFn;
543 *self.rec.count.get() = count;
544 }
545 self.f = Some(b);
546 true
547 }
548
549 pub fn set_count(&mut self, count: u64) -> bool {
551 if self.running() {
552 return false;
553 }
554 unsafe { *self.rec.count.get() = count };
556 true
557 }
558
559 pub fn start(&self) -> bool {
561 let j: &JobCtl = &self.rec;
562 if self.running() {
563 return false;
564 }
565 let n = thr_count();
566 j.next.store(0, SeqCst);
567 j.done.store(0, SeqCst);
568 j.cancel.store(0, SeqCst);
569 unsafe {
571 *j.link.get() = core::ptr::null();
572 *j.active.get() = 0;
573 *j.flags.get() = JF_ASYNC;
574 }
575 j.state.store(JS_RUNNING, SeqCst);
576 if j.count() == 0 || self.f.is_none() {
577 finish(j);
578 return true;
579 }
580 if n == 0 {
581 unsafe { *j.active.get() = 1 }; run_items(j);
584 finish(j);
585 return true;
586 }
587 q_lock();
588 unsafe { q_push_tail(j) };
590 q_unlock();
591 if let Some(s) = work_sem() {
592 s.post(j.count().min(n as u64) as u32);
593 }
594 true
595 }
596
597 pub fn wait(&self) -> Result<(), Cancelled> {
599 let j: &JobCtl = &self.rec;
600 if self.running() {
601 q_lock();
602 let help = unsafe {
604 if *j.flags.get() & JF_QUEUED != 0 {
605 *j.active.get() += 1;
606 true
607 } else {
608 false
609 }
610 };
611 q_unlock();
612 if help {
613 run_items(j);
614 leave(j);
615 }
616 wait_done(j);
617 }
618 j.result()
619 }
620
621 pub fn cancel(&self) {
623 job_cancel(&self.rec)
624 }
625
626 pub fn done(&self) -> bool {
628 !self.running()
629 }
630
631 pub fn items_done(&self) -> u64 {
632 self.rec.items_done()
633 }
634
635 pub fn count(&self) -> u64 {
636 self.rec.count()
637 }
638}
639
640impl Drop for Job {
641 fn drop(&mut self) {
642 if self.running() {
643 self.cancel();
644 let _ = self.wait();
645 }
646 }
647}
648
649pub fn thr_self() -> usize {
651 let here = 0u8;
652 let sp = core::hint::black_box(&here) as *const u8 as usize;
653 let n = (POOL.n.load(SeqCst) as usize).min(THR_MAX);
654 for k in 0..n {
655 let top = POOL.stk[k].load(SeqCst);
656 if top != 0 && sp <= top && sp > top.wrapping_sub(THR_STACK - 65536) {
657 return k;
658 }
659 }
660 n
661}