Skip to content

implement cpuset_getaffinity for freebsd #4287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ci/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions src/shims/unix/freebsd/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand Down
51 changes: 51 additions & 0 deletions tests/pass-dep/shims/freebsd-cpuset-affinity.rs
Original file line number Diff line number Diff line change
@@ -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();
}