]> git.lizzy.rs Git - rust.git/blob - src/shims/sync.rs
make mutex_unlock infallible
[rust.git] / src / shims / sync.rs
1 use std::convert::TryInto;
2 use std::time::{Duration, SystemTime};
3 use std::ops::Not;
4
5 use rustc_middle::ty::{layout::TyAndLayout, TyKind, TypeAndMut};
6 use rustc_target::abi::{LayoutOf, Size};
7
8 use crate::stacked_borrows::Tag;
9 use crate::thread::Time;
10
11 use crate::*;
12
13 fn assert_ptr_target_min_size<'mir, 'tcx: 'mir>(
14     ecx: &MiriEvalContext<'mir, 'tcx>,
15     operand: OpTy<'tcx, Tag>,
16     min_size: u64,
17 ) -> InterpResult<'tcx, ()> {
18     let target_ty = match operand.layout.ty.kind {
19         TyKind::RawPtr(TypeAndMut { ty, mutbl: _ }) => ty,
20         _ => panic!("Argument to pthread function was not a raw pointer"),
21     };
22     let target_layout = ecx.layout_of(target_ty)?;
23     assert!(target_layout.size.bytes() >= min_size);
24     Ok(())
25 }
26
27 fn get_at_offset<'mir, 'tcx: 'mir>(
28     ecx: &MiriEvalContext<'mir, 'tcx>,
29     op: OpTy<'tcx, Tag>,
30     offset: u64,
31     layout: TyAndLayout<'tcx>,
32     min_size: u64,
33 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
34     // Ensure that the following read at an offset to the attr pointer is within bounds
35     assert_ptr_target_min_size(ecx, op, min_size)?;
36     let op_place = ecx.deref_operand(op)?;
37     let value_place = op_place.offset(Size::from_bytes(offset), MemPlaceMeta::None, layout, ecx)?;
38     ecx.read_scalar(value_place.into())
39 }
40
41 fn set_at_offset<'mir, 'tcx: 'mir>(
42     ecx: &mut MiriEvalContext<'mir, 'tcx>,
43     op: OpTy<'tcx, Tag>,
44     offset: u64,
45     value: impl Into<ScalarMaybeUninit<Tag>>,
46     layout: TyAndLayout<'tcx>,
47     min_size: u64,
48 ) -> InterpResult<'tcx, ()> {
49     // Ensure that the following write at an offset to the attr pointer is within bounds
50     assert_ptr_target_min_size(ecx, op, min_size)?;
51     let op_place = ecx.deref_operand(op)?;
52     let value_place = op_place.offset(Size::from_bytes(offset), MemPlaceMeta::None, layout, ecx)?;
53     ecx.write_scalar(value.into(), value_place.into())
54 }
55
56 // pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform.
57
58 // Our chosen memory layout for emulation (does not have to match the platform layout!):
59 // store an i32 in the first four bytes equal to the corresponding libc mutex kind constant
60 // (e.g. PTHREAD_MUTEX_NORMAL).
61
62 /// A flag that allows to distinguish `PTHREAD_MUTEX_NORMAL` from
63 /// `PTHREAD_MUTEX_DEFAULT`. Since in `glibc` they have the same numeric values,
64 /// but different behaviour, we need a way to distinguish them. We do this by
65 /// setting this bit flag to the `PTHREAD_MUTEX_NORMAL` mutexes. See the comment
66 /// in `pthread_mutexattr_settype` function.
67 const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000;
68
69 const PTHREAD_MUTEXATTR_T_MIN_SIZE: u64 = 4;
70
71 fn is_mutex_kind_default<'mir, 'tcx: 'mir>(
72     ecx: &mut MiriEvalContext<'mir, 'tcx>,
73     kind: Scalar<Tag>,
74 ) -> InterpResult<'tcx, bool> {
75     Ok(kind == ecx.eval_libc("PTHREAD_MUTEX_DEFAULT")?)
76 }
77
78 fn is_mutex_kind_normal<'mir, 'tcx: 'mir>(
79     ecx: &mut MiriEvalContext<'mir, 'tcx>,
80     kind: Scalar<Tag>,
81 ) -> InterpResult<'tcx, bool> {
82     let kind = kind.to_i32()?;
83     let mutex_normal_kind = ecx.eval_libc("PTHREAD_MUTEX_NORMAL")?.to_i32()?;
84     Ok(kind == (mutex_normal_kind | PTHREAD_MUTEX_NORMAL_FLAG))
85 }
86
87 fn mutexattr_get_kind<'mir, 'tcx: 'mir>(
88     ecx: &MiriEvalContext<'mir, 'tcx>,
89     attr_op: OpTy<'tcx, Tag>,
90 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
91     get_at_offset(ecx, attr_op, 0, ecx.machine.layouts.i32, PTHREAD_MUTEXATTR_T_MIN_SIZE)
92 }
93
94 fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
95     ecx: &mut MiriEvalContext<'mir, 'tcx>,
96     attr_op: OpTy<'tcx, Tag>,
97     kind: impl Into<ScalarMaybeUninit<Tag>>,
98 ) -> InterpResult<'tcx, ()> {
99     set_at_offset(ecx, attr_op, 0, kind, ecx.machine.layouts.i32, PTHREAD_MUTEXATTR_T_MIN_SIZE)
100 }
101
102 // pthread_mutex_t is between 24 and 48 bytes, depending on the platform.
103
104 // Our chosen memory layout for the emulated mutex (does not have to match the platform layout!):
105 // bytes 0-3: reserved for signature on macOS
106 // (need to avoid this because it is set by static initializer macros)
107 // bytes 4-7: mutex id as u32 or 0 if id is not assigned yet.
108 // bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32
109 // (the kind has to be at its offset for compatibility with static initializer macros)
110
111 const PTHREAD_MUTEX_T_MIN_SIZE: u64 = 24;
112
113 fn mutex_get_kind<'mir, 'tcx: 'mir>(
114     ecx: &mut MiriEvalContext<'mir, 'tcx>,
115     mutex_op: OpTy<'tcx, Tag>,
116 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
117     let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
118     get_at_offset(ecx, mutex_op, offset, ecx.machine.layouts.i32, PTHREAD_MUTEX_T_MIN_SIZE)
119 }
120
121 fn mutex_set_kind<'mir, 'tcx: 'mir>(
122     ecx: &mut MiriEvalContext<'mir, 'tcx>,
123     mutex_op: OpTy<'tcx, Tag>,
124     kind: impl Into<ScalarMaybeUninit<Tag>>,
125 ) -> InterpResult<'tcx, ()> {
126     let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
127     set_at_offset(ecx, mutex_op, offset, kind, ecx.machine.layouts.i32, PTHREAD_MUTEX_T_MIN_SIZE)
128 }
129
130 fn mutex_get_id<'mir, 'tcx: 'mir>(
131     ecx: &MiriEvalContext<'mir, 'tcx>,
132     mutex_op: OpTy<'tcx, Tag>,
133 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
134     get_at_offset(ecx, mutex_op, 4, ecx.machine.layouts.u32, PTHREAD_MUTEX_T_MIN_SIZE)
135 }
136
137 fn mutex_set_id<'mir, 'tcx: 'mir>(
138     ecx: &mut MiriEvalContext<'mir, 'tcx>,
139     mutex_op: OpTy<'tcx, Tag>,
140     id: impl Into<ScalarMaybeUninit<Tag>>,
141 ) -> InterpResult<'tcx, ()> {
142     set_at_offset(ecx, mutex_op, 4, id, ecx.machine.layouts.u32, PTHREAD_MUTEX_T_MIN_SIZE)
143 }
144
145 fn mutex_get_or_create_id<'mir, 'tcx: 'mir>(
146     ecx: &mut MiriEvalContext<'mir, 'tcx>,
147     mutex_op: OpTy<'tcx, Tag>,
148 ) -> InterpResult<'tcx, MutexId> {
149     let id = mutex_get_id(ecx, mutex_op)?.to_u32()?;
150     if id == 0 {
151         // 0 is a default value and also not a valid mutex id. Need to allocate
152         // a new mutex.
153         let id = ecx.mutex_create();
154         mutex_set_id(ecx, mutex_op, id.to_u32_scalar())?;
155         Ok(id)
156     } else {
157         Ok(MutexId::from_u32(id))
158     }
159 }
160
161 // pthread_rwlock_t is between 32 and 56 bytes, depending on the platform.
162
163 // Our chosen memory layout for the emulated rwlock (does not have to match the platform layout!):
164 // bytes 0-3: reserved for signature on macOS
165 // (need to avoid this because it is set by static initializer macros)
166 // bytes 4-7: rwlock id as u32 or 0 if id is not assigned yet.
167
168 const PTHREAD_RWLOCK_T_MIN_SIZE: u64 = 32;
169
170 fn rwlock_get_id<'mir, 'tcx: 'mir>(
171     ecx: &MiriEvalContext<'mir, 'tcx>,
172     rwlock_op: OpTy<'tcx, Tag>,
173 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
174     get_at_offset(ecx, rwlock_op, 4, ecx.machine.layouts.u32, PTHREAD_RWLOCK_T_MIN_SIZE)
175 }
176
177 fn rwlock_set_id<'mir, 'tcx: 'mir>(
178     ecx: &mut MiriEvalContext<'mir, 'tcx>,
179     rwlock_op: OpTy<'tcx, Tag>,
180     id: impl Into<ScalarMaybeUninit<Tag>>,
181 ) -> InterpResult<'tcx, ()> {
182     set_at_offset(ecx, rwlock_op, 4, id, ecx.machine.layouts.u32, PTHREAD_RWLOCK_T_MIN_SIZE)
183 }
184
185 fn rwlock_get_or_create_id<'mir, 'tcx: 'mir>(
186     ecx: &mut MiriEvalContext<'mir, 'tcx>,
187     rwlock_op: OpTy<'tcx, Tag>,
188 ) -> InterpResult<'tcx, RwLockId> {
189     let id = rwlock_get_id(ecx, rwlock_op)?.to_u32()?;
190     if id == 0 {
191         // 0 is a default value and also not a valid rwlock id. Need to allocate
192         // a new read-write lock.
193         let id = ecx.rwlock_create();
194         rwlock_set_id(ecx, rwlock_op, id.to_u32_scalar())?;
195         Ok(id)
196     } else {
197         Ok(RwLockId::from_u32(id))
198     }
199 }
200
201 // pthread_condattr_t
202
203 // Our chosen memory layout for emulation (does not have to match the platform layout!):
204 // store an i32 in the first four bytes equal to the corresponding libc clock id constant
205 // (e.g. CLOCK_REALTIME).
206
207 const PTHREAD_CONDATTR_T_MIN_SIZE: u64 = 4;
208
209 fn condattr_get_clock_id<'mir, 'tcx: 'mir>(
210     ecx: &MiriEvalContext<'mir, 'tcx>,
211     attr_op: OpTy<'tcx, Tag>,
212 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
213     get_at_offset(ecx, attr_op, 0, ecx.machine.layouts.i32, PTHREAD_CONDATTR_T_MIN_SIZE)
214 }
215
216 fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
217     ecx: &mut MiriEvalContext<'mir, 'tcx>,
218     attr_op: OpTy<'tcx, Tag>,
219     clock_id: impl Into<ScalarMaybeUninit<Tag>>,
220 ) -> InterpResult<'tcx, ()> {
221     set_at_offset(ecx, attr_op, 0, clock_id, ecx.machine.layouts.i32, PTHREAD_CONDATTR_T_MIN_SIZE)
222 }
223
224 // pthread_cond_t
225
226 // Our chosen memory layout for the emulated conditional variable (does not have
227 // to match the platform layout!):
228
229 // bytes 0-3: reserved for signature on macOS
230 // bytes 4-7: the conditional variable id as u32 or 0 if id is not assigned yet.
231 // bytes 8-11: the clock id constant as i32
232
233 const PTHREAD_COND_T_MIN_SIZE: u64 = 12;
234
235 fn cond_get_id<'mir, 'tcx: 'mir>(
236     ecx: &MiriEvalContext<'mir, 'tcx>,
237     cond_op: OpTy<'tcx, Tag>,
238 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
239     get_at_offset(ecx, cond_op, 4, ecx.machine.layouts.u32, PTHREAD_COND_T_MIN_SIZE)
240 }
241
242 fn cond_set_id<'mir, 'tcx: 'mir>(
243     ecx: &mut MiriEvalContext<'mir, 'tcx>,
244     cond_op: OpTy<'tcx, Tag>,
245     id: impl Into<ScalarMaybeUninit<Tag>>,
246 ) -> InterpResult<'tcx, ()> {
247     set_at_offset(ecx, cond_op, 4, id, ecx.machine.layouts.u32, PTHREAD_COND_T_MIN_SIZE)
248 }
249
250 fn cond_get_or_create_id<'mir, 'tcx: 'mir>(
251     ecx: &mut MiriEvalContext<'mir, 'tcx>,
252     cond_op: OpTy<'tcx, Tag>,
253 ) -> InterpResult<'tcx, CondvarId> {
254     let id = cond_get_id(ecx, cond_op)?.to_u32()?;
255     if id == 0 {
256         // 0 is a default value and also not a valid conditional variable id.
257         // Need to allocate a new id.
258         let id = ecx.condvar_create();
259         cond_set_id(ecx, cond_op, id.to_u32_scalar())?;
260         Ok(id)
261     } else {
262         Ok(CondvarId::from_u32(id))
263     }
264 }
265
266 fn cond_get_clock_id<'mir, 'tcx: 'mir>(
267     ecx: &MiriEvalContext<'mir, 'tcx>,
268     cond_op: OpTy<'tcx, Tag>,
269 ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
270     get_at_offset(ecx, cond_op, 8, ecx.machine.layouts.i32, PTHREAD_COND_T_MIN_SIZE)
271 }
272
273 fn cond_set_clock_id<'mir, 'tcx: 'mir>(
274     ecx: &mut MiriEvalContext<'mir, 'tcx>,
275     cond_op: OpTy<'tcx, Tag>,
276     clock_id: impl Into<ScalarMaybeUninit<Tag>>,
277 ) -> InterpResult<'tcx, ()> {
278     set_at_offset(ecx, cond_op, 8, clock_id, ecx.machine.layouts.i32, PTHREAD_COND_T_MIN_SIZE)
279 }
280
281 /// Try to reacquire the mutex associated with the condition variable after we
282 /// were signaled.
283 fn reacquire_cond_mutex<'mir, 'tcx: 'mir>(
284     ecx: &mut MiriEvalContext<'mir, 'tcx>,
285     thread: ThreadId,
286     mutex: MutexId,
287 ) -> InterpResult<'tcx> {
288     ecx.unblock_thread(thread);
289     if ecx.mutex_is_locked(mutex) {
290         ecx.mutex_enqueue_and_block(mutex, thread);
291     } else {
292         ecx.mutex_lock(mutex, thread);
293     }
294     Ok(())
295 }
296
297 /// After a thread waiting on a condvar was signalled:
298 /// Reacquire the conditional variable and remove the timeout callback if any
299 /// was registered.
300 fn post_cond_signal<'mir, 'tcx: 'mir>(
301     ecx: &mut MiriEvalContext<'mir, 'tcx>,
302     thread: ThreadId,
303     mutex: MutexId,
304 ) -> InterpResult<'tcx> {
305     reacquire_cond_mutex(ecx, thread, mutex)?;
306     // Waiting for the mutex is not included in the waiting time because we need
307     // to acquire the mutex always even if we get a timeout.
308     ecx.unregister_timeout_callback_if_exists(thread);
309     Ok(())
310 }
311
312 /// Release the mutex associated with the condition variable because we are
313 /// entering the waiting state.
314 fn release_cond_mutex_and_block<'mir, 'tcx: 'mir>(
315     ecx: &mut MiriEvalContext<'mir, 'tcx>,
316     active_thread: ThreadId,
317     mutex: MutexId,
318 ) -> InterpResult<'tcx> {
319     if let Some(old_locked_count) = ecx.mutex_unlock(mutex, active_thread) {
320         if old_locked_count != 1 {
321             throw_unsup_format!("awaiting on a lock acquired multiple times is not supported");
322         }
323     } else {
324         throw_ub_format!("awaiting on unlocked or owned by a different thread mutex");
325     }
326     ecx.block_thread(active_thread);
327     Ok(())
328 }
329
330 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
331 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
332     fn pthread_mutexattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
333         let this = self.eval_context_mut();
334
335         let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?;
336         mutexattr_set_kind(this, attr_op, default_kind)?;
337
338         Ok(0)
339     }
340
341     fn pthread_mutexattr_settype(
342         &mut self,
343         attr_op: OpTy<'tcx, Tag>,
344         kind_op: OpTy<'tcx, Tag>,
345     ) -> InterpResult<'tcx, i32> {
346         let this = self.eval_context_mut();
347
348         let kind = this.read_scalar(kind_op)?.not_undef()?;
349         if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
350             // In `glibc` implementation, the numeric values of
351             // `PTHREAD_MUTEX_NORMAL` and `PTHREAD_MUTEX_DEFAULT` are equal.
352             // However, a mutex created by explicitly passing
353             // `PTHREAD_MUTEX_NORMAL` type has in some cases different behaviour
354             // from the default mutex for which the type was not explicitly
355             // specified. For a more detailed discussion, please see
356             // https://github.com/rust-lang/miri/issues/1419.
357             //
358             // To distinguish these two cases in already constructed mutexes, we
359             // use the same trick as glibc: for the case when
360             // `pthread_mutexattr_settype` is caled explicitly, we set the
361             // `PTHREAD_MUTEX_NORMAL_FLAG` flag.
362             let normal_kind = kind.to_i32()? | PTHREAD_MUTEX_NORMAL_FLAG;
363             // Check that after setting the flag, the kind is distinguishable
364             // from all other kinds.
365             assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_DEFAULT")?.to_i32()?);
366             assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?.to_i32()?);
367             assert_ne!(normal_kind, this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?.to_i32()?);
368             mutexattr_set_kind(this, attr_op, Scalar::from_i32(normal_kind))?;
369         } else if kind == this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
370             || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
371             || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
372         {
373             mutexattr_set_kind(this, attr_op, kind)?;
374         } else {
375             let einval = this.eval_libc_i32("EINVAL")?;
376             return Ok(einval);
377         }
378
379         Ok(0)
380     }
381
382     fn pthread_mutexattr_destroy(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
383         let this = self.eval_context_mut();
384
385         mutexattr_set_kind(this, attr_op, ScalarMaybeUninit::Uninit)?;
386
387         Ok(0)
388     }
389
390     fn pthread_mutex_init(
391         &mut self,
392         mutex_op: OpTy<'tcx, Tag>,
393         attr_op: OpTy<'tcx, Tag>,
394     ) -> InterpResult<'tcx, i32> {
395         let this = self.eval_context_mut();
396
397         let attr = this.read_scalar(attr_op)?.not_undef()?;
398         let kind = if this.is_null(attr)? {
399             this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
400         } else {
401             mutexattr_get_kind(this, attr_op)?.not_undef()?
402         };
403
404         // Write 0 to use the same code path as the static initializers.
405         mutex_set_id(this, mutex_op, Scalar::from_i32(0))?;
406
407         mutex_set_kind(this, mutex_op, kind)?;
408
409         Ok(0)
410     }
411
412     fn pthread_mutex_lock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
413         let this = self.eval_context_mut();
414
415         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
416         let id = mutex_get_or_create_id(this, mutex_op)?;
417         let active_thread = this.get_active_thread();
418
419         if this.mutex_is_locked(id) {
420             let owner_thread = this.mutex_get_owner(id);
421             if owner_thread != active_thread {
422                 // Enqueue the active thread.
423                 this.mutex_enqueue_and_block(id, active_thread);
424                 Ok(0)
425             } else {
426                 // Trying to acquire the same mutex again.
427                 if is_mutex_kind_default(this, kind)? {
428                     throw_ub_format!("trying to acquire already locked default mutex");
429                 } else if is_mutex_kind_normal(this, kind)? {
430                     throw_machine_stop!(TerminationInfo::Deadlock);
431                 } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
432                     this.eval_libc_i32("EDEADLK")
433                 } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
434                     this.mutex_lock(id, active_thread);
435                     Ok(0)
436                 } else {
437                     throw_unsup_format!(
438                         "called pthread_mutex_lock on an unsupported type of mutex"
439                     );
440                 }
441             }
442         } else {
443             // The mutex is unlocked. Let's lock it.
444             this.mutex_lock(id, active_thread);
445             Ok(0)
446         }
447     }
448
449     fn pthread_mutex_trylock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
450         let this = self.eval_context_mut();
451
452         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
453         let id = mutex_get_or_create_id(this, mutex_op)?;
454         let active_thread = this.get_active_thread();
455
456         if this.mutex_is_locked(id) {
457             let owner_thread = this.mutex_get_owner(id);
458             if owner_thread != active_thread {
459                 this.eval_libc_i32("EBUSY")
460             } else {
461                 if is_mutex_kind_default(this, kind)?
462                     || is_mutex_kind_normal(this, kind)?
463                     || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
464                 {
465                     this.eval_libc_i32("EBUSY")
466                 } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
467                     this.mutex_lock(id, active_thread);
468                     Ok(0)
469                 } else {
470                     throw_unsup_format!(
471                         "called pthread_mutex_trylock on an unsupported type of mutex"
472                     );
473                 }
474             }
475         } else {
476             // The mutex is unlocked. Let's lock it.
477             this.mutex_lock(id, active_thread);
478             Ok(0)
479         }
480     }
481
482     fn pthread_mutex_unlock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
483         let this = self.eval_context_mut();
484
485         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
486         let id = mutex_get_or_create_id(this, mutex_op)?;
487         let active_thread = this.get_active_thread();
488
489         if let Some(_old_locked_count) = this.mutex_unlock(id, active_thread) {
490             // The mutex was locked by the current thread.
491             Ok(0)
492         } else {
493             // The mutex was locked by another thread or not locked at all. See
494             // the “Unlock When Not Owner” column in
495             // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
496             if is_mutex_kind_default(this, kind)? {
497                 throw_ub_format!(
498                     "unlocked a default mutex that was not locked by the current thread"
499                 );
500             } else if is_mutex_kind_normal(this, kind)? {
501                 throw_ub_format!(
502                     "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
503                 );
504             } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
505                 this.eval_libc_i32("EPERM")
506             } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
507                 this.eval_libc_i32("EPERM")
508             } else {
509                 throw_unsup_format!("called pthread_mutex_unlock on an unsupported type of mutex");
510             }
511         }
512     }
513
514     fn pthread_mutex_destroy(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
515         let this = self.eval_context_mut();
516
517         let id = mutex_get_or_create_id(this, mutex_op)?;
518
519         if this.mutex_is_locked(id) {
520             throw_ub_format!("destroyed a locked mutex");
521         }
522
523         mutex_set_kind(this, mutex_op, ScalarMaybeUninit::Uninit)?;
524         mutex_set_id(this, mutex_op, ScalarMaybeUninit::Uninit)?;
525
526         Ok(0)
527     }
528
529     fn pthread_rwlock_rdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
530         let this = self.eval_context_mut();
531
532         let id = rwlock_get_or_create_id(this, rwlock_op)?;
533         let active_thread = this.get_active_thread();
534
535         if this.rwlock_is_write_locked(id) {
536             this.rwlock_enqueue_and_block_reader(id, active_thread);
537             Ok(0)
538         } else {
539             this.rwlock_reader_lock(id, active_thread);
540             Ok(0)
541         }
542     }
543
544     fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
545         let this = self.eval_context_mut();
546
547         let id = rwlock_get_or_create_id(this, rwlock_op)?;
548         let active_thread = this.get_active_thread();
549
550         if this.rwlock_is_write_locked(id) {
551             this.eval_libc_i32("EBUSY")
552         } else {
553             this.rwlock_reader_lock(id, active_thread);
554             Ok(0)
555         }
556     }
557
558     fn pthread_rwlock_wrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
559         let this = self.eval_context_mut();
560
561         let id = rwlock_get_or_create_id(this, rwlock_op)?;
562         let active_thread = this.get_active_thread();
563
564         if this.rwlock_is_locked(id) {
565             // Note: this will deadlock if the lock is already locked by this
566             // thread in any way.
567             //
568             // Relevant documentation:
569             // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
570             // An in-depth discussion on this topic:
571             // https://github.com/rust-lang/rust/issues/53127
572             //
573             // FIXME: Detect and report the deadlock proactively. (We currently
574             // report the deadlock only when no thread can continue execution,
575             // but we could detect that this lock is already locked and report
576             // an error.)
577             this.rwlock_enqueue_and_block_writer(id, active_thread);
578         } else {
579             this.rwlock_writer_lock(id, active_thread);
580         }
581
582         Ok(0)
583     }
584
585     fn pthread_rwlock_trywrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
586         let this = self.eval_context_mut();
587
588         let id = rwlock_get_or_create_id(this, rwlock_op)?;
589         let active_thread = this.get_active_thread();
590
591         if this.rwlock_is_locked(id) {
592             this.eval_libc_i32("EBUSY")
593         } else {
594             this.rwlock_writer_lock(id, active_thread);
595             Ok(0)
596         }
597     }
598
599     fn pthread_rwlock_unlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
600         let this = self.eval_context_mut();
601
602         let id = rwlock_get_or_create_id(this, rwlock_op)?;
603         let active_thread = this.get_active_thread();
604
605         if this.rwlock_reader_unlock(id, active_thread) {
606             // The thread was a reader.
607             if this.rwlock_is_locked(id).not() {
608                 // No more readers owning the lock. Give it to a writer if there
609                 // is any.
610                 this.rwlock_dequeue_and_lock_writer(id);
611             }
612             Ok(0)
613         } else if Some(active_thread) == this.rwlock_writer_unlock(id) {
614             // The thread was a writer.
615             //
616             // We are prioritizing writers here against the readers. As a
617             // result, not only readers can starve writers, but also writers can
618             // starve readers.
619             if this.rwlock_dequeue_and_lock_writer(id) {
620                 // Someone got the write lock, nice.
621             } else {
622                 // Give the lock to all readers.
623                 while this.rwlock_dequeue_and_lock_reader(id) {
624                     // Rinse and repeat.
625                 }
626             }
627             Ok(0)
628         } else {
629             throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
630         }
631     }
632
633     fn pthread_rwlock_destroy(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
634         let this = self.eval_context_mut();
635
636         let id = rwlock_get_or_create_id(this, rwlock_op)?;
637
638         if this.rwlock_is_locked(id) {
639             throw_ub_format!("destroyed a locked rwlock");
640         }
641
642         rwlock_set_id(this, rwlock_op, ScalarMaybeUninit::Uninit)?;
643
644         Ok(0)
645     }
646
647     fn pthread_condattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
648         let this = self.eval_context_mut();
649
650         // The default value of the clock attribute shall refer to the system
651         // clock.
652         // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
653         let default_clock_id = this.eval_libc("CLOCK_REALTIME")?;
654         condattr_set_clock_id(this, attr_op, default_clock_id)?;
655
656         Ok(0)
657     }
658
659     fn pthread_condattr_setclock(
660         &mut self,
661         attr_op: OpTy<'tcx, Tag>,
662         clock_id_op: OpTy<'tcx, Tag>,
663     ) -> InterpResult<'tcx, i32> {
664         let this = self.eval_context_mut();
665
666         let clock_id = this.read_scalar(clock_id_op)?.not_undef()?;
667         if clock_id == this.eval_libc("CLOCK_REALTIME")?
668             || clock_id == this.eval_libc("CLOCK_MONOTONIC")?
669         {
670             condattr_set_clock_id(this, attr_op, clock_id)?;
671         } else {
672             let einval = this.eval_libc_i32("EINVAL")?;
673             return Ok(einval);
674         }
675
676         Ok(0)
677     }
678
679     fn pthread_condattr_getclock(
680         &mut self,
681         attr_op: OpTy<'tcx, Tag>,
682         clk_id_op: OpTy<'tcx, Tag>,
683     ) -> InterpResult<'tcx, i32> {
684         let this = self.eval_context_mut();
685
686         let clock_id = condattr_get_clock_id(this, attr_op)?;
687         this.write_scalar(clock_id, this.deref_operand(clk_id_op)?.into())?;
688
689         Ok(0)
690     }
691
692     fn pthread_condattr_destroy(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
693         let this = self.eval_context_mut();
694
695         condattr_set_clock_id(this, attr_op, ScalarMaybeUninit::Uninit)?;
696
697         Ok(0)
698     }
699
700     fn pthread_cond_init(
701         &mut self,
702         cond_op: OpTy<'tcx, Tag>,
703         attr_op: OpTy<'tcx, Tag>,
704     ) -> InterpResult<'tcx, i32> {
705         let this = self.eval_context_mut();
706
707         let attr = this.read_scalar(attr_op)?.not_undef()?;
708         let clock_id = if this.is_null(attr)? {
709             this.eval_libc("CLOCK_REALTIME")?
710         } else {
711             condattr_get_clock_id(this, attr_op)?.not_undef()?
712         };
713
714         // Write 0 to use the same code path as the static initializers.
715         cond_set_id(this, cond_op, Scalar::from_i32(0))?;
716
717         cond_set_clock_id(this, cond_op, clock_id)?;
718
719         Ok(0)
720     }
721
722     fn pthread_cond_signal(&mut self, cond_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
723         let this = self.eval_context_mut();
724         let id = cond_get_or_create_id(this, cond_op)?;
725         if let Some((thread, mutex)) = this.condvar_signal(id) {
726             post_cond_signal(this, thread, mutex)?;
727         }
728
729         Ok(0)
730     }
731
732     fn pthread_cond_broadcast(&mut self, cond_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
733         let this = self.eval_context_mut();
734         let id = cond_get_or_create_id(this, cond_op)?;
735
736         while let Some((thread, mutex)) = this.condvar_signal(id) {
737             post_cond_signal(this, thread, mutex)?;
738         }
739
740         Ok(0)
741     }
742
743     fn pthread_cond_wait(
744         &mut self,
745         cond_op: OpTy<'tcx, Tag>,
746         mutex_op: OpTy<'tcx, Tag>,
747     ) -> InterpResult<'tcx, i32> {
748         let this = self.eval_context_mut();
749
750         let id = cond_get_or_create_id(this, cond_op)?;
751         let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
752         let active_thread = this.get_active_thread();
753
754         release_cond_mutex_and_block(this, active_thread, mutex_id)?;
755         this.condvar_wait(id, active_thread, mutex_id);
756
757         Ok(0)
758     }
759
760     fn pthread_cond_timedwait(
761         &mut self,
762         cond_op: OpTy<'tcx, Tag>,
763         mutex_op: OpTy<'tcx, Tag>,
764         abstime_op: OpTy<'tcx, Tag>,
765         dest: PlaceTy<'tcx, Tag>,
766     ) -> InterpResult<'tcx> {
767         let this = self.eval_context_mut();
768
769         this.check_no_isolation("pthread_cond_timedwait")?;
770
771         let id = cond_get_or_create_id(this, cond_op)?;
772         let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
773         let active_thread = this.get_active_thread();
774
775         release_cond_mutex_and_block(this, active_thread, mutex_id)?;
776         this.condvar_wait(id, active_thread, mutex_id);
777
778         // We return success for now and override it in the timeout callback.
779         this.write_scalar(Scalar::from_i32(0), dest)?;
780
781         // Extract the timeout.
782         let clock_id = cond_get_clock_id(this, cond_op)?.to_i32()?;
783         let duration = {
784             let tp = this.deref_operand(abstime_op)?;
785             let seconds_place = this.mplace_field(tp, 0)?;
786             let seconds = this.read_scalar(seconds_place.into())?;
787             let nanoseconds_place = this.mplace_field(tp, 1)?;
788             let nanoseconds = this.read_scalar(nanoseconds_place.into())?;
789             let (seconds, nanoseconds) = (
790                 seconds.to_machine_usize(this)?,
791                 nanoseconds.to_machine_usize(this)?.try_into().unwrap(),
792             );
793             Duration::new(seconds, nanoseconds)
794         };
795
796         let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? {
797             Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
798         } else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC")? {
799             Time::Monotonic(this.machine.time_anchor.checked_add(duration).unwrap())
800         } else {
801             throw_unsup_format!("unsupported clock id: {}", clock_id);
802         };
803
804         // Register the timeout callback.
805         this.register_timeout_callback(
806             active_thread,
807             timeout_time,
808             Box::new(move |ecx| {
809                 // We are not waiting for the condvar any more, wait for the
810                 // mutex instead.
811                 reacquire_cond_mutex(ecx, active_thread, mutex_id)?;
812
813                 // Remove the thread from the conditional variable.
814                 ecx.condvar_remove_waiter(id, active_thread);
815
816                 // Set the return value: we timed out.
817                 let timeout = ecx.eval_libc_i32("ETIMEDOUT")?;
818                 ecx.write_scalar(Scalar::from_i32(timeout), dest)?;
819
820                 Ok(())
821             }),
822         );
823
824         Ok(())
825     }
826
827     fn pthread_cond_destroy(&mut self, cond_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
828         let this = self.eval_context_mut();
829
830         let id = cond_get_or_create_id(this, cond_op)?;
831         if this.condvar_is_awaited(id) {
832             throw_ub_format!("destroying an awaited conditional variable");
833         }
834         cond_set_id(this, cond_op, ScalarMaybeUninit::Uninit)?;
835         cond_set_clock_id(this, cond_op, ScalarMaybeUninit::Uninit)?;
836
837         Ok(0)
838     }
839 }