From e3e20ed6fbdfd3e7173feb77eb94aa37ec1c7c9a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 27 Jun 2026 17:28:30 +0000 Subject: [PATCH 01/31] Rename HAS_CT_PROJECTION to HAS_CONST_ALIASES --- compiler/rustc_lint/src/builtin.rs | 2 +- compiler/rustc_middle/src/ty/abstract_const.rs | 2 +- compiler/rustc_mir_transform/src/impossible_predicates.rs | 2 +- .../rustc_trait_selection/src/traits/query/normalize.rs | 2 +- compiler/rustc_type_ir/src/flags.rs | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 383023aa24457..30c5185859ff6 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1325,7 +1325,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { // FIXME(generic_const_exprs): Revisit this before stabilization. // See also `tests/ui/const-generics/generic_const_exprs/type-alias-bounds.rs`. let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(); - if ty.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) + if ty.has_type_flags(ty::TypeFlags::HAS_CONST_ALIAS) && cx.tcx.features().generic_const_exprs() { return; diff --git a/compiler/rustc_middle/src/ty/abstract_const.rs b/compiler/rustc_middle/src/ty/abstract_const.rs index 43ed22927da56..f0692817fb60b 100644 --- a/compiler/rustc_middle/src/ty/abstract_const.rs +++ b/compiler/rustc_middle/src/ty/abstract_const.rs @@ -44,7 +44,7 @@ impl<'tcx> TyCtxt<'tcx> { self.tcx } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { - if ty.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) { + if ty.has_type_flags(ty::TypeFlags::HAS_CONST_ALIAS) { ty.super_fold_with(self) } else { ty diff --git a/compiler/rustc_mir_transform/src/impossible_predicates.rs b/compiler/rustc_mir_transform/src/impossible_predicates.rs index cc216c39b8599..cf3e22212f95a 100644 --- a/compiler/rustc_mir_transform/src/impossible_predicates.rs +++ b/compiler/rustc_mir_transform/src/impossible_predicates.rs @@ -45,7 +45,7 @@ pub(crate) fn has_impossible_predicates(tcx: TyCtxt<'_>, def_id: DefId) -> bool // Only consider global clauses to simplify. TypeFlags::HAS_FREE_LOCAL_NAMES // Clauses that refer to alias constants as they cause cycles. - | TypeFlags::HAS_CT_PROJECTION, + | TypeFlags::HAS_CONST_ALIAS, ) }); let predicates: Vec<_> = traits::elaborate(tcx, predicates).collect(); diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs index e8371a3c0f257..ac6100747970e 100644 --- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs @@ -376,7 +376,7 @@ impl<'a, 'tcx> QueryNormalizer<'a, 'tcx> { // of type/const and we need to continue folding it to reveal the TAIT behind it // or further normalize nested alias consts. if res != term.to_term(tcx, ty::IsRigid::No) - && (res.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) + && (res.has_type_flags(ty::TypeFlags::HAS_CONST_ALIAS) || matches!( term.kind, ty::AliasTermKind::FreeTy { .. } | ty::AliasTermKind::FreeConst { .. } diff --git a/compiler/rustc_type_ir/src/flags.rs b/compiler/rustc_type_ir/src/flags.rs index da7d8b5acbc1e..afbfc6ecb686b 100644 --- a/compiler/rustc_type_ir/src/flags.rs +++ b/compiler/rustc_type_ir/src/flags.rs @@ -80,7 +80,7 @@ bitflags::bitflags! { /// Does this have `Inherent`? const HAS_TY_INHERENT = 1 << 13; /// Does this have `ConstKind::Alias`? - const HAS_CT_PROJECTION = 1 << 14; + const HAS_CONST_ALIAS = 1 << 14; /// Does this have `Alias` or `ConstKind::Alias`? /// @@ -89,7 +89,7 @@ bitflags::bitflags! { | TypeFlags::HAS_TY_FREE_ALIAS.bits() | TypeFlags::HAS_TY_OPAQUE.bits() | TypeFlags::HAS_TY_INHERENT.bits() - | TypeFlags::HAS_CT_PROJECTION.bits(); + | TypeFlags::HAS_CONST_ALIAS.bits(); /// Is a type or const error reachable? const HAS_NON_REGION_ERROR = 1 << 15; @@ -480,7 +480,7 @@ impl FlagComputation { ty::ConstKind::Alias(is_rigid, alias_const) => { self.add_is_rigid(is_rigid); self.add_args(alias_const.args.as_slice()); - self.add_flags(TypeFlags::HAS_CT_PROJECTION); + self.add_flags(TypeFlags::HAS_CONST_ALIAS); } ty::ConstKind::Infer(infer) => match infer { ty::InferConst::Fresh(_) => self.add_flags(TypeFlags::HAS_CT_FRESH), From 22d6803b3e0f3b02530baa1b93019f327bd9818a Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sat, 20 Jun 2026 02:28:37 -0400 Subject: [PATCH 02/31] Added implementation on set_permissions_nofollow for all platforms supported (windows, unix, uefi, etc.); modified the Unix implementation to use fchmodat instead of open + fchmod; clarified documentations for set_permissions_nofollow; added a test case for set_permissions_nofollow --- library/std/src/fs.rs | 53 ++++++++++++++--- library/std/src/fs/tests.rs | 83 +++++++++++++++++++++++++++ library/std/src/sys/fs/hermit.rs | 4 ++ library/std/src/sys/fs/mod.rs | 24 +------- library/std/src/sys/fs/motor.rs | 5 ++ library/std/src/sys/fs/solid.rs | 5 ++ library/std/src/sys/fs/uefi.rs | 5 ++ library/std/src/sys/fs/unix.rs | 37 ++++++++++++ library/std/src/sys/fs/unsupported.rs | 4 ++ library/std/src/sys/fs/vexos.rs | 4 ++ library/std/src/sys/fs/windows.rs | 9 +++ 11 files changed, 203 insertions(+), 30 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 750ddb91c482b..05695dacbf912 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3337,23 +3337,60 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result fs_imp::set_permissions(path.as_ref(), perm.0) } -/// Set the permissions of a file, unless it is a symlink. +/// Changes the permissions found on a file or a directory. On certain platforms, if the file +/// is a symlink, it will change the permissions bits on the symlink itself rather than +/// the target (e.g. Windows, BSD, MacOS). On other platforms, this results in an error when +/// attempting to change permissions on a symlink (e.g. Linux). /// -/// Note that the non-final path elements are allowed to be symlinks. +/// Note that non-final path elements are allowed to be symlinks. /// /// # Platform-specific behavior /// -/// Currently unimplemented on Windows. +/// This function currently corresponds to `open` with `O_NOFOLLOW` flag enabled +/// + `fchmod` on WASI and the `fchmodat` function on all other Unix platforms with +/// the flag `AT_SYMLINK_NOFOLLOW` enabled. On Windows, the file is opened +/// with the flag `FILE_FLAG_OPEN_REPARSE_POINT` enabled and then the permissions +/// is set through `SetFileInformationByHandle`. On all other platforms, the behavior +/// remains the same with [`fs::set_permissions`]. /// -/// On Unix platforms, this results in a [`FilesystemLoop`] error if the last element is a symlink. +/// [`fs::set_permissions`]: crate::fs::set_permissions /// -/// This behavior may change in the future. +/// Note that, this [may change in the future][changes]. /// -/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop -#[doc(alias = "chmod", alias = "SetFileAttributes")] +/// [changes]: io#platform-specific-behavior +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `path` does not exist. +/// * The user lacks the permission to change attributes of the file. +/// +/// Note: On Linux, this will result in a [`Unsupported`] error +/// if the final element is a symlink. +/// +/// [`Unsupported`]: crate::io::ErrorKind::Unsupported +/// +/// # Examples +/// +/// ```no_run +/// #![feature(set_permissions_nofollow)] +/// use std::fs; +/// +/// fn main() -> std::io::Result<()> { +/// let mut perms = fs::symlink_metadata("foo.txt")?.permissions(); +/// perms.set_readonly(true); +/// // This should result in an error on certain platforms +/// // or succeed in modifying the permissions of a symlink +/// fs::set_permissions_nofollow("foo.txt", perms)?; +/// Ok(()) +/// } +/// ``` +#[doc(alias = "fchmodat", alias = "SetFileInformationByHandle")] #[unstable(feature = "set_permissions_nofollow", issue = "141607")] pub fn set_permissions_nofollow>(path: P, perm: Permissions) -> io::Result<()> { - fs_imp::set_permissions_nofollow(path.as_ref(), perm) + fs_imp::set_permissions_nofollow(path.as_ref(), perm.0) } impl DirBuilder { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index eb619aa4884c5..bf58660754876 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -613,6 +613,66 @@ fn set_get_unix_permissions() { assert_eq!(mask & metadata1.permissions().mode(), 0o0777); } +#[test] +fn set_get_permissions_nofollows() { + let tmpdir = tmpdir(); + let filename = tmpdir.join("set_get_unix_permissions_file"); + check!(File::create(&filename)); + let file_metadata = check!(fs::metadata(&filename)); + assert!(!file_metadata.permissions().readonly()); + let mut permission_bits = file_metadata.permissions(); + permission_bits.set_readonly(true); + let result = fs::set_permissions_nofollow(&filename, permission_bits); + + cfg_select! { + any(windows, unix, target_os = "uefi", target_os = "solid_asp3", target_os = "motor") => { + assert_eq!(result.unwrap(), ()); + let metadata0 = check!(fs::metadata(&filename)); + assert!(metadata0.permissions().readonly()); + }, + _ => { + let error_kind = result.unwrap_err().kind(); + assert_eq!(error_kind, crate::io::ErrorKind::Unsupported); + } + } +} + +// Only Windows and Unix support `fs::set_permissions_nofollow` +#[test] +#[cfg(any(windows, unix))] +fn set_get_permissions_nofollows_symlink() { + #[cfg(not(windows))] + use crate::os::unix::fs::symlink; + #[cfg(windows)] + use crate::os::windows::fs::symlink_dir; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("set_get_unix_permissions_file"); + let symlink_name = tmpdir.join("set_get_unix_permissions"); + check!(File::create(&filename)); + #[cfg(not(windows))] + check!(symlink(&filename, &symlink_name)); + #[cfg(windows)] + check!(symlink_dir(&filename, &symlink_name)); + + let sym_metadata = check!(fs::symlink_metadata(&symlink_name)); + let mut permission_bits = sym_metadata.permissions(); + permission_bits.set_readonly(true); + let result = fs::set_permissions_nofollow(&symlink_name, permission_bits); + + cfg_select! { + any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "espidf", target_os = "horizon") => { + assert_eq!(result.unwrap(), ()); + let metadata0 = check!(fs::symlink_metadata(&symlink_name)); + assert!(metadata0.permissions().readonly()); + }, + _ => { + let error_kind = result.unwrap_err().kind(); + assert_eq!(error_kind, crate::io::ErrorKind::Unsupported); + } + } +} + #[test] #[cfg(windows)] fn file_test_io_seek_read_write() { @@ -1330,6 +1390,29 @@ fn fchmod_works() { check!(file.set_permissions(p)); } +#[test] +fn fchmodat_works() { + let tmpdir = tmpdir(); + let file = tmpdir.join("in.txt"); + + check!(File::create(&file)); + let attr = check!(fs::metadata(&file)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(fs::set_permissions_nofollow(&file, p.clone())); + let attr = check!(fs::metadata(&file)); + assert!(attr.permissions().readonly()); + + match fs::set_permissions_nofollow(&tmpdir.join("foo"), p.clone()) { + Ok(..) => panic!("wanted an error"), + Err(..) => {} + } + + p.set_readonly(false); + check!(fs::set_permissions_nofollow(&file, p)); +} + #[test] fn sync_doesnt_kill_anything() { let tmpdir = tmpdir(); diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 5f69560998293..d29ea81c67e6d 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -566,6 +566,10 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { Err(Error::from_raw_os_error(22)) } +pub fn set_perm_nofollow(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + unsupported() +} + pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { Err(Error::from_raw_os_error(22)) } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0c297c5766b82..b5a5fa93fd491 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -120,28 +120,8 @@ pub fn set_permissions(path: &Path, perm: FilePermissions) -> io::Result<()> { with_native_path(path, &|path| imp::set_perm(path, perm.clone())) } -#[cfg(all(unix, not(target_os = "vxworks")))] -pub fn set_permissions_nofollow(path: &Path, perm: crate::fs::Permissions) -> io::Result<()> { - use crate::fs::OpenOptions; - - let mut options = OpenOptions::new(); - - // ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it. - // Their filesystems do not have symbolic links, so no special handling is required. - #[cfg(not(any(target_os = "espidf", target_os = "horizon")))] - { - use crate::os::unix::fs::OpenOptionsExt; - options.custom_flags(libc::O_NOFOLLOW); - } - - options.open(path)?.set_permissions(perm) -} - -#[cfg(any(not(unix), target_os = "vxworks"))] -pub fn set_permissions_nofollow(_path: &Path, _perm: crate::fs::Permissions) -> io::Result<()> { - crate::unimplemented!( - "`set_permissions_nofollow` is currently only implemented on Unix platforms" - ) +pub fn set_permissions_nofollow(path: &Path, perm: FilePermissions) -> io::Result<()> { + with_native_path(path, &|path| imp::set_perm_nofollow(path, perm.clone())) } pub fn canonicalize(path: &Path) -> io::Result { diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 51b7f347b0b17..a76f64a47c24a 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -319,6 +319,11 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> { } pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + // Motor does not support symlinks + set_perm_nofollow(path, perm) +} + +pub fn set_perm_nofollow(path: &Path, perm: FilePermissions) -> io::Result<()> { let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; moto_rt::fs::set_perm(path, perm.rt_perm).map_err(map_motor_error) } diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index b61147db57ed5..bd963b1d2f038 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -531,6 +531,11 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { } pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + // Solid does not support symlinks + set_perm_nofollow(p, perm) +} + +pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> { error::SolidError::err_if_negative(unsafe { abi::SOLID_FS_Chmod(cstr(p)?.as_ptr(), perm.0.into()) }) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index c4467ed23e125..1a0da329ce1a3 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -480,6 +480,11 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { } pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + // UEFI does not support symlinks + set_perm_nofollow(p, perm) +} + +pub fn set_perm_nofollow(p: &Path, perm: FilePermissions) -> io::Result<()> { let f = uefi_fs::File::from_path(p, file::MODE_READ | file::MODE_WRITE, 0)?; set_perm_inner(&f, perm) } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3152a22534f6c..9f65c486b8c26 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1989,6 +1989,43 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } +pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { + // ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it. + // Their filesystems do not have symbolic links, so no special handling is required. + cfg_select! { + // wasm32-wasip1 targets do not support fchmodat, so we fall down to + // open + fchmod + target_os = "wasi" => { + use crate::fs::OpenOptions; + use crate::fs::Permissions; + use crate::os::wasi::ffi::OsStrExt; + let mut options = OpenOptions::new(); + + #[cfg(not(any(target_os = "espidf", target_os = "horizon")))] + { + use crate::os::wasi::fs::OpenOptionsExt; + options.custom_flags(libc::O_NOFOLLOW); + } + + let bytes = p.to_bytes(); + let os_str = OsStr::from_bytes(bytes); + options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm)) + } + all(target_os = "linux", not(any(target_os = "espidf", target_os = "horizon"))) => { + cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW) + }) + .map(|_| ()) + }, + _ => { + cvt_r(|| unsafe { + libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, 0) + }) + .map(|_| ()) + } + } +} + pub fn rmdir(p: &CStr) -> io::Result<()> { cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()) } diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 703ebef380383..512af27401dfe 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -313,6 +313,10 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } +pub fn set_perm_nofollow(_p: &Path, perm: FilePermissions) -> io::Result<()> { + match perm.0 {} +} + pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 9de9570cb5b24..0ed0a4cbfc437 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -492,6 +492,10 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_perm_nofollow(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + unsupported() +} + pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index e3e7b081b47d5..a7e8e8b3ac630 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1564,6 +1564,15 @@ pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> { } } +pub fn set_perm_nofollow(p: &WCStr, perm: FilePermissions) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(c::FILE_WRITE_ATTRIBUTES); + // `FILE_FLAG_OPEN_REPARSE_POINT` for no_follow behavior + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT); + let file = File::open_native(p, &opts)?; + file.set_permissions(perm) +} + pub fn set_times(p: &WCStr, times: FileTimes) -> io::Result<()> { let mut opts = OpenOptions::new(); opts.access_mode(c::FILE_WRITE_ATTRIBUTES); From 4d1093d2d22cf5642a2a8abb84a7e5285327aaaf Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Fri, 3 Jul 2026 12:10:05 +0300 Subject: [PATCH 03/31] Do not inherit `ConstArgHasType` predicates when it is applied to const from delegation parent --- compiler/rustc_hir_analysis/src/delegation.rs | 64 +++++++++++++++++-- .../generics/const-predicates-ice-158675.rs | 30 +++++++++ .../const-predicates-ice-158675.stderr | 42 ++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 tests/ui/delegation/generics/const-predicates-ice-158675.rs create mode 100644 tests/ui/delegation/generics/const-predicates-ice-158675.stderr diff --git a/compiler/rustc_hir_analysis/src/delegation.rs b/compiler/rustc_hir_analysis/src/delegation.rs index 58474f0ee2e38..40adfeadc4411 100644 --- a/compiler/rustc_hir_analysis/src/delegation.rs +++ b/compiler/rustc_hir_analysis/src/delegation.rs @@ -4,7 +4,7 @@ use std::debug_assert_matches; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{DelegationSelfTyPropagationKind, PathSegment}; @@ -21,6 +21,7 @@ type RemapTable = FxHashMap; struct ParamIndexRemapper<'tcx> { tcx: TyCtxt<'tcx>, remap_table: RemapTable, + delegation_parent_consts: FxHashSet, } impl<'tcx> TypeFolder> for ParamIndexRemapper<'tcx> { @@ -337,7 +338,7 @@ fn create_generic_args<'tcx>( delegation_id: LocalDefId, mut parent_args: &[ty::GenericArg<'tcx>], mut child_args: &[ty::GenericArg<'tcx>], -) -> Vec> { +) -> (Vec>, &'tcx [ty::GenericArg<'tcx>]) { let delegation_generics = tcx.generics_of(delegation_id); let delegation_args = ty::GenericArgs::identity_for_item(tcx, delegation_id); @@ -389,7 +390,7 @@ fn create_generic_args<'tcx>( let zero_self = zero_self.as_ref().into_iter(); let after_lifetimes_self = after_lifetimes_self.as_ref().into_iter(); - zero_self + let args = zero_self .chain(delegation_parent_args) .chain(parent_args.iter().filter(|a| a.as_region().is_some())) .chain(child_args.iter().filter(|a| a.as_region().is_some())) @@ -398,7 +399,9 @@ fn create_generic_args<'tcx>( .chain(child_args.iter().filter(|a| a.as_region().is_none())) .chain(synth_args) .copied() - .collect::>() + .collect::>(); + + (args, delegation_parent_args) } pub(crate) fn inherit_predicates_for_delegation_item<'tcx>( @@ -434,6 +437,44 @@ pub(crate) fn inherit_predicates_for_delegation_item<'tcx>( continue; } + // If we have a constant in parent or child args that came from delegation + // parent: + // ```rust + // trait Trait { /* .. */} + // impl S { + // reuse Trait::, N>::foo; + // } + // ``` + // Then if we inherit const predicate from `Trait` then we end up with + // two `ConstArgHasType` for `N` constant: + // 1) ConstArgHasType(N/#0, bool) from `Trait` + // 2) ConstArgHasType(N/#0, usize) from delegation parent + // So in case the constant came from delegation parent we will not inherit + // ConstArgHasType from signature. + // The check is so complicated because we build generic args for signature + // and predicates inheritance, for the example above it will be + // `args = [S, N/#0, S, N/#0]`, where + // args[0] - Self type, args[1] - delegation parent const, args[2] - first + // arg of callee path, args[3] - second arg of callee path. + // When processing predicate ConstArgHasType(B/#2, bool) + // from delegation signature (`Trait::foo`), we need to map `B/#2` into some + // arg from `args`. The mapping which is built by `create_mapping` function is: + // `{0: 0, 2: 3, 1: 2}`, so as `B/#2` has index `2` it is mapped into third + // arg from `args` - `N/#0`. After we obtained mapped const param, we check if + // it came from delegation parent, and if so we do not process its `ConstArgHasType` + // predicate. + // (Issue #158675). + if let ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, _)) = + pred.0.as_predicate().fold_with(&mut self.folder).kind().skip_binder() + { + let unnorm_const = EarlyBinder::bind(self.tcx, ct).instantiate(self.tcx, args); + if let ty::ConstKind::Param(param) = unnorm_const.skip_norm_wip().kind() + && self.folder.delegation_parent_consts.contains(¶m) + { + continue; + } + } + let new_pred = pred.0.fold_with(&mut self.folder); self.preds.push(( EarlyBinder::bind(self.tcx, new_pred) @@ -497,10 +538,21 @@ fn create_folder_and_args<'tcx>( parent_args: &'tcx [ty::GenericArg<'tcx>], child_args: &'tcx [ty::GenericArg<'tcx>], ) -> (ParamIndexRemapper<'tcx>, Vec>) { - let args = create_generic_args(tcx, sig_id, def_id, parent_args, child_args); + let (args, delegation_parent_args) = + create_generic_args(tcx, sig_id, def_id, parent_args, child_args); + let remap_table = create_mapping(tcx, sig_id, def_id); - (ParamIndexRemapper { tcx, remap_table }, args) + let delegation_parent_consts = delegation_parent_args + .iter() + .filter_map(|a| { + a.as_const().and_then(|c| { + if let ty::ConstKind::Param(param) = c.kind() { Some(param) } else { None } + }) + }) + .collect(); + + (ParamIndexRemapper { tcx, remap_table, delegation_parent_consts }, args) } fn check_constraints<'tcx>( diff --git a/tests/ui/delegation/generics/const-predicates-ice-158675.rs b/tests/ui/delegation/generics/const-predicates-ice-158675.rs new file mode 100644 index 0000000000000..2e88ca8135534 --- /dev/null +++ b/tests/ui/delegation/generics/const-predicates-ice-158675.rs @@ -0,0 +1,30 @@ +#![feature(fn_delegation)] + +mod original_ice { + struct S; + + trait Trait { + fn fun(); + } + + impl S { + reuse Trait::, N>::fun; + //~^ ERROR: the constant `N` is not of type `bool` + } +} + +mod with_child_constants { + struct S; + + trait Trait { + fn fun(); + } + + impl S { + reuse Trait::, N>::fun::; + //~^ ERROR: the constant `N` is not of type `bool` + //~| ERROR: the constant `N` is not of type `char` + } +} + +fn main() {} diff --git a/tests/ui/delegation/generics/const-predicates-ice-158675.stderr b/tests/ui/delegation/generics/const-predicates-ice-158675.stderr new file mode 100644 index 0000000000000..6dbf017294822 --- /dev/null +++ b/tests/ui/delegation/generics/const-predicates-ice-158675.stderr @@ -0,0 +1,42 @@ +error: the constant `N` is not of type `bool` + --> $DIR/const-predicates-ice-158675.rs:11:29 + | +LL | reuse Trait::, N>::fun; + | ^ expected `bool`, found `usize` + | +note: required by a const generic parameter in `original_ice::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:6:20 + | +LL | trait Trait { + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` +LL | fn fun(); + | --- required by a bound in this associated function + +error: the constant `N` is not of type `bool` + --> $DIR/const-predicates-ice-158675.rs:24:29 + | +LL | reuse Trait::, N>::fun::; + | ^ expected `bool`, found `usize` + | +note: required by a const generic parameter in `with_child_constants::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:19:20 + | +LL | trait Trait { + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` +LL | fn fun(); + | --- required by a bound in this associated function + +error: the constant `N` is not of type `char` + --> $DIR/const-predicates-ice-158675.rs:24:39 + | +LL | reuse Trait::, N>::fun::; + | ^ expected `char`, found `usize` + | +note: required by a const generic parameter in `with_child_constants::Trait::fun` + --> $DIR/const-predicates-ice-158675.rs:20:16 + | +LL | fn fun(); + | ^^^^^^^^^^^^^ required by this const generic parameter in `Trait::fun` + +error: aborting due to 3 previous errors + From cc71d5c480899fe7841a01541943ed3df808d255 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 3 Jul 2026 17:50:49 +0200 Subject: [PATCH 04/31] simplify `Option::into_flat_iter` signature --- library/core/src/option.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 163e8bf714b1d..9e7a8d4472d81 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -2082,10 +2082,7 @@ impl Option { /// assert_eq!(o2.into_flat_iter().collect::>(), Vec::<&usize>::new()); /// ``` #[unstable(feature = "option_into_flat_iter", issue = "148441")] - pub fn into_flat_iter(self) -> OptionFlatten - where - T: IntoIterator, - { + pub fn into_flat_iter(self) -> OptionFlatten { OptionFlatten { iter: self.map(IntoIterator::into_iter) } } } From b66097bc94c055e77bcc84ae05da573ca3d3f7af Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:00 -0400 Subject: [PATCH 05/31] Move DuplicateEiiImpls to rustc_middle --- compiler/rustc_middle/src/error.rs | 29 ++++++++++++++++++++++++ compiler/rustc_passes/src/diagnostics.rs | 29 ------------------------ compiler/rustc_passes/src/eii.rs | 3 ++- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 2823b7ba4e22e..440634f376301 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -160,3 +160,32 @@ pub(crate) struct IncrementCompilation { pub run_cmd: String, pub dep_node: String, } + +#[derive(Diagnostic)] +#[diag("multiple implementations of `#[{$name}]`")] +pub struct DuplicateEiiImpls { + pub name: Symbol, + + #[primary_span] + #[label("first implemented here in crate `{$first_crate}`")] + pub first_span: Span, + pub first_crate: Symbol, + + #[label("also implemented here in crate `{$second_crate}`")] + pub second_span: Span, + pub second_crate: Symbol, + + #[note("in addition to these two, { $num_additional_crates -> + [one] another implementation was found in crate {$additional_crate_names} + *[other] more implementations were also found in the following crates: {$additional_crate_names} + }")] + pub additional_crates: Option<()>, + + pub num_additional_crates: usize, + pub additional_crate_names: String, + + #[help( + "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" + )] + pub help: (), +} diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 92562cc462b87..477f380b1c972 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -1104,35 +1104,6 @@ pub(crate) struct EiiWithoutImpl { pub help: (), } -#[derive(Diagnostic)] -#[diag("multiple implementations of `#[{$name}]`")] -pub(crate) struct DuplicateEiiImpls { - pub name: Symbol, - - #[primary_span] - #[label("first implemented here in crate `{$first_crate}`")] - pub first_span: Span, - pub first_crate: Symbol, - - #[label("also implemented here in crate `{$second_crate}`")] - pub second_span: Span, - pub second_crate: Symbol, - - #[note("in addition to these two, { $num_additional_crates -> - [one] another implementation was found in crate {$additional_crate_names} - *[other] more implementations were also found in the following crates: {$additional_crate_names} - }")] - pub additional_crates: Option<()>, - - pub num_additional_crates: usize, - pub additional_crate_names: String, - - #[help( - "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" - )] - pub help: (), -} - #[derive(Diagnostic)] #[diag("function doesn't have a default implementation")] pub(crate) struct FunctionNotHaveDefaultImplementation { diff --git a/compiler/rustc_passes/src/eii.rs b/compiler/rustc_passes/src/eii.rs index c9cfd1e050313..439450fa80bd4 100644 --- a/compiler/rustc_passes/src/eii.rs +++ b/compiler/rustc_passes/src/eii.rs @@ -6,10 +6,11 @@ use std::iter; use rustc_data_structures::fx::FxIndexMap; use rustc_hir::attrs::{EiiDecl, EiiImpl}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::ty::TyCtxt; use rustc_session::config::CrateType; -use crate::diagnostics::{DuplicateEiiImpls, EiiWithoutImpl}; +use crate::diagnostics::EiiWithoutImpl; #[derive(Clone, Copy, Debug)] enum CheckingMode { From 0c822870613e7025f724802f1af6bdbc755a023f Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:36 -0400 Subject: [PATCH 06/31] Reject linked dylib EII default overrides --- compiler/rustc_codegen_ssa/src/back/link.rs | 77 ++++++++++++++++++ compiler/rustc_codegen_ssa/src/base.rs | 80 +++++++++++++++++-- compiler/rustc_codegen_ssa/src/lib.rs | 18 ++++- .../eii/duplicate/auxiliary/dylib_default.rs | 5 ++ .../eii/duplicate/dylib_default_duplicate.rs | 20 +++++ .../duplicate/dylib_default_duplicate.stderr | 15 ++++ 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 tests/ui/eii/duplicate/auxiliary/dylib_default.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index c41f6f30a1da3..587515b84a587 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -30,6 +30,7 @@ use rustc_metadata::{ walk_native_lib_search_dirs, }; use rustc_middle::bug; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::lint::emit_lint_base; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; @@ -76,6 +77,73 @@ pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) { } } +fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol { + if cnum == LOCAL_CRATE { crate_info.local_crate_name } else { crate_info.crate_name[&cnum] } +} + +fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) { + let Some(eii_linkage) = &crate_info.eii_linkage else { + return; + }; + + // A crate can request multiple linked outputs with overlapping dependency + // formats, so report each underlying conflict once. + let mut emitted = FxHashSet::default(); + + // This needs the dependency formats selected for the final artifact. The + // earlier EII pass still handles missing impls and duplicate explicit impls. + for dependency_formats in crate_info.dependency_formats.values() { + for (eii_index, eii) in eii_linkage.iter().enumerate() { + let mut explicit_impls = + eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); + let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { + continue; + }; + // If the explicit impl is already coming from a dylib, that dylib + // has already resolved the default-vs-explicit choice. + if explicit_impls.next().is_some() + || matches!( + dependency_formats.get(explicit_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + { + continue; + } + + let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| { + imp.is_default + && matches!( + dependency_formats.get(imp.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + }); + let Some((default_index, default_impl)) = finalized_default_impls.next() else { + continue; + }; + + if !emitted.insert((eii_index, explicit_index, default_index)) { + continue; + } + + let additional_crate_names = finalized_default_impls + .map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate))) + .collect::>(); + + sess.dcx().emit_err(DuplicateEiiImpls { + name: eii.name, + first_span: explicit_impl.span, + first_crate: eii_impl_crate_name(crate_info, explicit_impl.impl_crate), + second_span: default_impl.span, + second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate), + help: (), + additional_crates: (!additional_crate_names.is_empty()).then_some(()), + num_additional_crates: additional_crate_names.len(), + additional_crate_names: additional_crate_names.join(", "), + }); + } + } +} + /// Performs the linkage portion of the compilation phase. This will generate all /// of the requested outputs for this compilation session. pub fn link_binary( @@ -91,6 +159,7 @@ pub fn link_binary( let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); let mut tempfiles_for_stdout_output: Vec = Vec::new(); let mut rmeta_link_cache = RmetaLinkCache::default(); + let mut checked_eii_linkage = false; for &crate_type in &crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -116,6 +185,14 @@ pub fn link_binary( }); if outputs.outputs.should_link() { + if !checked_eii_linkage { + sess.time("check_externally_implementable_item_linkage", || { + check_externally_implementable_item_linkage(sess, &crate_info); + }); + sess.dcx().abort_if_errors(); + checked_eii_linkage = true; + } + let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name); let tmpdir = TempDirBuilder::new() .prefix("rustc") diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 4e979df471318..b8b19721ef377 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -1,7 +1,7 @@ -use std::cmp; use std::collections::BTreeSet; use std::sync::Arc; use std::time::{Duration, Instant}; +use std::{cmp, iter}; use itertools::Itertools; use rustc_abi::FIRST_VARIANT; @@ -9,17 +9,17 @@ use rustc_ast::expand::allocator::{ ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput, AllocatorTy, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; use rustc_data_structures::unord::UnordMap; -use rustc_hir::attrs::{DebuggerVisualizerType, OptimizeAttr}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::attrs::{DebuggerVisualizerType, EiiDecl, EiiImpl, OptimizeAttr}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; use rustc_hir::lang_items::LangItem; use rustc_hir::{ItemId, Target, find_attr}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; -use rustc_middle::middle::dependency_format::Dependencies; +use rustc_middle::middle::dependency_format::{Dependencies, Linkage}; use rustc_middle::middle::exported_symbols::{self, SymbolExportKind}; use rustc_middle::middle::lang_items; use rustc_middle::mir::BinOp; @@ -50,7 +50,8 @@ use crate::mir::operand::OperandValue; use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ - CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, ModuleCodegen, errors, meth, mir, + CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo, + ModuleCodegen, errors, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -902,6 +903,63 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( && !tcx.should_codegen_locally(instance) } +fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { + #[derive(Debug)] + struct FoundImpl { + imp: EiiImpl, + impl_crate: CrateNum, + } + + #[derive(Debug)] + struct FoundEii { + decl: EiiDecl, + impls: FxIndexMap, + } + + let mut eiis = FxIndexMap::::default(); + + for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { + for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) { + eiis.entry(*did) + .or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() }) + .impls + .extend( + impls + .into_iter() + .map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })), + ); + } + } + + eiis.into_iter() + .filter_map(|(_, FoundEii { decl, impls })| { + let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count(); + let has_default_impl = impls.values().any(|imp| imp.imp.is_default); + // Only this case needs the link-time check. Missing impls and + // duplicate explicit impls are handled in `rustc_passes`. + (explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo { + name: decl.name.name, + impls: impls + .into_iter() + .map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo { + span: tcx.def_span(impl_did), + impl_crate, + is_default: imp.is_default, + }) + .collect(), + }) + }) + .collect() +} + +fn eii_linkage_needed(dependency_formats: &Dependencies) -> bool { + dependency_formats.values().any(|formats| { + formats + .iter() + .any(|&linkage| matches!(linkage, Linkage::Dynamic | Linkage::IncludedFromDylib)) + }) +} + impl CrateInfo { pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo { let crate_types = tcx.crate_types().to_vec(); @@ -913,6 +971,13 @@ impl CrateInfo { crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect(); let local_crate_name = tcx.crate_name(LOCAL_CRATE); let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind); + let dependency_formats = Arc::clone(tcx.dependency_formats(())); + let eii_linkage = if eii_linkage_needed(&dependency_formats) { + let eii_linkage = collect_eii_linkage(tcx); + (!eii_linkage.is_empty()).then_some(eii_linkage) + } else { + None + }; // This list is used when generating the command line to pass through to // system linker. The linker expects undefined symbols on the left of the @@ -957,7 +1022,8 @@ impl CrateInfo { crate_name: UnordMap::with_capacity(n_crates), used_crates, used_crate_source: UnordMap::with_capacity(n_crates), - dependency_formats: Arc::clone(tcx.dependency_formats(())), + dependency_formats, + eii_linkage, windows_subsystem, natvis_debugger_visualizers: Default::default(), lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx), diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index f3f19f9e90d83..8de2092489e93 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -39,7 +39,7 @@ use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::{self, CrateSource}; use rustc_session::lint::builtin::LINKER_MESSAGES; -use rustc_span::Symbol; +use rustc_span::{Span, Symbol}; pub mod assert_module_sources; pub mod back; @@ -255,6 +255,19 @@ impl SymbolExport { } } +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageImplInfo { + pub span: Span, + pub impl_crate: CrateNum, + pub is_default: bool, +} + +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageInfo { + pub name: Symbol, + pub impls: Vec, +} + /// Misc info we load from metadata to persist beyond the tcx. /// /// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo` @@ -281,6 +294,9 @@ pub struct CrateInfo { pub used_crate_source: UnordMap>, pub used_crates: Vec, pub dependency_formats: Arc, + /// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a + /// later `-Zlink-only` invocation. + pub eii_linkage: Option>, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, diff --git a/tests/ui/eii/duplicate/auxiliary/dylib_default.rs b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs new file mode 100644 index 0000000000000..d1136b0ebd636 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs @@ -0,0 +1,5 @@ +#![crate_type = "dylib"] +#![feature(extern_item_impls)] + +#[eii(eii1)] +fn decl1(x: u64) {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.rs b/tests/ui/eii/duplicate/dylib_default_duplicate.rs new file mode 100644 index 0000000000000..0ac3669715f85 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.rs @@ -0,0 +1,20 @@ +//@ aux-build: dylib_default.rs +//@ needs-crate-type: dylib +//@ compile-flags: --emit link +//@ ignore-backends: gcc +// FIXME: linking on windows (specifically mingw) not yet supported, see tracking issue #125418 +//@ ignore-windows +// Regression test for https://github.com/rust-lang/rust/issues/156320. +// A default implementation from an upstream dylib has already been selected and +// must not be overridden by a downstream explicit implementation. +#![feature(extern_item_impls)] + +extern crate dylib_default; + +#[unsafe(dylib_default::eii1)] +fn other(x: u64) { + //~^ ERROR multiple implementations of `#[eii1]` + println!("1{x}"); +} + +fn main() {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.stderr b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr new file mode 100644 index 0000000000000..04c6a13710e28 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr @@ -0,0 +1,15 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/dylib_default_duplicate.rs:15:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `dylib_default_duplicate` + | + ::: $DIR/auxiliary/dylib_default.rs:5:1 + | +LL | fn decl1(x: u64) {} + | ---------------- also implemented here in crate `dylib_default` + | + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error + From 53897bb1f15f10cdd4109e1f4856e1491103ab55 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Thu, 28 May 2026 13:24:47 -0400 Subject: [PATCH 07/31] address a few nits --- compiler/rustc_codegen_ssa/src/back/link.rs | 23 ++++++++++----------- compiler/rustc_codegen_ssa/src/base.rs | 5 ++--- compiler/rustc_codegen_ssa/src/lib.rs | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 587515b84a587..74631030e9b09 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -82,9 +82,9 @@ fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol { } fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) { - let Some(eii_linkage) = &crate_info.eii_linkage else { + if crate_info.eii_linkage.is_empty() { return; - }; + } // A crate can request multiple linked outputs with overlapping dependency // formats, so report each underlying conflict once. @@ -93,7 +93,7 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat // This needs the dependency formats selected for the final artifact. The // earlier EII pass still handles missing impls and duplicate explicit impls. for dependency_formats in crate_info.dependency_formats.values() { - for (eii_index, eii) in eii_linkage.iter().enumerate() { + for (eii_index, eii) in crate_info.eii_linkage.iter().enumerate() { let mut explicit_impls = eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { @@ -159,7 +159,14 @@ pub fn link_binary( let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); let mut tempfiles_for_stdout_output: Vec = Vec::new(); let mut rmeta_link_cache = RmetaLinkCache::default(); - let mut checked_eii_linkage = false; + + if outputs.outputs.should_link() { + sess.time("check_externally_implementable_item_linkage", || { + check_externally_implementable_item_linkage(sess, &crate_info); + }); + sess.dcx().abort_if_errors(); + } + for &crate_type in &crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -185,14 +192,6 @@ pub fn link_binary( }); if outputs.outputs.should_link() { - if !checked_eii_linkage { - sess.time("check_externally_implementable_item_linkage", || { - check_externally_implementable_item_linkage(sess, &crate_info); - }); - sess.dcx().abort_if_errors(); - checked_eii_linkage = true; - } - let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name); let tmpdir = TempDirBuilder::new() .prefix("rustc") diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index b8b19721ef377..e35c446d609fc 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -973,10 +973,9 @@ impl CrateInfo { let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind); let dependency_formats = Arc::clone(tcx.dependency_formats(())); let eii_linkage = if eii_linkage_needed(&dependency_formats) { - let eii_linkage = collect_eii_linkage(tcx); - (!eii_linkage.is_empty()).then_some(eii_linkage) + collect_eii_linkage(tcx) } else { - None + Vec::new() }; // This list is used when generating the command line to pass through to diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 8de2092489e93..ee8542e4c3c33 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -296,7 +296,7 @@ pub struct CrateInfo { pub dependency_formats: Arc, /// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a /// later `-Zlink-only` invocation. - pub eii_linkage: Option>, + pub eii_linkage: Vec, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, From f6ce3bacd1f682b3d8fb01d32d46875530cf0483 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Thu, 28 May 2026 18:21:44 -0400 Subject: [PATCH 08/31] preserve EII rlib-only aux builds --- tests/ui/eii/default/auxiliary/decl_with_default.rs | 1 + tests/ui/eii/default/auxiliary/impl1.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/eii/default/auxiliary/decl_with_default.rs b/tests/ui/eii/default/auxiliary/decl_with_default.rs index ba855cb854afd..8d962c19c94d9 100644 --- a/tests/ui/eii/default/auxiliary/decl_with_default.rs +++ b/tests/ui/eii/default/auxiliary/decl_with_default.rs @@ -1,3 +1,4 @@ +//@ no-prefer-dynamic #![crate_type = "rlib"] #![feature(extern_item_impls)] diff --git a/tests/ui/eii/default/auxiliary/impl1.rs b/tests/ui/eii/default/auxiliary/impl1.rs index 84edf24e12816..3510ea1eb3f27 100644 --- a/tests/ui/eii/default/auxiliary/impl1.rs +++ b/tests/ui/eii/default/auxiliary/impl1.rs @@ -1,3 +1,4 @@ +//@ no-prefer-dynamic //@ aux-build: decl_with_default.rs #![crate_type = "rlib"] #![feature(extern_item_impls)] From eef7f2eeb1ef187eab15dd4cab74c4ed712ea2e0 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Mon, 4 May 2026 08:13:09 +0000 Subject: [PATCH 09/31] Add library support for `aarch64-unknown-linux-pauthtest` --- .../std/src/sys/pal/unix/stack_overflow.rs | 20 +++++++++--- library/std/src/sys/personality/gcc.rs | 31 ++++++++++++++++++- library/std/tests/pipe_subprocess.rs | 6 ++++ library/std/tests/process_spawning.rs | 12 +++++-- .../std_detect/src/detect/os/linux/auxvec.rs | 4 +-- library/unwind/src/lib.rs | 5 +++ 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 3b951899dfec9..0b67f4f8b5e37 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -409,9 +409,15 @@ mod imp { unsafe { // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + if cfg!(all( + target_os = "linux", + not(any(target_env = "musl", target_env = "pauthtest")) + )) { install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { + } else if cfg!(all( + target_os = "linux", + any(target_env = "musl", target_env = "pauthtest") + )) { install_main_guard_linux_musl(page_size) } else if cfg!(target_os = "freebsd") { #[cfg(not(target_os = "freebsd"))] @@ -588,7 +594,10 @@ mod imp { let mut guardsize = 0; assert_eq!(libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guardsize), 0); if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { + if cfg!(all( + target_os = "linux", + any(target_env = "musl", target_env = "pauthtest") + )) { // musl versions before 1.1.19 always reported guard // size obtained from pthread_attr_get_np as zero. // Use page size as a fallback. @@ -604,7 +613,10 @@ mod imp { let stackaddr = stackptr.addr(); ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { + } else if cfg!(all( + target_os = "linux", + any(target_env = "musl", target_env = "pauthtest") + )) { Some(stackaddr - guardsize..stackaddr) } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) { diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index 019d5629d6d6e..ea1a6be30354d 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -89,6 +89,34 @@ const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11 #[cfg(any(target_arch = "loongarch32", target_arch = "loongarch64"))] const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 +unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { + cfg_select! { + all(target_env = "pauthtest", target_arch = "aarch64") => { + // DWARF register number for SP on AArch64. + const SP_REG: i32 = 31; + + unsafe { + let sp = uw::_Unwind_GetGR(context, SP_REG) as u64; + let mut addr = lpad as usize; + + // `pacib` corresponds to `ptrauth_key_process_dependent_code` in . + core::arch::asm!( + "pacib {addr}, {sp}", + addr = inout(reg) addr, + sp = in(reg) sp, + options(nostack, preserves_flags) + ); + + lpad.with_addr(addr) + } + } + _ => { + let _ = context; + lpad + } + } +} + // The following code is based on GCC's C and C++ personality routines. For reference, see: // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c @@ -239,7 +267,8 @@ cfg_select! { exception_object.cast(), ); uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, core::ptr::null()); - uw::_Unwind_SetIP(context, lpad); + let maybe_signed_lpad = sign_lpad(context, lpad); + uw::_Unwind_SetIP(context, maybe_signed_lpad); uw::_URC_INSTALL_CONTEXT } EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs index dad1ea6c57377..9643c3b7bdad8 100644 --- a/library/std/tests/pipe_subprocess.rs +++ b/library/std/tests/pipe_subprocess.rs @@ -13,10 +13,16 @@ fn main() { fn parent() { let me = env::current_exe().unwrap(); + // If a custom `runner` is set up for the current target, we'll be + // executing `./runner ./test`, not just `./test`. For such a case, + // use the same arguments for child to avoid executing `runner` + // without an actual executable. + let args = env::args(); let (rx, tx) = pipe().unwrap(); assert!( process::Command::new(me) + .args(args) .env("I_AM_THE_CHILD", "1") .stdout(tx) .status() diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs index 93f73ccad3ea4..80e712a2388a1 100644 --- a/library/std/tests/process_spawning.rs +++ b/library/std/tests/process_spawning.rs @@ -26,8 +26,16 @@ fn issue_15149() { env::join_paths(paths).unwrap() }; - let child_output = - process::Command::new("mytest").env("PATH", &path).arg("child").output().unwrap(); + // If a custom `runner` is set up for the current target, we'll be executing `./runner ./test`, + // not just `./test`. For such a case, use the same arguments for child to avoid executing + // `runner` without an actual executable. + let args = env::args(); + let child_output = process::Command::new("mytest") + .args(args) + .env("PATH", &path) + .arg("child") + .output() + .unwrap(); assert!( child_output.status.success(), diff --git a/library/std_detect/src/detect/os/linux/auxvec.rs b/library/std_detect/src/detect/os/linux/auxvec.rs index c0bbc7d4efa88..dec33ffeedd34 100644 --- a/library/std_detect/src/detect/os/linux/auxvec.rs +++ b/library/std_detect/src/detect/os/linux/auxvec.rs @@ -51,7 +51,7 @@ pub(crate) struct AuxVec { /// Note that run-time feature detection is not invoked for features that can /// be detected at compile-time. /// -/// Note: We always directly use `getauxval` on `*-linux-{gnu,musl,ohos}*` and +/// Note: We always directly use `getauxval` on `*-linux-{gnu,musl,ohos,pauthtest}*` and /// `*-android*` targets rather than `dlsym` it because we can safely assume /// `getauxval` is linked to the binary. /// - `*-linux-gnu*` targets ([since Rust 1.64](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html)) @@ -125,7 +125,7 @@ fn getauxval(key: usize) -> Result { any( all( target_os = "linux", - any(target_env = "gnu", target_env = "musl", target_env = "ohos"), + any(target_env = "gnu", target_env = "musl", target_env = "ohos", target_env = "pauthtest"), ), target_os = "android", ) => { diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index 4e380d8894781..11ea306c4923c 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -94,6 +94,11 @@ cfg_select! { } } +// For pauthtest the only supported unwinding mechanism is provided by libunwind. +#[cfg(target_env = "pauthtest")] +#[link(name = "unwind")] +unsafe extern "C" {} + // This is the same as musl except that we default to using the system libunwind // instead of libgcc. #[cfg(target_env = "ohos")] From 1a7135cd5f6b38cff90098ec418b26c37c6feb22 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Mon, 11 May 2026 12:46:32 +0000 Subject: [PATCH 10/31] Use target_env = "musl" & target_abi = "pauthtest" instead of env --- .../std/src/sys/pal/unix/stack_overflow.rs | 20 ++++--------------- library/std/src/sys/personality/gcc.rs | 2 +- .../std_detect/src/detect/os/linux/auxvec.rs | 4 ++-- library/unwind/src/lib.rs | 14 +++++++------ 4 files changed, 15 insertions(+), 25 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 0b67f4f8b5e37..3b951899dfec9 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -409,15 +409,9 @@ mod imp { unsafe { // this way someone on any unix-y OS can check that all these compile - if cfg!(all( - target_os = "linux", - not(any(target_env = "musl", target_env = "pauthtest")) - )) { + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { install_main_guard_linux(page_size) - } else if cfg!(all( - target_os = "linux", - any(target_env = "musl", target_env = "pauthtest") - )) { + } else if cfg!(all(target_os = "linux", target_env = "musl")) { install_main_guard_linux_musl(page_size) } else if cfg!(target_os = "freebsd") { #[cfg(not(target_os = "freebsd"))] @@ -594,10 +588,7 @@ mod imp { let mut guardsize = 0; assert_eq!(libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guardsize), 0); if guardsize == 0 { - if cfg!(all( - target_os = "linux", - any(target_env = "musl", target_env = "pauthtest") - )) { + if cfg!(all(target_os = "linux", target_env = "musl")) { // musl versions before 1.1.19 always reported guard // size obtained from pthread_attr_get_np as zero. // Use page size as a fallback. @@ -613,10 +604,7 @@ mod imp { let stackaddr = stackptr.addr(); ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all( - target_os = "linux", - any(target_env = "musl", target_env = "pauthtest") - )) { + } else if cfg!(all(target_os = "linux", target_env = "musl")) { Some(stackaddr - guardsize..stackaddr) } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) { diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index ea1a6be30354d..4a55613ca3f6b 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -91,7 +91,7 @@ const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { cfg_select! { - all(target_env = "pauthtest", target_arch = "aarch64") => { + all(target_abi = "pauthtest", target_arch = "aarch64") => { // DWARF register number for SP on AArch64. const SP_REG: i32 = 31; diff --git a/library/std_detect/src/detect/os/linux/auxvec.rs b/library/std_detect/src/detect/os/linux/auxvec.rs index dec33ffeedd34..c0bbc7d4efa88 100644 --- a/library/std_detect/src/detect/os/linux/auxvec.rs +++ b/library/std_detect/src/detect/os/linux/auxvec.rs @@ -51,7 +51,7 @@ pub(crate) struct AuxVec { /// Note that run-time feature detection is not invoked for features that can /// be detected at compile-time. /// -/// Note: We always directly use `getauxval` on `*-linux-{gnu,musl,ohos,pauthtest}*` and +/// Note: We always directly use `getauxval` on `*-linux-{gnu,musl,ohos}*` and /// `*-android*` targets rather than `dlsym` it because we can safely assume /// `getauxval` is linked to the binary. /// - `*-linux-gnu*` targets ([since Rust 1.64](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html)) @@ -125,7 +125,7 @@ fn getauxval(key: usize) -> Result { any( all( target_os = "linux", - any(target_env = "gnu", target_env = "musl", target_env = "ohos", target_env = "pauthtest"), + any(target_env = "gnu", target_env = "musl", target_env = "ohos"), ), target_os = "android", ) => { diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index 11ea306c4923c..b19694f25c646 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -56,7 +56,14 @@ cfg_select! { } } -#[cfg(target_env = "musl")] +// `target_abi = "pauthtest"` is currently only used by the Linux/musl +// `aarch64-unknown-linux-pauthtest` target. That target only supports unwinding +// via libunwind. +#[cfg(target_abi = "pauthtest")] +#[link(name = "unwind")] +unsafe extern "C" {} + +#[cfg(all(target_env = "musl", not(target_abi = "pauthtest")))] cfg_select! { all(feature = "llvm-libunwind", feature = "system-llvm-libunwind") => { compile_error!("`llvm-libunwind` and `system-llvm-libunwind` cannot be enabled at the same time"); @@ -94,11 +101,6 @@ cfg_select! { } } -// For pauthtest the only supported unwinding mechanism is provided by libunwind. -#[cfg(target_env = "pauthtest")] -#[link(name = "unwind")] -unsafe extern "C" {} - // This is the same as musl except that we default to using the system libunwind // instead of libgcc. #[cfg(target_env = "ohos")] From d0b4c469d3efbb2c7d79286b395b9c5a98a23810 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Wed, 3 Jun 2026 13:45:14 +0000 Subject: [PATCH 11/31] Update landing pad to use addr() --- library/std/src/sys/personality/gcc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index 4a55613ca3f6b..549d3ec1313c6 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -96,8 +96,8 @@ unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *cons const SP_REG: i32 = 31; unsafe { - let sp = uw::_Unwind_GetGR(context, SP_REG) as u64; - let mut addr = lpad as usize; + let sp = uw::_Unwind_GetGR(context, SP_REG).addr() as u64; + let mut addr = lpad.addr(); // `pacib` corresponds to `ptrauth_key_process_dependent_code` in . core::arch::asm!( From 038cb8da9b57a59b1ca36a2ca4396001e51425a2 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 3 Jul 2026 10:35:46 +0000 Subject: [PATCH 12/31] Use rust_force_inline on sign_lpad function --- library/std/src/sys/personality/gcc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index 549d3ec1313c6..379b0f6a1187c 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -89,6 +89,7 @@ const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11 #[cfg(any(target_arch = "loongarch32", target_arch = "loongarch64"))] const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 +#[rustc_force_inline] unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { cfg_select! { all(target_abi = "pauthtest", target_arch = "aarch64") => { From b7496302dbe5be357aab9de0f1c1be5ecb44c834 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Mon, 6 Jul 2026 15:53:08 +0000 Subject: [PATCH 13/31] Set correct cfg_select for sign_lpad --- library/std/src/sys/personality/gcc.rs | 58 +++++++++++++------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index 379b0f6a1187c..0e20a4192b1b9 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -89,35 +89,6 @@ const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11 #[cfg(any(target_arch = "loongarch32", target_arch = "loongarch64"))] const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 -#[rustc_force_inline] -unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { - cfg_select! { - all(target_abi = "pauthtest", target_arch = "aarch64") => { - // DWARF register number for SP on AArch64. - const SP_REG: i32 = 31; - - unsafe { - let sp = uw::_Unwind_GetGR(context, SP_REG).addr() as u64; - let mut addr = lpad.addr(); - - // `pacib` corresponds to `ptrauth_key_process_dependent_code` in . - core::arch::asm!( - "pacib {addr}, {sp}", - addr = inout(reg) addr, - sp = in(reg) sp, - options(nostack, preserves_flags) - ); - - lpad.with_addr(addr) - } - } - _ => { - let _ = context; - lpad - } - } -} - // The following code is based on GCC's C and C++ personality routines. For reference, see: // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c @@ -233,6 +204,35 @@ cfg_select! { } } _ => { + #[rustc_force_inline] + unsafe fn sign_lpad(context: *mut uw::_Unwind_Context, lpad: *const u8) -> *const u8 { + cfg_select! { + all(target_abi = "pauthtest", target_arch = "aarch64") => { + // DWARF register number for SP on AArch64. + const SP_REG: i32 = 31; + + unsafe { + let sp = uw::_Unwind_GetGR(context, SP_REG).addr() as u64; + let mut addr = lpad.addr(); + + // `pacib` corresponds to `ptrauth_key_process_dependent_code` in . + core::arch::asm!( + "pacib {addr}, {sp}", + addr = inout(reg) addr, + sp = in(reg) sp, + options(nostack, preserves_flags) + ); + + lpad.with_addr(addr) + } + } + _ => { + let _ = context; + lpad + } + } + } + /// Default personality routine, which is used directly on most targets /// and indirectly on Windows x86_64 and AArch64 via SEH. unsafe extern "C" fn rust_eh_personality_impl( From 017255b2e7f2763a613bac8a56a8bbecb07f36bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 15:51:51 +0200 Subject: [PATCH 14/31] Add new PGO config section to bootstrap --- bootstrap.example.toml | 14 +++++ src/bootstrap/src/core/build_steps/compile.rs | 14 ++--- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/bootstrap/src/core/config/config.rs | 60 +++++++++++++++---- src/bootstrap/src/core/config/flags.rs | 8 ++- src/bootstrap/src/core/config/toml/mod.rs | 18 +++++- src/bootstrap/src/core/config/toml/pgo.rs | 30 ++++++++++ src/bootstrap/src/core/config/toml/rust.rs | 6 +- src/bootstrap/src/utils/change_tracker.rs | 5 ++ 9 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 src/bootstrap/src/core/config/toml/pgo.rs diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 1ca4bc9159e7c..caa342e360d34 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -973,6 +973,20 @@ #dist.vendor = if "is a tarball source" || "is a git repository" { true } else { false } +# ============================================================================= +# Profile-guided optimization options +# +# Configure the PGO profile to be used for compilation, or a path where the +# profile will be written by an instrumented binary, for individual components +# ============================================================================= +[pgo] +# Use the following profile to PGO optimize the Rust compiler. +#rustc.use = "/tmp/profiles/foo.profraw" +# Instrument the Rust compiler, so that when executed, it will gather profiles +# to this path. +#rustc.generate = "/tmp/profiles/foo.profraw" + + # ============================================================================= # Options for specific targets # diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3216fd671c3f8..5a762867320c1 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1293,12 +1293,12 @@ pub fn rustc_cargo( cargo.rustflag("-Clink-args=-Wl,--icf=all"); } - if builder.config.rust_profile_use.is_some() && builder.config.rust_profile_generate.is_some() { - panic!("Cannot use and generate PGO profiles at the same time"); - } - let is_collecting = if let Some(path) = &builder.config.rust_profile_generate { + eprintln!("Profile: {:?}", builder.config.rust_pgo.generate_profile); + + let is_collecting = if let Some(path) = &builder.config.rust_pgo.generate_profile { if build_compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-generate={path}")); + cargo + .rustflag(&format!("-Cprofile-generate={}", path.to_str().expect("non-UTF8 path"))); // Apparently necessary to avoid overflowing the counters during // a Cargo build profile cargo.rustflag("-Cllvm-args=-vp-counters-per-site=4"); @@ -1306,9 +1306,9 @@ pub fn rustc_cargo( } else { false } - } else if let Some(path) = &builder.config.rust_profile_use { + } else if let Some(path) = &builder.config.rust_pgo.use_profile { if build_compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-use={path}")); + cargo.rustflag(&format!("-Cprofile-use={}", path.to_str().expect("non-UTF8 path"))); if builder.is_verbose() { cargo.rustflag("-Cllvm-args=-pgo-warn-missing-function"); } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 114387d963a6d..d9aa6f7c18815 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -3035,7 +3035,7 @@ impl Step for ReproducibleArtifacts { fn run(self, builder: &Builder<'_>) -> Self::Output { let mut added_anything = false; let tarball = Tarball::new(builder, "reproducible-artifacts", &self.target.triple); - if let Some(path) = builder.config.rust_profile_use.as_ref() { + if let Some(path) = builder.config.rust_pgo.use_profile.as_ref() { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 267e21c599728..ddd2442802d10 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -41,6 +41,7 @@ use crate::core::config::toml::dist::Dist; use crate::core::config::toml::gcc::Gcc; use crate::core::config::toml::install::Install; use crate::core::config::toml::llvm::Llvm; +use crate::core::config::toml::pgo::{Pgo, PgoConfig}; use crate::core::config::toml::rust::{ BootstrapOverrideLld, Rust, RustOptimize, check_incompatible_options_for_ci_rustc, parse_codegen_backends, @@ -225,14 +226,13 @@ pub struct Config { pub rust_remap_debuginfo: bool, pub rust_new_symbol_mangling: Option, pub rust_annotate_moves_size_limit: Option, - pub rust_profile_use: Option, - pub rust_profile_generate: Option, pub rust_lto: RustcLto, pub rust_validate_mir_opts: Option, pub rust_std_features: BTreeSet, pub rust_break_on_ice: bool, pub rust_parallel_frontend_threads: Option, pub rust_rustflags: Vec, + pub rust_pgo: PgoConfig, pub llvm_profile_use: Option, pub llvm_profile_generate: bool, @@ -456,8 +456,20 @@ impl Config { // Now load the TOML config, as soon as possible let (mut toml, toml_path) = load_toml_config(&src, flags_config, &get_toml); - postprocess_toml(&mut toml, &src, toml_path.clone(), &exec_ctx, &flags_set, &get_toml); + let TomlConfig { + change_id: toml_change_id, + build: toml_build, + install: toml_install, + llvm: toml_llvm, + gcc: toml_gcc, + rust: toml_rust, + target: toml_target, + dist: toml_dist, + pgo: toml_pgo, + profile: _, + include: _, + } = toml; // Now override TOML values with flags, to make sure that we won't later override flags with // TOML values by accident instead, because flags have higher priority. @@ -523,7 +535,7 @@ impl Config { ccache: build_ccache, exclude: build_exclude, compiletest_allow_stage0: build_compiletest_allow_stage0, - } = toml.build.unwrap_or_default(); + } = toml_build.unwrap_or_default(); let Install { prefix: install_prefix, @@ -533,7 +545,7 @@ impl Config { libdir: install_libdir, mandir: install_mandir, datadir: install_datadir, - } = toml.install.unwrap_or_default(); + } = toml_install.unwrap_or_default(); let Rust { optimize: rust_optimize, @@ -595,7 +607,7 @@ impl Config { std_features: rust_std_features, break_on_ice: rust_break_on_ice, rustflags: rust_rustflags, - } = toml.rust.unwrap_or_default(); + } = toml_rust.unwrap_or_default(); let Llvm { optimize: llvm_optimize, @@ -627,7 +639,7 @@ impl Config { enable_warnings: llvm_enable_warnings, download_ci_llvm: llvm_download_ci_llvm, build_config: llvm_build_config, - } = toml.llvm.unwrap_or_default(); + } = toml_llvm.unwrap_or_default(); let Dist { sign_folder: dist_sign_folder, @@ -637,12 +649,35 @@ impl Config { compression_profile: dist_compression_profile, include_mingw_linker: dist_include_mingw_linker, vendor: dist_vendor, - } = toml.dist.unwrap_or_default(); + } = toml_dist.unwrap_or_default(); let Gcc { download_ci_gcc: gcc_download_ci_gcc, libgccjit_libs_dir: gcc_libgccjit_libs_dir, - } = toml.gcc.unwrap_or_default(); + } = toml_gcc.unwrap_or_default(); + + let Pgo { rustc: pgo_rustc } = toml_pgo.unwrap_or_default(); + let mut pgo_rustc = pgo_rustc.unwrap_or_default(); + + // Backcompat: flags have priority over config + if flags_rust_profile_use.is_some() || flags_rust_profile_generate.is_some() { + eprintln!( + "WARNING: the `--rust-profile-generate` and `--rust-profile-use` flags have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." + ); + } + if rust_profile_use.is_some() || rust_profile_generate.is_some() { + eprintln!( + "WARNING: the `rust.profile-generate` and `rust.profile-use` config options have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." + ); + } + + pgo_rustc.use_profile = + flags_rust_profile_use.or(pgo_rustc.use_profile).or(rust_profile_use); + pgo_rustc.generate_profile = + flags_rust_profile_generate.or(pgo_rustc.generate_profile).or(rust_profile_generate); + if pgo_rustc.use_profile.is_some() && pgo_rustc.generate_profile.is_some() { + panic!("Cannot use and generate rust PGO profiles at the same time"); + } if rust_bootstrap_override_lld.is_some() && rust_bootstrap_override_lld_legacy.is_some() { panic!( @@ -877,7 +912,7 @@ impl Config { // Linux targets for which the user explicitly overrode the used linker let mut targets_with_user_linker_override = HashSet::new(); - if let Some(t) = toml.target { + if let Some(t) = toml_target { for (triple, cfg) in t { let TomlTarget { cc: target_cc, @@ -1337,7 +1372,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to cargo_info, cargo_native_static: build_cargo_native_static.unwrap_or(false), ccache, - change_id: toml.change_id.inner, + change_id: toml_change_id.inner, channel, ci_env, clippy_info, @@ -1496,8 +1531,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to .or(rust_overflow_checks) .unwrap_or(rust_debug == Some(true)), rust_parallel_frontend_threads: rust_parallel_frontend_threads.map(threads_from_config), - rust_profile_generate: flags_rust_profile_generate.or(rust_profile_generate), - rust_profile_use: flags_rust_profile_use.or(rust_profile_use), + rust_pgo: pgo_rustc, rust_randomize_layout: rust_randomize_layout.unwrap_or(false), rust_remap_debuginfo: rust_remap_debuginfo.unwrap_or(false), rust_rpath: rust_rpath.unwrap_or(true), diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index bf171f1de34e0..d6020262e0579 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -153,11 +153,14 @@ pub struct Flags { /// generate PGO profile with rustc build #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub rust_profile_generate: Option, + // FIXME: Remove this option at the end of 2026 + pub rust_profile_generate: Option, /// use PGO profile for rustc build + // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub rust_profile_use: Option, + pub rust_profile_use: Option, /// use PGO profile for LLVM build + // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] pub llvm_profile_use: Option, // LLVM doesn't support a custom location for generating profile @@ -165,6 +168,7 @@ pub struct Flags { // // llvm_out/build/profiles/ is the location this writes to. /// generate PGO profile with llvm built for rustc + // FIXME: Remove this option at the end of 2026 #[arg(global = true, long)] pub llvm_profile_generate: bool, /// Enable BOLT link flags diff --git a/src/bootstrap/src/core/config/toml/mod.rs b/src/bootstrap/src/core/config/toml/mod.rs index f6dc5b67e1010..74c21617746bf 100644 --- a/src/bootstrap/src/core/config/toml/mod.rs +++ b/src/bootstrap/src/core/config/toml/mod.rs @@ -15,6 +15,7 @@ pub mod dist; pub mod gcc; pub mod install; pub mod llvm; +pub mod pgo; pub mod rust; pub mod target; @@ -27,6 +28,7 @@ use llvm::Llvm; use rust::Rust; use target::TomlTarget; +use crate::core::config::toml::pgo::Pgo; use crate::core::config::{Merge, ReplaceOpt}; use crate::{Config, HashMap, HashSet, Path, PathBuf, exit, fs, t}; @@ -47,6 +49,7 @@ pub(crate) struct TomlConfig { pub(super) rust: Option, pub(super) target: Option>, pub(super) dist: Option, + pub(super) pgo: Option, pub(super) profile: Option, pub(super) include: Option>, } @@ -56,7 +59,19 @@ impl Merge for TomlConfig { &mut self, parent_config_path: Option, included_extensions: &mut HashSet, - TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self, + TomlConfig { + build, + install, + llvm, + gcc, + rust, + dist, + target, + pgo, + profile, + change_id, + include, + }: Self, replace: ReplaceOpt, ) { fn do_merge(x: &mut Option, y: Option, replace: ReplaceOpt) { @@ -78,6 +93,7 @@ impl Merge for TomlConfig { do_merge(&mut self.gcc, gcc, replace); do_merge(&mut self.rust, rust, replace); do_merge(&mut self.dist, dist, replace); + do_merge(&mut self.pgo, pgo, replace); match (self.target.as_mut(), target) { (_, None) => {} diff --git a/src/bootstrap/src/core/config/toml/pgo.rs b/src/bootstrap/src/core/config/toml/pgo.rs new file mode 100644 index 0000000000000..dde76f078fc39 --- /dev/null +++ b/src/bootstrap/src/core/config/toml/pgo.rs @@ -0,0 +1,30 @@ +//! This module defines the `Pgo` struct, which represents the `[pgo]` table +//! in the `bootstrap.toml` configuration file. +//! +//! The `[pgo]` table contains options related PGO (Profile-Guided Optimization) of various +//! components built by bootstrap. + +use serde::{Deserialize, Deserializer}; + +use crate::core::config::Merge; +use crate::core::config::toml::ReplaceOpt; +use crate::{HashSet, PathBuf, define_config, exit}; + +#[derive(Clone, Default, Debug, serde_derive::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PgoConfig { + /// Use the given PGO profile to optimize a component. + #[serde(default, rename = "use")] + pub use_profile: Option, + /// Build a component with PGO instrumentation. Once executed, the profiles will be stored + /// into this path. + #[serde(default, rename = "generate")] + pub generate_profile: Option, +} + +define_config! { + #[derive(Default)] + struct Pgo { + rustc: Option = "rustc", + } +} diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 2facf839521d3..5c0fd37e46c2e 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -64,8 +64,10 @@ define_config! { ehcont_guard: Option = "ehcont-guard", new_symbol_mangling: Option = "new-symbol-mangling", annotate_moves_size_limit: Option = "annotate-moves-size-limit", - profile_generate: Option = "profile-generate", - profile_use: Option = "profile-use", + // FIXME: Remove this option at the end of 2026 + profile_generate: Option = "profile-generate", + // FIXME: Remove this option at the end of 2026 + profile_use: Option = "profile-use", // ignored; this is set from an env var set by bootstrap.py download_rustc: Option = "download-rustc", lto: Option = "lto", diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index b34c1b55700f8..94a7acf30d4e9 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -636,4 +636,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New option `rust.compress-debuginfo` allows configuring whether Rust and C/C++ debuginfo should be compressed.", }, + ChangeInfo { + change_id: 999999, + severity: ChangeSeverity::Info, + summary: "New config section `pgo` was introduced, to configure PGO profiling options. The `--rust-profile-use`/`--rust-profile-generate`/`--llvm-profile-use`/`--llvm-profile-generate` flags and the `rust.profile-use`/`rust.profile-generate` config options have been deprecated.", + }, ]; From 33da2c897239a35693804f91e3697cf481d170d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:04:32 +0200 Subject: [PATCH 15/31] Port LLVM PGO to the new PGO config section --- bootstrap.example.toml | 6 ++- src/bootstrap/src/core/build_steps/compile.rs | 2 +- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/bootstrap/src/core/build_steps/llvm.rs | 15 +++--- src/bootstrap/src/core/config/config.rs | 46 ++++++++++++++++--- src/bootstrap/src/core/config/flags.rs | 2 +- src/bootstrap/src/core/config/toml/pgo.rs | 1 + 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index caa342e360d34..b48f7ad7da742 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -985,7 +985,11 @@ # Instrument the Rust compiler, so that when executed, it will gather profiles # to this path. #rustc.generate = "/tmp/profiles/foo.profraw" - +# Use the following profile to PGO optimize LLVM. +#llvm.use = "/tmp/profiles/foo.profraw" +# Instrument LLVM, so that when executed, it will gather profiles +# Note: for LLVM specifically, this should be a directory, not a file path. +#llvm.generate = "/tmp/profiles" # ============================================================================= # Options for specific targets diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 5a762867320c1..3ef6428b2f746 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1464,7 +1464,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect // found. This is to avoid the linker errors about undefined references to // `__llvm_profile_instrument_memop` when linking `rustc_driver`. let mut llvm_linker_flags = String::new(); - if builder.config.llvm_profile_generate + if builder.config.llvm_pgo.generate_profile.is_some() && target.is_msvc() && let Some(ref clang_cl_path) = builder.config.llvm_clang_cl { diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index d9aa6f7c18815..fdb13d3ca6a34 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -3039,7 +3039,7 @@ impl Step for ReproducibleArtifacts { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } - if let Some(path) = builder.config.llvm_profile_use.as_ref() { + if let Some(path) = builder.config.llvm_pgo.use_profile.as_ref() { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 6f1b8532f031b..f40b384a589ba 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -19,7 +19,7 @@ use build_helper::git::PathFreshness; use crate::core::build_steps::llvm; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata}; -use crate::core::config::{Config, TargetSelection}; +use crate::core::config::{Config, LlvmPgoGenerationMode, TargetSelection}; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; use crate::utils::exec::command; use crate::utils::helpers::{ @@ -369,14 +369,17 @@ impl Step for Llvm { // This flag makes sure `FileCheck` is copied in the final binaries directory. cfg.define("LLVM_INSTALL_UTILS", "ON"); - if builder.config.llvm_profile_generate { + if let Some(mode) = builder.config.llvm_pgo.generate_profile.as_ref() { cfg.define("LLVM_BUILD_INSTRUMENTED", "IR"); - if let Ok(llvm_profile_dir) = std::env::var("LLVM_PROFILE_DIR") { - cfg.define("LLVM_PROFILE_DATA_DIR", llvm_profile_dir); + match mode { + LlvmPgoGenerationMode::Implicit => {} + LlvmPgoGenerationMode::Directory(llvm_profile_dir) => { + cfg.define("LLVM_PROFILE_DATA_DIR", llvm_profile_dir); + } } cfg.define("LLVM_BUILD_RUNTIME", "No"); } - if let Some(path) = builder.config.llvm_profile_use.as_ref() { + if let Some(path) = builder.config.llvm_pgo.use_profile.as_ref() { cfg.define("LLVM_PROFDATA_FILE", path); } @@ -1326,7 +1329,7 @@ impl Step for Lld { // when doing PGO on CI, cmake or clang-cl don't automatically link clang's // profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid // linking errors, much like LLVM's cmake setup does in that situation. - if builder.config.llvm_profile_generate + if builder.config.llvm_pgo.generate_profile.is_some() && target.is_msvc() && let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() { diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index ddd2442802d10..a76792a58252b 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -191,6 +191,7 @@ pub struct Config { pub llvm_cxxflags: Option, pub llvm_ldflags: Option, pub llvm_use_libcxx: bool, + pub llvm_pgo: LlvmPgoConfig, // gcc codegen options pub gcc_ci_mode: GccCiMode, @@ -234,8 +235,6 @@ pub struct Config { pub rust_rustflags: Vec, pub rust_pgo: PgoConfig, - pub llvm_profile_use: Option, - pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option, pub enable_bolt_settings: bool, @@ -656,8 +655,7 @@ impl Config { libgccjit_libs_dir: gcc_libgccjit_libs_dir, } = toml_gcc.unwrap_or_default(); - let Pgo { rustc: pgo_rustc } = toml_pgo.unwrap_or_default(); - let mut pgo_rustc = pgo_rustc.unwrap_or_default(); + let Pgo { rustc: pgo_rustc, llvm: pgo_llvm } = toml_pgo.unwrap_or_default(); // Backcompat: flags have priority over config if flags_rust_profile_use.is_some() || flags_rust_profile_generate.is_some() { @@ -670,7 +668,13 @@ impl Config { "WARNING: the `rust.profile-generate` and `rust.profile-use` config options have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." ); } + if flags_llvm_profile_use.is_some() || flags_llvm_profile_generate { + eprintln!( + "WARNING: the `--llvm-profile-generate` and `--llvm-profile-use` flags have been deprecated. Configure PGO through the config file instead, in the [pgo.llvm] section." + ); + } + let mut pgo_rustc = pgo_rustc.unwrap_or_default(); pgo_rustc.use_profile = flags_rust_profile_use.or(pgo_rustc.use_profile).or(rust_profile_use); pgo_rustc.generate_profile = @@ -679,6 +683,23 @@ impl Config { panic!("Cannot use and generate rust PGO profiles at the same time"); } + let pgo_llvm = pgo_llvm.unwrap_or_default(); + let pgo_llvm = LlvmPgoConfig { + use_profile: flags_llvm_profile_use.or(pgo_llvm.use_profile), + generate_profile: if flags_llvm_profile_generate { + Some(if let Ok(llvm_profile_dir) = std::env::var("LLVM_PROFILE_DIR") { + LlvmPgoGenerationMode::Directory(PathBuf::from(llvm_profile_dir)) + } else { + LlvmPgoGenerationMode::Implicit + }) + } else { + pgo_llvm.generate_profile.map(LlvmPgoGenerationMode::Directory) + }, + }; + if pgo_llvm.use_profile.is_some() && pgo_llvm.generate_profile.is_some() { + panic!("Cannot use and generate LLVM PGO profiles at the same time"); + } + if rust_bootstrap_override_lld.is_some() && rust_bootstrap_override_lld_legacy.is_some() { panic!( "Cannot use both `rust.use-lld` and `rust.bootstrap-override-lld`. Please use only `rust.bootstrap-override-lld`" @@ -1463,10 +1484,9 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to ), llvm_offload: llvm_offload.unwrap_or(false), llvm_optimize: llvm_optimize.unwrap_or(true), + llvm_pgo: pgo_llvm, llvm_plugins: llvm_plugin.unwrap_or(false), llvm_polly: llvm_polly.unwrap_or(false), - llvm_profile_generate: flags_llvm_profile_generate, - llvm_profile_use: flags_llvm_profile_use, llvm_release_debuginfo: llvm_release_debuginfo.unwrap_or(false), llvm_static_stdcpp: llvm_static_libstdcpp.unwrap_or(false), llvm_targets, @@ -2064,6 +2084,20 @@ fn compute_src_directory(src_dir: Option, exec_ctx: &ExecutionContext) None } +#[derive(Clone)] +pub enum LlvmPgoGenerationMode { + /// Enable PGO instrumentation that will write profiles into a default path. + Implicit, + /// Enable PGO instrumentation that will write profiles into the specified directory. + Directory(PathBuf), +} + +#[derive(Clone)] +pub struct LlvmPgoConfig { + pub use_profile: Option, + pub generate_profile: Option, +} + /// Loads bootstrap TOML config and returns the config together with a path from where /// it was loaded. /// `src` is the source root directory, and `config_path` is an optionally provided path to the diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index d6020262e0579..775de587b3e54 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -162,7 +162,7 @@ pub struct Flags { /// use PGO profile for LLVM build // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub llvm_profile_use: Option, + pub llvm_profile_use: Option, // LLVM doesn't support a custom location for generating profile // information. // diff --git a/src/bootstrap/src/core/config/toml/pgo.rs b/src/bootstrap/src/core/config/toml/pgo.rs index dde76f078fc39..b845f3c182b53 100644 --- a/src/bootstrap/src/core/config/toml/pgo.rs +++ b/src/bootstrap/src/core/config/toml/pgo.rs @@ -26,5 +26,6 @@ define_config! { #[derive(Default)] struct Pgo { rustc: Option = "rustc", + llvm: Option = "llvm", } } From b576701d43c6ac88af39c9bd31e6ae136957af54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:13:00 +0200 Subject: [PATCH 16/31] Use the new bootstrap PGO options in `opt-dist` --- src/tools/opt-dist/src/exec.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index a3935f9835956..7c6b07b8c8bb0 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -134,20 +134,21 @@ impl Bootstrap { pub fn llvm_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { self.cmd = self .cmd - .arg("--llvm-profile-generate") - .env("LLVM_PROFILE_DIR", profile_dir.join("prof-%p").as_str()); + .arg("--set") + .arg(format!(r#"pgo.llvm.generate="{}""#, profile_dir.join("prof-%p").as_str())); self } pub fn llvm_pgo_optimize(mut self, profile: Option<&LlvmPGOProfile>) -> Self { if let Some(prof) = profile { - self.cmd = self.cmd.arg("--llvm-profile-use").arg(prof.0.as_str()); + self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.llvm.use="{}""#, prof.0.as_str())); } self } pub fn rustc_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { - self.cmd = self.cmd.arg("--rust-profile-generate").arg(profile_dir.as_str()); + self.cmd = + self.cmd.arg("--set").arg(format!(r#"pgo.rustc.generate="{}""#, profile_dir.as_str())); self } @@ -162,7 +163,7 @@ impl Bootstrap { } pub fn rustc_pgo_optimize(mut self, profile: &RustcPGOProfile) -> Self { - self.cmd = self.cmd.arg("--rust-profile-use").arg(profile.0.as_str()); + self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.rustc.use="{}""#, profile.0.as_str())); self } From 5d96f17e0e98e8945298e35861faba8c73c512a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 8 Jul 2026 00:30:58 +0200 Subject: [PATCH 17/31] Do not build the compiler when invoking `x perf compare` --- src/bootstrap/src/core/build_steps/perf.rs | 55 ++++++++++++---------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs index 7bdcb483eb923..0ac18df2084e3 100644 --- a/src/bootstrap/src/core/build_steps/perf.rs +++ b/src/bootstrap/src/core/build_steps/perf.rs @@ -140,6 +140,18 @@ pub fn perf(builder: &Builder<'_>, args: &PerfArgs) { target: builder.config.host_target, }); + let rustc_perf_dir = builder.build.tempdir().join("rustc-perf"); + let results_dir = rustc_perf_dir.join("results"); + builder.create_dir(&results_dir); + + let mut cmd = command(collector.tool_path); + + // We need to set the working directory to `src/tools/rustc-perf`, so that it can find the directory + // with compile-time benchmarks. + cmd.current_dir(builder.src.join("src/tools/rustc-perf")); + + let db_path = results_dir.join("results.db"); + let is_profiling = match &args.cmd { PerfCommand::Eprintln { .. } | PerfCommand::Samply { .. } @@ -151,32 +163,23 @@ pub fn perf(builder: &Builder<'_>, args: &PerfArgs) { Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); } - let compiler = builder.compiler(builder.top_stage, builder.config.host_target); - builder.std(compiler, builder.config.host_target); - - if let Some(opts) = args.cmd.shared_opts() - && opts.profiles.contains(&Profile::Doc) - { - builder.ensure(Rustdoc { target_compiler: compiler }); - } + let prepare_rustc = || { + let compiler = builder.compiler(builder.top_stage, builder.config.host_target); + builder.std(compiler, builder.config.host_target); - let sysroot = builder.ensure(Sysroot::new(compiler)); - let mut rustc = sysroot.clone(); - rustc.push("bin"); - rustc.push("rustc"); - rustc.set_extension(EXE_EXTENSION); - - let rustc_perf_dir = builder.build.tempdir().join("rustc-perf"); - let results_dir = rustc_perf_dir.join("results"); - builder.create_dir(&results_dir); - - let mut cmd = command(collector.tool_path); - - // We need to set the working directory to `src/tools/rustc-perf`, so that it can find the directory - // with compile-time benchmarks. - cmd.current_dir(builder.src.join("src/tools/rustc-perf")); + if let Some(opts) = args.cmd.shared_opts() + && opts.profiles.contains(&Profile::Doc) + { + builder.ensure(Rustdoc { target_compiler: compiler }); + } - let db_path = results_dir.join("results.db"); + let sysroot = builder.ensure(Sysroot::new(compiler)); + let mut rustc = sysroot.clone(); + rustc.push("bin"); + rustc.push("rustc"); + rustc.set_extension(EXE_EXTENSION); + rustc + }; match &args.cmd { PerfCommand::Eprintln { opts } @@ -191,7 +194,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); }); cmd.arg("--out-dir").arg(&results_dir); - cmd.arg(rustc); + cmd.arg(prepare_rustc()); apply_shared_opts(&mut cmd, opts); cmd.run(builder); @@ -202,7 +205,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); cmd.arg("bench_local"); cmd.arg("--db").arg(&db_path); cmd.arg("--id").arg(id); - cmd.arg(rustc); + cmd.arg(prepare_rustc()); apply_shared_opts(&mut cmd, opts); cmd.run(builder); From 1f6b1d5683f47618b29c0bc5514c6e8b571dc8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:34:10 +0200 Subject: [PATCH 18/31] Fix bootstrap example TOML --- bootstrap.example.toml | 10 +++++----- src/bootstrap/src/core/build_steps/compile.rs | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index b48f7ad7da742..2c9a5ab1a152c 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -979,17 +979,17 @@ # Configure the PGO profile to be used for compilation, or a path where the # profile will be written by an instrumented binary, for individual components # ============================================================================= -[pgo] + # Use the following profile to PGO optimize the Rust compiler. -#rustc.use = "/tmp/profiles/foo.profraw" +#pgo.rustc.use = "/tmp/profiles/foo.profraw" # Instrument the Rust compiler, so that when executed, it will gather profiles # to this path. -#rustc.generate = "/tmp/profiles/foo.profraw" +#pgo.rustc.generate = "/tmp/profiles/foo.profraw" # Use the following profile to PGO optimize LLVM. -#llvm.use = "/tmp/profiles/foo.profraw" +#pgo.llvm.use = "/tmp/profiles/foo.profraw" # Instrument LLVM, so that when executed, it will gather profiles # Note: for LLVM specifically, this should be a directory, not a file path. -#llvm.generate = "/tmp/profiles" +#pgo.llvm.generate = "/tmp/profiles" # ============================================================================= # Options for specific targets diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3ef6428b2f746..bc6d1f94d698b 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1293,8 +1293,6 @@ pub fn rustc_cargo( cargo.rustflag("-Clink-args=-Wl,--icf=all"); } - eprintln!("Profile: {:?}", builder.config.rust_pgo.generate_profile); - let is_collecting = if let Some(path) = &builder.config.rust_pgo.generate_profile { if build_compiler.stage == 1 { cargo From 08d5ee38e3e7168e18b7af6fceee842c7dae9b87 Mon Sep 17 00:00:00 2001 From: Chuanqi Xu Date: Wed, 24 Jun 2026 14:24:27 +0800 Subject: [PATCH 19/31] codegen_ssa: pack small const aggregates into immediate stores For small repr(C) aggregates with padding, direct constant initialization can still lower into field-wise construction plus memcpy. That leaves the backend to rediscover that the whole object is a single constant byte pattern. This is especially visible for non-zero constant aggregates. Instead of materializing them as separate field stores, we want codegen_ssa to emit the packed value directly. For example, a value like InnerPadded { a: 0, b: 1, c: 0 } can otherwise lower to something like store i16 0, ptr %val, align 4 store i8 1, ptr %val_plus_2, align 2 store i32 0, ptr %val_plus_4, align 4 call void @llvm.memcpy(..., ptr %val, ...) while this change produces store i64 65536, ptr %val, align 4 call void @llvm.memcpy(..., ptr %val, ...) Why not solve this in LLVM? At the problematic lowering point, rustc still knows that the MIR aggregate is small and fully constant. LLVM only sees a lowered stack temporary built from per-field stores and then copied out. Recovering that packed constant there would require rediscovering front-end aggregate semantics after lowering, so emitting the packed store in rustc is the simpler and more local fix. Why not keep the previous typed-copy approach? An earlier approach zeroed padding on typed copies whose source could be traced back to a constant assignment. That helped some cases, but it also widened the optimization to runtime copy paths and could introduce extra runtime stores purely to maintain padding knowledge. That is not an acceptable tradeoff here. Keep the scope narrow instead: only handle direct MIR aggregates whose fields are all constants, and pack them according to the target endianness before emitting a single integer store. Add a focused codegen test covering the original PR 157690 entry points together with non-zero constant cases. The test uses -Cno-prepopulate-passes so it checks the immediate-store shape directly at rustc codegen time, instead of depending on later LLVM store merging. --- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 63 ++++++ tests/codegen-llvm/aggregate-padding-zero.rs | 213 +++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 tests/codegen-llvm/aggregate-padding-zero.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 469d6af12923e..6b1add6511137 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -1,5 +1,6 @@ use itertools::Itertools as _; use rustc_abi::{self as abi, BackendRepr, FIRST_VARIANT}; +use rustc_index::IndexVec; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; @@ -15,6 +16,64 @@ use crate::traits::*; use crate::{MemFlags, base}; impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { + fn try_codegen_const_aggregate_as_immediate( + &mut self, + bx: &mut Bx, + dest: PlaceRef<'tcx, Bx::Value>, + kind: &mir::AggregateKind<'tcx>, + operands: &IndexVec>, + ) -> bool { + // Keep this allowlist limited to aggregate kinds with direct codegen coverage. + if !matches!(kind, mir::AggregateKind::Tuple | mir::AggregateKind::Adt(_, _, _, _, None)) { + return false; + } + if !matches!(dest.layout.fields, abi::FieldsShape::Arbitrary { .. }) { + return false; + } + if !matches!(dest.layout.variants, abi::Variants::Single { .. }) { + return false; + } + debug_assert_eq!(operands.len(), dest.layout.fields.count()); + + let size = dest.layout.size.bytes(); + let llty = match size { + 1 => bx.cx().type_i8(), + 2 => bx.cx().type_i16(), + 4 => bx.cx().type_i32(), + 8 => bx.cx().type_i64(), + 16 => bx.cx().type_i128(), + _ => return false, + }; + + let mut value = 0u128; + for (field_idx, operand) in operands.iter_enumerated() { + let field_layout = dest.layout.field(bx.cx(), field_idx.as_usize()); + if field_layout.is_zst() { + continue; + } + let mir::Operand::Constant(constant) = operand else { + return false; + }; + let Some(field_value) = self.eval_mir_constant(constant).try_to_bits(field_layout.size) + else { + return false; + }; + + let field_size = field_layout.size.bytes(); + let field_offset = dest.layout.fields.offset(field_idx.as_usize()).bytes(); + debug_assert!(field_offset + field_size <= size); + let shift = match bx.tcx().data_layout.endian { + abi::Endian::Little => field_offset * 8, + abi::Endian::Big => (size - field_offset - field_size) * 8, + }; + value |= field_value << shift; + } + + let value = bx.cx().const_uint_big(llty, value); + bx.store_to_place(value, dest.val); + true + } + #[instrument(level = "trace", skip(self, bx))] pub(crate) fn codegen_rvalue( &mut self, @@ -168,6 +227,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::Rvalue::Aggregate(ref kind, ref operands) if !matches!(**kind, mir::AggregateKind::RawPtr(..)) => { + if self.try_codegen_const_aggregate_as_immediate(bx, dest, kind, operands) { + return; + } + let (variant_index, variant_dest, active_field_index) = match **kind { mir::AggregateKind::Adt(_, variant_index, _, _, active_field_index) => { let variant_dest = dest.project_downcast(bx, variant_index); diff --git a/tests/codegen-llvm/aggregate-padding-zero.rs b/tests/codegen-llvm/aggregate-padding-zero.rs new file mode 100644 index 0000000000000..9fe4b201198b7 --- /dev/null +++ b/tests/codegen-llvm/aggregate-padding-zero.rs @@ -0,0 +1,213 @@ +//@ add-minicore +//@ compile-flags: -Copt-level=3 -Cno-prepopulate-passes -Z merge-functions=disabled -Z randomize-layout=no +//@ revisions: powerpc64 x86_64 +//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu +//@[powerpc64] needs-llvm-components: powerpc +//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@[x86_64] needs-llvm-components: x86 + +// Regression test for . +// +// These cases specifically exercise direct codegen of small non-zero constant +// aggregates as a single integer store. They are chosen so they fail without +// `try_codegen_const_aggregate_as_immediate`. + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +use minicore::*; + +#[inline(always)] +unsafe fn ptr_write(dest: *mut T, value: T) { + *dest = value; +} + +trait MaybeUninitExt { + fn as_mut_ptr(&mut self) -> *mut T; + fn write(&mut self, value: T); +} + +impl MaybeUninitExt for MaybeUninit { + fn as_mut_ptr(&mut self) -> *mut T { + self as *mut _ as *mut T + } + + fn write(&mut self, value: T) { + unsafe { + ptr_write(self.as_mut_ptr(), value); + } + } +} + +// Inner padding between b (offset 2, size 1) and c (offset 4, size 4). +#[repr(C)] +pub struct InnerPadded { + a: u16, + b: u8, + c: u32, +} + +#[repr(transparent)] +pub struct Nested1(InnerPadded); + +#[repr(transparent)] +pub struct Nested2(Nested1); + +// PR 157690's original ptr::write entry point, checked against the current +// aggregate-immediate codegen shape. +// CHECK-LABEL: @via_ptr_write( +#[no_mangle] +pub fn via_ptr_write(dest: &mut MaybeUninit) { + let val = InnerPadded { a: 0, b: 0, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // CHECK-NEXT: store i64 0, ptr %val, align 4 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) + unsafe { + ptr_write(dest.as_mut_ptr(), val); + } +} + +// PR 157690's original MaybeUninit::write entry point. +// CHECK-LABEL: @via_maybe_uninit_write( +#[no_mangle] +pub fn via_maybe_uninit_write(dest: &mut MaybeUninit) { + let val = InnerPadded { a: 0, b: 0, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // CHECK-NEXT: store i64 0, ptr %val, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %{{.*}}, i64 8, i1 false) + dest.write(val); +} + +// Constant non-zero initialization: emitted as a single store including zero padding. +// CHECK-LABEL: @const_init_non_zero( +#[no_mangle] +pub fn const_init_non_zero(dest: *mut InnerPadded) { + let val = InnerPadded { a: 0, b: 1, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // x86_64-NEXT: store i64 65536, ptr %val, align 4 + // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) + unsafe { + ptr_write(dest, val); + } +} + +// From issue #157373: nesting wrapper structs used to change the lowering +// shape enough that LLVM would sometimes find the wide store only in the +// nested case. +// CHECK-LABEL: @bad( +#[no_mangle] +pub fn bad(a: &mut InnerPadded) { + let x = InnerPadded { a: 0, b: 1, c: 0 }; + // x86_64: store i64 65536, ptr %x, align 4 + // powerpc64: store i64 1099511627776, ptr %x, align 4 + *a = x; +} + +// CHECK-LABEL: @good( +#[no_mangle] +pub fn good(a: &mut Nested2) { + let x = InnerPadded { a: 0, b: 1, c: 0 }; + // x86_64: store i64 65536, ptr %x, align 4 + // powerpc64: store i64 1099511627776, ptr %x, align 4 + *a = Nested2(Nested1(x)); +} + +// The same direct constant aggregate packing should apply through ptr::write on MaybeUninit. +// CHECK-LABEL: @via_ptr_write_non_zero( +#[no_mangle] +pub fn via_ptr_write_non_zero(dest: &mut MaybeUninit) { + let val = InnerPadded { a: 0, b: 1, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // x86_64-NEXT: store i64 65536, ptr %val, align 4 + // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) + unsafe { + ptr_write(dest.as_mut_ptr(), val); + } +} + +// The same direct constant aggregate packing should apply through MaybeUninit::write. +// CHECK-LABEL: @via_maybe_uninit_write_non_zero( +#[no_mangle] +pub fn via_maybe_uninit_write_non_zero(dest: &mut MaybeUninit) { + let val = InnerPadded { a: 0, b: 1, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // x86_64-NEXT: store i64 65536, ptr %val, align 4 + // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %{{.*}}, i64 8, i1 false) + dest.write(val); +} + +// CHECK-LABEL: @bad_non_zero( +#[no_mangle] +pub fn bad_non_zero(a: &mut InnerPadded) { + let x = InnerPadded { a: 0, b: 1, c: 0 }; + // CHECK: %x = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %x) + // x86_64-NEXT: store i64 65536, ptr %x, align 4 + // powerpc64-NEXT: store i64 1099511627776, ptr %x, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %x, i64 8, i1 false) + *a = x; +} + +// CHECK-LABEL: @good_non_zero( +#[no_mangle] +pub fn good_non_zero(a: &mut Nested2) { + let x = InnerPadded { a: 0, b: 1, c: 0 }; + // CHECK: %x = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %x) + // x86_64-NEXT: store i64 65536, ptr %x, align 4 + // powerpc64-NEXT: store i64 1099511627776, ptr %x, align 4 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %{{.*}}, i64 8, i1 false) + *a = Nested2(Nested1(x)); +} + +// Trailing padding only (no inter-field padding): a (offset 0, size 4), +// b (offset 4, size 2), c (offset 6, size 1), trailing pad (offset 7, size 1). +#[repr(C)] +pub struct TailOnly { + a: u32, + b: u16, + c: u8, +} + +type TupleTailOnly = (u32, u16, u8); + +// PR 157690's trailing-padding-only entry point. +// CHECK-LABEL: @tail_only_write( +#[no_mangle] +pub fn tail_only_write(dest: &mut MaybeUninit) { + let val = TailOnly { a: 0, b: 0, c: 0 }; + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // CHECK-NEXT: store i64 0, ptr %val, align 4 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) + unsafe { + ptr_write(dest.as_mut_ptr(), val); + } +} + +// Tuple aggregates should use the same const-packing path as structs when the +// whole tuple is constant and small enough to fit in an integer store. +// CHECK-LABEL: @tuple_tail_only_non_zero( +#[no_mangle] +pub fn tuple_tail_only_non_zero(dest: *mut TupleTailOnly) { + let val: TupleTailOnly = (0, 1, 0); + // CHECK: %val = alloca [8 x i8], align 4 + // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) + // x86_64-NEXT: store i64 4294967296, ptr %val, align 4 + // powerpc64-NEXT: store i64 65536, ptr %val, align 4 + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) + unsafe { + ptr_write(dest, val); + } +} From fc267a6d557edb0d1cbe76a2c12fc4e9b7d07133 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Sun, 21 Jun 2026 15:16:08 +0200 Subject: [PATCH 20/31] Improve generic parameters handling for #[diagnostic::on_const] This prints generics parameters when these are concrete Adt generics args. A lint is emitted we try to format a generic that is found anywhere. Moreover, generics parameters that are ty infer vars are no longer displayed "as-is", it is not clear and might be confusing, in this specific case the name of the generic parameter will be displayed instead. --- compiler/rustc_passes/src/check_attr.rs | 33 ++++++++++++++--- compiler/rustc_passes/src/diagnostics.rs | 7 ++++ .../traits/fulfillment_errors.rs | 20 +++++++++-- .../traits/on_unimplemented.rs | 11 ++++-- ...generic_parameters_handling.current.stderr | 11 ++++++ .../generic_parameters_handling.next.stderr | 11 ++++++ .../on_const/generic_parameters_handling.rs | 25 +++++++++++++ .../on_const/malformed_format_literals.rs | 25 +++++++++++++ .../on_const/malformed_format_literals.stderr | 36 +++++++++++++++++++ 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr create mode 100644 tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ae6005e9bc98..863e4d88872c9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -233,8 +233,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::OnUnimplemented { directive } => { self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref()) } - AttributeKind::OnConst { span, .. } => { - self.check_diagnostic_on_const(*span, hir_id, target, item) + AttributeKind::OnConst { span, directive } => { + self.check_diagnostic_on_const(*span, hir_id, target, item, directive.as_deref()) } AttributeKind::OnMove { directive } => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) @@ -545,10 +545,36 @@ impl<'tcx> CheckAttrVisitor<'tcx> { hir_id: HirId, target: Target, item: Option>, + directive: Option<&Directive>, ) { // We only check the non-constness here. A diagnostic for use // on not-trait impl items is issued during attribute parsing. if target == (Target::Impl { of_trait: true }) { + if let Some(directive) = directive + && let Node::Item(Item { kind: ItemKind::Impl(hir::Impl { generics, .. }), .. }) = + self.tcx.hir_node(hir_id) + { + directive.visit_params(&mut |argument_name, span| { + let has_generic = generics.params.iter().any(|p| { + if !matches!(p.kind, GenericParamKind::Lifetime { .. }) + && let ParamName::Plain(name) = p.name + && name.name == argument_name + { + true + } else { + false + } + }); + if !has_generic { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + diagnostics::OnConstMalformedFormatLiterals { name: argument_name }, + ) + } + }); + } match item.unwrap() { ItemLike::Item(it) => match it.expect_impl().constness { Constness::Const { .. } => { @@ -566,9 +592,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ItemLike::ForeignItem => {} } } - // FIXME(#155570) Can we do something with generic args here? - // regardless, we don't check the validity of generic args here - // ...whose generics would that be, anyway? The traits' or the impls'? } /// Checks use of generic formatting parameters in `#[diagnostic::on_move]` diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 92562cc462b87..605d54ac0511a 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -1196,6 +1196,13 @@ pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help(r#"expect either a generic argument name or {"`{Self}`"} as format argument"#)] +pub(crate) struct OnConstMalformedFormatLiterals { + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("unused target expression is specified for glob or list delegation")] pub(crate) struct GlobOrListDelegationUnusedTargetExpr { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 617ec6c4ac229..b38c933151eb2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -29,8 +29,8 @@ use rustc_middle::ty::print::{ PrintTraitRefExt as _, with_forced_trimmed_paths, }; use rustc_middle::ty::{ - self, GenericArgKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeVisitableExt, Unnormalized, Upcast, + self, GenericArgKind, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, + TypeSuperFoldable, TypeVisitableExt, Unnormalized, Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::CrateNum; @@ -916,11 +916,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()) .flatten() { - let (_, format_args) = self.on_unimplemented_components( + let (_, mut format_args) = self.on_unimplemented_components( trait_ref, main_obligation, diag.long_ty_path(), + false, ); + if let ty::Adt(def, args) = trait_ref.self_ty().skip_binder().kind() { + for param in self.tcx.generics_of(def.did()).own_params.iter() { + match param.kind { + GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. } => { + format_args + .generic_args + .push((param.name, args[param.index as usize].to_string())); + } + _ => continue, + } + } + } let CustomDiagnostic { message, label, notes, parent_label: _ } = command.eval(None, &format_args); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 138c53d013ebc..f7e4ec6164c99 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -46,7 +46,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return CustomDiagnostic::default(); } let (filter_options, format_args) = - self.on_unimplemented_components(trait_pred, obligation, long_ty_path); + self.on_unimplemented_components(trait_pred, obligation, long_ty_path, true); if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { command.eval( Some(&filter_options), @@ -62,6 +62,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, + print_infer_ty_var: bool, ) -> (FilterOptions, FormatArgs) { let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); @@ -244,7 +245,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type() { - self.tcx.short_string(ty, long_ty_path) + if print_infer_ty_var == false + && let ty::Infer(ty::TyVar(_)) = ty.kind() + { + format!("{}", param.name) + } else { + self.tcx.short_string(ty, long_ty_path) + } } else { trait_pred.trait_ref.args[param.index as usize].to_string() } diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.current.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr new file mode 100644 index 0000000000000..f9e33ff4fac58 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.next.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `X: const Y<_>` is not satisfied + --> $DIR/generic_parameters_handling.rs:22:6 + | +LL | .blah(); + | ^^^^ + | + = note: Self = X, Z = Z, A = u8, B = &str + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs new file mode 100644 index 0000000000000..32a0caf96988a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/generic_parameters_handling.rs @@ -0,0 +1,25 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +#![crate_type = "lib"] +#![feature(diagnostic_on_const, const_trait_impl)] + +pub struct X { + field: (A, B), +} + +pub const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const(note = "Self = {Self}, Z = {Z}, A = {A}, B = {B}")] +impl Y for X {} + +const _: () = { + X { + field: (42_u8, "hello"), + } + .blah(); + //~^ ERROR the trait bound `X: const Y<_>` is not satisfied [E0277] + //~| NOTE Self = X, Z = Z, A = u8, B = &str +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs new file mode 100644 index 0000000000000..0c36ce25febb5 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.rs @@ -0,0 +1,25 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_const)] +#![feature(const_trait_impl)] + +pub struct X; + +const trait Y { + fn blah(&self) {} +} + +#[diagnostic::on_const( + message = "my message {Foo}", + //~^ WARN unknown parameter `Foo` + label = "my label {Bar}", + //~^ WARN unknown parameter `Bar` + note = "my label {Baz}", + //~^ WARN unknown parameter `Baz` +)] +impl Y for X {} + +const _: () = { + X {}.blah(); + //~^ ERROR my message {Foo} [E0277] + +}; diff --git a/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr new file mode 100644 index 0000000000000..48bdc2c239a85 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/malformed_format_literals.stderr @@ -0,0 +1,36 @@ +warning: unknown parameter `Foo` + --> $DIR/malformed_format_literals.rs:12:28 + | +LL | message = "my message {Foo}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: unknown parameter `Bar` + --> $DIR/malformed_format_literals.rs:14:24 + | +LL | label = "my label {Bar}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +warning: unknown parameter `Baz` + --> $DIR/malformed_format_literals.rs:16:23 + | +LL | note = "my label {Baz}", + | ^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + +error[E0277]: my message {Foo} + --> $DIR/malformed_format_literals.rs:22:10 + | +LL | X {}.blah(); + | ^^^^ my label {Bar} + | + = note: my label {Baz} + +error: aborting due to 1 previous error; 3 warnings emitted + +For more information about this error, try `rustc --explain E0277`. From abc324c79f9d9f2fb9eff05e3c7257f7020b7857 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 21 May 2026 15:40:10 +1000 Subject: [PATCH 21/31] Move `std::io::default_write_vectored` to `core::io` --- library/alloc/src/io/mod.rs | 4 +++- library/core/src/io/mod.rs | 2 ++ library/core/src/io/write.rs | 11 +++++++++++ library/std/src/io/mod.rs | 10 +--------- 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 library/core/src/io/write.rs diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 44a1d1b71d164..91bf95ab589be 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -18,4 +18,6 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{IoHandle, OsFunctions, SizeHint, chain, stream_len_default, take}; +pub use core::io::{ + IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, stream_len_default, take, +}; diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 1c27e42229bb7..36ec7d8d2adff 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -8,6 +8,7 @@ mod io_slice; mod seek; mod size_hint; mod util; +mod write; #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] pub use self::borrowed_buf::{BorrowedBuf, BorrowedCursor}; @@ -32,6 +33,7 @@ pub use self::{ seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, + write::default_write_vectored, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs new file mode 100644 index 0000000000000..cd0d2eb37249b --- /dev/null +++ b/library/core/src/io/write.rs @@ -0,0 +1,11 @@ +use crate::io::{IoSlice, Result}; + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result +where + F: FnOnce(&[u8]) -> Result, +{ + let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); + write(buf) +} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 38c8120d66fc6..f2f3aa8bf8dd9 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -312,7 +312,7 @@ pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; pub use alloc_crate::io::{ Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, empty, repeat, sink, }; -pub(crate) use alloc_crate::io::{IoHandle, stream_len_default}; +pub(crate) use alloc_crate::io::{IoHandle, default_write_vectored, stream_len_default}; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; use alloc_crate::io::{OsFunctions, SizeHint}; @@ -549,14 +549,6 @@ where read(buf) } -pub(crate) fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result -where - F: FnOnce(&[u8]) -> Result, -{ - let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); - write(buf) -} - pub(crate) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { while !buf.is_empty() { match this.read(buf) { From 55585ea8b44d6c676b24a5ada113f25a312242e7 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 22 May 2026 13:58:21 +1000 Subject: [PATCH 22/31] Move `std::io::cursor`internals to `core::io` --- library/alloc/src/io/mod.rs | 3 +- library/core/src/io/cursor.rs | 59 ++++++++++++++++++++++++++++++++++- library/core/src/io/mod.rs | 1 + library/std/src/io/cursor.rs | 52 +++--------------------------- 4 files changed, 65 insertions(+), 50 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 91bf95ab589be..6b05d38125654 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -19,5 +19,6 @@ pub use core::io::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, + slice_write_all_vectored, slice_write_vectored, stream_len_default, take, }; diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index c104b426f48df..7ee77a0419fda 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -1,4 +1,5 @@ -use crate::io::{self, ErrorKind, SeekFrom}; +use crate::cmp; +use crate::io::{self, ErrorKind, IoSlice, SeekFrom}; /// A `Cursor` wraps an in-memory buffer and provides it with a /// [`Seek`] implementation. @@ -294,6 +295,62 @@ where } } +// Non-resizing write implementation +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result { + let pos = cmp::min(*pos_mut, slice.len() as u64); + let dst = &mut slice[(pos as usize)..]; + let amt = cmp::min(buf.len(), dst.len()); + dst[..amt].copy_from_slice(&buf[..amt]); + *pos_mut += amt as u64; + Ok(amt) +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_vectored( + pos_mut: &mut u64, + slice: &mut [u8], + bufs: &[IoSlice<'_>], +) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + let n = slice_write(pos_mut, slice, buf)?; + nwritten += n; + if n < buf.len() { + break; + } + } + Ok(nwritten) +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result<()> { + let n = slice_write(pos_mut, slice, buf)?; + if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } +} + +#[inline] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn slice_write_all_vectored( + pos_mut: &mut u64, + slice: &mut [u8], + bufs: &[IoSlice<'_>], +) -> io::Result<()> { + for buf in bufs { + let n = slice_write(pos_mut, slice, buf)?; + if n < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) +} #[stable(feature = "rust1", since = "1.0.0")] impl io::Seek for Cursor where diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 36ec7d8d2adff..92bd55a01538e 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -29,6 +29,7 @@ pub use self::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ + cursor::{slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored}, error::{Custom, CustomOwner, OsFunctions}, seek::stream_len_default, size_hint::SizeHint, diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 5399789b4242f..87b721cf1d4b1 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -4,8 +4,11 @@ mod tests; #[stable(feature = "rust1", since = "1.0.0")] pub use core::io::Cursor; +use alloc_crate::io::{ + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, +}; + use crate::alloc::Allocator; -use crate::cmp; use crate::io::prelude::*; use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; @@ -150,53 +153,6 @@ impl Write for Cursor { } } -// Non-resizing write implementation -#[inline] -fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result { - let pos = cmp::min(*pos_mut, slice.len() as u64); - let amt = (&mut slice[(pos as usize)..]).write(buf)?; - *pos_mut += amt as u64; - Ok(amt) -} - -#[inline] -fn slice_write_vectored( - pos_mut: &mut u64, - slice: &mut [u8], - bufs: &[IoSlice<'_>], -) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - let n = slice_write(pos_mut, slice, buf)?; - nwritten += n; - if n < buf.len() { - break; - } - } - Ok(nwritten) -} - -#[inline] -fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result<()> { - let n = slice_write(pos_mut, slice, buf)?; - if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } -} - -#[inline] -fn slice_write_all_vectored( - pos_mut: &mut u64, - slice: &mut [u8], - bufs: &[IoSlice<'_>], -) -> io::Result<()> { - for buf in bufs { - let n = slice_write(pos_mut, slice, buf)?; - if n < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) -} - /// Reserves the required space, and pads the vec with 0s if necessary. fn reserve_and_pad( pos_mut: &mut u64, From 66f79f18cd581438c1ed0d80c443baf64c10a14e Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 11:09:32 +1000 Subject: [PATCH 23/31] Move `WriteThroughCursor` internals to `alloc::io` --- library/alloc/src/io/cursor.rs | 144 +++++++++++++++++++++++++++++++++ library/alloc/src/io/mod.rs | 5 ++ library/std/src/io/cursor.rs | 131 +----------------------------- 3 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 library/alloc/src/io/cursor.rs diff --git a/library/alloc/src/io/cursor.rs b/library/alloc/src/io/cursor.rs new file mode 100644 index 0000000000000..a75d6e7df76ae --- /dev/null +++ b/library/alloc/src/io/cursor.rs @@ -0,0 +1,144 @@ +use crate::alloc::Allocator; +use crate::io::{self, ErrorKind, IoSlice}; +use crate::vec::Vec; + +/// Reserves the required space, and pads the vec with 0s if necessary. +fn reserve_and_pad( + pos_mut: &mut u64, + vec: &mut Vec, + buf_len: usize, +) -> io::Result { + let pos: usize = (*pos_mut).try_into().map_err(|_| { + io::const_error!( + ErrorKind::InvalidInput, + "cursor position exceeds maximum possible vector length", + ) + })?; + + // For safety reasons, we don't want these numbers to overflow + // otherwise our allocation won't be enough + let desired_cap = pos.saturating_add(buf_len); + if desired_cap > vec.capacity() { + // We want our vec's total capacity + // to have room for (pos+buf_len) bytes. Reserve allocates + // based on additional elements from the length, so we need to + // reserve the difference + cfg_select! { + no_global_oom_handling => { + vec.try_reserve(desired_cap - vec.len())?; + } + _ => { + vec.reserve(desired_cap - vec.len()); + } + } + } + // Pad if pos is above the current len. + if pos > vec.len() { + let diff = pos - vec.len(); + // Unfortunately, `resize()` would suffice but the optimiser does not + // realise the `reserve` it does can be eliminated. So we do it manually + // to eliminate that extra branch + let spare = vec.spare_capacity_mut(); + debug_assert!(spare.len() >= diff); + // Safety: we have allocated enough capacity for this. + // And we are only writing, not reading + unsafe { + spare.get_unchecked_mut(..diff).fill(core::mem::MaybeUninit::new(0)); + vec.set_len(pos); + } + } + + Ok(pos) +} + +/// Writes the slice to the vec without allocating. +/// +/// # Safety +/// +/// `vec` must have `buf.len()` spare capacity. +unsafe fn vec_write_all_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize +where + A: Allocator, +{ + debug_assert!(vec.capacity() >= pos + buf.len()); + unsafe { vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()) }; + pos + buf.len() +} + +/// Resizing `write_all` implementation for [`Cursor`]. +/// +/// Cursor is allowed to have a pre-allocated and initialised +/// vector body, but with a position of 0. This means the `Write` +/// will overwrite the contents of the vec. +/// +/// This also allows for the vec body to be empty, but with a position of N. +/// This means that `Write` will pad the vec with 0 initially, +/// before writing anything from that point +/// +/// [`Cursor`]: crate::io::Cursor +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result +where + A: Allocator, +{ + let buf_len = buf.len(); + let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; + + // Write the buf then progress the vec forward if necessary + // Safety: we have ensured that the capacity is available + // and that all bytes get written up to pos + unsafe { + pos = vec_write_all_unchecked(pos, vec, buf); + if pos > vec.len() { + vec.set_len(pos); + } + }; + + // Bump us forward + *pos_mut += buf_len as u64; + Ok(buf_len) +} + +/// Resizing `write_all_vectored` implementation for [`Cursor`]. +/// +/// Cursor is allowed to have a pre-allocated and initialised +/// vector body, but with a position of 0. This means the `Write` +/// will overwrite the contents of the vec. +/// +/// This also allows for the vec body to be empty, but with a position of N. +/// This means that `Write` will pad the vec with 0 initially, +/// before writing anything from that point +/// +/// [`Cursor`]: crate::io::Cursor +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn vec_write_all_vectored( + pos_mut: &mut u64, + vec: &mut Vec, + bufs: &[IoSlice<'_>], +) -> io::Result +where + A: Allocator, +{ + // For safety reasons, we don't want this sum to overflow ever. + // If this saturates, the reserve should panic to avoid any unsound writing. + let buf_len = bufs.iter().fold(0usize, |a, b| a.saturating_add(b.len())); + let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; + + // Write the buf then progress the vec forward if necessary + // Safety: we have ensured that the capacity is available + // and that all bytes get written up to the last pos + unsafe { + for buf in bufs { + pos = vec_write_all_unchecked(pos, vec, buf); + } + if pos > vec.len() { + vec.set_len(pos); + } + } + + // Bump us forward + *pos_mut += buf_len as u64; + Ok(buf_len) +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 6b05d38125654..0830ac8d5a72e 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -1,5 +1,6 @@ //! Traits, helpers, and type definitions for core I/O functionality. +mod cursor; mod error; mod impls; @@ -22,3 +23,7 @@ pub use core::io::{ IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, stream_len_default, take, }; + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub use self::cursor::{vec_write_all, vec_write_all_vectored}; diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 87b721cf1d4b1..69b1a3311fe21 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -5,12 +5,13 @@ mod tests; pub use core::io::Cursor; use alloc_crate::io::{ - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, vec_write_all, + vec_write_all_vectored, }; use crate::alloc::Allocator; use crate::io::prelude::*; -use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut}; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Cursor @@ -153,132 +154,6 @@ impl Write for Cursor { } } -/// Reserves the required space, and pads the vec with 0s if necessary. -fn reserve_and_pad( - pos_mut: &mut u64, - vec: &mut Vec, - buf_len: usize, -) -> io::Result { - let pos: usize = (*pos_mut).try_into().map_err(|_| { - io::const_error!( - ErrorKind::InvalidInput, - "cursor position exceeds maximum possible vector length", - ) - })?; - - // For safety reasons, we don't want these numbers to overflow - // otherwise our allocation won't be enough - let desired_cap = pos.saturating_add(buf_len); - if desired_cap > vec.capacity() { - // We want our vec's total capacity - // to have room for (pos+buf_len) bytes. Reserve allocates - // based on additional elements from the length, so we need to - // reserve the difference - vec.reserve(desired_cap - vec.len()); - } - // Pad if pos is above the current len. - if pos > vec.len() { - let diff = pos - vec.len(); - // Unfortunately, `resize()` would suffice but the optimiser does not - // realise the `reserve` it does can be eliminated. So we do it manually - // to eliminate that extra branch - let spare = vec.spare_capacity_mut(); - debug_assert!(spare.len() >= diff); - // Safety: we have allocated enough capacity for this. - // And we are only writing, not reading - unsafe { - spare.get_unchecked_mut(..diff).fill(core::mem::MaybeUninit::new(0)); - vec.set_len(pos); - } - } - - Ok(pos) -} - -/// Writes the slice to the vec without allocating. -/// -/// # Safety -/// -/// `vec` must have `buf.len()` spare capacity. -unsafe fn vec_write_all_unchecked(pos: usize, vec: &mut Vec, buf: &[u8]) -> usize -where - A: Allocator, -{ - debug_assert!(vec.capacity() >= pos + buf.len()); - unsafe { vec.as_mut_ptr().add(pos).copy_from(buf.as_ptr(), buf.len()) }; - pos + buf.len() -} - -/// Resizing `write_all` implementation for [`Cursor`]. -/// -/// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the [`Write`] -/// will overwrite the contents of the vec. -/// -/// This also allows for the vec body to be empty, but with a position of N. -/// This means that [`Write`] will pad the vec with 0 initially, -/// before writing anything from that point -fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result -where - A: Allocator, -{ - let buf_len = buf.len(); - let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; - - // Write the buf then progress the vec forward if necessary - // Safety: we have ensured that the capacity is available - // and that all bytes get written up to pos - unsafe { - pos = vec_write_all_unchecked(pos, vec, buf); - if pos > vec.len() { - vec.set_len(pos); - } - }; - - // Bump us forward - *pos_mut += buf_len as u64; - Ok(buf_len) -} - -/// Resizing `write_all_vectored` implementation for [`Cursor`]. -/// -/// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the [`Write`] -/// will overwrite the contents of the vec. -/// -/// This also allows for the vec body to be empty, but with a position of N. -/// This means that [`Write`] will pad the vec with 0 initially, -/// before writing anything from that point -fn vec_write_all_vectored( - pos_mut: &mut u64, - vec: &mut Vec, - bufs: &[IoSlice<'_>], -) -> io::Result -where - A: Allocator, -{ - // For safety reasons, we don't want this sum to overflow ever. - // If this saturates, the reserve should panic to avoid any unsound writing. - let buf_len = bufs.iter().fold(0usize, |a, b| a.saturating_add(b.len())); - let mut pos = reserve_and_pad(pos_mut, vec, buf_len)?; - - // Write the buf then progress the vec forward if necessary - // Safety: we have ensured that the capacity is available - // and that all bytes get written up to the last pos - unsafe { - for buf in bufs { - pos = vec_write_all_unchecked(pos, vec, buf); - } - if pos > vec.len() { - vec.set_len(pos); - } - } - - // Bump us forward - *pos_mut += buf_len as u64; - Ok(buf_len) -} - #[stable(feature = "rust1", since = "1.0.0")] impl Write for Cursor<&mut [u8]> { #[inline] From ec0534fc4ce9686d01c3ab916d3653bc95ec7b39 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 11:37:13 +1000 Subject: [PATCH 24/31] Move `std::io::Write` to `core::io` --- library/alloc/src/io/cursor.rs | 142 +++++++- library/alloc/src/io/impls.rs | 199 ++++++++++- library/alloc/src/io/mod.rs | 11 +- library/alloc/src/lib.rs | 2 + library/core/src/io/cursor.rs | 127 ++++++- library/core/src/io/impls.rs | 147 +++++++- library/core/src/io/mod.rs | 8 +- library/core/src/io/util.rs | 158 ++++++++- library/core/src/io/write.rs | 408 +++++++++++++++++++++- library/std/src/io/cursor.rs | 246 +------------- library/std/src/io/impls.rs | 311 +---------------- library/std/src/io/mod.rs | 409 +---------------------- library/std/src/io/util.rs | 161 +-------- library/std/src/lib.rs | 2 + tests/ui/suggestions/issue-105645.stderr | 2 +- 15 files changed, 1188 insertions(+), 1145 deletions(-) diff --git a/library/alloc/src/io/cursor.rs b/library/alloc/src/io/cursor.rs index a75d6e7df76ae..21f6b8b5b3ffd 100644 --- a/library/alloc/src/io/cursor.rs +++ b/library/alloc/src/io/cursor.rs @@ -1,5 +1,9 @@ use crate::alloc::Allocator; -use crate::io::{self, ErrorKind, IoSlice}; +use crate::boxed::Box; +use crate::io::{ + self, Cursor, ErrorKind, IoSlice, WriteThroughCursor, slice_write, slice_write_all, + slice_write_all_vectored, slice_write_vectored, +}; use crate::vec::Vec; /// Reserves the required space, and pads the vec with 0s if necessary. @@ -68,17 +72,15 @@ where /// Resizing `write_all` implementation for [`Cursor`]. /// /// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the `Write` +/// vector body, but with a position of 0. This means the [`Write`] /// will overwrite the contents of the vec. /// /// This also allows for the vec body to be empty, but with a position of N. -/// This means that `Write` will pad the vec with 0 initially, +/// This means that [`Write`] will pad the vec with 0 initially, /// before writing anything from that point /// -/// [`Cursor`]: crate::io::Cursor -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result +/// [`Write`]: crate::io::Write +fn vec_write_all(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result where A: Allocator, { @@ -103,17 +105,15 @@ where /// Resizing `write_all_vectored` implementation for [`Cursor`]. /// /// Cursor is allowed to have a pre-allocated and initialised -/// vector body, but with a position of 0. This means the `Write` +/// vector body, but with a position of 0. This means the [`Write`] /// will overwrite the contents of the vec. /// /// This also allows for the vec body to be empty, but with a position of N. -/// This means that `Write` will pad the vec with 0 initially, +/// This means that [`Write`] will pad the vec with 0 initially, /// before writing anything from that point /// -/// [`Cursor`]: crate::io::Cursor -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn vec_write_all_vectored( +/// [`Write`]: crate::io::Write +fn vec_write_all_vectored( pos_mut: &mut u64, vec: &mut Vec, bufs: &[IoSlice<'_>], @@ -142,3 +142,119 @@ where *pos_mut += buf_len as u64; Ok(buf_len) } + +#[stable(feature = "cursor_mut_vec", since = "1.25.0")] +impl WriteThroughCursor for &mut Vec +where + A: Allocator, +{ + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf) + } + + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf)?; + Ok(()) + } + + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs)?; + Ok(()) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl WriteThroughCursor for Vec +where + A: Allocator, +{ + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf) + } + + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all(pos, inner, buf)?; + Ok(()) + } + + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + vec_write_all_vectored(pos, inner, bufs)?; + Ok(()) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "cursor_box_slice", since = "1.5.0")] +impl WriteThroughCursor for Box<[u8], A> +where + A: Allocator, +{ + #[inline] + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = this.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(_this: &Cursor) -> bool { + true + } + + #[inline] + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = this.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(_this: &mut Cursor) -> io::Result<()> { + Ok(()) + } +} diff --git a/library/alloc/src/io/impls.rs b/library/alloc/src/io/impls.rs index 732842bc65df7..0f6bfe244dba2 100644 --- a/library/alloc/src/io/impls.rs +++ b/library/alloc/src/io/impls.rs @@ -1,7 +1,12 @@ +use crate::alloc::Allocator; use crate::boxed::Box; -use crate::io::{self, Seek, SeekFrom, SizeHint}; +#[cfg(not(no_global_oom_handling))] +use crate::collections::VecDeque; +use crate::fmt; +use crate::io::{self, IoSlice, Seek, SeekFrom, SizeHint, Write}; #[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] use crate::sync::Arc; +use crate::vec::Vec; // ============================================================================= // Forwarding implementations @@ -20,6 +25,43 @@ impl SizeHint for Box { } } +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} #[stable(feature = "rust1", since = "1.0.0")] impl Seek for Box { #[inline] @@ -51,6 +93,161 @@ impl Seek for Box { // ============================================================================= // In-memory buffer implementations +/// Write is implemented for `Vec` by appending to the vector. +/// The vector will grow as needed. +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + ::write_all(self, buf)?; + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let len = bufs.iter().map(|b| b.len()).sum(); + cfg_select! { + no_global_oom_handling => { + self.try_reserve(len)?; + } + _ => { + self.reserve(len); + } + } + for buf in bufs { + ::write_all(self, buf)?; + } + Ok(len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + cfg_select! { + no_global_oom_handling => { + let n = buf.len(); + self.try_reserve(n)?; + // SAFETY: + // * self and buf are non-overlapping + // * self[..len] is already initialized + // * self[len..len + n] is initialized by copy_nonoverlapping + // * len + n is within the capacity of self based on the reservation completed above + unsafe { + let len = self.len(); + let src = buf.as_ptr(); + let dst = self.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, n); + self.set_len(len + n); + } + } + _ => { + self.extend_from_slice(buf); + } + } + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + self.write_vectored(bufs)?; + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Write is implemented for `VecDeque` by appending to the `VecDeque`, growing it as needed. +#[cfg(not(no_global_oom_handling))] +#[stable(feature = "vecdeque_read_write", since = "1.63.0")] +impl Write for VecDeque { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend(buf); + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let len = bufs.iter().map(|b| b.len()).sum(); + self.reserve(len); + for buf in bufs { + self.extend(&**buf); + } + Ok(len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.extend(buf); + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + self.write_vectored(bufs)?; + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] +#[stable(feature = "io_traits_arc", since = "1.73.0")] +impl Write for Arc +where + for<'a> &'a W: Write, + W: crate::io::IoHandle, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (&**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (&**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (&**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (&**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (&**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (&**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (&**self).write_fmt(fmt) + } +} #[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))] #[stable(feature = "io_traits_arc", since = "1.73.0")] impl Seek for Arc diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 0830ac8d5a72e..678934b91da71 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -15,15 +15,12 @@ pub use core::io::{BorrowedBuf, BorrowedCursor}; #[unstable(feature = "alloc_io", issue = "154046")] pub use core::io::{ Chain, Cursor, Empty, Error, ErrorKind, IoSlice, IoSliceMut, Repeat, Result, Seek, SeekFrom, - Sink, Take, empty, repeat, sink, + Sink, Take, Write, empty, repeat, sink, }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, chain, default_write_vectored, slice_write, slice_write_all, - slice_write_all_vectored, slice_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_fmt, + default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, stream_len_default, take, }; - -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use self::cursor::{vec_write_all, vec_write_all_vectored}; diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 01b4e6f938616..9f82f5be82a68 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -96,6 +96,7 @@ #![feature(async_iterator)] #![feature(bstr)] #![feature(bstr_internals)] +#![feature(can_vector)] #![feature(case_ignorable)] #![feature(cast_maybe_uninit)] #![feature(cell_get_cloned)] @@ -180,6 +181,7 @@ #![feature(unicode_internals)] #![feature(unsize)] #![feature(unwrap_infallible)] +#![feature(write_all_vectored)] #![feature(wtf8_internals)] // tidy-alphabetical-end // diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 7ee77a0419fda..15b313142c963 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -1,5 +1,5 @@ use crate::cmp; -use crate::io::{self, ErrorKind, IoSlice, SeekFrom}; +use crate::io::{self, ErrorKind, IoSlice, SeekFrom, Write}; /// A `Cursor` wraps an in-memory buffer and provides it with a /// [`Seek`] implementation. @@ -351,6 +351,81 @@ pub fn slice_write_all_vectored( } Ok(()) } + +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Cursor<&mut [u8]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "cursor_array", since = "1.61.0")] +impl Write for Cursor<[u8; N]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write(pos, inner, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let (pos, inner) = self.into_parts_mut(); + slice_write_vectored(pos, inner, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all(pos, inner, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + let (pos, inner) = self.into_parts_mut(); + slice_write_all_vectored(pos, inner, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl io::Seek for Cursor where @@ -385,3 +460,53 @@ where Ok(self.position()) } } + +/// Trait used to allow indirect implementation of `Write` for `Cursor`. +/// Since [`Cursor`] is not a foundational type, it is not possible to implement +/// `Write` for `Cursor` if `Write` is defined in `libcore` and `T` is in a +/// downstream crate (e.g., `liballoc` or `libstd`). +/// +/// Methods are identical in purpose and meaning to their `Write` namesakes. +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub trait WriteThroughCursor: Sized { + fn write(this: &mut Cursor, buf: &[u8]) -> io::Result; + fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result; + fn is_write_vectored(this: &Cursor) -> bool; + fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()>; + fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()>; + fn flush(this: &mut Cursor) -> io::Result<()>; +} + +#[doc(hidden)] +impl Write for Cursor { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + WriteThroughCursor::write(self, buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + WriteThroughCursor::write_vectored(self, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + WriteThroughCursor::is_write_vectored(self) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + WriteThroughCursor::write_all(self, buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + WriteThroughCursor::write_all_vectored(self, bufs) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + WriteThroughCursor::flush(self) + } +} diff --git a/library/core/src/io/impls.rs b/library/core/src/io/impls.rs index e8c716f060119..218bd7adc2a55 100644 --- a/library/core/src/io/impls.rs +++ b/library/core/src/io/impls.rs @@ -1,4 +1,5 @@ -use crate::io::{self, Seek, SeekFrom, SizeHint}; +use crate::io::{self, IoSlice, Seek, SeekFrom, SizeHint, Write}; +use crate::{cmp, fmt, mem}; // ============================================================================= // Forwarding implementations @@ -17,6 +18,43 @@ impl SizeHint for &mut T { } } +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for &mut W { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + (**self).write_all_vectored(bufs) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} #[stable(feature = "rust1", since = "1.0.0")] impl Seek for &mut S { #[inline] @@ -61,3 +99,110 @@ impl SizeHint for &[u8] { Some(self.len()) } } + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, data: &[u8]) -> io::Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&data[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + nwritten += self.write(buf)?; + if self.is_empty() { + break; + } + } + + Ok(nwritten) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, data: &[u8]) -> io::Result<()> { + if self.write(data)? < data.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + for buf in bufs { + if self.write(buf)? < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[unstable(feature = "read_buf", issue = "78485")] +impl<'a> io::Write for core::io::BorrowedCursor<'a, u8> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let amt = cmp::min(buf.len(), self.capacity()); + self.append(&buf[..amt]); + Ok(amt) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let mut nwritten = 0; + for buf in bufs { + let n = self.write(buf)?; + nwritten += n; + if n < buf.len() { + break; + } + } + Ok(nwritten) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + if self.write(buf)? < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } + } + + #[inline] + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + for buf in bufs { + if self.write(buf)? < buf.len() { + return Err(io::Error::WRITE_ALL_EOF); + } + } + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 92bd55a01538e..98680ad390afc 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -25,16 +25,20 @@ pub use self::{ io_slice::{IoSlice, IoSliceMut}, seek::{Seek, SeekFrom}, util::{Chain, Empty, Repeat, Sink, Take, empty, repeat, sink}, + write::Write, }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ - cursor::{slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored}, + cursor::{ + WriteThroughCursor, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, + }, error::{Custom, CustomOwner, OsFunctions}, seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, - write::default_write_vectored, + write::{default_write_fmt, default_write_vectored}, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/util.rs b/library/core/src/io/util.rs index de37690436c80..4ffd38a906f1c 100644 --- a/library/core/src/io/util.rs +++ b/library/core/src/io/util.rs @@ -1,4 +1,4 @@ -use crate::io::{ErrorKind, Result, Seek, SeekFrom, SizeHint}; +use crate::io::{self, ErrorKind, IoSlice, Result, Seek, SeekFrom, SizeHint, Write}; use crate::{cmp, fmt}; /// `Empty` ignores any data written via [`Write`], and will always be empty @@ -23,6 +23,84 @@ impl SizeHint for Empty { } } +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for &Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + #[stable(feature = "empty_seek", since = "1.51.0")] impl Seek for Empty { #[inline] @@ -142,6 +220,84 @@ impl fmt::Debug for Repeat { #[derive(Copy, Clone, Debug, Default)] pub struct Sink; +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for Sink { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "write_mt", since = "1.48.0")] +impl Write for &Sink { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + /// Creates an instance of a writer which will successfully consume all data. /// /// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index cd0d2eb37249b..91d3d2c2f6787 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -1,4 +1,370 @@ -use crate::io::{IoSlice, Result}; +use crate::fmt; +use crate::io::{Error, IoSlice, Result}; + +/// A trait for objects which are byte-oriented sinks. +/// +/// Implementors of the `Write` trait are sometimes called 'writers'. +/// +/// Writers are defined by two required methods, [`write`] and [`flush`]: +/// +/// * The [`write`] method will attempt to write some data into the object, +/// returning how many bytes were successfully written. +/// +/// * The [`flush`] method is useful for adapters and explicit buffers +/// themselves for ensuring that all buffered data has been pushed out to the +/// 'true sink'. +/// +/// Writers are intended to be composable with one another. Many implementors +/// throughout [`std::io`] take and provide types which implement the `Write` +/// trait. +/// +/// [`write`]: Write::write +/// [`flush`]: Write::flush +/// [`std::io`]: crate::io +/// +/// # Examples +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::fs::File; +/// +/// fn main() -> std::io::Result<()> { +/// let data = b"some bytes"; +/// +/// let mut pos = 0; +/// let mut buffer = File::create("foo.txt")?; +/// +/// while pos < data.len() { +/// let bytes_written = buffer.write(&data[pos..])?; +/// pos += bytes_written; +/// } +/// Ok(()) +/// } +/// ``` +/// +/// The trait also provides convenience methods like [`write_all`], which calls +/// `write` in a loop until its entire input has been written. +/// +/// [`write_all`]: Write::write_all +#[stable(feature = "rust1", since = "1.0.0")] +#[doc(notable_trait)] +#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] +pub trait Write { + /// Writes a buffer into this writer, returning how many bytes were written. + /// + /// This function will attempt to write the entire contents of `buf`, but + /// the entire write might not succeed, or the write may also generate an + /// error. Typically, a call to `write` represents one attempt to write to + /// any wrapped object. + /// + /// Calls to `write` are not guaranteed to block waiting for data to be + /// written, and a write which would otherwise block can be indicated through + /// an [`Err`] variant. + /// + /// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`]. + /// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`. + /// A return value of `Ok(0)` typically means that the underlying object is + /// no longer able to accept bytes and will likely not be able to in the + /// future as well, or that the buffer provided is empty. + /// + /// # Errors + /// + /// Each call to `write` may generate an I/O error indicating that the + /// operation could not be completed. If an error is returned then no bytes + /// in the buffer were written to this writer. + /// + /// It is **not** considered an error if the entire buffer could not be + /// written to this writer. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the + /// write operation should be retried if there is nothing else to do. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Writes some prefix of the byte string, not necessarily all of it. + /// buffer.write(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + /// + /// [`Ok(n)`]: Ok + #[stable(feature = "rust1", since = "1.0.0")] + fn write(&mut self, buf: &[u8]) -> Result; + + /// Like [`write`], except that it writes from a slice of buffers. + /// + /// Data is copied from each buffer in order, with the final buffer + /// read from possibly being only partially consumed. This method must + /// behave as a call to [`write`] with the buffers concatenated would. + /// + /// The default implementation calls [`write`] with either the first nonempty + /// buffer provided, or an empty one if none exists. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::IoSlice; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let data1 = [1; 8]; + /// let data2 = [15; 8]; + /// let io_slice1 = IoSlice::new(&data1); + /// let io_slice2 = IoSlice::new(&data2); + /// + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Writes some prefix of the byte string, not necessarily all of it. + /// buffer.write_vectored(&[io_slice1, io_slice2])?; + /// Ok(()) + /// } + /// ``` + /// + /// [`write`]: Write::write + #[stable(feature = "iovec", since = "1.36.0")] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { + default_write_vectored(|b| self.write(b), bufs) + } + + /// Determines if this `Write`r has an efficient [`write_vectored`] + /// implementation. + /// + /// If a `Write`r does not override the default [`write_vectored`] + /// implementation, code using it may want to avoid the method all together + /// and coalesce writes into a single buffer for higher performance. + /// + /// The default implementation returns `false`. + /// + /// [`write_vectored`]: Write::write_vectored + #[unstable(feature = "can_vector", issue = "69941")] + fn is_write_vectored(&self) -> bool { + false + } + + /// Flushes this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + /// + /// # Errors + /// + /// It is considered an error if not all bytes could be written due to + /// I/O errors or EOF being reached. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::io::BufWriter; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = BufWriter::new(File::create("foo.txt")?); + /// + /// buffer.write_all(b"some bytes")?; + /// buffer.flush()?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn flush(&mut self) -> Result<()>; + + /// Attempts to write an entire buffer into this writer. + /// + /// This method will continuously call [`write`] until there is no more data + /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// If the buffer contains no data, this will never call [`write`]. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`ErrorKind::Interrupted`] kind that [`write`] returns. + /// + /// [`write`]: Write::write + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// buffer.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(Error::WRITE_ALL_EOF); + } + Ok(n) => buf = &buf[n..], + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Attempts to write multiple buffers into this writer. + /// + /// This method will continuously call [`write_vectored`] until there is no + /// more data to be written or an error of non-[`ErrorKind::Interrupted`] + /// kind is returned. This method will not return until all buffers have + /// been successfully written or such an error occurs. The first error that + /// is not of [`ErrorKind::Interrupted`] kind generated from this method + /// will be returned. + /// + /// [`ErrorKind::Interrupted`]: crate::io::ErrorKind::Interrupted + /// + /// If the buffer contains no data, this will never call [`write_vectored`]. + /// + /// # Notes + /// + /// Unlike [`write_vectored`], this takes a *mutable* reference to + /// a slice of [`IoSlice`]s, not an immutable one. That's because we need to + /// modify the slice to keep track of the bytes already written. + /// + /// Once this function returns, the contents of `bufs` are unspecified, as + /// this depends on how many calls to [`write_vectored`] were necessary. It is + /// best to understand this function as taking ownership of `bufs` and to + /// not use `bufs` afterwards. The underlying buffers, to which the + /// [`IoSlice`]s point (but not the [`IoSlice`]s themselves), are unchanged and + /// can be reused. + /// + /// [`write_vectored`]: Write::write_vectored + /// + /// # Examples + /// + /// ``` + /// #![feature(write_all_vectored)] + /// # fn main() -> std::io::Result<()> { + /// + /// use std::io::{Write, IoSlice}; + /// + /// let mut writer = Vec::new(); + /// let bufs = &mut [ + /// IoSlice::new(&[1]), + /// IoSlice::new(&[2, 3]), + /// IoSlice::new(&[4, 5, 6]), + /// ]; + /// + /// writer.write_all_vectored(bufs)?; + /// // Note: the contents of `bufs` is now undefined, see the Notes section. + /// + /// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]); + /// # Ok(()) } + /// ``` + #[unstable(feature = "write_all_vectored", issue = "70436")] + fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> Result<()> { + // Guarantee that bufs is empty if it contains no data, + // to avoid calling write_vectored if there is no data to be written. + IoSlice::advance_slices(&mut bufs, 0); + while !bufs.is_empty() { + match self.write_vectored(bufs) { + Ok(0) => { + return Err(Error::WRITE_ALL_EOF); + } + Ok(n) => IoSlice::advance_slices(&mut bufs, n), + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Writes a formatted string into this writer, returning any error + /// encountered. + /// + /// This method is primarily used to interface with the + /// [`format_args!()`] macro, and it is rare that this should + /// explicitly be called. The [`write!()`] macro should be favored to + /// invoke this method instead. + /// + /// This function internally uses the [`write_all`] method on + /// this trait and hence will continuously write data so long as no errors + /// are received. This also means that partial writes are not indicated in + /// this signature. + /// + /// [`write_all`]: Write::write_all + /// + /// # Errors + /// + /// This function will return any I/O error reported while formatting. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // this call + /// write!(buffer, "{:.*}", 2, 1.234567)?; + /// // turns into this: + /// buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { + if let Some(s) = args.as_statically_known_str() { + self.write_all(s.as_bytes()) + } else { + default_write_fmt(self, args) + } + } + + /// Creates a "by reference" adapter for this instance of `Write`. + /// + /// The returned adapter also implements `Write` and will simply borrow this + /// current writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::Write; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// let reference = buffer.by_ref(); + /// + /// // we can use reference just like our original buffer + /// reference.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } +} #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -9,3 +375,43 @@ where let buf = bufs.iter().find(|b| !b.is_empty()).map_or(&[][..], |b| &**b); write(buf) } + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { + // Create a shim which translates a `Write` to a `fmt::Write` and saves off + // I/O errors, instead of discarding them. + struct Adapter<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { inner: this, error: Ok(()) }; + match fmt::write(&mut output, args) { + Ok(()) => Ok(()), + Err(..) => { + // Check whether the error came from the underlying `Write`. + if output.error.is_err() { + output.error + } else { + // This shouldn't happen: the underlying stream did not error, + // but somehow the formatter still errored? + panic!( + "a formatting trait implementation returned an error when the underlying stream did not" + ); + } + } + } +} diff --git a/library/std/src/io/cursor.rs b/library/std/src/io/cursor.rs index 69b1a3311fe21..e0b127bef8e2b 100644 --- a/library/std/src/io/cursor.rs +++ b/library/std/src/io/cursor.rs @@ -4,14 +4,8 @@ mod tests; #[stable(feature = "rust1", since = "1.0.0")] pub use core::io::Cursor; -use alloc_crate::io::{ - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, vec_write_all, - vec_write_all_vectored, -}; - -use crate::alloc::Allocator; use crate::io::prelude::*; -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::io::{self, BorrowedCursor, IoSliceMut}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Cursor @@ -105,241 +99,3 @@ where self.set_position(self.position() + amt as u64); } } - -/// Trait used to allow indirect implementation of `Write` for `Cursor`. -/// Since [`Cursor`] is not a foundational type, it is not possible to implement -/// `Write` for `Cursor` if `Write` is defined in `libcore` and `T` is in a -/// downstream crate (e.g., `liballoc` or `libstd`). -/// -/// Methods are identical in purpose and meaning to their `Write` namesakes. -trait WriteThroughCursor: Sized { - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result; - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result; - fn is_write_vectored(this: &Cursor) -> bool; - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()>; - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()>; - fn flush(this: &mut Cursor) -> io::Result<()>; -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Cursor { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - WriteThroughCursor::write(self, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - WriteThroughCursor::write_vectored(self, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - WriteThroughCursor::is_write_vectored(self) - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - WriteThroughCursor::write_all(self, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - WriteThroughCursor::write_all_vectored(self, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - WriteThroughCursor::flush(self) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Cursor<&mut [u8]> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_mut_vec", since = "1.25.0")] -impl WriteThroughCursor for &mut Vec -where - A: Allocator, -{ - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf) - } - - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf)?; - Ok(()) - } - - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs)?; - Ok(()) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl WriteThroughCursor for Vec -where - A: Allocator, -{ - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf) - } - - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all(pos, inner, buf)?; - Ok(()) - } - - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - vec_write_all_vectored(pos, inner, bufs)?; - Ok(()) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_box_slice", since = "1.5.0")] -impl WriteThroughCursor for Box<[u8], A> -where - A: Allocator, -{ - #[inline] - fn write(this: &mut Cursor, buf: &[u8]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(this: &mut Cursor, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = this.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(_this: &Cursor) -> bool { - true - } - - #[inline] - fn write_all(this: &mut Cursor, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(this: &mut Cursor, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = this.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(_this: &mut Cursor) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "cursor_array", since = "1.61.0")] -impl Write for Cursor<[u8; N]> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write(pos, inner, buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let (pos, inner) = self.into_parts_mut(); - slice_write_vectored(pos, inner, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all(pos, inner, buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - let (pos, inner) = self.into_parts_mut(); - slice_write_all_vectored(pos, inner, bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/library/std/src/io/impls.rs b/library/std/src/io/impls.rs index 08bb34376b075..61f82828149aa 100644 --- a/library/std/src/io/impls.rs +++ b/library/std/src/io/impls.rs @@ -3,9 +3,9 @@ mod tests; use crate::alloc::Allocator; use crate::collections::VecDeque; -use crate::io::{self, BorrowedCursor, BufRead, IoSlice, IoSliceMut, Read, Write}; +use crate::io::{self, BorrowedCursor, BufRead, IoSliceMut, Read}; use crate::sync::Arc; -use crate::{cmp, fmt, mem, str}; +use crate::{cmp, str}; // ============================================================================= // Forwarding implementations @@ -53,43 +53,6 @@ impl Read for &mut R { } } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for &mut W { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (**self).write_fmt(fmt) - } -} -#[stable(feature = "rust1", since = "1.0.0")] impl BufRead for &mut B { #[inline] fn fill_buf(&mut self) -> io::Result<&[u8]> { @@ -165,43 +128,6 @@ impl Read for Box { } } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for Box { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (**self).write_fmt(fmt) - } -} -#[stable(feature = "rust1", since = "1.0.0")] impl BufRead for Box { #[inline] fn fill_buf(&mut self) -> io::Result<&[u8]> { @@ -362,108 +288,6 @@ impl BufRead for &[u8] { } } -/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting -/// its data. -/// -/// Note that writing updates the slice to point to the yet unwritten part. -/// The slice will be empty when it has been completely overwritten. -/// -/// If the number of bytes to be written exceeds the size of the slice, write operations will -/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of -/// kind `ErrorKind::WriteZero`. -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for &mut [u8] { - #[inline] - fn write(&mut self, data: &[u8]) -> io::Result { - let amt = cmp::min(data.len(), self.len()); - let (a, b) = mem::take(self).split_at_mut(amt); - a.copy_from_slice(&data[..amt]); - *self = b; - Ok(amt) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - nwritten += self.write(buf)?; - if self.is_empty() { - break; - } - } - - Ok(nwritten) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, data: &[u8]) -> io::Result<()> { - if self.write(data)? < data.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - for buf in bufs { - if self.write(buf)? < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Write is implemented for `Vec` by appending to the vector. -/// The vector will grow as needed. -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Vec { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.extend_from_slice(buf); - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let len = bufs.iter().map(|b| b.len()).sum(); - self.reserve(len); - for buf in bufs { - self.extend_from_slice(buf); - } - Ok(len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.extend_from_slice(buf); - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - self.write_vectored(bufs)?; - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// Read is implemented for `VecDeque` by consuming bytes from the front of the `VecDeque`. #[stable(feature = "vecdeque_read_write", since = "1.63.0")] impl Read for VecDeque { @@ -573,96 +397,6 @@ impl BufRead for VecDeque { } } -/// Write is implemented for `VecDeque` by appending to the `VecDeque`, growing it as needed. -#[stable(feature = "vecdeque_read_write", since = "1.63.0")] -impl Write for VecDeque { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.extend(buf); - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let len = bufs.iter().map(|b| b.len()).sum(); - self.reserve(len); - for buf in bufs { - self.extend(&**buf); - } - Ok(len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.extend(buf); - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - self.write_vectored(bufs)?; - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[unstable(feature = "read_buf", issue = "78485")] -impl<'a> io::Write for core::io::BorrowedCursor<'a, u8> { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - let amt = cmp::min(buf.len(), self.capacity()); - self.append(&buf[..amt]); - Ok(amt) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let mut nwritten = 0; - for buf in bufs { - let n = self.write(buf)?; - nwritten += n; - if n < buf.len() { - break; - } - } - Ok(nwritten) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - if self.write(buf)? < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - for buf in bufs { - if self.write(buf)? < buf.len() { - return Err(io::Error::WRITE_ALL_EOF); - } - } - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - #[stable(feature = "io_traits_arc", since = "1.73.0")] impl Read for Arc where @@ -709,44 +443,3 @@ where (&**self).read_buf_exact(cursor) } } -#[stable(feature = "io_traits_arc", since = "1.73.0")] -impl Write for Arc -where - for<'a> &'a W: Write, - W: crate::io::IoHandle, -{ - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (&**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - (&**self).write_vectored(bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - (&**self).is_write_vectored() - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (&**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (&**self).write_all(buf) - } - - #[inline] - fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - (&**self).write_all_vectored(bufs) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (&**self).write_fmt(fmt) - } -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index f2f3aa8bf8dd9..b1dbcf1f5bd46 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -306,13 +306,16 @@ pub use alloc_crate::io::RawOsError; pub use alloc_crate::io::SimpleMessage; #[unstable(feature = "io_const_error", issue = "133448")] pub use alloc_crate::io::const_error; +#[allow(unused_imports, reason = "only used by certain platforms")] +pub(crate) use alloc_crate::io::default_write_vectored; #[unstable(feature = "read_buf", issue = "78485")] pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::io::{ - Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, empty, repeat, sink, + Chain, Empty, Error, ErrorKind, Repeat, Result, Seek, SeekFrom, Sink, Take, Write, empty, + repeat, sink, }; -pub(crate) use alloc_crate::io::{IoHandle, default_write_vectored, stream_len_default}; +pub(crate) use alloc_crate::io::{IoHandle, stream_len_default}; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; use alloc_crate::io::{OsFunctions, SizeHint}; @@ -338,7 +341,7 @@ pub use self::{ stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, }; use crate::mem::MaybeUninit; -use crate::{cmp, fmt, slice, str}; +use crate::{cmp, slice, str}; mod buffered; pub(crate) mod copy; @@ -592,47 +595,6 @@ pub(crate) fn default_read_buf_exact( Ok(()) } -pub(crate) fn default_write_fmt( - this: &mut W, - args: fmt::Arguments<'_>, -) -> Result<()> { - // Create a shim which translates a `Write` to a `fmt::Write` and saves off - // I/O errors, instead of discarding them. - struct Adapter<'a, T: ?Sized + 'a> { - inner: &'a mut T, - error: Result<()>, - } - - impl fmt::Write for Adapter<'_, T> { - fn write_str(&mut self, s: &str) -> fmt::Result { - match self.inner.write_all(s.as_bytes()) { - Ok(()) => Ok(()), - Err(e) => { - self.error = Err(e); - Err(fmt::Error) - } - } - } - } - - let mut output = Adapter { inner: this, error: Ok(()) }; - match fmt::write(&mut output, args) { - Ok(()) => Ok(()), - Err(..) => { - // Check whether the error came from the underlying `Write`. - if output.error.is_err() { - output.error - } else { - // This shouldn't happen: the underlying stream did not error, - // but somehow the formatter still errored? - panic!( - "a formatting trait implementation returned an error when the underlying stream did not" - ); - } - } - } -} - /// The `Read` trait allows for reading bytes from a source. /// /// Implementors of the `Read` trait are called 'readers'. @@ -1395,365 +1357,6 @@ pub fn read_to_string(mut reader: R) -> Result { Ok(buf) } -/// A trait for objects which are byte-oriented sinks. -/// -/// Implementors of the `Write` trait are sometimes called 'writers'. -/// -/// Writers are defined by two required methods, [`write`] and [`flush`]: -/// -/// * The [`write`] method will attempt to write some data into the object, -/// returning how many bytes were successfully written. -/// -/// * The [`flush`] method is useful for adapters and explicit buffers -/// themselves for ensuring that all buffered data has been pushed out to the -/// 'true sink'. -/// -/// Writers are intended to be composable with one another. Many implementors -/// throughout [`std::io`] take and provide types which implement the `Write` -/// trait. -/// -/// [`write`]: Write::write -/// [`flush`]: Write::flush -/// [`std::io`]: self -/// -/// # Examples -/// -/// ```no_run -/// use std::io::prelude::*; -/// use std::fs::File; -/// -/// fn main() -> std::io::Result<()> { -/// let data = b"some bytes"; -/// -/// let mut pos = 0; -/// let mut buffer = File::create("foo.txt")?; -/// -/// while pos < data.len() { -/// let bytes_written = buffer.write(&data[pos..])?; -/// pos += bytes_written; -/// } -/// Ok(()) -/// } -/// ``` -/// -/// The trait also provides convenience methods like [`write_all`], which calls -/// `write` in a loop until its entire input has been written. -/// -/// [`write_all`]: Write::write_all -#[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] -#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] -pub trait Write { - /// Writes a buffer into this writer, returning how many bytes were written. - /// - /// This function will attempt to write the entire contents of `buf`, but - /// the entire write might not succeed, or the write may also generate an - /// error. Typically, a call to `write` represents one attempt to write to - /// any wrapped object. - /// - /// Calls to `write` are not guaranteed to block waiting for data to be - /// written, and a write which would otherwise block can be indicated through - /// an [`Err`] variant. - /// - /// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`]. - /// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`. - /// A return value of `Ok(0)` typically means that the underlying object is - /// no longer able to accept bytes and will likely not be able to in the - /// future as well, or that the buffer provided is empty. - /// - /// # Errors - /// - /// Each call to `write` may generate an I/O error indicating that the - /// operation could not be completed. If an error is returned then no bytes - /// in the buffer were written to this writer. - /// - /// It is **not** considered an error if the entire buffer could not be - /// written to this writer. - /// - /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the - /// write operation should be retried if there is nothing else to do. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// // Writes some prefix of the byte string, not necessarily all of it. - /// buffer.write(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - /// - /// [`Ok(n)`]: Ok - #[stable(feature = "rust1", since = "1.0.0")] - fn write(&mut self, buf: &[u8]) -> Result; - - /// Like [`write`], except that it writes from a slice of buffers. - /// - /// Data is copied from each buffer in order, with the final buffer - /// read from possibly being only partially consumed. This method must - /// behave as a call to [`write`] with the buffers concatenated would. - /// - /// The default implementation calls [`write`] with either the first nonempty - /// buffer provided, or an empty one if none exists. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::IoSlice; - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let data1 = [1; 8]; - /// let data2 = [15; 8]; - /// let io_slice1 = IoSlice::new(&data1); - /// let io_slice2 = IoSlice::new(&data2); - /// - /// let mut buffer = File::create("foo.txt")?; - /// - /// // Writes some prefix of the byte string, not necessarily all of it. - /// buffer.write_vectored(&[io_slice1, io_slice2])?; - /// Ok(()) - /// } - /// ``` - /// - /// [`write`]: Write::write - #[stable(feature = "iovec", since = "1.36.0")] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { - default_write_vectored(|b| self.write(b), bufs) - } - - /// Determines if this `Write`r has an efficient [`write_vectored`] - /// implementation. - /// - /// If a `Write`r does not override the default [`write_vectored`] - /// implementation, code using it may want to avoid the method all together - /// and coalesce writes into a single buffer for higher performance. - /// - /// The default implementation returns `false`. - /// - /// [`write_vectored`]: Write::write_vectored - #[unstable(feature = "can_vector", issue = "69941")] - fn is_write_vectored(&self) -> bool { - false - } - - /// Flushes this output stream, ensuring that all intermediately buffered - /// contents reach their destination. - /// - /// # Errors - /// - /// It is considered an error if not all bytes could be written due to - /// I/O errors or EOF being reached. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::io::BufWriter; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = BufWriter::new(File::create("foo.txt")?); - /// - /// buffer.write_all(b"some bytes")?; - /// buffer.flush()?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn flush(&mut self) -> Result<()>; - - /// Attempts to write an entire buffer into this writer. - /// - /// This method will continuously call [`write`] until there is no more data - /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is - /// returned. This method will not return until the entire buffer has been - /// successfully written or such an error occurs. The first error that is - /// not of [`ErrorKind::Interrupted`] kind generated from this method will be - /// returned. - /// - /// If the buffer contains no data, this will never call [`write`]. - /// - /// # Errors - /// - /// This function will return the first error of - /// non-[`ErrorKind::Interrupted`] kind that [`write`] returns. - /// - /// [`write`]: Write::write - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// buffer.write_all(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { - while !buf.is_empty() { - match self.write(buf) { - Ok(0) => { - return Err(Error::WRITE_ALL_EOF); - } - Ok(n) => buf = &buf[n..], - Err(ref e) if e.is_interrupted() => {} - Err(e) => return Err(e), - } - } - Ok(()) - } - - /// Attempts to write multiple buffers into this writer. - /// - /// This method will continuously call [`write_vectored`] until there is no - /// more data to be written or an error of non-[`ErrorKind::Interrupted`] - /// kind is returned. This method will not return until all buffers have - /// been successfully written or such an error occurs. The first error that - /// is not of [`ErrorKind::Interrupted`] kind generated from this method - /// will be returned. - /// - /// If the buffer contains no data, this will never call [`write_vectored`]. - /// - /// # Notes - /// - /// Unlike [`write_vectored`], this takes a *mutable* reference to - /// a slice of [`IoSlice`]s, not an immutable one. That's because we need to - /// modify the slice to keep track of the bytes already written. - /// - /// Once this function returns, the contents of `bufs` are unspecified, as - /// this depends on how many calls to [`write_vectored`] were necessary. It is - /// best to understand this function as taking ownership of `bufs` and to - /// not use `bufs` afterwards. The underlying buffers, to which the - /// [`IoSlice`]s point (but not the [`IoSlice`]s themselves), are unchanged and - /// can be reused. - /// - /// [`write_vectored`]: Write::write_vectored - /// - /// # Examples - /// - /// ``` - /// #![feature(write_all_vectored)] - /// # fn main() -> std::io::Result<()> { - /// - /// use std::io::{Write, IoSlice}; - /// - /// let mut writer = Vec::new(); - /// let bufs = &mut [ - /// IoSlice::new(&[1]), - /// IoSlice::new(&[2, 3]), - /// IoSlice::new(&[4, 5, 6]), - /// ]; - /// - /// writer.write_all_vectored(bufs)?; - /// // Note: the contents of `bufs` is now undefined, see the Notes section. - /// - /// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]); - /// # Ok(()) } - /// ``` - #[unstable(feature = "write_all_vectored", issue = "70436")] - fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> Result<()> { - // Guarantee that bufs is empty if it contains no data, - // to avoid calling write_vectored if there is no data to be written. - IoSlice::advance_slices(&mut bufs, 0); - while !bufs.is_empty() { - match self.write_vectored(bufs) { - Ok(0) => { - return Err(Error::WRITE_ALL_EOF); - } - Ok(n) => IoSlice::advance_slices(&mut bufs, n), - Err(ref e) if e.is_interrupted() => {} - Err(e) => return Err(e), - } - } - Ok(()) - } - - /// Writes a formatted string into this writer, returning any error - /// encountered. - /// - /// This method is primarily used to interface with the - /// [`format_args!()`] macro, and it is rare that this should - /// explicitly be called. The [`write!()`] macro should be favored to - /// invoke this method instead. - /// - /// This function internally uses the [`write_all`] method on - /// this trait and hence will continuously write data so long as no errors - /// are received. This also means that partial writes are not indicated in - /// this signature. - /// - /// [`write_all`]: Write::write_all - /// - /// # Errors - /// - /// This function will return any I/O error reported while formatting. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::prelude::*; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// // this call - /// write!(buffer, "{:.*}", 2, 1.234567)?; - /// // turns into this: - /// buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { - if let Some(s) = args.as_statically_known_str() { - self.write_all(s.as_bytes()) - } else { - default_write_fmt(self, args) - } - } - - /// Creates a "by reference" adapter for this instance of `Write`. - /// - /// The returned adapter also implements `Write` and will simply borrow this - /// current writer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::Write; - /// use std::fs::File; - /// - /// fn main() -> std::io::Result<()> { - /// let mut buffer = File::create("foo.txt")?; - /// - /// let reference = buffer.by_ref(); - /// - /// // we can use reference just like our original buffer - /// reference.write_all(b"some bytes")?; - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - fn by_ref(&mut self) -> &mut Self - where - Self: Sized, - { - self - } -} - fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> Result { let mut read = 0; loop { diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index ce2137f9567c7..ac3e0f84d1e80 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -3,10 +3,7 @@ #[cfg(test)] mod tests; -use crate::fmt; -use crate::io::{ - self, BorrowedCursor, BufRead, Empty, IoSlice, IoSliceMut, Read, Repeat, Sink, Write, -}; +use crate::io::{self, BorrowedCursor, BufRead, Empty, IoSliceMut, Read, Repeat}; #[stable(feature = "rust1", since = "1.0.0")] impl Read for Empty { @@ -83,84 +80,6 @@ impl BufRead for Empty { } } -#[stable(feature = "empty_write", since = "1.73.0")] -impl Write for Empty { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "empty_write", since = "1.73.0")] -impl Write for &Empty { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - #[stable(feature = "rust1", since = "1.0.0")] impl Read for Repeat { #[inline] @@ -213,81 +132,3 @@ impl Read for Repeat { true } } - -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for Sink { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[stable(feature = "write_mt", since = "1.48.0")] -impl Write for &Sink { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - let total_len = bufs.iter().map(|b| b.len()).sum(); - Ok(total_len) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_all_vectored(&mut self, _bufs: &mut [IoSlice<'_>]) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, _args: fmt::Arguments<'_>) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index db37df63c5763..7cf576ecd8803 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -322,6 +322,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(bstr_internals)] +#![feature(can_vector)] #![feature(cast_maybe_uninit)] #![feature(char_internals)] #![feature(clone_to_uninit)] @@ -389,6 +390,7 @@ #![feature(ub_checks)] #![feature(uint_carryless_mul)] #![feature(used_with_arg)] +#![feature(write_all_vectored)] // tidy-alphabetical-end // // Library features (alloc): diff --git a/tests/ui/suggestions/issue-105645.stderr b/tests/ui/suggestions/issue-105645.stderr index 2b9a94d8e0ab2..2342c22cfa80a 100644 --- a/tests/ui/suggestions/issue-105645.stderr +++ b/tests/ui/suggestions/issue-105645.stderr @@ -7,7 +7,7 @@ LL | foo(&mut bref); | required by a bound introduced by this call | help: the trait `std::io::Write` is implemented for `&mut [u8]` - --> $SRC_DIR/std/src/io/impls.rs:LL:COL + --> $SRC_DIR/core/src/io/impls.rs:LL:COL note: required by a bound in `foo` --> $DIR/issue-105645.rs:8:21 | From 4ecb0cd6a621e90ecd6bd2b0054091b7be853de8 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 22 May 2026 10:28:42 +1000 Subject: [PATCH 25/31] Fix documentation links to `Write` --- library/core/src/io/cursor.rs | 2 +- library/core/src/io/error.rs | 4 ++-- library/core/src/io/mod.rs | 2 +- library/core/src/io/util.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 15b313142c963..645e69a4278e5 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -23,7 +23,7 @@ use crate::io::{self, ErrorKind, IoSlice, SeekFrom, Write}; /// [bytes]: crate::slice "slice" /// [`File`]: ../../std/fs/struct.File.html /// [`Read`]: ../../std/io/trait.Read.html -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Seek`]: crate::io::Seek /// [Vec]: ../../alloc/vec/struct.Vec.html /// diff --git a/library/core/src/io/error.rs b/library/core/src/io/error.rs index 4214a5f87e3ef..eac7396bcfd83 100644 --- a/library/core/src/io/error.rs +++ b/library/core/src/io/error.rs @@ -79,7 +79,7 @@ pub type Result = result::Result; /// // FIXME(#74481): Hard-links required to link from `core` to `std` /// [Read]: ../../std/io/trait.Read.html -/// [Write]: ../../std/io/trait.Write.html +/// [Write]: crate::io::Write /// [Seek]: crate::io::Seek #[stable(feature = "rust1", since = "1.0.0")] #[rustc_has_incoherent_inherent_impls] @@ -853,7 +853,7 @@ pub enum ErrorKind { /// particular number of bytes but only a smaller number of bytes could be /// written. /// - /// [write]: ../../std/io/trait.Write.html#tymethod.write + /// [write]: crate::io::Write::write /// [`Ok(0)`]: Ok #[stable(feature = "rust1", since = "1.0.0")] WriteZero, diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 98680ad390afc..0879d14d7ee04 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -55,7 +55,7 @@ pub use self::{ // FIXME(#74481): Hard-links required to link from `core` to `std` /// [file]: ../../std/fs/struct.File.html /// [arc]: ../../alloc/sync/struct.Arc.html -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Seek`]: crate::io::Seek #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] diff --git a/library/core/src/io/util.rs b/library/core/src/io/util.rs index 4ffd38a906f1c..b022d876eb8f5 100644 --- a/library/core/src/io/util.rs +++ b/library/core/src/io/util.rs @@ -4,7 +4,7 @@ use crate::{cmp, fmt}; /// `Empty` ignores any data written via [`Write`], and will always be empty /// (returning zero bytes) when read via [`Read`]. /// -/// [`Write`]: ../../std/io/trait.Write.html +/// [`Write`]: crate::io::Write /// [`Read`]: ../../std/io/trait.Read.html /// /// This struct is generally created by calling [`empty()`]. Please @@ -129,7 +129,7 @@ impl Seek for Empty { /// [`Ok(buf.len())`]: Ok /// [`Ok(0)`]: Ok /// -/// [`write`]: ../../std/io/trait.Write.html#tymethod.write +/// [`write`]: crate::io::Write::write /// [`read`]: ../../std/io/trait.Read.html#tymethod.read /// /// # Examples @@ -303,7 +303,7 @@ impl Write for &Sink { /// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] /// and the contents of the buffer will not be inspected. /// -/// [`write`]: ../../std/io/trait.Write.html#tymethod.write +/// [`write`]: crate::io::Write::write /// [`Ok(buf.len())`]: Ok /// /// # Examples From 53727e6e468fb9a0fd8ab30f1f176605e8f5cc4d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 11:23:55 +1000 Subject: [PATCH 26/31] Add documentation to `core::io` internal methods Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/core/src/io/cursor.rs | 9 ++++++++- library/core/src/io/write.rs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library/core/src/io/cursor.rs b/library/core/src/io/cursor.rs index 645e69a4278e5..fe4def531a84a 100644 --- a/library/core/src/io/cursor.rs +++ b/library/core/src/io/cursor.rs @@ -295,7 +295,8 @@ where } } -// Non-resizing write implementation +/// Non-resizing [`Write::write`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -308,6 +309,8 @@ pub fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Resul Ok(amt) } +/// Non-resizing [`Write::write_vectored`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -327,6 +330,8 @@ pub fn slice_write_vectored( Ok(nwritten) } +/// Non-resizing [`Write::write_all`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] @@ -335,6 +340,8 @@ pub fn slice_write_all(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::R if n < buf.len() { Err(io::Error::WRITE_ALL_EOF) } else { Ok(()) } } +/// Non-resizing [`Write::write_all_vectored`] implementation for slices. +/// Exported for `Cursor>`'s implementation of [`Write`]. #[inline] #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index 91d3d2c2f6787..90cfb94582d7f 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -366,6 +366,8 @@ pub trait Write { } } +/// Default implementation of [`Write::write_vectored`], which is currently used +/// in `libstd` for file system implementations of similar methods. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub fn default_write_vectored(write: F, bufs: &[IoSlice<'_>]) -> Result From 0e6181228a88e7e0f37591aca3cb64a8b0fdecf9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 12:12:19 +1000 Subject: [PATCH 27/31] Reduce visibility of `core::io::write::default_write_fmt` Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/mod.rs | 6 +++--- library/core/src/io/mod.rs | 2 +- library/core/src/io/write.rs | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 678934b91da71..ef7e95ed5bd7c 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -20,7 +20,7 @@ pub use core::io::{ #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use core::io::{ - IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_fmt, - default_write_vectored, slice_write, slice_write_all, slice_write_all_vectored, - slice_write_vectored, stream_len_default, take, + IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_vectored, + slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, + stream_len_default, take, }; diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 0879d14d7ee04..0134540ae86c8 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -38,7 +38,7 @@ pub use self::{ seek::stream_len_default, size_hint::SizeHint, util::{chain, take}, - write::{default_write_fmt, default_write_vectored}, + write::default_write_vectored, }; /// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index 90cfb94582d7f..cdddd380885f4 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -378,9 +378,7 @@ where write(buf) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { +fn default_write_fmt(this: &mut W, args: fmt::Arguments<'_>) -> Result<()> { // Create a shim which translates a `Write` to a `fmt::Write` and saves off // I/O errors, instead of discarding them. struct Adapter<'a, T: ?Sized + 'a> { From cb09bff72240065593630cac6c54aa15e691f26f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 7 Jul 2026 13:52:32 +1000 Subject: [PATCH 28/31] Add `Vec::try_extend_from_slice_of_bytes` Moves unsafe code from `alloc::io` into `alloc::vec`. A more general `try_extend_from_slice` may be useful in the future, but that requires changing how specialization is done for `SpecExtend` and that's a lot for an internal-only function. --- library/alloc/src/io/impls.rs | 15 +-------------- library/alloc/src/vec/mod.rs | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/library/alloc/src/io/impls.rs b/library/alloc/src/io/impls.rs index 0f6bfe244dba2..1351d3e3ef3a1 100644 --- a/library/alloc/src/io/impls.rs +++ b/library/alloc/src/io/impls.rs @@ -129,20 +129,7 @@ impl Write for Vec { fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { cfg_select! { no_global_oom_handling => { - let n = buf.len(); - self.try_reserve(n)?; - // SAFETY: - // * self and buf are non-overlapping - // * self[..len] is already initialized - // * self[len..len + n] is initialized by copy_nonoverlapping - // * len + n is within the capacity of self based on the reservation completed above - unsafe { - let len = self.len(); - let src = buf.as_ptr(); - let dst = self.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, n); - self.set_len(len + n); - } + self.try_extend_from_slice_of_bytes(buf)?; } _ => { self.extend_from_slice(buf); diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index c3b86a635bd0f..a5d03ef76ade6 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2938,8 +2938,26 @@ impl Vec { #[cfg(not(no_global_oom_handling))] #[inline] unsafe fn append_elements(&mut self, other: *const [T]) { + self.reserve(other.len()); + unsafe { + self.append_elements_unreserved(other); + } + } + + /// Appends elements to `self` from other buffer, returning [`TryReserveError`] on OOM. + #[inline] + unsafe fn try_append_elements(&mut self, other: *const [T]) -> Result<(), TryReserveError> { + self.try_reserve(other.len())?; + unsafe { + self.append_elements_unreserved(other); + } + Ok(()) + } + + /// Appends elements to `self` from other buffer without reserving additional capacity. + #[inline] + unsafe fn append_elements_unreserved(&mut self, other: *const [T]) { let count = other.len(); - self.reserve(count); let len = self.len(); if count > 0 { unsafe { @@ -3609,6 +3627,22 @@ impl Vec { } } +impl Vec { + #[cfg_attr( + not(no_global_oom_handling), + expect( + dead_code, + reason = "currently only used in IO module when global OOM handling is disabled" + ) + )] + pub(crate) fn try_extend_from_slice_of_bytes( + &mut self, + other: &[u8], + ) -> Result<(), TryReserveError> { + unsafe { self.try_append_elements(other) } + } +} + impl Vec<[T; N], A> { /// Takes a `Vec<[T; N]>` and flattens it into a `Vec`. /// From 8118399031cd3be8a5254fbd836259445e7ffb5a Mon Sep 17 00:00:00 2001 From: heinwol Date: Wed, 8 Jul 2026 11:56:25 +0000 Subject: [PATCH 29/31] Attempt to run parallel frontend CI * attempt to run parallel CI * fix `tests/rustdoc-ui/doctest/no-capture.rs` fail by setting `RUST_TEST_THREADS=1` for ui tests only * more efficient implementation There are two limitations though for the thread count to work properly: 1. The machine is not performing any other tasks 2. It has only efficiency cores Anyway, it's up to the infra team to decide if this design is ok * small adjustments * fix: make sure `RUST_TEST_THREADS >= 1` --- .../Dockerfile | 11 +++++++++-- .../optional-x86_64-gnu-parallel-frontend.sh | 16 ++++++++++++++++ src/ci/github-actions/jobs.yml | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100755 src/ci/docker/scripts/optional-x86_64-gnu-parallel-frontend.sh diff --git a/src/ci/docker/host-x86_64/optional-x86_64-gnu-parallel-frontend/Dockerfile b/src/ci/docker/host-x86_64/optional-x86_64-gnu-parallel-frontend/Dockerfile index 8f7c7ebe35559..d8bfd6f52cb8b 100644 --- a/src/ci/docker/host-x86_64/optional-x86_64-gnu-parallel-frontend/Dockerfile +++ b/src/ci/docker/host-x86_64/optional-x86_64-gnu-parallel-frontend/Dockerfile @@ -24,13 +24,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh +ENV PARALLEL_FRONTEND_THREADS=4 +ENV ITERATION_COUNT=2 + ENV RUST_CONFIGURE_ARGS \ --build=x86_64-unknown-linux-gnu \ --enable-sanitizers \ --enable-profiler \ --enable-compiler-docs \ - --set llvm.libzstd=true + --set llvm.libzstd=true \ + --set rust.parallel-frontend-threads=${PARALLEL_FRONTEND_THREADS} # Build the toolchain with multiple parallel frontend threads and then run tests # Tests are still compiled serially at the moment (intended to be changed in follow-ups). -ENV SCRIPT python3 ../x.py --stage 2 test --set rust.parallel-frontend-threads=4 +# Running compiletest only tests for parallel frontend, then the rest without compiletest-only +# options (i.e. `--parallel-frontend-threads=4 --iteration-count=2`) +COPY ./scripts/optional-x86_64-gnu-parallel-frontend.sh /scripts/ +ENV SCRIPT="Must specify DOCKER_SCRIPT for this image" diff --git a/src/ci/docker/scripts/optional-x86_64-gnu-parallel-frontend.sh b/src/ci/docker/scripts/optional-x86_64-gnu-parallel-frontend.sh new file mode 100755 index 0000000000000..ed9aed263f25e --- /dev/null +++ b/src/ci/docker/scripts/optional-x86_64-gnu-parallel-frontend.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -eux -o pipefail + +if [ ! -v RUST_TEST_THREADS ]; then + RUST_TEST_THREADS_=$(python3 -c "print(max(1, $(nproc) // ${PARALLEL_FRONTEND_THREADS}))") +else + RUST_TEST_THREADS_=${RUST_TEST_THREADS} +fi + +RUST_TEST_THREADS=${RUST_TEST_THREADS_} \ + python3 ../x.py --stage 2 test \ + tests/ui \ + -- \ + --parallel-frontend-threads="${PARALLEL_FRONTEND_THREADS}" \ + --iteration-count="${ITERATION_COUNT}" diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 2bdf83a9c006b..834a8ddafb3d7 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -363,6 +363,8 @@ auto: - name: optional-x86_64-gnu-parallel-frontend # This test can be flaky, so do not cancel CI if it fails, for now. continue_on_error: true + env: + DOCKER_SCRIPT: optional-x86_64-gnu-parallel-frontend.sh <<: *job-linux-4c # This job ensures commits landing on nightly still pass the full From 33788973ce2455924e0371c710ac08de154ca709 Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 8 Jul 2026 08:20:29 -0400 Subject: [PATCH 30/31] address suggestions --- compiler/rustc_codegen_ssa/src/back/link.rs | 41 ++++++++------------- compiler/rustc_codegen_ssa/src/base.rs | 36 +++++++++++------- compiler/rustc_codegen_ssa/src/lib.rs | 2 +- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 74631030e9b09..7ca6479548b55 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -94,40 +94,31 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat // earlier EII pass still handles missing impls and duplicate explicit impls. for dependency_formats in crate_info.dependency_formats.values() { for (eii_index, eii) in crate_info.eii_linkage.iter().enumerate() { - let mut explicit_impls = - eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); - let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { + let Some(explicit_impl) = eii.impls.first() else { continue; }; // If the explicit impl is already coming from a dylib, that dylib // has already resolved the default-vs-explicit choice. - if explicit_impls.next().is_some() - || matches!( - dependency_formats.get(explicit_impl.impl_crate), - Some(Linkage::Dynamic | Linkage::IncludedFromDylib) - ) - { + if matches!( + dependency_formats.get(explicit_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) { continue; } - let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| { - imp.is_default - && matches!( - dependency_formats.get(imp.impl_crate), - Some(Linkage::Dynamic | Linkage::IncludedFromDylib) - ) - }); - let Some((default_index, default_impl)) = finalized_default_impls.next() else { + let Some(default_impl) = &eii.default_impl else { continue; }; - - if !emitted.insert((eii_index, explicit_index, default_index)) { + if !matches!( + dependency_formats.get(default_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) { continue; } - let additional_crate_names = finalized_default_impls - .map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate))) - .collect::>(); + if !emitted.insert(eii_index) { + continue; + } sess.dcx().emit_err(DuplicateEiiImpls { name: eii.name, @@ -136,9 +127,9 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat second_span: default_impl.span, second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate), help: (), - additional_crates: (!additional_crate_names.is_empty()).then_some(()), - num_additional_crates: additional_crate_names.len(), - additional_crate_names: additional_crate_names.join(", "), + additional_crates: None, + num_additional_crates: 0, + additional_crate_names: String::new(), }); } } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index e35c446d609fc..213d943cb1a7d 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -933,21 +933,31 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { eiis.into_iter() .filter_map(|(_, FoundEii { decl, impls })| { - let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count(); - let has_default_impl = impls.values().any(|imp| imp.imp.is_default); + let mut explicit_impls = Vec::new(); + let mut default_impl = None; + + for (impl_did, FoundImpl { imp, impl_crate }) in impls { + let impl_info = EiiLinkageImplInfo { span: tcx.def_span(impl_did), impl_crate }; + if imp.is_default { + default_impl = Some(impl_info); + } else { + explicit_impls.push(impl_info); + } + } + // Only this case needs the link-time check. Missing impls and // duplicate explicit impls are handled in `rustc_passes`. - (explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo { - name: decl.name.name, - impls: impls - .into_iter() - .map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo { - span: tcx.def_span(impl_did), - impl_crate, - is_default: imp.is_default, - }) - .collect(), - }) + if explicit_impls.len() == 1 + && let Some(default_impl) = default_impl + { + Some(EiiLinkageInfo { + name: decl.name.name, + impls: explicit_impls, + default_impl: Some(default_impl), + }) + } else { + None + } }) .collect() } diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index ee8542e4c3c33..fc7627d7c0d5b 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -259,13 +259,13 @@ impl SymbolExport { pub struct EiiLinkageImplInfo { pub span: Span, pub impl_crate: CrateNum, - pub is_default: bool, } #[derive(Clone, Debug, Encodable, Decodable)] pub struct EiiLinkageInfo { pub name: Symbol, pub impls: Vec, + pub default_impl: Option, } /// Misc info we load from metadata to persist beyond the tcx. From 41bdaed53bc09ef3b9340af3dde256f8f06a99bc Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:35:19 -0400 Subject: [PATCH 31/31] address follow up nits --- compiler/rustc_codegen_ssa/src/base.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 213d943cb1a7d..c71def400f730 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -919,14 +919,14 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { let mut eiis = FxIndexMap::::default(); for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { - for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) { - eiis.entry(*did) - .or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() }) + for (&did, &(decl, ref impls)) in tcx.externally_implementable_items(cnum) { + eiis.entry(did) + .or_insert_with(|| FoundEii { decl, impls: Default::default() }) .impls .extend( impls .into_iter() - .map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })), + .map(|(&did, &imp)| (did, FoundImpl { imp, impl_crate: cnum })), ); } } @@ -945,11 +945,9 @@ fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { } } - // Only this case needs the link-time check. Missing impls and - // duplicate explicit impls are handled in `rustc_passes`. - if explicit_impls.len() == 1 - && let Some(default_impl) = default_impl - { + // Link time check is only needed when there may be a default impl in a dylib. + // Other cases emit an error in `rustc_passes` already. + if let Some(default_impl) = default_impl { Some(EiiLinkageInfo { name: decl.name.name, impls: explicit_impls,