1use std::cell::Cell;
16use std::time::Duration;
17
18use rustc_abi::{Endian, FieldIdx, Size};
19use rustc_middle::ty::layout::TyAndLayout;
20
21use crate::concurrency::sync::FutexRef;
22use crate::meta_obj::OPAQUE_OBJ_COOKIE;
23use crate::*;
24
25#[derive(Clone)]
26enum MacOsUnfairLock {
27 Active {
28 mutex_ref: MutexRef,
29 },
30 PermanentlyLockedByUnknown,
35}
36
37impl MetaObj for MacOsUnfairLock {
38 fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
39 if let MacOsUnfairLock::Active { mutex_ref } = self
40 && !mutex_ref.queue_is_empty()
41 {
42 throw_ub_format!(
43 "accessing the memory backing a `os_unfair_lock` is forbidden while the queue is non-empty"
44 );
45 }
46 interp_ok(())
47 }
48
49 fn opaque(&self) -> bool {
50 true
51 }
52}
53
54pub enum MacOsFutexTimeout<'a, 'tcx> {
55 None,
56 Relative { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
57 Absolute { clock_op: &'a OpTy<'tcx>, timeout_op: &'a OpTy<'tcx> },
58}
59
60struct MacOsFutex {
67 futex: FutexRef,
68 size: Cell<u64>,
70}
71
72impl MetaObj for MacOsFutex {
73 fn opaque(&self) -> bool {
74 false
75 }
76}
77
78#[derive(Clone)]
79struct MacOsSemaphore {
80 semaphore: SemaphoreRef,
81 init_value: u64,
82}
83
84const DISPATCH_TIME_NOW: u64 = 0;
86const DISPATCH_TIME_FOREVER: u64 = !0;
87
88impl MetaObj for MacOsSemaphore {
89 fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
90 if !self.semaphore.queue_is_empty() {
91 throw_ub_format!(
92 "accessing the memory behind a `dispatch_semaphore_t` is forbidden while the queue is non-empty"
93 );
94 }
95 interp_ok(())
96 }
97
98 fn opaque(&self) -> bool {
99 true
100 }
101}
102
103fn dispatch_semaphore_layout<'tcx>(ecx: &MiriInterpCx<'tcx>) -> TyAndLayout<'tcx> {
104 ecx.machine.layouts.usize
106}
107
108impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
109trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
110 fn os_unfair_lock_get_data<'a>(
111 &'a mut self,
112 lock_ptr: &OpTy<'tcx>,
113 ) -> InterpResult<'tcx, &'a mut MacOsUnfairLock>
114 where
115 'tcx: 'a,
116 {
117 let ecx = self.eval_context_mut();
120 assert!(ecx.tcx.data_layout.endian == Endian::Little);
121
122 let lock = ecx.deref_pointer(lock_ptr)?;
123 ecx.get_or_insert_meta(
124 &lock,
125 |ecx| {
126 let field = ecx.project_field(&lock, FieldIdx::from_u32(0))?;
127 let val = ecx.read_scalar(&field)?.to_u32()?;
128 if val == 0 {
129 interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() })
130 } else if val == u32::from_ne_bytes([OPAQUE_OBJ_COOKIE; 4]) {
131 interp_ok(MacOsUnfairLock::PermanentlyLockedByUnknown)
141 } else {
142 throw_ub_format!("`os_unfair_lock` was not properly initialized at this location, or it got overwritten");
143 }
144 },
145 )
146 }
147
148 fn dispatch_semaphore_get_data<'a>(
149 &'a self,
150 sem_ptr: Pointer,
151 ) -> InterpResult<'tcx, &'a MacOsSemaphore>
152 where
153 'tcx: 'a,
154 {
155 let ecx = self.eval_context_ref();
156 interp_ok(ecx.try_get_meta(
157 sem_ptr,
158 ).map_err(|_| err_ub_format!("`dispatch_semaphore_t` value has not been initialized with `dispatch_semaphore_create`"))?)
159 }
160}
161
162impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
163pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
164 fn os_sync_wait_on_address(
171 &mut self,
172 addr_op: &OpTy<'tcx>,
173 value_op: &OpTy<'tcx>,
174 size_op: &OpTy<'tcx>,
175 flags_op: &OpTy<'tcx>,
176 timeout: MacOsFutexTimeout<'_, 'tcx>,
177 dest: &MPlaceTy<'tcx>,
178 ) -> InterpResult<'tcx> {
179 let ecx = self.eval_context_mut();
180 let none = ecx.eval_libc_u32("OS_SYNC_WAIT_ON_ADDRESS_NONE");
181 let absolute_clock = ecx.eval_libc_u32("OS_CLOCK_MACH_ABSOLUTE_TIME");
182
183 let ptr = ecx.read_pointer(addr_op)?;
184 let value = ecx.read_scalar(value_op)?.to_u64()?;
185 let size = ecx.read_target_usize(size_op)?;
186 let flags = ecx.read_scalar(flags_op)?.to_u32()?;
187
188 let clock_timeout = match timeout {
189 MacOsFutexTimeout::None => None,
190 MacOsFutexTimeout::Relative { clock_op, timeout_op } => {
191 let clock = ecx.read_scalar(clock_op)?.to_u32()?;
192 let timeout = ecx.read_scalar(timeout_op)?.to_u64()?;
193 Some((clock, TimeoutStyle::Relative, timeout))
194 }
195 MacOsFutexTimeout::Absolute { clock_op, timeout_op } => {
196 let clock = ecx.read_scalar(clock_op)?.to_u32()?;
197 let timeout = ecx.read_scalar(timeout_op)?.to_u64()?;
198 Some((clock, TimeoutStyle::Absolute, timeout))
199 }
200 };
201
202 let addr = ptr.addr().bytes();
204 if addr == 0
205 || !matches!(size, 4 | 8)
206 || !addr.is_multiple_of(size)
207 || (flags != none)
208 || clock_timeout
209 .is_some_and(|(clock, _, timeout)| clock != absolute_clock || timeout == 0)
210 {
211 ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
212 return interp_ok(());
213 }
214
215 let deadline = clock_timeout.map(|(_, style, timeout)| {
216 ecx.machine.timeout(TimeoutClock::Monotonic, style, Duration::from_nanos(timeout))
221 });
222
223 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
225
226 let layout = ecx.machine.layouts.uint(Size::from_bytes(size)).unwrap();
227 let futex_place = ecx.ptr_to_mplace(ptr, layout);
228 let futex_val = ecx
229 .read_scalar_atomic(&futex_place, AtomicReadOrd::Acquire)?
230 .to_bits(Size::from_bytes(size))?;
231
232 let futex = ecx.get_or_insert_meta(&futex_place, |_| {
233 interp_ok(MacOsFutex { futex: Default::default(), size: Cell::new(size) })
234 })?;
235
236 if futex.futex.waiters() == 0 {
241 futex.size.set(size);
242 } else if futex.size.get() != size {
243 ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
244 return interp_ok(());
245 }
246
247 if futex_val == value.into() {
248 let futex_ref = futex.futex.clone();
250 let dest = dest.clone();
251 ecx.futex_wait(
252 futex_ref.clone(),
253 u32::MAX, deadline,
255 callback!(
256 @capture<'tcx> {
257 dest: MPlaceTy<'tcx>,
258 futex_ref: FutexRef,
259 }
260 |ecx, unblock: UnblockKind| {
261 match unblock {
262 UnblockKind::Ready => {
263 let remaining = futex_ref.waiters().try_into().unwrap();
264 ecx.write_scalar(Scalar::from_i32(remaining), &dest)
265 }
266 UnblockKind::TimedOut => {
267 ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
268 }
269 }
270 }
271 ),
272 );
273 } else {
274 let waiters = futex.futex.waiters().try_into().unwrap();
276 ecx.write_scalar(Scalar::from_i32(waiters), dest)?;
277 }
278
279 interp_ok(())
280 }
281
282 fn os_sync_wake_by_address(
287 &mut self,
288 addr_op: &OpTy<'tcx>,
289 size_op: &OpTy<'tcx>,
290 flags_op: &OpTy<'tcx>,
291 all: bool,
292 dest: &MPlaceTy<'tcx>,
293 ) -> InterpResult<'tcx> {
294 let ecx = self.eval_context_mut();
295 let none = ecx.eval_libc_u32("OS_SYNC_WAKE_BY_ADDRESS_NONE");
296
297 let ptr = ecx.read_pointer(addr_op)?;
298 let size = ecx.read_target_usize(size_op)?;
299 let flags = ecx.read_scalar(flags_op)?.to_u32()?;
300
301 let addr = ptr.addr().bytes();
303 if addr == 0 || !matches!(size, 4 | 8) || (flags != none) {
304 ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
305 return interp_ok(());
306 }
307
308 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
310
311 let Ok(futex) = ecx.try_get_meta::<MacOsFutex>(ptr) else {
312 return ecx.set_errno_and_return_neg1(LibcError("ENOENT"), dest);
314 };
315
316 if futex.futex.waiters() == 0 {
317 return ecx.set_errno_and_return_neg1(LibcError("ENOENT"), dest);
318 } else if futex.size.get() != size {
323 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
324 }
325
326 ecx.futex_wake(&futex.futex.clone(), u32::MAX, if all { usize::MAX } else { 1 })?;
327 ecx.write_scalar(Scalar::from_i32(0), dest)?;
328 interp_ok(())
329 }
330
331 fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
332 let ecx = self.eval_context_mut();
333
334 let MacOsUnfairLock::Active { mutex_ref } = ecx.os_unfair_lock_get_data(lock_op)? else {
335 throw_unsup_format!(
339 "attempted to lock an os_unfair_lock that was copied while being locked"
340 );
341 };
342 let mutex_ref = mutex_ref.clone();
343
344 if let Some(owner) = mutex_ref.owner() {
345 if owner == ecx.active_thread() {
346 throw_machine_stop!(TerminationInfo::Abort(
348 "attempted to lock an os_unfair_lock that is already locked by the current thread".to_owned()
349 ));
350 }
351
352 ecx.mutex_enqueue_and_block(mutex_ref, None);
353 } else {
354 ecx.mutex_lock(&mutex_ref)?;
355 }
356
357 interp_ok(())
358 }
359
360 fn os_unfair_lock_trylock(
361 &mut self,
362 lock_op: &OpTy<'tcx>,
363 dest: &MPlaceTy<'tcx>,
364 ) -> InterpResult<'tcx> {
365 let ecx = self.eval_context_mut();
366
367 let MacOsUnfairLock::Active { mutex_ref } = ecx.os_unfair_lock_get_data(lock_op)? else {
368 ecx.write_scalar(Scalar::from_bool(false), dest)?;
371 return interp_ok(());
372 };
373 let mutex_ref = mutex_ref.clone();
374
375 if mutex_ref.owner().is_some() {
376 ecx.write_scalar(Scalar::from_bool(false), dest)?;
378 } else {
379 ecx.mutex_lock(&mutex_ref)?;
380 ecx.write_scalar(Scalar::from_bool(true), dest)?;
381 }
382
383 interp_ok(())
384 }
385
386 fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
387 let ecx = self.eval_context_mut();
388
389 let MacOsUnfairLock::Active { mutex_ref } = ecx.os_unfair_lock_get_data(lock_op)? else {
390 throw_unsup_format!(
392 "attempted to unlock an os_unfair_lock that was copied while being locked"
393 );
394 };
395 let mutex_ref = mutex_ref.clone();
396
397 if ecx.mutex_unlock(&mutex_ref)?.is_none() {
399 throw_machine_stop!(TerminationInfo::Abort(
401 "attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
402 ));
403 }
404
405 if mutex_ref.owner().is_none() {
408 let lock_place =
409 ecx.project_field(&ecx.deref_pointer(lock_op)?, FieldIdx::from_u32(0))?;
410 ecx.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?;
411 }
412
413 interp_ok(())
414 }
415
416 fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
417 let ecx = self.eval_context_mut();
418
419 let MacOsUnfairLock::Active { mutex_ref } = ecx.os_unfair_lock_get_data(lock_op)? else {
420 throw_unsup_format!(
422 "attempted to assert the owner of an os_unfair_lock that was copied while being locked"
423 );
424 };
425 let mutex_ref = mutex_ref.clone();
426
427 if mutex_ref.owner().is_none_or(|o| o != ecx.active_thread()) {
428 throw_machine_stop!(TerminationInfo::Abort(
429 "called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
430 ));
431 }
432
433 interp_ok(())
436 }
437
438 fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
439 let ecx = self.eval_context_mut();
440
441 let MacOsUnfairLock::Active { mutex_ref } = ecx.os_unfair_lock_get_data(lock_op)? else {
442 throw_unsup_format!(
444 "attempted to assert the owner of an os_unfair_lock that was copied while being locked"
445 );
446 };
447 let mutex_ref = mutex_ref.clone();
448
449 if mutex_ref.owner().is_some_and(|o| o == ecx.active_thread()) {
450 throw_machine_stop!(TerminationInfo::Abort(
451 "called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread".to_owned()
452 ));
453 }
454
455 if mutex_ref.owner().is_none() {
458 let lock_place =
459 ecx.project_field(&ecx.deref_pointer(lock_op)?, FieldIdx::from_u32(0))?;
460 ecx.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?;
461 }
462
463 interp_ok(())
464 }
465
466 fn dispatch_semaphore_create(
467 &mut self,
468 value: &OpTy<'tcx>,
469 dest: &MPlaceTy<'tcx>,
470 ) -> InterpResult<'tcx> {
471 let ecx = self.eval_context_mut();
472
473 let value = ecx.read_target_isize(value)?;
474
475 let Ok(value) = u64::try_from(value) else {
476 throw_machine_stop!(TerminationInfo::Abort(
477 "dispatch_semaphore_create called with a negative value".to_owned()
478 ));
479 };
480
481 let alloc = ecx.allocate(dispatch_semaphore_layout(ecx), MiriMemoryKind::Runtime.into())?;
483 let data = MacOsSemaphore { semaphore: SemaphoreRef::new(value), init_value: value };
485 ecx.insert_meta(&alloc, Box::new(data))?;
486
487 ecx.write_pointer(alloc.ptr(), dest)?;
488 interp_ok(())
489 }
490
491 fn dispatch_semaphore_wait(
492 &mut self,
493 sem_ptr: &OpTy<'tcx>,
494 timeout: &OpTy<'tcx>,
495 dest: &MPlaceTy<'tcx>,
496 ) -> InterpResult<'tcx> {
497 let ecx = self.eval_context_mut();
498
499 let sem_ptr = ecx.read_pointer(sem_ptr)?;
500 let timeout = ecx.read_scalar(timeout)?.to_u64()?;
501
502 let deadline = if timeout == DISPATCH_TIME_FOREVER {
503 None
504 } else {
505 let deadline =
508 ecx.machine.monotonic_clock.epoch().add_lossy(Duration::from_nanos(timeout));
509 Some(deadline.into())
510 };
511
512 let data = ecx.dispatch_semaphore_get_data(sem_ptr)?.clone();
513 let dest = dest.clone();
514 ecx.semaphore_decrement(
515 data.semaphore,
516 deadline,
517 callback!(
518 @capture<'tcx> {
519 dest: MPlaceTy<'tcx>,
520 }
521 |ecx, unblock: UnblockKind| {
522 match unblock {
523 UnblockKind::Ready => ecx.write_int(0, &dest),
524 UnblockKind::TimedOut => ecx.write_int(49, &dest),
525 }
526 }
527 ),
528 )?;
529
530 interp_ok(())
531 }
532
533 fn dispatch_semaphore_signal(
534 &mut self,
535 sem_ptr: &OpTy<'tcx>,
536 dest: &MPlaceTy<'tcx>,
537 ) -> InterpResult<'tcx> {
538 let ecx = self.eval_context_mut();
539
540 let sem_ptr = ecx.read_pointer(sem_ptr)?;
541
542 let data = ecx.dispatch_semaphore_get_data(sem_ptr)?.clone();
543 let woke_thread = ecx.semaphore_increment(&data.semaphore)?;
544
545 ecx.write_int(woke_thread, dest)?;
550 interp_ok(())
551 }
552
553 fn dispatch_release(&mut self, sem_ptr: &OpTy<'tcx>) -> InterpResult<'tcx> {
554 let ecx = self.eval_context_mut();
555
556 let sem_ptr = ecx.read_pointer(sem_ptr)?;
557
558 let data = ecx.dispatch_semaphore_get_data(sem_ptr)?.clone();
559 if data.semaphore.value() < data.init_value {
561 throw_machine_stop!(TerminationInfo::Abort(
562 "dispatch_semaphore destroyed while in use".to_owned()
563 ));
564 }
565
566 let layout = dispatch_semaphore_layout(ecx);
568 ecx.deallocate_ptr(
569 sem_ptr,
570 Some((layout.size, layout.align)),
571 MiriMemoryKind::Runtime.into(),
572 )?;
573
574 interp_ok(())
575 }
576
577 fn dispatch_time(
578 &mut self,
579 when: &OpTy<'tcx>,
580 delta: &OpTy<'tcx>,
581 dest: &MPlaceTy<'tcx>,
582 ) -> InterpResult<'tcx> {
583 let ecx = self.eval_context_mut();
584
585 let when = ecx.read_scalar(when)?.to_u64()?;
586 let delta = ecx.read_scalar(delta)?.to_i64()?;
587
588 if when != DISPATCH_TIME_NOW {
589 throw_unsup_format!(
590 "dispatch_time is only supported with `when` being `DISPATCH_TIME_NOW`"
591 );
592 }
593 let Ok(delta) = u64::try_from(delta) else {
594 throw_unsup_format!("dispatch_time with negative `delta` is not supported");
595 };
596
597 let deadline = ecx.machine.monotonic_clock.now().add_lossy(Duration::from_nanos(delta));
599 let duration = deadline.duration_since(ecx.machine.monotonic_clock.epoch());
601 let Ok(nanos) = u64::try_from(duration.as_nanos()) else {
602 throw_unsup_format!("dispatch_time with too big `delta` is not supported");
603 };
604 ecx.write_int(nanos, dest)?;
605 interp_ok(())
606 }
607}