Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5d2de1b
miri: require (almost) all 1-ZST arguments to be actually passed
RalfJung May 2, 2026
54ee0ee
Force copy `rustc-dev` artifacts for rustfmt/clippy under download-rustc
jieyouxu May 13, 2026
9e3e1ca
Rename a confusing local
jieyouxu May 24, 2026
71487e8
Explicitly add rustc libs to path for `RustcPrivate` tools
jieyouxu May 24, 2026
fed6279
library: use strict provenance lints consistently
hanna-kruppe May 22, 2026
ecdf683
update TargetFeature::Forbidden docs
RalfJung May 28, 2026
375c51c
move target feature list explanation to module-level doc comment
RalfJung May 28, 2026
80a8b21
Fix CI free-disk-space-linux script
ehuss May 30, 2026
732998a
allow target-dependent int2ptr cast for pthread_t
hanna-kruppe May 31, 2026
9c25035
Mention how to fill a buffer with random bytes
joshtriplett May 31, 2026
f65c565
Add doc aliases for `DefaultRandomSource`, for people looking for ran…
joshtriplett May 31, 2026
3467b6f
Clean resolved signature for delegated functions in rustdoc
Dnreikronos May 31, 2026
93e5713
Rollup merge of #156085 - RalfJung:miri-ignored-args, r=saethlin
jhpratt Jun 1, 2026
0e0b0ba
Rollup merge of #156832 - hanna-kruppe:consistent-strict-provenance-l…
jhpratt Jun 1, 2026
a94321c
Rollup merge of #157223 - Dnreikronos:rustdoc-async-delegation-157040…
jhpratt Jun 1, 2026
b5abd7f
Rollup merge of #156528 - jieyouxu:jieyouxu/fix/rustfmt-download-rust…
jhpratt Jun 1, 2026
1a799ee
Rollup merge of #157059 - RalfJung:forbidden-target-features, r=worki…
jhpratt Jun 1, 2026
54dead6
Rollup merge of #157159 - ehuss:fix-free-disk-space, r=Mark-Simulacrum
jhpratt Jun 1, 2026
86b42c9
Rollup merge of #157221 - joshtriplett:random-source-doc-alias, r=jhp…
jhpratt Jun 1, 2026
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
86 changes: 52 additions & 34 deletions compiler/rustc_const_eval/src/interpret/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
caller_args: &mut impl Iterator<
Item = (&'x FnArg<'tcx, M::Provenance>, &'y ArgAbi<'tcx, Ty<'tcx>>),
>,
callee_abi: &ArgAbi<'tcx, Ty<'tcx>>,
callee_arg_idx: usize,
callee_args_abis: &mut impl Iterator<Item = (usize, &'y ArgAbi<'tcx, Ty<'tcx>>)>,
callee_arg: &mir::Place<'tcx>,
callee_ty: Ty<'tcx>,
already_live: bool,
Expand All @@ -282,15 +281,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
'tcx: 'x,
'tcx: 'y,
{
// Get next callee arg.
let (callee_arg_idx, callee_abi) = callee_args_abis.next().unwrap();
assert_eq!(callee_ty, callee_abi.layout.ty);
if callee_abi.is_ignore() {
// This one is skipped. Still must be made live though!
if !already_live {
self.storage_live(callee_arg.as_local().unwrap())?;
}
return interp_ok(());
}
// Find next caller arg.
// Get next caller arg.
let Some((caller_arg, caller_abi)) = caller_args.next() else {
throw_ub_format!("calling a function with fewer arguments than it requires");
};
Expand Down Expand Up @@ -348,6 +342,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
mut cont: ReturnContinuation,
) -> InterpResult<'tcx> {
let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty);
let def_id = instance.def_id();

// The first order of business is to figure out the callee signature.
// However, that requires the list of variadic arguments.
Expand Down Expand Up @@ -423,10 +418,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
"spread_arg: {:?}, locals: {:#?}",
body.spread_arg,
body.args_iter()
.map(|local| (local, self.layout_of_local(self.frame(), local, None).unwrap().ty,))
.map(|local| (local, self.layout_of_local(self.frame(), local, None).unwrap().ty))
.collect::<Vec<_>>()
);

// Determine whether there is a special VaList argument. This is always the
// last argument, and since arguments start at index 1 that's `arg_count`.
let va_list_arg = callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count));
// Determine whether this is a non-capturing closure. That's relevant as their first
// argument can be skipped (and that's the only kind of argument skipping we allow).
let is_non_capturing_closure =
(matches!(instance.def, ty::InstanceKind::ClosureOnceShim { .. })
|| self.tcx.is_closure_like(def_id))
&& {
let arg = &callee_fn_abi.args[0];
matches!(arg.layout.ty.kind(), ty::Closure (_def, closure_args) if {
closure_args.as_closure().upvar_tys().is_empty()
})
};

// In principle, we have two iterators: Where the arguments come from, and where
// they go to.

Expand All @@ -439,21 +449,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
caller_fn_abi.args.len(),
"mismatch between caller ABI and caller arguments",
);
let mut caller_args = args
.iter()
.zip(caller_fn_abi.args.iter())
.filter(|arg_and_abi| !arg_and_abi.1.is_ignore());
let mut caller_args = args.iter().zip(caller_fn_abi.args.iter());

// Now we have to spread them out across the callee's locals,
// taking into account the `spread_arg`. If we could write
// this is a single iterator (that handles `spread_arg`), then
// `pass_argument` would be the loop body. It takes care to
// not advance `caller_iter` for ignored arguments.
// `pass_argument` would be the loop body.
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
// Determine whether there is a special VaList argument. This is always the
// last argument, and since arguments start at index 1 that's `arg_count`.
let va_list_arg = callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count));

// During argument passing, we want retagging with protectors.
M::with_retag_mode(self, RetagMode::FnEntry, |ecx| {
for local in body.args_iter() {
Expand All @@ -466,7 +468,32 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// type, but the result gets cached so this avoids calling the instantiation
// query *again* the next time this local is accessed.
let ty = ecx.layout_of_local(ecx.frame(), local, None)?.ty;
if Some(local) == va_list_arg {

// Some arguments are special: the first (`self`) argument of a non-capturing
// closure; the va_list argument; and the spread_arg.
if is_non_capturing_closure && local == mir::Local::arg(0) {
assert!(va_list_arg.is_none());
assert!(Some(local) != body.spread_arg);
// This argument might be missing on the caller side. So just initialize it in
// the callee.
let (callee_arg_idx, callee_abi) = callee_args_abis.next().unwrap();
assert!(callee_abi.layout.is_1zst() && callee_abi.is_ignore());
ecx.storage_live(local)?;
// And skip it in the caller, if present. We can tell whether it is present by
// comparing the number of arguments on the caller and callee side.
if caller_fn_abi.args.len() == callee_fn_abi.args.len() {
let (_caller_arg, caller_abi) = caller_args.next().unwrap();
if !caller_abi.layout.is_1zst() {
// The caller gave us some other, non-ignorable argument.
throw_ub!(AbiMismatchArgument {
arg_idx: callee_arg_idx,
caller_ty: caller_abi.layout.ty,
callee_ty: callee_abi.layout.ty
});
}
assert!(caller_abi.is_ignore());
}
} else if Some(local) == va_list_arg {
// This is the last callee-side argument of a variadic function.
// This argument is a VaList holding the remaining caller-side arguments.
ecx.storage_live(local)?;
Expand All @@ -476,12 +503,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {

// Consume the remaining arguments by putting them into the variable argument
// list.
let varargs = ecx.allocate_varargs(
&mut caller_args,
// "Ignored" arguments aren't actually passed, so the callee should also
// ignore them. (`pass_argument` does this for regular arguments.)
(&mut callee_args_abis).filter(|(_, abi)| !abi.is_ignore()),
)?;
let varargs = ecx.allocate_varargs(&mut caller_args, &mut callee_args_abis)?;
// When the frame is dropped, these variable arguments are deallocated.
ecx.frame_mut().va_list = varargs.clone();
let key = ecx.va_list_ptr(varargs.into());
Expand All @@ -507,23 +529,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
&[mir::ProjectionElem::Field(FieldIdx::from_usize(i), field_ty)],
*ecx.tcx,
);
let (idx, callee_abi) = callee_args_abis.next().unwrap();
ecx.pass_argument(
&mut caller_args,
callee_abi,
idx,
&mut callee_args_abis,
&dest,
field_ty,
/* already_live */ true,
)?;
}
} else {
// Normal argument. Cannot mark it as live yet, it might be unsized!
let (idx, callee_abi) = callee_args_abis.next().unwrap();
ecx.pass_argument(
&mut caller_args,
callee_abi,
idx,
&mut callee_args_abis,
&dest,
ty,
/* already_live */ false,
Expand Down
96 changes: 49 additions & 47 deletions compiler/rustc_target/src/target_features.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
//! Declares Rust's target feature names for each target.
//! Note that these are similar to but not always identical to LLVM's feature names,
//! and Rust adds some features that do not correspond to LLVM features at all.
//!
//! The target features listed here can be used in `#[target_feature]` and `#[cfg(target_feature)]`.
//! They also do not trigger any warnings when used with `-Ctarget-feature`.
//!
//! Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature`
//! on stable. Using a feature not on the list of Rust target features only emits a warning.
//! Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating.
//! `cfg(target_feature)` for unstable features just works on nightly without any feature gate.
//! `#[target_feature]` requires a feature gate.
//!
//! When adding features to the below lists
//! check whether they're named already elsewhere in rust
//! e.g. in stdarch and whether the given name matches LLVM's
//! if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted.
//! Additionally, if the feature is not available in older version of LLVM supported by the current
//! rust, the same function must be updated to filter out these features to avoid triggering
//! warnings.
//!
//! Also note that all target features listed here must be purely additive: for target_feature 1.1 to
//! be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a
//! per-function level, since we would then allow safe calls from functions with `+soft-float` to
//! functions without that feature!
//!
//! It is important for soundness to consider the interaction of target features and the function
//! call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as
//! arguments, so letting people toggle that feature would be unsound. To this end, the
//! [`Target::abi_required_features`] function computes which target features must and must not be
//! enabled for any given target, and individual features can also be marked as [`Forbidden`]. See
//! <https://github.com/rust-lang/rust/issues/116344> for some more context.
//!
//! The one exception to features that change the ABI is features that enable larger vector
//! registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store
//! information about which target feature is ABI-required for which vector size; this is used to
//! ensure that vectors can only be passed via `extern "C"` when the right feature is enabled. (For
//! the "Rust" ABI we generally pass vectors by-ref exactly to avoid these issues.)
//! Also see <https://github.com/rust-lang/rust/issues/116558>.
//!
//! Stabilizing a target feature requires t-lang approval.
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_macros::StableHash;
use rustc_span::{Symbol, sym};
Expand Down Expand Up @@ -32,9 +70,12 @@ pub enum Stability {
Symbol,
),
/// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be
/// set in the target spec. It is never set in `cfg(target_feature)`. Used in
/// particular for features are actually ABI configuration flags (not all targets are as nice as
/// RISC-V and have an explicit way to set the ABI separate from target features).
/// set in the target spec. It is never set in `cfg(target_feature)`. Used in particular for
/// features are actually ABI configuration flags (such as "soft-float" on many targets).
/// However, "forbidden" target features can still sometimes be enabled via `-Ctarget-cpu` or
/// target feature implications (on the Rust/LLVM level). To prevent that, ABI-relevant target
/// features are ideally pinned down (required or forbidden) in
/// [`Target::abi_required_features`].
Forbidden {
reason: &'static str,
/// True if this is always an error, false if this can be reported as a warning when set via
Expand Down Expand Up @@ -102,50 +143,11 @@ impl Stability {
}
}

// Here we list target features that rustc "understands": they can be used in `#[target_feature]`
// and `#[cfg(target_feature)]`. They also do not trigger any warnings when used with
// `-Ctarget-feature`.
//
// Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature`
// on stable. Using a feature not on the list of Rust target features only emits a warning.
// Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating.
// `cfg(target_feature)` for unstable features just works on nightly without any feature gate.
// `#[target_feature]` requires a feature gate.
//
// When adding features to the below lists
// check whether they're named already elsewhere in rust
// e.g. in stdarch and whether the given name matches LLVM's
// if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted.
// Additionally, if the feature is not available in older version of LLVM supported by the current
// rust, the same function must be updated to filter out these features to avoid triggering
// warnings.
//
// Also note that all target features listed here must be purely additive: for target_feature 1.1 to
// be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a
// per-function level, since we would then allow safe calls from functions with `+soft-float` to
// functions without that feature!
//
// It is important for soundness to consider the interaction of targets features and the function
// call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as
// arguments, so letting people toggle that feature would be unsound. To this end, the
// `abi_required_features` function computes which target features must and must not be enabled for
// any given target, and individual features can also be marked as `Forbidden`.
// See https://github.com/rust-lang/rust/issues/116344 for some more context.
//
// The one exception to features that change the ABI is features that enable larger vector
// registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store
// information about which target feature is ABI-required for which vector size; this is used to
// ensure that vectors can only be passed via `extern "C"` when the right feature is enabled. (For
// the "Rust" ABI we generally pass vectors by-ref exactly to avoid these issues.)
// Also see https://github.com/rust-lang/rust/issues/116558.
//
// Stabilizing a target feature requires t-lang approval.

// If feature A "implies" feature B, then:
// - when A gets enabled (via `-Ctarget-feature` or `#[target_feature]`), we also enable B
// - when B gets disabled (via `-Ctarget-feature`), we also disable A
//
// Both of these are also applied transitively.
/// If feature A "implies" feature B, then:
/// - when A gets enabled (via `-Ctarget-feature` or `#[target_feature]`), we also enable B
/// - when B gets disabled (via `-Ctarget-feature`), we also disable A
///
/// Both of these are also applied transitively.
type ImpliedFeatures = &'static [&'static str];

static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
// Lints:
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(fuzzy_provenance_casts)]
#![deny(lossy_provenance_casts)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
Expand Down
1 change: 1 addition & 0 deletions library/alloctests/benches/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![feature(strict_provenance_lints)]
#![feature(test)]
#![deny(fuzzy_provenance_casts)]
#![deny(lossy_provenance_casts)]

extern crate test;

Expand Down
4 changes: 2 additions & 2 deletions library/alloctests/tests/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ fn box_clone_from_ptr_stability() {
for size in (0..8).map(|i| 2usize.pow(i)) {
let control = vec![Dummy { _data: 42 }; size].into_boxed_slice();
let mut copy = vec![Dummy { _data: 84 }; size].into_boxed_slice();
let copy_raw = copy.as_ptr() as usize;
let copy_raw = copy.as_ptr();
copy.clone_from(&control);
assert_eq!(copy.as_ptr() as usize, copy_raw);
assert_eq!(copy.as_ptr(), copy_raw);
}
}

Expand Down
2 changes: 1 addition & 1 deletion library/alloctests/tests/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn check_overalign_requests<T: Allocator>(allocator: T) {
.collect();
for &ptr in &pointers {
assert_eq!(
(ptr.as_non_null_ptr().as_ptr() as usize) % align,
ptr.as_non_null_ptr().as_ptr().addr() % align,
0,
"Got a pointer less aligned than requested"
)
Expand Down
1 change: 1 addition & 0 deletions library/alloctests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#![feature(ptr_cast_slice)]
#![allow(internal_features)]
#![deny(fuzzy_provenance_casts)]
#![deny(lossy_provenance_casts)]
#![deny(unsafe_op_in_unsafe_fn)]

extern crate alloc;
Expand Down
2 changes: 1 addition & 1 deletion library/alloctests/tests/sort/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ fn self_cmp<T: Ord + Clone + Debug, S: Sort>(
pattern_fn(len).into_iter().map(|val| type_into_fn(val)).collect::<Vec<_>>();

let comparison_fn = |a: &T, b: &T| {
assert_ne!(a as *const T as usize, b as *const T as usize);
assert_ne!(a as *const T, b as *const T);
a.cmp(b)
};

Expand Down
8 changes: 4 additions & 4 deletions library/alloctests/tests/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ fn test_into_iter_zst() {
struct AlignedZstWithDrop([u64; 0]);
impl Drop for AlignedZstWithDrop {
fn drop(&mut self) {
let addr = self as *mut _ as usize;
let addr = (self as *mut Self).addr();
assert!(hint::black_box(addr) % align_of::<u64>() == 0);
}
}
Expand Down Expand Up @@ -1356,10 +1356,10 @@ fn overaligned_allocations() {
for i in 0..0x1000 {
v.reserve_exact(i);
assert!(v[0].0 == 273);
assert!(v.as_ptr() as usize & 0xff == 0);
assert!(v.as_ptr().addr() & 0xff == 0);
v.shrink_to_fit();
assert!(v[0].0 == 273);
assert!(v.as_ptr() as usize & 0xff == 0);
assert!(v.as_ptr().addr() & 0xff == 0);
}
}

Expand Down Expand Up @@ -2574,7 +2574,7 @@ fn test_box_zero_allocator() {

unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() == 0 {
let addr = ptr.as_ptr() as usize;
let addr = ptr.as_ptr().addr();
let mut state = self.state.borrow_mut();
std::println!("freeing {addr}");
assert!(state.0.remove(&addr), "ZST free that wasn't allocated");
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#![deny(rust_2021_incompatible_or_patterns)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(fuzzy_provenance_casts)]
#![deny(lossy_provenance_casts)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
Expand Down
1 change: 1 addition & 0 deletions library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ impl<T: PointeeSized> *const T {
/// [`with_exposed_provenance`]: with_exposed_provenance
#[inline(always)]
#[stable(feature = "exposed_provenance", since = "1.84.0")]
#[expect(lossy_provenance_casts, reason = "this *is* the replacement")]
pub fn expose_provenance(self) -> usize {
self.cast::<()>() as usize
}
Expand Down
Loading
Loading