about summary refs log tree commit diff
path: root/src/tools/miri/tests/pass-dep/libc/libc-affinity.rs
blob: 400e3ca3d7db3229f5621fada6102e7056d3bb9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//@only-target: linux # these are Linux-specific APIs
//@compile-flags: -Zmiri-disable-isolation -Zmiri-num-cpus=4
#![feature(io_error_more)]
#![feature(pointer_is_aligned_to)]

use std::mem::{size_of, size_of_val};

use libc::{cpu_set_t, sched_getaffinity, sched_setaffinity};

// If pid is zero, then the calling thread is used.
const PID: i32 = 0;

fn null_pointers() {
    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), std::ptr::null_mut()) };
    assert_eq!(err, -1);

    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), std::ptr::null()) };
    assert_eq!(err, -1);
}

fn configure_no_cpus() {
    let cpu_count = std::thread::available_parallelism().unwrap().get();

    let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

    // configuring no CPUs will fail
    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) };
    assert_eq!(err, -1);
    assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::InvalidInput);

    // configuring no (physically available) CPUs will fail
    unsafe { libc::CPU_SET(cpu_count, &mut cpuset) };
    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) };
    assert_eq!(err, -1);
    assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::InvalidInput);
}

fn configure_unavailable_cpu() {
    let cpu_count = std::thread::available_parallelism().unwrap().get();

    // Safety: valid value for this type
    let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
    assert_eq!(err, 0);

    // by default, only available CPUs are configured
    for i in 0..cpu_count {
        assert!(unsafe { libc::CPU_ISSET(i, &cpuset) });
    }
    assert!(unsafe { !libc::CPU_ISSET(cpu_count, &cpuset) });

    // configure CPU that we don't have
    unsafe { libc::CPU_SET(cpu_count, &mut cpuset) };

    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) };
    assert_eq!(err, 0);

    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
    assert_eq!(err, 0);

    // the CPU is not set because it is not available
    assert!(!unsafe { libc::CPU_ISSET(cpu_count, &cpuset) });
}

fn large_set() {
    // rust's libc does not currently implement dynamic cpu set allocation
    // and related functions like `CPU_ZERO_S`. So we have to be creative

    // i.e. this has 2048 bits, twice the standard number
    let mut cpuset = [u64::MAX; 32];

    let err = unsafe { sched_setaffinity(PID, size_of_val(&cpuset), cpuset.as_ptr().cast()) };
    assert_eq!(err, 0);

    let err = unsafe { sched_getaffinity(PID, size_of_val(&cpuset), cpuset.as_mut_ptr().cast()) };
    assert_eq!(err, 0);
}

fn get_small_cpu_mask() {
    let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

    // should be 4 on 32-bit systems and 8 otherwise for systems that implement sched_getaffinity
    let step = size_of::<std::ffi::c_ulong>();

    for i in (0..=2).map(|x| x * step) {
        if i == 0 {
            // 0 always fails
            let err = unsafe { sched_getaffinity(PID, i, &mut cpuset) };
            assert_eq!(err, -1, "fail for {}", i);
            assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::InvalidInput);
        } else {
            // other whole multiples of the size of c_ulong works
            let err = unsafe { sched_getaffinity(PID, i, &mut cpuset) };
            assert_eq!(err, 0, "fail for {i}");
        }

        // anything else returns an error
        for j in 1..step {
            let err = unsafe { sched_getaffinity(PID, i + j, &mut cpuset) };
            assert_eq!(err, -1, "success for {}", i + j);
            assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::InvalidInput);
        }
    }
}

fn set_small_cpu_mask() {
    let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
    assert_eq!(err, 0);

    // setting a mask of size 0 is invalid
    let err = unsafe { sched_setaffinity(PID, 0, &cpuset) };
    assert_eq!(err, -1);
    assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::InvalidInput);

    // on LE systems, any other number of bytes (at least up to `size_of<cpu_set_t>()`) will work.
    // on BE systems the CPUs 0..8 are stored in the right-most byte of the first chunk. If that
    // byte is not included, no valid CPUs are configured. We skip those cases.
    let cpu_zero_included_length =
        if cfg!(target_endian = "little") { 1 } else { core::mem::size_of::<std::ffi::c_ulong>() };

    for i in cpu_zero_included_length..24 {
        let err = unsafe { sched_setaffinity(PID, i, &cpuset) };
        assert_eq!(err, 0, "fail for {i}");
    }
}

fn set_custom_cpu_mask() {
    let cpu_count = std::thread::available_parallelism().unwrap().get();

    assert!(cpu_count > 1, "this test cannot do anything interesting with just one thread");

    let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

    // at the start, thread 1 should be set
    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
    assert_eq!(err, 0);
    assert!(unsafe { libc::CPU_ISSET(1, &cpuset) });

    // make a valid mask
    unsafe { libc::CPU_ZERO(&mut cpuset) };
    unsafe { libc::CPU_SET(0, &mut cpuset) };

    // giving a smaller mask is fine
    let err = unsafe { sched_setaffinity(PID, 8, &cpuset) };
    assert_eq!(err, 0);

    // and actually disables other threads
    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
    assert_eq!(err, 0);
    assert!(unsafe { !libc::CPU_ISSET(1, &cpuset) });

    // it is important that we reset the cpu mask now for future tests
    for i in 0..cpu_count {
        unsafe { libc::CPU_SET(i, &mut cpuset) };
    }

    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) };
    assert_eq!(err, 0);
}

fn parent_child() {
    let cpu_count = std::thread::available_parallelism().unwrap().get();

    assert!(cpu_count > 1, "this test cannot do anything interesting with just one thread");

    // configure the parent thread to only run only on CPU 0
    let mut parent_cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
    unsafe { libc::CPU_SET(0, &mut parent_cpuset) };

    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &parent_cpuset) };
    assert_eq!(err, 0);

    std::thread::scope(|spawner| {
        spawner.spawn(|| {
            let mut cpuset: cpu_set_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };

            let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut cpuset) };
            assert_eq!(err, 0);

            // the child inherits its parent's set
            assert!(unsafe { libc::CPU_ISSET(0, &cpuset) });
            assert!(unsafe { !libc::CPU_ISSET(1, &cpuset) });

            // configure cpu 1 for the child
            unsafe { libc::CPU_SET(1, &mut cpuset) };
        });
    });

    let err = unsafe { sched_getaffinity(PID, size_of::<cpu_set_t>(), &mut parent_cpuset) };
    assert_eq!(err, 0);

    // the parent's set should be unaffected
    assert!(unsafe { !libc::CPU_ISSET(1, &parent_cpuset) });

    // it is important that we reset the cpu mask now for future tests
    let mut cpuset = parent_cpuset;
    for i in 0..cpu_count {
        unsafe { libc::CPU_SET(i, &mut cpuset) };
    }

    let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>(), &cpuset) };
    assert_eq!(err, 0);
}

fn main() {
    null_pointers();
    configure_no_cpus();
    configure_unavailable_cpu();
    large_set();
    get_small_cpu_mask();
    set_small_cpu_mask();
    set_custom_cpu_mask();
    parent_child();
}