1use std::cell::Cell;
16use std::time::Duration;
17
18use rustc_abi::{Endian, FieldIdx, Size};
19
20use crate::concurrency::sync::{AccessKind, FutexRef, SyncObj};
21use crate::*;
22
23const SYNC_UNINIT: u8 = 0;
25const SYNC_INIT: u8 = 1;
26
27#[derive(Clone)]
28enum MacOsUnfairLock {
29 Active {
30 mutex_ref: MutexRef,
31 },
32 PermanentlyLockedByUnknown,
37}
38
39impl SyncObj for MacOsUnfairLock {
40 fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
41 if let MacOsUnfairLock::Active { mutex_ref } = self
42 && !mutex_ref.queue_is_empty()
43 {
44 throw_ub_format!(
45 "{access_kind} of `os_unfair_lock` is forbidden while the queue is non-empty"
46 );
47 }
48 interp_ok(())
49 }
50
51 fn delete_on_write(&self) -> bool {
52 true
53 }
54}
55
56pub enum MacOsFutexTimeout<'a, 'tcx> {
57 None,
58 Relative { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
59 Absolute { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
60}
61
62struct MacOsFutex {
69 futex: FutexRef,
70 size: Cell<u64>,
72 shared: Cell<bool>,
74}
75
76impl SyncObj for MacOsFutex {}
77
78#[derive(Clone)]
79struct MacOsSemaphore {
80 semaphore: SemaphoreRef,
81 init_value: u64,
82}
83
84const SEM_INIT_OFFSET: Size = Size::ZERO;
86
87const DISPATCH_TIME_NOW: u64 = 0;
89const DISPATCH_TIME_FOREVER: u64 = !0;
90
91impl SyncObj for MacOsSemaphore {
92 fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
93 if !self.semaphore.queue_is_empty() {
94 throw_ub_format!(
95 "{access_kind} of `os_unfair_lock` is forbidden while the queue is non-empty"
96 );
97 }
98 interp_ok(())
99 }
100
101 fn delete_on_write(&self) -> bool {
102 true
103 }
104}
105
106impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
107trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
108 fn os_unfair_lock_get_data<'a>(
109 &'a mut self,
110 lock_ptr: &OpTy<'tcx>,
111 ) -> InterpResult<'tcx, &'a MacOsUnfairLock>
112 where
113 'tcx: 'a,
114 {
115 let this = self.eval_context_mut();
118 assert!(this.tcx.data_layout.endian == Endian::Little);
119
120 let lock = this.deref_pointer_as(lock_ptr, this.libc_ty_layout("os_unfair_lock_s"))?;
121 this.get_immovable_sync_with_static_init(
122 &lock,
123 Size::ZERO, SYNC_UNINIT,
125 SYNC_INIT,
126 |this| {
127 let field = this.project_field(&lock, FieldIdx::from_u32(0))?;
128 let val = this.read_scalar(&field)?.to_u32()?;
129 if val == 0 {
130 interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() })
131 } else if val == 1 {
132 interp_ok(MacOsUnfairLock::PermanentlyLockedByUnknown)
142 } else {
143 throw_ub_format!("`os_unfair_lock` was not properly initialized at this location, or it got overwritten");
144 }
145 },
146 )
147 }
148
149 fn dispatch_semaphore_get_data<'a>(
150 &'a mut self,
151 sem_ptr: &ImmTy<'tcx>,
152 ) -> InterpResult<'tcx, &'a MacOsSemaphore>
153 where
154 'tcx: 'a,
155 {
156 let this = self.eval_context_mut();
157 let sem = this.deref_pointer_as(sem_ptr, this.machine.layouts.u8)?;
158 this.get_immovable_sync_with_static_init(
159 &sem,
160 SEM_INIT_OFFSET,
161 SYNC_UNINIT,
162 SYNC_INIT,
163 |_ecx| throw_ub_format!("`dispatch_semaphore_t` value has not been initialized with `dispatch_semaphore_create`"),
164 )
165 }
166}
167
168impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
169pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
170 fn os_sync_wait_on_address(
177 &mut self,
178 addr_op: &OpTy<'tcx>,
179 value_op: &OpTy<'tcx>,
180 size_op: &OpTy<'tcx>,
181 flags_op: &OpTy<'tcx>,
182 timeout: MacOsFutexTimeout<'_, 'tcx>,
183 dest: &MPlaceTy<'tcx>,
184 ) -> InterpResult<'tcx> {
185 let this = self.eval_context_mut();
186 let none = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_NONE");
187 let shared = this.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_SHARED");
188 let absolute_clock = this.eval_libc_u32("OS_CLOCK_MACH_ABSOLUTE_TIME");
189
190 let ptr = this.read_pointer(addr_op)?;
191 let value = this.read_scalar(value_op)?.to_u64()?;
192 let size = this.read_target_usize(size_op)?;
193 let flags = this.read_scalar(flags_op)?.to_u32()?;
194
195 let clock_timeout = match timeout {
196 MacOsFutexTimeout::None => None,
197 MacOsFutexTimeout::Relative { clock_op, timeout_op } => {
198 let clock = this.read_scalar(clock_op)?.to_u32()?;
199 let timeout = this.read_scalar(timeout_op)?.to_u64()?;
200 Some((clock, TimeoutStyle::Relative, timeout))
201 }
202 MacOsFutexTimeout::Absolute { clock_op, timeout_op } => {
203 let clock = this.read_scalar(clock_op)?.to_u32()?;
204 let timeout = this.read_scalar(timeout_op)?.to_u64()?;
205 Some((clock, TimeoutStyle::Absolute, timeout))
206 }
207 };
208
209 let addr = ptr.addr().bytes();
211 if addr == 0
212 || !matches!(size, 4 | 8)
213 || !addr.is_multiple_of(size)
214 || (flags != none && flags != shared)
215 || clock_timeout
216 .is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0)
217 {
218 this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
219 return interp_ok(());
220 }
221
222 let is_shared = flags == shared;
223 let deadline = clock_timeout.map(|(_, style, timeout)| {
224 this.machine.timeout(TimeoutClock::Monotonic, style, Duration::from_nanos(timeout))
229 });
230
231 this.atomic_fence(AtomicFenceOrd::SeqCst)?;
233
234 let layout = this.machine.layouts.uint(Size::from_bytes(size)).unwrap();
235 let futex_val = this
236 .read_scalar_atomic(&this.ptr_to_mplace(ptr, layout), AtomicReadOrd::Acquire)?
237 .to_bits(Size::from_bytes(size))?;
238
239 let futex = this
240 .get_sync_or_init(ptr, |_| {
241 MacOsFutex {
242 futex: Default::default(),
243 size: Cell::new(size),
244 shared: Cell::new(is_shared),
245 }
246 })
247 .unwrap();
248
249 if futex.futex.waiters() == 0 {
254 futex.size.set(size);
255 futex.shared.set(is_shared);
256 } else if futex.size.get() != size || futex.shared.get() != is_shared {
257 this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
258 return interp_ok(());
259 }
260
261 if futex_val == value.into() {
262 let futex_ref = futex.futex.clone();
264 let dest = dest.clone();
265 this.futex_wait(
266 futex_ref.clone(),
267 u32::MAX, deadline,
269 callback!(
270 @capture<'tcx> {
271 dest: MPlaceTy<'tcx>,
272 futex_ref: FutexRef,
273 }
274 |this, unblock: UnblockKind| {
275 match unblock {
276 UnblockKind::Ready => {
277 let remaining = futex_ref.waiters().try_into().unwrap();
278 this.write_scalar(Scalar::from_i32(remaining), &dest)
279 }
280 UnblockKind::TimedOut => {
281 this.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
282 }
283 }
284 }
285 ),
286 );
287 } else {
288 let waiters = futex.futex.waiters().try_into().unwrap();
290 this.write_scalar(Scalar::from_i32(waiters), dest)?;
291 }
292
293 interp_ok(())
294 }
295
296 fn os_sync_wake_by_address(
301 &mut self,
302 addr_op: &OpTy<'tcx>,
303 size_op: &OpTy<'tcx>,
304 flags_op: &OpTy<'tcx>,
305 all: bool,
306 dest: &MPlaceTy<'tcx>,
307 ) -> InterpResult<'tcx> {
308 let this = self.eval_context_mut();
309 let none = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_NONE");
310 let shared = this.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_SHARED");
311
312 let ptr = this.read_pointer(addr_op)?;
313 let size = this.read_target_usize(size_op)?;
314 let flags = this.read_scalar(flags_op)?.to_u32()?;
315
316 let addr = ptr.addr().bytes();
318 if addr == 0 || !matches!(size, 4 | 8) || (flags != none && flags != shared) {
319 this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
320 return interp_ok(());
321 }
322
323 let is_shared = flags == shared;
324
325 let Some(futex) = this.get_sync_or_init(ptr, |_| {
326 MacOsFutex {
327 futex: Default::default(),
328 size: Cell::new(size),
329 shared: Cell::new(is_shared),
330 }
331 }) else {
332 this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?;
338 return interp_ok(());
339 };
340
341 if futex.futex.waiters() == 0 {
342 this.set_errno_and_return_neg1(LibcError("ENOENT"), dest)?;
343 return interp_ok(());
344 } else if futex.size.get() != size || futex.shared.get() != is_shared {
349 this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
350 return interp_ok(());
351 }
352
353 let futex_ref = futex.futex.clone();
354
355 this.atomic_fence(AtomicFenceOrd::SeqCst)?;
357 this.futex_wake(&futex_ref, u32::MAX, if all { usize::MAX } else { 1 })?;
358 this.write_scalar(Scalar::from_i32(0), dest)?;
359 interp_ok(())
360 }
361
362 fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
363 let this = self.eval_context_mut();
364
365 let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
366 throw_unsup_format!(
370 "attempted to lock an os_unfair_lock that was copied while being locked"
371 );
372 };
373 let mutex_ref = mutex_ref.clone();
374
375 if let Some(owner) = mutex_ref.owner() {
376 if owner == this.active_thread() {
377 throw_machine_stop!(TerminationInfo::Abort(
379 "attempted to lock an os_unfair_lock that is already locked by the current thread".to_owned()
380 ));
381 }
382
383 this.mutex_enqueue_and_block(mutex_ref, None);
384 } else {
385 this.mutex_lock(&mutex_ref)?;
386 }
387
388 interp_ok(())
389 }
390
391 fn os_unfair_lock_trylock(
392 &mut self,
393 lock_op: &OpTy<'tcx>,
394 dest: &MPlaceTy<'tcx>,
395 ) -> InterpResult<'tcx> {
396 let this = self.eval_context_mut();
397
398 let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
399 this.write_scalar(Scalar::from_bool(false), dest)?;
402 return interp_ok(());
403 };
404 let mutex_ref = mutex_ref.clone();
405
406 if mutex_ref.owner().is_some() {
407 this.write_scalar(Scalar::from_bool(false), dest)?;
409 } else {
410 this.mutex_lock(&mutex_ref)?;
411 this.write_scalar(Scalar::from_bool(true), dest)?;
412 }
413
414 interp_ok(())
415 }
416
417 fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
418 let this = self.eval_context_mut();
419
420 let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
421 throw_unsup_format!(
423 "attempted to unlock an os_unfair_lock that was copied while being locked"
424 );
425 };
426 let mutex_ref = mutex_ref.clone();
427
428 if this.mutex_unlock(&mutex_ref)?.is_none() {
430 throw_machine_stop!(TerminationInfo::Abort(
432 "attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
433 ));
434 }
435
436 if mutex_ref.owner().is_none() {
439 let lock_place = this.deref_pointer_as(lock_op, this.machine.layouts.u32)?;
440 this.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?;
441 }
442
443 interp_ok(())
444 }
445
446 fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
447 let this = self.eval_context_mut();
448
449 let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
450 throw_unsup_format!(
452 "attempted to assert the owner of an os_unfair_lock that was copied while being locked"
453 );
454 };
455 let mutex_ref = mutex_ref.clone();
456
457 if mutex_ref.owner().is_none_or(|o| o != this.active_thread()) {
458 throw_machine_stop!(TerminationInfo::Abort(
459 "called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
460 ));
461 }
462
463 interp_ok(())
466 }
467
468 fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
469 let this = self.eval_context_mut();
470
471 let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
472 throw_unsup_format!(
474 "attempted to assert the owner of an os_unfair_lock that was copied while being locked"
475 );
476 };
477 let mutex_ref = mutex_ref.clone();
478
479 if mutex_ref.owner().is_some_and(|o| o == this.active_thread()) {
480 throw_machine_stop!(TerminationInfo::Abort(
481 "called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread".to_owned()
482 ));
483 }
484
485 if mutex_ref.owner().is_none() {
488 let lock_place = this.deref_pointer_as(lock_op, this.machine.layouts.u32)?;
489 this.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?;
490 }
491
492 interp_ok(())
493 }
494
495 fn dispatch_semaphore_create(
496 &mut self,
497 value: &OpTy<'tcx>,
498 dest: &MPlaceTy<'tcx>,
499 ) -> InterpResult<'tcx> {
500 let this = self.eval_context_mut();
501
502 let value = this.read_target_isize(value)?;
503
504 let Ok(value) = u64::try_from(value) else {
505 throw_machine_stop!(TerminationInfo::Abort(
506 "dispatch_semaphore_create called with a negative value".to_owned()
507 ));
508 };
509
510 let alloc = this.allocate(this.machine.layouts.u8, MiriMemoryKind::Runtime.into())?;
512 let data = MacOsSemaphore { semaphore: SemaphoreRef::new(value), init_value: value };
514 this.init_immovable_sync(&alloc, SEM_INIT_OFFSET, SYNC_INIT, data)?;
515
516 this.write_pointer(alloc.ptr(), dest)?;
517 interp_ok(())
518 }
519
520 fn dispatch_semaphore_wait(
521 &mut self,
522 sem_ptr: &OpTy<'tcx>,
523 timeout: &OpTy<'tcx>,
524 dest: &MPlaceTy<'tcx>,
525 ) -> InterpResult<'tcx> {
526 let this = self.eval_context_mut();
527
528 let sem_ptr = this.read_immediate(sem_ptr)?;
529 let timeout = this.read_scalar(timeout)?.to_u64()?;
530
531 let deadline = if timeout == DISPATCH_TIME_FOREVER {
532 None
533 } else {
534 let deadline =
537 this.machine.monotonic_clock.epoch().add_lossy(Duration::from_nanos(timeout));
538 Some(deadline.into())
539 };
540
541 let data = this.dispatch_semaphore_get_data(&sem_ptr)?.clone();
542 let dest = dest.clone();
543 this.semaphore_decrement(
544 data.semaphore,
545 deadline,
546 callback!(
547 @capture<'tcx> {
548 dest: MPlaceTy<'tcx>,
549 }
550 |this, unblock: UnblockKind| {
551 match unblock {
552 UnblockKind::Ready => this.write_null(&dest),
553 UnblockKind::TimedOut => this.write_int(1, &dest),
554 }
555 }
556 ),
557 )?;
558
559 interp_ok(())
560 }
561
562 fn dispatch_semaphore_signal(
563 &mut self,
564 sem_ptr: &OpTy<'tcx>,
565 dest: &MPlaceTy<'tcx>,
566 ) -> InterpResult<'tcx> {
567 let this = self.eval_context_mut();
568
569 let sem_ptr = this.read_immediate(sem_ptr)?;
570
571 let data = this.dispatch_semaphore_get_data(&sem_ptr)?.clone();
572 let woke_thread = this.semaphore_increment(&data.semaphore)?;
573
574 this.write_int(woke_thread, dest)?;
579 interp_ok(())
580 }
581
582 fn dispatch_release(&mut self, sem_ptr: &OpTy<'tcx>) -> InterpResult<'tcx> {
583 let this = self.eval_context_mut();
584
585 let sem_ptr = this.read_immediate(sem_ptr)?;
586
587 let data = this.dispatch_semaphore_get_data(&sem_ptr)?.clone();
588 if data.semaphore.value() < data.init_value {
590 throw_machine_stop!(TerminationInfo::Abort(
591 "dispatch_semaphore destroyed while in use".to_owned()
592 ));
593 }
594
595 let sem_ptr = sem_ptr.to_scalar().to_pointer(this);
597 this.deallocate_ptr(sem_ptr, None, MiriMemoryKind::Runtime.into())?;
598
599 interp_ok(())
600 }
601
602 fn dispatch_time(
603 &mut self,
604 when: &OpTy<'tcx>,
605 delta: &OpTy<'tcx>,
606 dest: &MPlaceTy<'tcx>,
607 ) -> InterpResult<'tcx> {
608 let this = self.eval_context_mut();
609
610 let when = this.read_scalar(when)?.to_u64()?;
611 let delta = this.read_scalar(delta)?.to_i64()?;
612
613 if when != DISPATCH_TIME_NOW {
614 throw_unsup_format!(
615 "dispatch_time is only supported with `when` being `DISPATCH_TIME_NOW`"
616 );
617 }
618 let Ok(delta) = u64::try_from(delta) else {
619 throw_unsup_format!("dispatch_time with negative `delta` is not supported");
620 };
621
622 let deadline = this.machine.monotonic_clock.now().add_lossy(Duration::from_nanos(delta));
624 let duration = deadline.duration_since(this.machine.monotonic_clock.epoch());
626 let Ok(nanos) = u64::try_from(duration.as_nanos()) else {
627 throw_unsup_format!("dispatch_time with too big `delta` is not supported");
628 };
629 this.write_int(nanos, dest)?;
630 interp_ok(())
631 }
632}