about summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-xsrc/tools/miri/ci/ci.sh4
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/foreign_items.rs64
-rw-r--r--src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs51
3 files changed, 117 insertions, 2 deletions
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index 00102bb2677..3a9e55f5738 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -165,8 +165,8 @@ case $HOST_TARGET in
     # Partially supported targets (tier 2)
     BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
     UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
-    TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe
-    TEST_TARGET=i686-unknown-freebsd   run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe
+    TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe affinity
+    TEST_TARGET=i686-unknown-freebsd   run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency fs libc-pipe affinity
     TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency epoll eventfd
     TEST_TARGET=wasm32-wasip2          run_tests_minimal $BASIC wasm
     TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
index c0872daee8d..533a741fea3 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -56,6 +56,70 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
 
+            "cpuset_getaffinity" => {
+                // The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
+                let [level, which, id, set_size, mask] =
+                    this.check_shim(abi, Conv::C, link_name, args)?;
+
+                let level = this.read_scalar(level)?.to_i32()?;
+                let which = this.read_scalar(which)?.to_i32()?;
+                let id = this.read_scalar(id)?.to_i64()?;
+                let set_size = this.read_target_usize(set_size)?; // measured in bytes
+                let mask = this.read_pointer(mask)?;
+
+                let _level_root = this.eval_libc_i32("CPU_LEVEL_ROOT");
+                let _level_cpuset = this.eval_libc_i32("CPU_LEVEL_CPUSET");
+                let level_which = this.eval_libc_i32("CPU_LEVEL_WHICH");
+
+                let _which_tid = this.eval_libc_i32("CPU_WHICH_TID");
+                let which_pid = this.eval_libc_i32("CPU_WHICH_PID");
+                let _which_jail = this.eval_libc_i32("CPU_WHICH_JAIL");
+                let _which_cpuset = this.eval_libc_i32("CPU_WHICH_CPUSET");
+                let _which_irq = this.eval_libc_i32("CPU_WHICH_IRQ");
+
+                // For sched_getaffinity, the current process is identified by -1.
+                // TODO: Use gettid? I'm (LorrensP-2158466) not that familiar with this api .
+                let id = match id {
+                    -1 => this.active_thread(),
+                    _ =>
+                        throw_unsup_format!(
+                            "`cpuset_getaffinity` is only supported with a pid of -1 (indicating the current thread)"
+                        ),
+                };
+
+                if this.ptr_is_null(mask)? {
+                    this.set_last_error_and_return(LibcError("EFAULT"), dest)?;
+                }
+                // We only support CPU_LEVEL_WHICH and CPU_WHICH_PID for now.
+                // This is the bare minimum to make the tests pass.
+                else if level != level_which || which != which_pid {
+                    throw_unsup_format!(
+                        "`cpuset_getaffinity` is only supported with `level` set to CPU_LEVEL_WHICH and `which` set to CPU_WHICH_PID."
+                    );
+                } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&id) {
+                    // `cpusetsize` must be large enough to contain the entire CPU mask.
+                    // FreeBSD only uses `cpusetsize` to verify that it's sufficient for the kernel's CPU mask.
+                    // If it's too small, the syscall returns ERANGE.
+                    // If it's large enough, copying the kernel mask to user space is safe, regardless of the actual size.
+                    // See https://github.com/freebsd/freebsd-src/blob/909aa6781340f8c0b4ae01c6366bf1556ee2d1be/sys/kern/kern_cpuset.c#L1985
+                    if set_size < u64::from(this.machine.num_cpus).div_ceil(8) {
+                        this.set_last_error_and_return(LibcError("ERANGE"), dest)?;
+                    } else {
+                        let cpuset = cpuset.clone();
+                        let byte_count =
+                            Ord::min(cpuset.as_slice().len(), set_size.try_into().unwrap());
+                        this.write_bytes_ptr(
+                            mask,
+                            cpuset.as_slice()[..byte_count].iter().copied(),
+                        )?;
+                        this.write_null(dest)?;
+                    }
+                } else {
+                    // `id` is always that of the active thread, so this is currently unreachable.
+                    unreachable!();
+                }
+            }
+
             // Synchronization primitives
             "_umtx_op" => {
                 let [obj, op, val, uaddr, uaddr2] =
diff --git a/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs b/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs
new file mode 100644
index 00000000000..9a868128d27
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/freebsd-cpuset-affinity.rs
@@ -0,0 +1,51 @@
+//@only-target: freebsd
+//@compile-flags: -Zmiri-num-cpus=256
+
+use std::mem;
+
+fn getaffinity() {
+    let mut set: libc::cpuset_t = unsafe { mem::zeroed() };
+    unsafe {
+        if libc::cpuset_getaffinity(
+            libc::CPU_LEVEL_WHICH,
+            libc::CPU_WHICH_PID,
+            -1,
+            size_of::<libc::cpuset_t>(),
+            &mut set,
+        ) == 0
+        {
+            assert!(libc::CPU_COUNT(&set) == 256);
+        }
+    }
+}
+
+fn get_small_cpu_mask() {
+    let mut set: libc::cpuset_t = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
+
+    // 256 CPUs so we need 32 bytes to represent this mask.
+    // According to Freebsd only when `cpusetsize` is smaller than this value, does it return with ERANGE
+
+    let err = unsafe {
+        libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 32, &mut set)
+    };
+    assert_eq!(err, 0, "Success Expected");
+
+    // 31 is not enough, so it should fail.
+    let err = unsafe {
+        libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 31, &mut set)
+    };
+    assert_eq!(err, -1, "Expected Failure");
+    assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
+
+    // Zero should fail as well.
+    let err = unsafe {
+        libc::cpuset_getaffinity(libc::CPU_LEVEL_WHICH, libc::CPU_WHICH_PID, -1, 0, &mut set)
+    };
+    assert_eq!(err, -1, "Expected Failure");
+    assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), libc::ERANGE);
+}
+
+fn main() {
+    getaffinity();
+    get_small_cpu_mask();
+}