miri/shims/windows/
sync.rs

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