Support running no_core Linux programs

Co-authored-by: Ralf Jung <post@ralfj.de>
This commit is contained in:
Ben Kimock
2026-04-14 22:10:51 -04:00
parent b06adbf96f
commit da3727f193
7 changed files with 91 additions and 18 deletions
+5 -2
View File
@@ -962,8 +962,11 @@ fn start_regular_thread(
let old_thread_id = this.machine.threads.set_active_thread_id(new_thread_id);
// The child inherits its parent's cpu affinity.
if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&old_thread_id).cloned() {
this.machine.thread_cpu_affinity.insert(new_thread_id, cpuset);
// Skips this if `machine.thread_cpu_affinity` is not initialized.
if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
&& let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
{
thread_cpu_affinity.insert(new_thread_id, cpuset);
}
// Perform the function pointer load in the new thread frame.
+14 -7
View File
@@ -21,6 +21,7 @@
use crate::concurrency::GenmcCtx;
use crate::concurrency::thread::TlsAllocAction;
use crate::diagnostics::report_leaks;
use crate::helpers::is_no_core;
use crate::shims::{global_ctor, tls};
use crate::*;
@@ -289,14 +290,20 @@ pub fn create_ecx<'tcx>(
MiriMachine::new(config, layout_cx, genmc_ctx),
);
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
let sentinel =
helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
tcx.dcx().fatal(
"the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore. However,
// if the current crate is #![no_core] it's fine to be missing the usual items from libcore.
if !is_no_core(tcx) {
let sentinel = helpers::try_resolve_path(
tcx,
&["core", "ascii", "escape_default"],
Namespace::ValueNS,
);
if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
tcx.dcx().fatal(
"the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
);
}
}
// Compute argc and argv from `config.args`.
+5
View File
@@ -1085,6 +1085,11 @@ pub(crate) fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 {
}
}
/// Check whether the local crate has the `#![no_core]` attribute.
pub fn is_no_core(tcx: TyCtxt<'_>) -> bool {
rustc_hir::find_attr!(tcx, crate, NoCore)
}
/// We don't support 16-bit systems, so let's have ergonomic conversion from `u32` to `usize`.
pub trait ToUsize {
fn to_usize(self) -> usize;
+16 -6
View File
@@ -40,6 +40,7 @@
use crate::concurrency::{
AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory,
};
use crate::helpers::is_no_core;
use crate::*;
/// First real-time signal.
@@ -546,7 +547,8 @@ pub struct MiriMachine<'tcx> {
/// Stores which thread is eligible to run on which CPUs.
/// This has no effect at all, it is just tracked to produce the correct result
/// in `sched_getaffinity`
pub(crate) thread_cpu_affinity: FxHashMap<ThreadId, CpuAffinityMask>,
/// This will be `None` when running `#![no_core]` crates.
pub(crate) thread_cpu_affinity: Option<FxHashMap<ThreadId, CpuAffinityMask>>,
/// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri.
pub(crate) layouts: PrimitiveLayouts<'tcx>,
@@ -735,11 +737,19 @@ pub(crate) fn new(
config.num_cpus
);
let threads = ThreadManager::new(config);
let mut thread_cpu_affinity = FxHashMap::default();
if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android) {
thread_cpu_affinity
.insert(threads.active_thread(), CpuAffinityMask::new(&layout_cx, config.num_cpus));
}
let thread_cpu_affinity =
if matches!(&tcx.sess.target.os, Os::Linux | Os::FreeBsd | Os::Android)
&& !is_no_core(tcx)
{
let mut affinity = FxHashMap::default();
affinity.insert(
threads.active_thread(),
CpuAffinityMask::new(&layout_cx, config.num_cpus),
);
Some(affinity)
} else {
None
};
let blocking_io = BlockingIoManager::new(config.isolated_op == IsolatedOp::Allow)
.expect("Couldn't create poll instance");
let alloc_addresses =
+20 -2
View File
@@ -1126,6 +1126,12 @@ fn emulate_foreign_item_inner(
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`sched_getaffinity` is not supported on #![no_core] programs"
)
}
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
@@ -1149,7 +1155,9 @@ fn emulate_foreign_item_inner(
} else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 {
// we only copy whole chunks of size_of::<c_ulong>()
this.set_errno_and_return_neg1(LibcError("EINVAL"), dest)?;
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) {
} else if let Some(cpuset) =
this.machine.thread_cpu_affinity.as_ref().unwrap().get(&thread_id)
{
let cpuset = cpuset.clone();
// we only copy whole chunks of size_of::<c_ulong>()
let byte_count =
@@ -1171,6 +1179,12 @@ fn emulate_foreign_item_inner(
let cpusetsize = this.read_target_usize(cpusetsize)?;
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`sched_setaffinity` is not supported on #![no_core] programs"
)
}
let thread_id = if pid == 0 {
this.active_thread()
} else if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
@@ -1199,7 +1213,11 @@ fn emulate_foreign_item_inner(
std::array::from_fn(|i| bits_slice.get(i).copied().unwrap_or(0));
match CpuAffinityMask::from_array(this, this.machine.num_cpus, bits_array) {
Some(cpuset) => {
this.machine.thread_cpu_affinity.insert(thread_id, cpuset);
this.machine
.thread_cpu_affinity
.as_mut()
.unwrap()
.insert(thread_id, cpuset);
this.write_null(dest)?;
}
None => {
@@ -75,6 +75,12 @@ fn emulate_foreign_item_inner(
let set_size = this.read_target_usize(set_size)?; // measured in bytes
let mask = this.read_pointer(mask)?;
if this.machine.thread_cpu_affinity.is_none() {
throw_unsup_format!(
"`cpuset_getaffinity` is not supported on #![no_core] programs"
)
}
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");
@@ -104,7 +110,9 @@ fn emulate_foreign_item_inner(
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) {
} else if let Some(cpuset) =
this.machine.thread_cpu_affinity.as_ref().unwrap().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.
+22
View File
@@ -0,0 +1,22 @@
//! Test that Miri is able to run no_core programs.
//! This ensures that we don't depend on any paths from core when no_core is set.
#![no_std]
#![no_core]
#![no_main]
#![feature(rustc_attrs, no_core, lang_items, intrinsics)]
#![allow(internal_features)]
#[lang = "pointee_sized"]
pub trait PointeeSized {}
#[lang = "meta_sized"]
pub trait MetaSized: PointeeSized {}
#[lang = "sized"]
pub trait Sized: MetaSized {}
#[no_mangle]
fn miri_start(_argc: isize, _argv: *const *const u8) -> isize {
0
}