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