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