From da3727f1935b69f610f1fb3f2cc9c00b08061399 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Tue, 14 Apr 2026 22:10:51 -0400 Subject: [PATCH] Support running no_core Linux programs Co-authored-by: Ralf Jung --- src/tools/miri/src/concurrency/thread.rs | 7 ++++-- src/tools/miri/src/eval.rs | 21 ++++++++++++------ src/tools/miri/src/helpers.rs | 5 +++++ src/tools/miri/src/machine.rs | 22 ++++++++++++++----- .../miri/src/shims/unix/foreign_items.rs | 22 +++++++++++++++++-- .../src/shims/unix/freebsd/foreign_items.rs | 10 ++++++++- src/tools/miri/tests/pass/no_core.rs | 22 +++++++++++++++++++ 7 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/tools/miri/tests/pass/no_core.rs diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 2b293a59ddb7..4dabdb48c4f6 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -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. diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index cf4f7d689ac2..c9b2b7634b7b 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -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`. diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 349cb2d66482..926dd0c24da3 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -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; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index d0bde4b1f052..80f392363ba0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -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, + /// This will be `None` when running `#![no_core]` crates. + pub(crate) thread_cpu_affinity: Option>, /// 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 = diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 467304defb9b..fa2af98b9fc8 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -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::() 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::() 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 => { 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 db3975a5bdfa..1cc87050e59d 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -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. diff --git a/src/tools/miri/tests/pass/no_core.rs b/src/tools/miri/tests/pass/no_core.rs new file mode 100644 index 000000000000..0cbffcb1ae59 --- /dev/null +++ b/src/tools/miri/tests/pass/no_core.rs @@ -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 +}