Skip to main content

miri/shims/windows/
sync.rs

1use std::time::Duration;
2
3use rustc_abi::{FieldIdx, Size};
4
5use crate::concurrency::init_once::{EvalContextExt as _, InitOnceStatus};
6use crate::concurrency::sync::FutexRef;
7use crate::*;
8
9#[derive(Clone)]
10struct WindowsInitOnce {
11    init_once: InitOnceRef,
12}
13
14impl MetaObj for WindowsInitOnce {
15    fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
16        if !self.init_once.queue_is_empty() {
17            throw_ub_format!(
18                "accessing the memory backing a `INIT_ONCE` is forbidden while the queue is non-empty"
19            );
20        }
21        interp_ok(())
22    }
23
24    fn opaque(&self) -> bool {
25        true
26    }
27}
28
29struct WindowsFutex {
30    futex: FutexRef,
31}
32
33impl MetaObj for WindowsFutex {
34    fn opaque(&self) -> bool {
35        false
36    }
37}
38
39impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
40trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
41    // Windows sync primitives are pointer sized.
42    // We only use the first byte for the "init" flag.
43
44    fn init_once_get_data<'a>(
45        &'a mut self,
46        init_once_ptr: &OpTy<'tcx>,
47    ) -> InterpResult<'tcx, &'a mut WindowsInitOnce>
48    where
49        'tcx: 'a,
50    {
51        let ecx = self.eval_context_mut();
52
53        let init_once = ecx.deref_pointer(init_once_ptr)?;
54
55        ecx.get_or_insert_meta(
56            &init_once,
57            |ecx| {
58                let ptr_field = ecx.project_field(&init_once, FieldIdx::from_u32(0))?;
59                let val = ecx.read_target_usize(&ptr_field)?;
60                if val == 0 {
61                    interp_ok(WindowsInitOnce { init_once: InitOnceRef::new() })
62                } else {
63                    throw_ub_format!("`INIT_ONCE` was not properly initialized at this location, or it got overwritten");
64                }
65            },
66        )
67    }
68
69    /// Returns `true` if we were successful, `false` if we would block.
70    fn init_once_try_begin(
71        &mut self,
72        init_once_ref: &InitOnceRef,
73        pending_place: &MPlaceTy<'tcx>,
74        dest: &MPlaceTy<'tcx>,
75    ) -> InterpResult<'tcx, bool> {
76        let ecx = self.eval_context_mut();
77        interp_ok(match init_once_ref.status() {
78            InitOnceStatus::Uninitialized => {
79                init_once_ref.begin();
80                ecx.write_scalar(ecx.eval_windows("c", "TRUE"), pending_place)?;
81                ecx.write_scalar(ecx.eval_windows("c", "TRUE"), dest)?;
82                true
83            }
84            InitOnceStatus::Complete => {
85                ecx.init_once_observe_completed(init_once_ref)?;
86                ecx.write_scalar(ecx.eval_windows("c", "FALSE"), pending_place)?;
87                ecx.write_scalar(ecx.eval_windows("c", "TRUE"), dest)?;
88                true
89            }
90            InitOnceStatus::Begun => false,
91        })
92    }
93}
94
95impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
96#[allow(non_snake_case)]
97pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
98    fn InitOnceBeginInitialize(
99        &mut self,
100        init_once_op: &OpTy<'tcx>,
101        flags_op: &OpTy<'tcx>,
102        pending_op: &OpTy<'tcx>,
103        context_op: &OpTy<'tcx>,
104        dest: &MPlaceTy<'tcx>,
105    ) -> InterpResult<'tcx> {
106        let ecx = self.eval_context_mut();
107
108        let init_once = ecx.init_once_get_data(init_once_op)?.init_once.clone();
109        let flags = ecx.read_scalar(flags_op)?.to_u32()?;
110        // PBOOL is int*
111        let pending_place = ecx.deref_pointer(pending_op)?;
112        let context = ecx.read_pointer(context_op)?;
113
114        if flags != 0 {
115            throw_unsup_format!("unsupported `dwFlags` {flags} in `InitOnceBeginInitialize`");
116        }
117
118        if !ecx.ptr_is_null(context)? {
119            throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
120        }
121
122        if ecx.init_once_try_begin(&init_once, &pending_place, dest)? {
123            // Done!
124            return interp_ok(());
125        }
126
127        // We have to block, and then try again when we are woken up.
128        let dest = dest.clone();
129        ecx.init_once_enqueue_and_block(
130            init_once.clone(),
131            callback!(
132                @capture<'tcx> {
133                    init_once: InitOnceRef,
134                    pending_place: MPlaceTy<'tcx>,
135                    dest: MPlaceTy<'tcx>,
136                }
137                |ecx, unblock: UnblockKind| {
138                    assert_eq!(unblock, UnblockKind::Ready);
139                    let ret = ecx.init_once_try_begin(&init_once, &pending_place, &dest)?;
140                    assert!(ret, "we were woken up but init_once_try_begin still failed");
141                    interp_ok(())
142                }
143            ),
144        );
145        interp_ok(())
146    }
147
148    fn InitOnceComplete(
149        &mut self,
150        init_once_op: &OpTy<'tcx>,
151        flags_op: &OpTy<'tcx>,
152        context_op: &OpTy<'tcx>,
153    ) -> InterpResult<'tcx, Scalar> {
154        let ecx = self.eval_context_mut();
155
156        let init_once = ecx.init_once_get_data(init_once_op)?.init_once.clone();
157        let flags = ecx.read_scalar(flags_op)?.to_u32()?;
158        let context = ecx.read_pointer(context_op)?;
159
160        let success = if flags == 0 {
161            true
162        } else if flags == ecx.eval_windows_u32("c", "INIT_ONCE_INIT_FAILED") {
163            false
164        } else {
165            throw_unsup_format!("unsupported `dwFlags` {flags} in `InitOnceBeginInitialize`");
166        };
167
168        if !ecx.ptr_is_null(context)? {
169            throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
170        }
171
172        if init_once.status() != InitOnceStatus::Begun {
173            // The docs do not say anything about this case, but it seems better to not allow it.
174            throw_ub_format!(
175                "calling InitOnceComplete on a one time initialization that has not begun or is already completed"
176            );
177        }
178
179        if success {
180            ecx.init_once_complete(&init_once)?;
181        } else {
182            ecx.init_once_fail(&init_once)?;
183        }
184
185        interp_ok(ecx.eval_windows("c", "TRUE"))
186    }
187
188    fn WaitOnAddress(
189        &mut self,
190        ptr_op: &OpTy<'tcx>,
191        compare_op: &OpTy<'tcx>,
192        size_op: &OpTy<'tcx>,
193        timeout_op: &OpTy<'tcx>,
194        dest: &MPlaceTy<'tcx>,
195    ) -> InterpResult<'tcx> {
196        let ecx = self.eval_context_mut();
197
198        let ptr = ecx.read_pointer(ptr_op)?;
199        let compare = ecx.read_pointer(compare_op)?;
200        let size = ecx.read_target_usize(size_op)?;
201        let timeout_ms = ecx.read_scalar(timeout_op)?.to_u32()?;
202
203        if size > 8 || !size.is_power_of_two() {
204            let invalid_param = ecx.eval_windows("c", "ERROR_INVALID_PARAMETER");
205            ecx.set_last_error(invalid_param)?;
206            ecx.write_scalar(Scalar::from_i32(0), dest)?;
207            return interp_ok(());
208        };
209        let size = Size::from_bytes(size);
210
211        let deadline = if timeout_ms == ecx.eval_windows_u32("c", "INFINITE") {
212            None
213        } else {
214            let duration = Duration::from_millis(timeout_ms.into());
215            Some(ecx.machine.monotonic_clock.now().add_lossy(duration).into())
216        };
217
218        // See the Linux futex implementation for why this fence exists.
219        ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
220
221        let layout = ecx.machine.layouts.uint(size).unwrap();
222        let futex_place = ecx.ptr_to_mplace(ptr, layout);
223        let futex_val = ecx.read_scalar_atomic(&futex_place, AtomicReadOrd::Acquire)?;
224        let compare_val = ecx.read_scalar(&ecx.ptr_to_mplace(compare, layout))?;
225
226        if futex_val == compare_val {
227            // If the values are the same, we have to block.
228
229            let futex_ref = ecx
230                .get_or_insert_meta(&futex_place, |_| {
231                    interp_ok(WindowsFutex { futex: Default::default() })
232                })?
233                .futex
234                .clone();
235
236            let dest = dest.clone();
237            ecx.futex_wait(
238                futex_ref,
239                u32::MAX, // bitset
240                deadline,
241                callback!(
242                    @capture<'tcx> {
243                        dest: MPlaceTy<'tcx>
244                    }
245                    |ecx, unblock: UnblockKind| {
246                        match unblock {
247                            UnblockKind::Ready => {
248                                ecx.write_int(1, &dest)
249                            }
250                            UnblockKind::TimedOut => {
251                                ecx.set_last_error(IoError::WindowsError("ERROR_TIMEOUT"))?;
252                                ecx.write_int(0, &dest)
253                            }
254                        }
255                    }
256                ),
257            );
258        }
259
260        ecx.write_scalar(Scalar::from_i32(1), dest)?;
261
262        interp_ok(())
263    }
264
265    fn WakeByAddressSingle(&mut self, ptr_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
266        let ecx = self.eval_context_mut();
267
268        let ptr = ecx.read_pointer(ptr_op)?;
269
270        // See the Linux futex implementation for why this fence exists.
271        ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
272
273        let Ok(futex) = ecx.try_get_meta::<WindowsFutex>(ptr) else {
274            // Seems like this cannot return an error, so we just wake nobody.
275            return interp_ok(());
276        };
277        ecx.futex_wake(&futex.futex.clone(), u32::MAX, 1)?;
278
279        interp_ok(())
280    }
281    fn WakeByAddressAll(&mut self, ptr_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
282        let ecx = self.eval_context_mut();
283
284        let ptr = ecx.read_pointer(ptr_op)?;
285
286        // See the Linux futex implementation for why this fence exists.
287        ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
288
289        let Ok(futex) = ecx.try_get_meta::<WindowsFutex>(ptr) else {
290            // Seems like this cannot return an error, so we just wake nobody.
291            return interp_ok(());
292        };
293        ecx.futex_wake(&futex.futex.clone(), u32::MAX, usize::MAX)?;
294
295        interp_ok(())
296    }
297}