miri/shims/windows/
sync.rs

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